CSharpGL(41)改进获取字形贴图的方法

在(http://www.cnblogs.com/bitzhuwei/p/CSharpGL-28-simplest-way-to-creating-font-bitmap.html)中我实现了纯C#获取字形贴图的方法。

最近发现这个方法有些缺陷:

  1. 单纯地将每个字形左右两侧的部分剔除,这可能会损失某些信息。例如"新宋体"的小数点和数字宽度几乎是相同的,但是这个方法将小数点的宽度大大减少了。看下图就会发现区别。
  2. 整个方法过程比较长,而我的代码逻辑也没有稳妥地分步进行,显得混乱难看。

于是我重新设计实现了这个方法。

试验

为了完美地得到最终结果,先得确认一些基本的前提条件。

同高?

在之前的实现版本里,用 Graphics.MeasureString() 能够得到任意字符串的Size,但是返回的字形宽度大于字形实际宽度,所以需要缩减一下。这个缩减也是导致缺陷1的原因。

因此我想了这样一个获取字符实际宽度的方法。举例来说,想知道在某Font下字符x的宽度,可以先用 SizeF oneSize = graphics.MeasureString("x", font) 获取这个单字符的宽度(左右有白边),再用 SizeF doubleSize = graphics.MeasureString("xx", font) 获取双字符的宽度(左右有白边),然后 doubleSize.Width - oneSize.Width 就是字符x的实际宽度了。最后用oneSize.Width左右分别去掉相同的空白量,就可以得到紧凑且无损的x的宽度了。

然而这个方法得到的x的高度取谁?oneSize.Height还是doubleSize.Height?如果两者相等(直觉上是),那么没问题了;如果两者不等,该肿么办?

这里第一个试验,就是要试试会不会有不等高的奇葩字符。

 
  1. /// <summary>
  2. /// result: only (char)10 and (char)132 triggers ("!!!!!!!!!!!!!!");
  3. /// </summary>
  4. static void TestIfDoubleCharChangesHeight()
  5. {
  6. var font = new Font("Arial", );
  7. using (var bmp = new Bitmap(, ))
  8. {
  9. using (var graphics = Graphics.FromImage(bmp))
  10. {
  11. for (int i = ; i <= char.MaxValue; i++)
  12. {
  13. SizeF oneSize = graphics.MeasureString(string.Format("{0}", (char)i), font);
  14. SizeF doubleSize = graphics.MeasureString(string.Format("{0}{0}", (char)i), font);
  15. if (oneSize.Height != doubleSize.Height)
  16. {
  17. Console.WriteLine("!!!!!!!!!!!!!!");
  18. }
  19. }
  20. }
  21. }
  22. }

试验结果证明只有(char)10 和 (char)132这两个特殊字符是不符合直觉的。所幸这2个字符是特殊字符,不可见的。所以以后直接忽略(跳过)即可。

同高?

仍然是个同高的问题,即:同一Font下的所有字形,通过 Graphics.MeasureString() 获得的高度都相同吗?直觉上是相同的,还是试验让你眼见为实。

  1. /// <summary>
  2. /// 有2种高度
  3. /// </summary>
  4. private static void TestIfAllHeightSame()
  5. {
  6. var font = new Font("Arial", );
  7. var heightDict = new Dictionary<float, List<char>>();
  8.  
  9. using (var bmp = new Bitmap(, ))
  10. {
  11. using (var graphics = Graphics.FromImage(bmp))
  12. {
  13. for (int i = ; i <= char.MaxValue; i++)
  14. {
  15. SizeF oneSize = graphics.MeasureString(string.Format("{0}", (char)i), font);
  16. SizeF doubleSize = graphics.MeasureString(string.Format("{0}{0}", (char)i), font);
  17. if (oneSize.Height != doubleSize.Height) { continue; }
  18.  
  19. if (heightDict.ContainsKey(oneSize.Height))
  20. {
  21. heightDict[oneSize.Height].Add((char)i);
  22. }
  23. else
  24. {
  25. heightDict.Add(oneSize.Height, new List<char>((char)i));
  26. }
  27. }
  28. }
  29. }
  30.  
  31. Console.WriteLine("{0} heights", heightDict.Count);
  32. }

试验以Arial字体为例,结果出现了2种高度的字形。这说明,一般普遍的,同一Font下的所有字形,通过 Graphics.MeasureString() 获得的高度是不同的。(不过相差不会大)

左右空白相等?

在第一个"同高"试验里,我说"最后用oneSize.Width左右分别去掉相同的空白量,就可以得到紧凑且无损的x的宽度了。"。这里包含一个假设,就是任意字符,其左右两侧的空白都是相等的。那么果真这么美好吗?试验让你眼见为实。

  1. private static void PrintAllUnicodeChars()
  2. {
  3. var font = new Font("Arial", );
  4. using (var bmp = new Bitmap(, ))
  5. {
  6. using (var graphics = Graphics.FromImage(bmp))
  7. {
  8. for (int i = ; i <= char.MaxValue; i++)
  9. {
  10. Console.WriteLine("Processing {0}/{1}", i, char.MaxValue);
  11. Size oneSize = graphics.MeasureString(string.Format("{0}", (char)i), font).ToSize();
  12. Size doubleSize = graphics.MeasureString(string.Format("{0}{0}", (char)i), font).ToSize();
  13.  
  14. if (oneSize.Height != doubleSize.Height) { continue; }
  15. if (oneSize.Width >= doubleSize.Width) { continue; }
  16.  
  17. Size charSize = new Size(doubleSize.Width - oneSize.Width, oneSize.Height);
  18. string dirName = string.Format("{0}x{1}", charSize.Width, charSize.Height);
  19. if (!Directory.Exists(dirName)) { Directory.CreateDirectory(dirName); }
  20.  
  21. using (var oneBitmap = new Bitmap(oneSize.Width, oneSize.Height))
  22. {
  23. using (var g = Graphics.FromImage(oneBitmap))
  24. { g.DrawString(string.Format("{0}", (char)i), font, Brushes.Red, , ); }
  25.  
  26. using (var charBitmap = new Bitmap(charSize.Width, charSize.Height))
  27. {
  28. using (var g = Graphics.FromImage(charBitmap))
  29. {
  30. g.DrawImage(oneBitmap, -(oneSize.Width - charSize.Width) / , );
  31. }
  32.  
  33. charBitmap.Save(string.Format(@"{0}x{1}\{2}.png", charSize.Width, charSize.Height, i));
  34. }
  35. }
  36. }
  37. }
  38. }
  39. }

这个试验的代码会把所有Unicode字符都保存为一个单独的png图片,且相同大小的字符保存到同一目录下。

还是以Arial字体为例,高度只有52、54两种,宽度出现了128种。逐个打开这些文件夹查看,我是没有发现被截肢的字形。(其实我就挑着看了几个,而且很多国家的文字我不认识)

开工

上面的试验说明我已经可以用oneSize/doubleSize的方法获取一个紧凑无损的字形。那么剩下的就是好好梳理整个流程了。

  1. /// <summary>
  2. /// Gets a <see cref="FontBitmap"/>'s intance.
  3. /// </summary>
  4. /// <param name="font">建议最大字体不超过32像素高度,否则可能无法承载所有Unicode字符。</param>
  5. /// <param name="charSet"></param>
  6. /// <param name="drawBoundary"></param>
  7. /// <returns></returns>
  8. public static FontBitmap GetFontBitmap(this Font font, string charSet, bool drawBoundary = false)
  9. {
  10. var fontBitmap = new FontBitmap();// font, glyph dict, bitmap
  11. fontBitmap.GlyphFont = font;
  12. // 先获取各个glyph的width和height
  13. fontBitmap.GlyphInfoDictionary = GetGlyphDict(font, charSet);
  14. // 获取所有glyph的面积之和,开方得到最终贴图的宽度textureWidth
  15. int textureWidth = GetTextureWidth(fontBitmap.GlyphInfoDictionary);
  16. // 以所有glyph中height最大的为标准高度
  17. fontBitmap.GlyphHeight = GetGlyphHeight(fontBitmap.GlyphInfoDictionary, textureWidth);
  18. // 摆放glyph,得到x偏移和y偏移量,同时顺便得到最终贴图的高度textureHeight
  19. int textureHeight = LayoutGlyphs(fontBitmap.GlyphInfoDictionary, textureWidth, fontBitmap.GlyphHeight);
  20. // 根据glyph的摆放位置,生成最终的贴图
  21. fontBitmap.GlyphBitmap = PaintTexture(textureWidth, textureHeight, fontBitmap.GlyphInfoDictionary, font);
  22.  
  23. return fontBitmap;
  24. }

对于"新宋体"的ASCII码,会得到这样的贴图:

如果想观察各个glyph的偏移量和宽高,就是这样的:

你可以注意到"小数点"终于和数字"0"到"9"是一样的宽度了。真正的紧凑且无损。

下载

CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

总结

下面是"新宋体"Unicode的前面若干字形。

CSharpGL(41)改进获取字形贴图的方法的更多相关文章

  1. CSharpGL(28)得到高精度可定制字形贴图的极简方法

    CSharpGL(28)得到高精度可定制字形贴图的极简方法 回顾 以前我用SharpFont实现了解析TTF文件从而获取字形贴图的功能,并最终实现了用OpenGL渲染文字. 使用SharpFont,美 ...

  2. C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

    C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图 +BIT祝威+悄悄在此留下版了个权的信息说: 最近需要用OpenGL绘制文字,这是个很费时费力的事.一般的思路就是 ...

  3. 关于 MaxScript 获取所有贴图

    相关内容记录在官方文档 BitmapTexture : TextureMap 中 fn allUsedMaps = ( sceneMaps = usedMaps() for m in meditmat ...

  4. iOS启动图-从网络获取的gif图,在本地一直是没有动画,还模糊的

    背景介绍:APP启动页,常有静态图加链接,gif加链接,短视频等几种形式.我们APP前期只有静态图这一种,功能已经实现.之后,有了添加gif的需求,按理说,只要添加一个类型判断,按照数据类型,通过不同 ...

  5. iOS 获取LaunchImage启动图

    iOS开发中,LaunchImage图片会根据手机机型的不同,自动匹配对应的图片,而我们如果想要拿到对应的图片,无法直接通过图片的名字获取该启动图,而需要通过以下方式 + (NSString *)ge ...

  6. 免费获取思维导图Mindmaster会员教程

    免费获取思维导图Mindmaster会员教程 步骤一.下载安装 搜索亿图官方网页,下载安装免费版Mindmaster思维导图. 步骤二.点击登录 打开Mindmaster思维导图,点击登录,可以使用微 ...

  7. JS快速获取图片宽高的方法

    快速获取图片的宽高其实是为了预先做好排版样式布局做准备,通过快速获取图片宽高的方法比onload方法要节省很多时间,甚至一分钟以上都有可能,并且这种方法适用主流浏览器包括IE低版本浏览器. 我们一步一 ...

  8. WPF 获取程序路径的一些方法,根据程序路径获取程序集信息

    一.WPF 获取程序路径的一些方法方式一 应用程序域 //获取基目录即当前工作目录 string str_1 = System.AppDomain.CurrentDomain.BaseDirector ...

  9. 转载:JS快速获取图片宽高的方法

    快速获取图片的宽高其实是为了预先做好排版样式布局做准备,通过快速获取图片宽高的方法比onload方法要节省很多时间,甚至一分钟以上都有可能,并且这种方法适用主流浏览器包括IE低版本浏览器. 我们一步一 ...

随机推荐

  1. Android SDK教程

    Android SDK 网络问题解析 Android 客户端网络不稳定,会导致App 有时候无法及时收到 Push 消息. 很多开发者认为这是因为 JPush 推送不稳定.延迟,甚至有时候认为 JPu ...

  2. abstract和interface的区别

    abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力. abstract class和inte ...

  3. java基础:数组查询,同一数组一个元素最多出现两次

  4. AtomicInteger源码分析——基于CAS的乐观锁实

    1. 悲观锁与乐观锁 我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换.切换涉及 ...

  5. 2729: [HNOI2012]排队

    2729: [HNOI2012]排队 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 957  Solved: 449[Submit][Status] ...

  6. tomcat升级,tomcat窗体改名,一台电脑安装多版本JDK

    1 tomcat改名:在bin目录下找到次文件(如图),按图上指示修改(比如我窗体是主数据) 修改后: 2 一台电脑安装多个版本的JDK 为什么我们要安装多个版本JDK?--我是因为tomcat修复漏 ...

  7. .NET Core中的包、元包与框架

    本文为翻译文章,原文:Packages, Metapackages and Frameworks .NET Core是一个由NuGet包组成的平台.一些产品受益于细粒度包的定义,也有一些受益于粗粒度包 ...

  8. final 、finally 和 finalize()的区别

    1. final 是一个关键字.可以修饰数据.方法.类. 1)final 数据:final 用来修饰一个永不改变的编译时常量,或者运行时初始化但是不希望被改变的常量.一个既是 static又是 fin ...

  9. Knockoutjs : Unable to process binding "value:

    刚刚自学knockoutjs,老是碰到稀奇古怪的问题. 在自学knockout.js的时候经常遇到 Unable to process binding "value:的问题.目前总结了以下几 ...

  10. 在WPF应用程序中使用Font Awesome图标

    Font Awesome 在网站开发中,经常用到.今天介绍如何在WPF应用程序中使用Font Awesome . 如果是自定义的图标字体,使用方法相同. 下载图标字体 在官方网站或github上下载资 ...