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


    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", 32);
     7             using (var bmp = new Bitmap(1, 1))
     8             {
     9                 using (var graphics = Graphics.FromImage(bmp))
    10                 {
    11                     for (int i = 0; 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", 32);
     7             var heightDict = new Dictionary<float, List<char>>();
     8 
     9             using (var bmp = new Bitmap(1, 1))
    10             {
    11                 using (var graphics = Graphics.FromImage(bmp))
    12                 {
    13                     for (int i = 0; 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", 32);
     4             using (var bmp = new Bitmap(1, 1))
     5             {
     6                 using (var graphics = Graphics.FromImage(bmp))
     7                 {
     8                     for (int i = 0; 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, 0, 0); }
    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) / 2, 0);
    31                                 }
    32 
    33                                 charBitmap.Save(string.Format(@"{0}x{1}{2}.png", charSize.Width, charSize.Height, i));
    34                             }
    35                         }
    36                     }
    37                 }
    38             }
    39         }
    View Code

    这个试验的代码会把所有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的前面若干字形。

  • 相关阅读:
    【bzoj3172】 Tjoi2013—单词
    【bzoj2434】 Noi2011—阿狸的打字机
    【bzoj1030】 JSOI2007—文本生成器
    【bzoj2001】 Hnoi2010—City 城市建设
    【bzoj3196】 Tyvj1730—二逼平衡树
    【bzoj3932】 CQOI2015—任务查询系统
    【bzoj3224】 Tyvj1728—普通平衡树
    【bzoj3514】 Codechef MARCH14 GERALD07加强版
    maven build脚本笔记
    jvm参数
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-41-improve-glyph-generation.html
Copyright © 2020-2023  润新知