文章出處

C#+OpenGL+FreeType顯示3D文字(1) - 從TTF文件導出字形貼圖

+BIT祝威+悄悄在此留下版了個權的信息說:

最近需要用OpenGL繪制文字,這是個很費時費力的事。一般的思路就是解析TTF文件從而得到字形的貼圖,然后通過OpenGL繪制貼圖的方式顯示文字。

本篇記錄了解析TTF文件并把所有字形安排到一張大貼圖上的過程。

使用FreeType

想從零開始解析TTF文件是一個比較大的工程,所以目前就借助FreeType。FreeType是一個開源的跨平臺的TTF文件解析器。當然,它還支持解析OpenType等格式的文件。

+BIT祝威+悄悄在此留下版了個權的信息說:

預備工作

找一個TTF文件

你可以從C:\Windows\Fonts里找到很多TTF文件,我就不提供了。TTF文件里包含了文字的字形。比如下面這兩個就是本篇開頭的兩張貼圖展示的TTF:

下載FreeType源碼

你可以在Github上搜索到FreeType項目,也可以到別的什么地方下載某個版本的FreeType。比如我下載的是2.6版。

源碼下載解壓后如上圖所示。

下載CMake

我想用visual studio打開并編譯它,但它沒有sln工程文件。所以我們先要用CMake來自動生成一個freetype.sln文件。CMake你可以自行百度找到,由于博客園提供的空間有限,我這里也不保留CMake的安裝包了。

安裝完CMake后桌面上會有這樣一個圖標。這就是CMake。

 

+BIT祝威+悄悄在此留下版了個權的信息說:

創建FreeType.sln

有了CMake,就可以創建freetype.sln了。

打開CMake,按下圖所示指定所需的參數,即可生成freetype.sln。

 

下面這一步指定Visual Studio。

 

看到"Generating Done"就說明成功了。

 

打開文件夾,可以看到出現了freetype.sln。

 

編譯生成freetype.dll

有了freetype.sln,用visual studio打開,即可編譯生成freetype.lib。但我們需要的是DLL,怎么辦?按照下述步驟即可實現。

修改宏定義

找到ftoption.h文件,如下圖所示,添加兩個宏定義。

1 #define FT_EXPORT(x) __declspec(dllexport) x
2 #define FT_BASE(x) __declspec(dllexport) x

修改項目配置

如下圖所示,在freetype屬性頁,把配置類型和目標文件擴展名改為.dll。

生成freetype.DLL

現在重新編譯項目,就會生成freetype.dll了。

+BIT祝威+悄悄在此留下版了個權的信息說:

C#調用FreeType.dll

效果圖

拿到了freetype.dll,下一步就是在C#里調用它。本篇期望用它得到的效果圖已經貼到本文開頭,這里再貼一下:

字形(Glyph)信息

英文單詞Glyph的意思是"字形;圖象字符;縱溝紋"。這就是字形。

一個字形有3個重要的信息:寬度、高度、基線高度。

如上圖所示,從f到k,這是6個字形。

每個字形都用紅色矩形圍了起來。這些紅色的矩形高度對每個字形都是相同的,只有寬度不同。

每個字形還用黃色矩形圍了起來,這些黃矩形的寬度、高度就是字形的寬度、高度。

圖中還有一個藍色的矩形,它的上邊沿與紅色矩形是重合的(為了方便看,我沒有畫重合),它的下邊沿就是"基線"。對于字母"g",它有一部分在基線上方,一部分在基線下方,在基線上方那部分的高度就稱為"基線高度"。

所以,基線高度就是一個字形的黃色矩形框上邊沿到藍色矩形框下邊沿的距離。有了這個基線高度,各個字形才能整齊地排列到貼圖上。

如果沒有基線高度的概念,你看的到貼圖就會是這個樣子:

與上面的效果圖對比一下,你就知道它們的區別了。

封裝freetype.dll

有了上面的基礎,就可以開始干活了。首先要封裝一些freetype相關的類型。這一步比較枯燥且冗長,我把核心類型放在這里,不想看直接跳過即可。

  1     public abstract class FreeTypeObjectBase<T> : IDisposable where T : class
  2     {
  3         /// <summary>
  4         /// 指針
  5         /// </summary>
  6         public IntPtr pointer;
  7 
  8         /// <summary>
  9         /// 對象
 10         /// </summary>
 11         public T obj;
 12 
 13         public override string ToString()
 14         {
 15             return string.Format("{0}: [{1}]", this.pointer, this.obj);
 16         }
 17 
 18         #region IDisposable Members
 19 
 20         /// <summary>
 21         /// Internal variable which checks if Dispose has already been called
 22         /// </summary>
 23         private Boolean disposed;
 24 
 25         /// <summary>
 26         /// Releases unmanaged and - optionally - managed resources
 27         /// </summary>
 28         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
 29         private void Dispose(Boolean disposing)
 30         {
 31             if (disposed)
 32             {
 33                 return;
 34             }
 35 
 36             if (disposing)
 37             {
 38                 //TODO: Managed cleanup code here, while managed refs still valid
 39             }
 40             //TODO: Unmanaged cleanup code here
 41             ReleaseResource();
 42             this.pointer = IntPtr.Zero;
 43             this.obj = null;
 44 
 45             disposed = true;
 46         }
 47 
 48         /// <summary>
 49         /// Unmanaged cleanup code here
 50         /// </summary>
 51         protected abstract void ReleaseResource();
 52 
 53         /// <summary>
 54         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 55         /// </summary>
 56         public void Dispose()
 57         {
 58             // Call the private Dispose(bool) helper and indicate 
 59             // that we are explicitly disposing
 60             this.Dispose(true);
 61 
 62             // Tell the garbage collector that the object doesn't require any
 63             // cleanup when collected since Dispose was called explicitly.
 64             GC.SuppressFinalize(this);
 65         }
 66 
 67         #endregion
 68 
 69     }
 70 
 71     /// <summary>
 72     /// FreeType庫
 73     /// </summary>
 74     public class FreeTypeLibrary : FreeTypeObjectBase<FT_Library>
 75     {
 76         /// <summary>
 77         /// 初始化FreeType庫
 78         /// </summary>
 79         public FreeTypeLibrary()
 80         {
 81             int ret = FreeTypeAPI.FT_Init_FreeType(out this.pointer);
 82             if (ret != 0) { throw new Exception("Could not init freetype library!"); }
 83 
 84             this.obj = (FT_Library)Marshal.PtrToStructure(this.pointer, typeof(FT_Library));
 85             //lib = Marshal.PtrToStructure<Library>(libptr);
 86         }
 87 
 88         protected override void ReleaseResource()
 89         {
 90             FreeTypeAPI.FT_Done_FreeType(this.pointer);
 91         }
 92 
 93     }
 94 
 95     /// <summary>
 96     /// 初始化字體庫
 97     /// </summary>
 98     public class FreeTypeFace : FreeTypeObjectBase<FT_Face>
 99     {
100 
101         /// <summary>
102         /// 初始化字體庫
103         /// </summary>
104         /// <param name="library"></param>
105         /// <param name="fontFullname"></param>
106         /// <param name="size"></param>
107         public FreeTypeFace(FreeTypeLibrary library, string fontFullname)//, int size)
108         {
109             int retb = FreeTypeAPI.FT_New_Face(library.pointer, fontFullname, 0, out pointer);
110             if (retb != 0) { throw new Exception("Could not open font"); }
111 
112             this.obj = (FT_Face)Marshal.PtrToStructure(pointer, typeof(FT_Face));
113 
114         }
115 
116         /// <summary>
117         /// Unmanaged cleanup code here
118         /// </summary>
119         protected override void ReleaseResource()
120         {
121             FreeTypeAPI.FT_Done_Face(this.pointer);
122         }
123 
124     }
125  
126     /// <summary>
127     /// 把字形轉換為紋理
128     /// </summary>
129     public class FreeTypeBitmapGlyph : FreeTypeObjectBase<FT_BitmapGlyph>
130     {
131         /// <summary>
132         /// char 
133         /// </summary>
134         public char glyphChar;
135         public GlyphRec glyphRec;
136 
137         /// <summary>
138         /// 把字形轉換為紋理    
139         /// </summary>
140         /// <param name="face"></param>
141         /// <param name="c"></param>
142         public FreeTypeBitmapGlyph(FreeTypeFace face, char c, int size)
143         {
144             // Freetype measures the font size in 1/64th of pixels for accuracy 
145             // so we need to request characters in size*64
146             // 設置字符大小?
147             FreeTypeAPI.FT_Set_Char_Size(face.pointer, size << 6, size << 6, 96, 96);
148 
149             // Provide a reasonably accurate estimate for expected pixel sizes
150             // when we later on create the bitmaps for the font
151             // 設置像素大小?
152             FreeTypeAPI.FT_Set_Pixel_Sizes(face.pointer, size, size);
153 
154             // We first convert the number index to a character index
155             // 根據字符獲取其編號
156             int index = FreeTypeAPI.FT_Get_Char_Index(face.pointer, Convert.ToChar(c));
157 
158             // Here we load the actual glyph for the character
159             // 加載此字符的字形
160             int ret = FreeTypeAPI.FT_Load_Glyph(face.pointer, index, FT_LOAD_TYPES.FT_LOAD_DEFAULT);
161             if (ret != 0) { throw new Exception(string.Format("Could not load character '{0}'", Convert.ToChar(c))); }
162             
163             int retb = FreeTypeAPI.FT_Get_Glyph(face.obj.glyphrec, out this.pointer);
164             if (retb != 0) return;
165             glyphRec = (GlyphRec)Marshal.PtrToStructure(face.obj.glyphrec, typeof(GlyphRec));
166 
167             FreeTypeAPI.FT_Glyph_To_Bitmap(out this.pointer, FT_RENDER_MODES.FT_RENDER_MODE_NORMAL, 0, 1);
168             this.obj = (FT_BitmapGlyph)Marshal.PtrToStructure(this.pointer, typeof(FT_BitmapGlyph));
169         }
170 
171         protected override void ReleaseResource()
172         {
173             //throw new NotImplementedException();
174         }
175     }
封裝freetype的核心類型

 

使用freetype時,首先要初始化庫

1             // 初始化FreeType庫:創建FreeType庫指針
2             FreeTypeLibrary library = new FreeTypeLibrary();
3 
4             // 初始化字體庫
5             FreeTypeFace face = new FreeTypeFace(library, this.fontFullname);

 

之后需要獲取一個字形時,就

1                 char c = '&';
2                 int fontHeight = 48;
3                 FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, fontHeight);

glyph里包含了字形的寬度、高度和用byte表示的灰度圖。

字形集->貼圖

得到glyph就可以生成整個貼圖了,代碼如下。

  1     /// <summary>
  2     /// 用一個紋理繪制ASCII表上所有可見字符(具有指定的高度和字體)
  3     /// </summary>
  4     public class ModernSingleTextureFont
  5     {
  6         private string fontFullname;
  7         private char firstChar;
  8         private char lastChar;
  9         private int maxWidth;
 10         private int fontHeight;
 11         private int textureWidth;
 12         private int textureHeight;
 13         Dictionary<char, CharacterInfo> charInfoDict = new Dictionary<char, CharacterInfo>();
 14 
 15         /// <summary>
 16         /// 用一個紋理繪制ASCII表上所有可見字符(具有指定的高度和字體)
 17         /// </summary>
 18         /// <param name="fontFullname"></param>
 19         /// <param name="fontHeight">此值越大,繪制文字的清晰度越高,但占用的紋理資源就越多。</param>
 20         /// <param name="firstChar"></param>
 21         /// <param name="lastChar"></param>
 22         /// <param name="maxWidth">生成的紋理的最大寬度。</param>
 23         public ModernSingleTextureFont(string fontFullname, int fontHeight, char firstChar, char lastChar, int maxWidth)
 24         {
 25             this.fontFullname = fontFullname;
 26             this.fontHeight = fontHeight;
 27             this.firstChar = firstChar;
 28             this.lastChar = lastChar;
 29             this.maxWidth = maxWidth;
 30         }
 31 
 32         public System.Drawing.Bitmap GetBitmap()
 33         {
 34             // 初始化FreeType庫:創建FreeType庫指針
 35             FreeTypeLibrary library = new FreeTypeLibrary();
 36 
 37             // 初始化字體庫
 38             FreeTypeFace face = new FreeTypeFace(library, this.fontFullname);
 39 
 40             GetTextureBlueprint(face, this.fontHeight, this.maxWidth, out this.textureWidth, out this.textureHeight);
 41 
 42             System.Drawing.Bitmap bigBitmap = GetBigBitmap(face, this.maxWidth, this.textureWidth, this.textureHeight);
 43 
 44             face.Dispose();
 45             library.Dispose();
 46 
 47             return bigBitmap;
 48         }
 49 
 50         private System.Drawing.Bitmap GetBigBitmap(FreeTypeFace face, int maxTextureWidth, int widthOfTexture, int heightOfTexture)
 51         {
 52             System.Drawing.Bitmap bigBitmap = new System.Drawing.Bitmap(widthOfTexture, heightOfTexture);
 53             Graphics graphics = Graphics.FromImage(bigBitmap);
 54 
 55             //for (int i = (int)this.firstChar; i <= (int)this.lastChar; i++)
 56             for (char c = this.firstChar; c <= this.lastChar; c++)
 57             {
 58                 //char c = Convert.ToChar(i);
 59                 FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, this.fontHeight);
 60                 bool zeroSize = (glyph.obj.bitmap.rows == 0 && glyph.obj.bitmap.width == 0);
 61                 bool zeroBuffer = glyph.obj.bitmap.buffer == IntPtr.Zero;
 62                 if (zeroSize && (!zeroBuffer)) { throw new Exception(); }
 63                 if ((!zeroSize) && zeroBuffer) { throw new Exception(); }
 64 
 65                 if (!zeroSize)
 66                 {
 67                     int size = glyph.obj.bitmap.width * glyph.obj.bitmap.rows;
 68                     byte[] byteBitmap = new byte[size];
 69                     Marshal.Copy(glyph.obj.bitmap.buffer, byteBitmap, 0, byteBitmap.Length);
 70                     CharacterInfo cInfo;
 71                     if (this.charInfoDict.TryGetValue(c, out cInfo))
 72                     {
 73                         if (cInfo.width > 0)
 74                         {
 75                             System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(cInfo.width, cInfo.height);
 76                             for (int tmpRow = 0; tmpRow < cInfo.height; ++tmpRow)
 77                             {
 78                                 for (int tmpWidth = 0; tmpWidth < cInfo.width; ++tmpWidth)
 79                                 {
 80                                     byte color = byteBitmap[tmpRow * cInfo.width + tmpWidth];
 81                                     bitmap.SetPixel(tmpWidth, tmpRow, Color.FromArgb(color, color, color));
 82                                 }
 83                             }
 84 
 85                             int baseLine = this.fontHeight / 4 * 3;
 86                             graphics.DrawImage(bitmap, cInfo.xoffset,
 87                                 cInfo.yoffset + baseLine - glyph.obj.top);
 88                         }
 89                     }
 90                     else
 91                     { throw new Exception(string.Format("Not support for display the char [{0}]", c)); }
 92                 }
 93 
 94             }
 95 
 96             graphics.Dispose();
 97 
 98             return bigBitmap;
 99         }
100 
101         private void GetTextureBlueprint(FreeTypeFace face, int fontHeight, int maxTextureWidth, out int widthOfTexture, out int heightOfTexture)
102         {
103             widthOfTexture = 0;
104             heightOfTexture = this.fontHeight;
105 
106             int glyphX = 0;
107             int glyphY = 0;
108 
109             for (int i = (int)this.firstChar; i <= (int)this.lastChar; i++)
110             {
111                 char c = Convert.ToChar(i);
112                 FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, fontHeight);
113                 bool zeroSize = (glyph.obj.bitmap.rows == 0 && glyph.obj.bitmap.width == 0);
114                 bool zeroBuffer = glyph.obj.bitmap.buffer == IntPtr.Zero;
115                 if (zeroSize && (!zeroBuffer)) { throw new Exception(); }
116                 if ((!zeroSize) && zeroBuffer) { throw new Exception(); }
117                 if (zeroSize) { continue; }
118 
119                 int glyphWidth = glyph.obj.bitmap.width;
120                 int glyphHeight = glyph.obj.bitmap.rows;
121 
122                 if (glyphX + glyphWidth + 1 > maxTextureWidth)
123                 {
124                     heightOfTexture += this.fontHeight;
125 
126                     glyphX = 0;
127                     glyphY = heightOfTexture - this.fontHeight;
128 
129                     CharacterInfo cInfo = new CharacterInfo();
130                     cInfo.xoffset = glyphX; cInfo.yoffset = glyphY;
131                     cInfo.width = glyphWidth; cInfo.height = glyphHeight;
132                     this.charInfoDict.Add(c, cInfo);
133                 }
134                 else
135                 {
136                     widthOfTexture = Math.Max(widthOfTexture, glyphX + glyphWidth + 1);
137 
138                     CharacterInfo cInfo = new CharacterInfo();
139                     cInfo.xoffset = glyphX; cInfo.yoffset = glyphY;
140                     cInfo.width = glyphWidth; cInfo.height = glyphHeight;
141                     this.charInfoDict.Add(c, cInfo);
142                 }
143 
144                 glyphX += glyphWidth + 1;
145             }
146 
147         }
148 
149     }
生成貼圖

開源TTF2Bmps下載

根據本篇記錄的內容,我寫了TTF2Bmps這個程序,可以將任何一個TTF文件解析為BMP圖片。你可以在此下載

2015-08-08

TTF2Bmps現在能夠獲取所有unicode字符的字形,對輸入方式也做了改進。新版的下載地址在此
下面展示了獲取某些中文字符的情形。
 
下面展示了獲取所有unicode字符的情形。由于整個unicode字符太多,所以只顯示了一部分。
 
改進了UI,可以一次指定多個TTF文件,開始計算后有進度條展示進度。下圖展示的是生成多個TTF文件的漢字部分貼圖。此版下載地址在這里
 

2015-08-10

改進了UI,可以詳細顯示進度。可以生成貼圖對應的Xml文件(包含貼圖每個字形的位置信息)。貼圖增加紅色虛線標識各行的界限。可選擇生成各個字形及其位置信息的列表(多個PNG)。此版下載地址在這里。
 

2015-08-12

特殊處理空格、tab。調整UI。修復若干bug。
 
 
+BIT祝威+悄悄在此留下版了個權的信息說:

總結

有了貼圖,下一步就可以用OpenGL繪制文字了。下回再敘。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()