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文件,如下图所示,添加两个宏定义。

 #define FT_EXPORT(x) __declspec(dllexport) x
#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相关的类型。这一步比较枯燥且冗长,我把核心类型放在这里,不想看直接跳过即可。

     public abstract class FreeTypeObjectBase<T> : IDisposable where T : class
{
/// <summary>
/// 指针
/// </summary>
public IntPtr pointer; /// <summary>
/// 对象
/// </summary>
public T obj; public override string ToString()
{
return string.Format("{0}: [{1}]", this.pointer, this.obj);
} #region IDisposable Members /// <summary>
/// Internal variable which checks if Dispose has already been called
/// </summary>
private Boolean disposed; /// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(Boolean disposing)
{
if (disposed)
{
return;
} if (disposing)
{
//TODO: Managed cleanup code here, while managed refs still valid
}
//TODO: Unmanaged cleanup code here
ReleaseResource();
this.pointer = IntPtr.Zero;
this.obj = null; disposed = true;
} /// <summary>
/// Unmanaged cleanup code here
/// </summary>
protected abstract void ReleaseResource(); /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
// Call the private Dispose(bool) helper and indicate
// that we are explicitly disposing
this.Dispose(true); // Tell the garbage collector that the object doesn't require any
// cleanup when collected since Dispose was called explicitly.
GC.SuppressFinalize(this);
} #endregion } /// <summary>
/// FreeType库
/// </summary>
public class FreeTypeLibrary : FreeTypeObjectBase<FT_Library>
{
/// <summary>
/// 初始化FreeType库
/// </summary>
public FreeTypeLibrary()
{
int ret = FreeTypeAPI.FT_Init_FreeType(out this.pointer);
if (ret != ) { throw new Exception("Could not init freetype library!"); } this.obj = (FT_Library)Marshal.PtrToStructure(this.pointer, typeof(FT_Library));
//lib = Marshal.PtrToStructure<Library>(libptr);
} protected override void ReleaseResource()
{
FreeTypeAPI.FT_Done_FreeType(this.pointer);
} } /// <summary>
/// 初始化字体库
/// </summary>
public class FreeTypeFace : FreeTypeObjectBase<FT_Face>
{ /// <summary>
/// 初始化字体库
/// </summary>
/// <param name="library"></param>
/// <param name="fontFullname"></param>
/// <param name="size"></param>
public FreeTypeFace(FreeTypeLibrary library, string fontFullname)//, int size)
{
int retb = FreeTypeAPI.FT_New_Face(library.pointer, fontFullname, , out pointer);
if (retb != ) { throw new Exception("Could not open font"); } this.obj = (FT_Face)Marshal.PtrToStructure(pointer, typeof(FT_Face)); } /// <summary>
/// Unmanaged cleanup code here
/// </summary>
protected override void ReleaseResource()
{
FreeTypeAPI.FT_Done_Face(this.pointer);
} } /// <summary>
/// 把字形转换为纹理
/// </summary>
public class FreeTypeBitmapGlyph : FreeTypeObjectBase<FT_BitmapGlyph>
{
/// <summary>
/// char
/// </summary>
public char glyphChar;
public GlyphRec glyphRec; /// <summary>
/// 把字形转换为纹理
/// </summary>
/// <param name="face"></param>
/// <param name="c"></param>
public FreeTypeBitmapGlyph(FreeTypeFace face, char c, int size)
{
// Freetype measures the font size in 1/64th of pixels for accuracy
// so we need to request characters in size*64
// 设置字符大小?
FreeTypeAPI.FT_Set_Char_Size(face.pointer, size << , size << , , ); // Provide a reasonably accurate estimate for expected pixel sizes
// when we later on create the bitmaps for the font
// 设置像素大小?
FreeTypeAPI.FT_Set_Pixel_Sizes(face.pointer, size, size); // We first convert the number index to a character index
// 根据字符获取其编号
int index = FreeTypeAPI.FT_Get_Char_Index(face.pointer, Convert.ToChar(c)); // Here we load the actual glyph for the character
// 加载此字符的字形
int ret = FreeTypeAPI.FT_Load_Glyph(face.pointer, index, FT_LOAD_TYPES.FT_LOAD_DEFAULT);
if (ret != ) { throw new Exception(string.Format("Could not load character '{0}'", Convert.ToChar(c))); } int retb = FreeTypeAPI.FT_Get_Glyph(face.obj.glyphrec, out this.pointer);
if (retb != ) return;
glyphRec = (GlyphRec)Marshal.PtrToStructure(face.obj.glyphrec, typeof(GlyphRec)); FreeTypeAPI.FT_Glyph_To_Bitmap(out this.pointer, FT_RENDER_MODES.FT_RENDER_MODE_NORMAL, , );
this.obj = (FT_BitmapGlyph)Marshal.PtrToStructure(this.pointer, typeof(FT_BitmapGlyph));
} protected override void ReleaseResource()
{
//throw new NotImplementedException();
}
}

封装freetype的核心类型

使用freetype时,首先要初始化库

             // 初始化FreeType库:创建FreeType库指针
FreeTypeLibrary library = new FreeTypeLibrary(); // 初始化字体库
FreeTypeFace face = new FreeTypeFace(library, this.fontFullname);

之后需要获取一个字形时,就

                 char c = '&';
int fontHeight = ;
FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, fontHeight);

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

字形集->贴图

得到glyph就可以生成整个贴图了,代码如下。

     /// <summary>
/// 用一个纹理绘制ASCII表上所有可见字符(具有指定的高度和字体)
/// </summary>
public class ModernSingleTextureFont
{
private string fontFullname;
private char firstChar;
private char lastChar;
private int maxWidth;
private int fontHeight;
private int textureWidth;
private int textureHeight;
Dictionary<char, CharacterInfo> charInfoDict = new Dictionary<char, CharacterInfo>(); /// <summary>
/// 用一个纹理绘制ASCII表上所有可见字符(具有指定的高度和字体)
/// </summary>
/// <param name="fontFullname"></param>
/// <param name="fontHeight">此值越大,绘制文字的清晰度越高,但占用的纹理资源就越多。</param>
/// <param name="firstChar"></param>
/// <param name="lastChar"></param>
/// <param name="maxWidth">生成的纹理的最大宽度。</param>
public ModernSingleTextureFont(string fontFullname, int fontHeight, char firstChar, char lastChar, int maxWidth)
{
this.fontFullname = fontFullname;
this.fontHeight = fontHeight;
this.firstChar = firstChar;
this.lastChar = lastChar;
this.maxWidth = maxWidth;
} public System.Drawing.Bitmap GetBitmap()
{
// 初始化FreeType库:创建FreeType库指针
FreeTypeLibrary library = new FreeTypeLibrary(); // 初始化字体库
FreeTypeFace face = new FreeTypeFace(library, this.fontFullname); GetTextureBlueprint(face, this.fontHeight, this.maxWidth, out this.textureWidth, out this.textureHeight); System.Drawing.Bitmap bigBitmap = GetBigBitmap(face, this.maxWidth, this.textureWidth, this.textureHeight); face.Dispose();
library.Dispose(); return bigBitmap;
} private System.Drawing.Bitmap GetBigBitmap(FreeTypeFace face, int maxTextureWidth, int widthOfTexture, int heightOfTexture)
{
System.Drawing.Bitmap bigBitmap = new System.Drawing.Bitmap(widthOfTexture, heightOfTexture);
Graphics graphics = Graphics.FromImage(bigBitmap); //for (int i = (int)this.firstChar; i <= (int)this.lastChar; i++)
for (char c = this.firstChar; c <= this.lastChar; c++)
{
//char c = Convert.ToChar(i);
FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, this.fontHeight);
bool zeroSize = (glyph.obj.bitmap.rows == && glyph.obj.bitmap.width == );
bool zeroBuffer = glyph.obj.bitmap.buffer == IntPtr.Zero;
if (zeroSize && (!zeroBuffer)) { throw new Exception(); }
if ((!zeroSize) && zeroBuffer) { throw new Exception(); } if (!zeroSize)
{
int size = glyph.obj.bitmap.width * glyph.obj.bitmap.rows;
byte[] byteBitmap = new byte[size];
Marshal.Copy(glyph.obj.bitmap.buffer, byteBitmap, , byteBitmap.Length);
CharacterInfo cInfo;
if (this.charInfoDict.TryGetValue(c, out cInfo))
{
if (cInfo.width > )
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(cInfo.width, cInfo.height);
for (int tmpRow = ; tmpRow < cInfo.height; ++tmpRow)
{
for (int tmpWidth = ; tmpWidth < cInfo.width; ++tmpWidth)
{
byte color = byteBitmap[tmpRow * cInfo.width + tmpWidth];
bitmap.SetPixel(tmpWidth, tmpRow, Color.FromArgb(color, color, color));
}
} int baseLine = this.fontHeight / * ;
graphics.DrawImage(bitmap, cInfo.xoffset,
cInfo.yoffset + baseLine - glyph.obj.top);
}
}
else
{ throw new Exception(string.Format("Not support for display the char [{0}]", c)); }
} } graphics.Dispose(); return bigBitmap;
} private void GetTextureBlueprint(FreeTypeFace face, int fontHeight, int maxTextureWidth, out int widthOfTexture, out int heightOfTexture)
{
widthOfTexture = ;
heightOfTexture = this.fontHeight; int glyphX = ;
int glyphY = ; for (int i = (int)this.firstChar; i <= (int)this.lastChar; i++)
{
char c = Convert.ToChar(i);
FreeTypeBitmapGlyph glyph = new FreeTypeBitmapGlyph(face, c, fontHeight);
bool zeroSize = (glyph.obj.bitmap.rows == && glyph.obj.bitmap.width == );
bool zeroBuffer = glyph.obj.bitmap.buffer == IntPtr.Zero;
if (zeroSize && (!zeroBuffer)) { throw new Exception(); }
if ((!zeroSize) && zeroBuffer) { throw new Exception(); }
if (zeroSize) { continue; } int glyphWidth = glyph.obj.bitmap.width;
int glyphHeight = glyph.obj.bitmap.rows; if (glyphX + glyphWidth + > maxTextureWidth)
{
heightOfTexture += this.fontHeight; glyphX = ;
glyphY = heightOfTexture - this.fontHeight; CharacterInfo cInfo = new CharacterInfo();
cInfo.xoffset = glyphX; cInfo.yoffset = glyphY;
cInfo.width = glyphWidth; cInfo.height = glyphHeight;
this.charInfoDict.Add(c, cInfo);
}
else
{
widthOfTexture = Math.Max(widthOfTexture, glyphX + glyphWidth + ); CharacterInfo cInfo = new CharacterInfo();
cInfo.xoffset = glyphX; cInfo.yoffset = glyphY;
cInfo.width = glyphWidth; cInfo.height = glyphHeight;
this.charInfoDict.Add(c, cInfo);
} glyphX += glyphWidth + ;
} } }

生成贴图

开源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绘制文字了。下回再叙。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图的更多相关文章

  1. C#+OpenGL+FreeType显示3D文字(3) - 用PointSprite绘制文字

    C#+OpenGL+FreeType显示3D文字(3) - 用PointSprite绘制文字 上一篇实现了把文字绘制到OpenGL窗口,但实质上只是把含有文字的贴图贴到矩形模型上.本篇我们介绍用Poi ...

  2. C#+OpenGL+FreeType显示3D文字(2) - 用GLSL+VBO绘制文字

    C#+OpenGL+FreeType显示3D文字(2) - 用GLSL+VBO绘制文字 +BIT祝威+悄悄在此留下版了个权的信息说: 上一篇得到了字形贴图及其位置字典(可导出为XML).本篇就利用此贴 ...

  3. windows下3D文字

    windows下3D文字 简单概述 需要在每一帧的视频图像上面添加3D文字,文字可以自由移动位置,变换各种字体属性,还能进行一些简单动画.然后把处理好的视频图像传个下一个步骤去处理.做的过程中参考了G ...

  4. [OpenGL ES 03]3D变换:模型,视图,投影与Viewport

    [OpenGL ES 03]3D变换:模型,视图,投影与Viewport 罗朝辉 (http://blog.csdn.net/kesalin) 本文遵循“署名-非商业用途-保持一致”创作公用协议 系列 ...

  5. [iTyran原创]iPhone中OpenGL ES显示3DS MAX模型之二:lib3ds加载模型

    [iTyran原创]iPhone中OpenGL ES显示3DS MAX模型之二:lib3ds加载模型 作者:u0u0 - iTyran 在上一节中,我们分析了OBJ格式.OBJ格式优点是文本形式,可读 ...

  6. [iTyran原创]iPhone中OpenGL ES显示3DS MAX模型之一:OBJ格式分析

    [iTyran原创]iPhone中OpenGL ES显示3DS MAX模型之一:OBJ文件格式分析作者:yuezang - iTyran     在iOS的3D开发中常常需要导入通过3DS MAX之类 ...

  7. 使用Three.js实现神奇的3D文字悬浮效果

    声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 背景 在 Three.js Journey 课程示例中,提供了一个使用 Thre ...

  8. Windows MFC 两个OpenGL窗口显示与线程RC问题

    问题为:背景界面是一个OpenGL窗口(对话框),在其上弹出一个OpenGL窗口(模态对话框)时, 1.上方的OpenGL窗口能响应鼠标操作等并刷新: 2.当移动或放大缩小上方的OpenGL窗口时,其 ...

  9. HTML5/CSS3(PrefixFree.js) 3D文字特效

    之前在园子里看到一个HTML5/CSS3的文字特效(这里),觉得挺好玩的所以小小的研究了下,不过发现代码都是针对webkit以及FF的所以IE跪了. Runjs 我将示例中的代码进行了精简,后来发现C ...

随机推荐

  1. CentOS 7 安装后没有ifconfig命令

    /bin,/sbin,/usr/bin,/usr/sbin下面都没有ifconfig命令. 执行命令  yum install net-tools 即可.

  2. 学习微信小程序之css11内外边距集合

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. flume使用示例

    flume的特点: flume是一个分布式.可靠.和高可用的海量日志采集.聚合和传输的系统.支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受 ...

  4. Daily Scrum Meeting ——FifthDay(Beta)12.13

    一.Daily Scrum Meeting照片 二.Burndown Chart 三.项目进展(check-in) 1.制作注册分流的头像 发布者头像 参与者头像 2.完成参与者上传头像的功能:通过本 ...

  5. Nodemanager Out of heap memory[fix bug全过程]

    问题: 自己写了一个yarn上的application,发现nodemanager过段时间,会out of memory退出,把nodemanager的heap memory从1G增大到2G也是无法避 ...

  6. 网站开发网页广告条不显示,出现ERR_BLOCKED_BY_CLIENT

    原因: 原因很简单,是因为被360浏览器的ad block给屏蔽掉了. 现象: 解决方案: 更换图片文件名,去掉ad,改成其他的字符.

  7. javaWeb高级编程(1)

    十月 24, 2016 10:41:43 上午 org.apache.catalina.core.StandardContext setPath警告: A context path must eith ...

  8. 【BZOJ2442】 [Usaco2011 Open]修剪草坪 斜率优化DP

    第一次斜率优化. 大致有两种思路: 1.f[i]表示第i个不选的最优情况(最小损失和)f[i]=f[j]+e[i] 显然n^2会T,但是可以发现f的移动情况可以用之前单调队列优化,就优化成O(n)的了 ...

  9. Golang之chan/goroutine(转)

    原文地址:http://tchen.me/posts/2014-01-27-golang-chatroom.html?utm_source=tuicool&utm_medium=referra ...

  10. Linux上Tomcat部署JavaWeb项目

    一.安装JDK 配置java的环境变量,修改/etc/profile文件:vi /etc/profile 然后按下字母i进入插入模式, shift+insert粘贴; esc退出编辑; :wq保存退出 ...