CSharpGL(28)得到高精度可定制字形贴图的极简方法
CSharpGL(28)得到高精度可定制字形贴图的极简方法
回顾
以前我用SharpFont实现了解析TTF文件从而获取字形贴图的功能,并最终实现了用OpenGL渲染文字。
使用SharpFont,美中不足的是:
SharpFont太大了,有上千行代码,且逻辑复杂难懂。
SharpFont画出的字形精度有限,虽然也很高,但是确实有限。用OpenGL渲染出来后会发现边缘不是特别清晰。
SharpFont对加粗、斜体、下划线、删除线如何支持,能否支持?完全不知道。
Graphics+Font
最近我在分析GLGUI(https://github.com/bitzhuwei/GLGUI)的代码时,惊喜地发现它给出了一个极其简单的方案,就是SizeF MeasureString(string text, Font font);和DrawString(string s, Font font, Brush brush, float x, float y);。
Graphics.MeasureString()能够得到任意字符串的Size。
Graphics.DrawString()能把任意字符串写到Bitmap上。
单个字形
首先我们要得到每个字形的Size。
由于MeasureString()返回的字形宽度大于字形实际宽度,所以需要缩减一下。
1 /// <summary> 2 /// Get glyph's size by graphics.MeasureString(). 3 /// Then shrink glyph's size. 4 /// xoffset now means offset in a single glyph's bitmap. 5 /// </summary> 6 /// <param name="fontBitmap"></param> 7 /// <param name="charSet"></param> 8 /// <param name="singleCharWidth"></param> 9 /// <param name="singleCharHeight"></param> 10 private static void PrepareInitialGlyphDict(FontBitmap fontBitmap, string charSet, out int singleCharWidth, out int singleCharHeight) 11 { 12 // Get glyph's size by graphics.MeasureString(). 13 { 14 int maxWidth = 0, maxHeight = 0; 15 16 float fontSize = fontBitmap.GlyphFont.Size; 17 18 using (var bitmap = new Bitmap(1, 1, PixelFormat.Format24bppRgb)) 19 { 20 using (Graphics graphics = Graphics.FromImage(bitmap)) 21 { 22 foreach (char c in charSet) 23 { 24 SizeF size = graphics.MeasureString(c.ToString(), fontBitmap.GlyphFont); 25 var info = new GlyphInfo(0, 0, (int)size.Width, (int)size.Height); 26 fontBitmap.GlyphInfoDictionary.Add(c, info); 27 if (maxWidth < (int)size.Width) { maxWidth = (int)size.Width; } 28 if (maxHeight < (int)size.Height) { maxHeight = (int)size.Height; } 29 } 30 } 31 } 32 singleCharWidth = maxWidth; 33 singleCharHeight = maxHeight; 34 } 35 // shrink glyph's size. 36 // xoffset now means offset in a single glyph's bitmap. 37 { 38 using (var bitmap = new Bitmap(singleCharWidth, singleCharHeight)) 39 { 40 using (var graphics = Graphics.FromImage(bitmap)) 41 { 42 Color clearColor = Color.FromArgb(0, 0, 0, 0); 43 foreach (var item in fontBitmap.GlyphInfoDictionary) 44 { 45 if (item.Key == ' ' || item.Key == '\t' || item.Key == '\r' || item.Key == '\n') { continue; } 46 47 graphics.Clear(clearColor); 48 graphics.DrawString(item.Key.ToString(), fontBitmap.GlyphFont, Brushes.White, 0, 0); 49 BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 50 RetargetGlyphRectangleInwards(data, item.Value); 51 bitmap.UnlockBits(data); 52 } 53 } 54 } 55 } 56 } 57 /// <summary> 58 /// Returns true if the given pixel is empty (i.e. black) 59 /// </summary> 60 /// <param name="bitmapData"></param> 61 /// <param name="x"></param> 62 /// <param name="y"></param> 63 private static unsafe bool IsEmptyPixel(BitmapData bitmapData, int x, int y) 64 { 65 var addr = (byte*)(bitmapData.Scan0) + bitmapData.Stride * y + x * 3; 66 return (*addr == 0 && *(addr + 1) == 0 && *(addr + 2) == 0); 67 } 68 69 /// <summary> 70 /// shrink glyph's width to fit in exactly. 71 /// </summary> 72 /// <param name="bitmapData"></param> 73 /// <param name="glyph"></param> 74 private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, GlyphInfo glyph) 75 { 76 int startX, endX; 77 78 { 79 bool done = false; 80 for (startX = glyph.xoffset; startX < bitmapData.Width; startX++) 81 { 82 for (int j = glyph.yoffset; j < glyph.yoffset + glyph.height; j++) 83 { 84 if (!IsEmptyPixel(bitmapData, startX, j)) 85 { 86 done = true; 87 break; 88 } 89 } 90 if (done) { break; } 91 } 92 } 93 { 94 bool done = false; 95 for (endX = glyph.xoffset + glyph.width - 1; endX >= 0; endX--) 96 { 97 for (int j = glyph.yoffset; j < glyph.yoffset + glyph.height; j++) 98 { 99 if (!IsEmptyPixel(bitmapData, endX, j)) 100 { 101 done = true; 102 break; 103 } 104 } 105 if (done) { break; } 106 } 107 } 108 109 if (endX < startX) 110 { 111 //startX = endX = glyph.xoffset; 112 glyph.width = 0; 113 } 114 else 115 { 116 glyph.xoffset = startX; 117 glyph.width = endX - startX + 1; 118 } 119 }
PrepareInitialGlyphDict
如下图所示,这是经过这一步后得到的字形信息:height、width和xoffset。这里xoffset暂时描述了单个字形的左边距,在最后,xoffset会描述字形左上角在整个贴图中的位置。
最后贴图的Size
由于在创建Bitmap对象时就得指定它的Size,所以这一步要先算出这个Size。
为了能够尽可能使用最小的贴图,我们按下图所示的方式依次排布所有字形。
如上图所示,每个黑框代表一个字形,尽量按正方形来排布,结束后就能得到所需的Size(width和height)
制作贴图
万事俱备,可以创建贴图了。
按照上一步的方式来排布各个字形,并且这次真的把字形贴上去。
1 /// <summary> 2 /// Print the final bitmap that contains all glyphs. 3 /// And also setup glyph's xoffset, yoffset. 4 /// </summary> 5 /// <param name="fontBitmap"></param> 6 /// <param name="singleCharWidth"></param> 7 /// <param name="singleCharHeight"></param> 8 /// <param name="width"></param> 9 /// <param name="height"></param> 10 private static void PrintBitmap(FontBitmap fontBitmap, int singleCharWidth, int singleCharHeight, int width, int height) 11 { 12 var bitmap = new Bitmap(width, height); 13 using (var graphics = Graphics.FromImage(bitmap)) 14 { 15 using (var glyphBitmap = new Bitmap(singleCharWidth, singleCharHeight)) 16 { 17 using (var glyphGraphics = Graphics.FromImage(glyphBitmap)) 18 { 19 int currentX = leftMargin, currentY = 0; 20 Color clearColor = Color.FromArgb(0, 0, 0, 0); 21 foreach (KeyValuePair<char, GlyphInfo> item in fontBitmap.GlyphInfoDictionary) 22 { 23 glyphGraphics.Clear(clearColor); 24 glyphGraphics.DrawString(item.Key.ToString(), fontBitmap.GlyphFont, 25 Brushes.White, 0, 0); 26 // move to new line if this line is full. 27 if (currentX + item.Value.width > width) 28 { 29 currentX = leftMargin; 30 currentY += singleCharHeight; 31 } 32 // draw the current glyph. 33 graphics.DrawImage(glyphBitmap, 34 new Rectangle(currentX, currentY, item.Value.width, item.Value.height), 35 item.Value.ToRectangle(), 36 GraphicsUnit.Pixel); 37 // move line cursor to next(right) position. 38 item.Value.xoffset = currentX; 39 item.Value.yoffset = currentY; 40 // prepare for next glyph's position. 41 currentX += item.Value.width + glyphInterval; 42 } 43 } 44 } 45 } 46 47 fontBitmap.GlyphBitmap = bitmap; 48 }
PrintBitmap
结果示意图如下。
Demo
为了便于debug和观看效果,我在CSharpGL.Demos里加了下面这个Demo。你可以指定任意字体,设置是否启用加粗、斜体、下划线、删除线等效果。
用OpenGL渲染文字时,边缘的效果也很令人满意了。
总结
由于使用了.NET自带的Graphics和Font类型,就完全去掉了SharpFont那上千行代码。CSharpGL.dll由此下降了200K。效果增强,体积下降,代码简化,各个方面都获得提升。
CSharpGL(28)得到高精度可定制字形贴图的极简方法相关推荐
- 定制化和极简主义风格的安卓,看你pick谁?
首先得了解一下什么是Android(安卓)?wiki上对Android的定义 : Android(安卓)是一个基于linux核心的开放原始码行动作业系统,是由谷歌成立的开放手机联盟持续领导与开发. 不 ...
- 极简轻奢全屋定制样板7天被打卡了100遍,栓Q了
极简轻奢全屋定制样板7天被打卡了100遍,栓Q了 作全屋定制,怕价格高?怕被坑?还是怕翻车?到底怎样才算一款好定制呢? 这是一个长沙北辰三角洲朋友带来的.刚刚装修完工,就迫不及待的拍上美照分享了.既好 ...
- 九号公司第三季营收28亿:小米定制产品分销收入3亿同比降59%
雷递网 雷建平 10月24日 由易烊千玺代言的九号公司今日发布财报.财报显示,九号公司2022年前9个月营收为76.32亿元,较上年同期增长5.97%:净利为3.94亿元,同步增长1.37%:扣非后净 ...
- 承接数字油画图稿/线条图定制(出图)业务
集异璧实验室对外提供数字油画图纸(图稿/线条图)定制和自选业务,欢迎洽谈. ==== 定制流程: (1)贵方将图片传给我方: (2)我方出图,提供效果图: (3)贵方对效果图满意,则支付后,我方提供全 ...
- nginx模块定制开发中介入http模块的方法及NGX_HTTP_CONTENT_PHASE阶段的详细介绍
ginx模块定制开发中,大多数都是针对http的模块的定制开发,比如添加一个打印"hello world"的功能.记录http请求内容.对http请求作出一个特殊响应等,此时,需要 ...
- python绘制不带颜色曲线图_绘制一条曲线,并根据每个点的值和定制的颜色图对其进行着色...
我有一个定制的ROYGBIV彩虹颜色图:import numpy as np import matplotlib.pyplot as plt # dictionary with RGB values ...
- 定制geojson的一些小技巧和方法
毕业设计是海洋数据的可视化,选取的海域是东海黄海,主要使用D3.js这个可视化库,那么少不了的就是一幅该海域的地图数据. 一.web地图数据 基于JavaScript 对象表示法的地理空间信息数据交换 ...
- 关于 QMessageBox定制大小重写showEvent失败的 解决方法
需求 对QMessageBox进行尺寸定制. 截图,定制前 截图,定制后 原因 原因:QMessageBox::showEvent() 强制将其大小改变成了QT认为比较合适的大小. 要改变它的大小可使 ...
- 阿里组织变革:设立六大业务集团,成熟一个,上市一个;微软软件工程师最高年薪28.8万美元;iOS 16.4 发布|极客头条
「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 整理 | 梦依丹 出品 | CSDN(ID:CSDNnews ...
最新文章
- 什么样的人适合做产品经理
- AOP 在Spring 中的应用
- java在线支付---06,07,08_在线支付_编写将数据提交给易宝支付的JSP页面,集成和测试向易宝发送支付请求,实现浏览器自动向易宝发送支付请求
- c/c++教程 - 2.4.2.6 初始化列表语法,初始化列表参数
- 元宇宙会成为IPv6的拐点吗?
- 用户权限管理shiro
- java基础英语---第二十四天
- integer超出范围_BigInteger:可以让超过Integer范围内的数据进行运算
- 解释下列术语堆栈型计算机,第二章 计算机指令集结构
- C语言程序设计 余贞侠(课后习题答案)
- mschart控件_使用MSChart控件绘制图表。
- oracle队列java_oracle 队列
- Java+Servlet+Jsp(el, jstl)+MyBatis的CRUD练习小项目
- 戴尔服务器无限重启6,戴尔笔记本无限重启具体解决办法
- 数据库理论——概念模型、逻辑模型、物理模型
- 朋友圈都在说的央行数字货币,究竟跟你有什么关系
- 多分类模型Accuracy, Precision, Recall和F1-score的超级无敌深入探讨
- etho失败,无法上网的解决方法
- [构造]Array 2022牛客多校第6场 A
- CentOs 7 安装图形界面
热门文章
- Rootkit Hunter Sourcecode Learning
- 众多Android 开源项目推荐,给力工作给力学习
- SqlServer一些用法
- 神经网络与机器学习 笔记—Rosenblatt感知机
- Windows Pe 第三章 PE头文件-EX-相关编程-1(PE头内容获取)
- hdu4279 找规律+小想法
- 【Linux 内核】Linux 内核体系架构 ( 进程调度 | 内存管理 | 中断管理 | 设备管理 | 文件系统 )
- 【Android Gradle 插件】Module 目录下 build.gradle 配置文件 ( android 闭包块配置 | AppExtension 扩展类型参考文档 )
- 【Groovy】Groovy 脚本调用 ( Groovy 配置文件格式 | Groovy 配置文件读取 | 完整配置文件及解析代码示例 )
- 【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )