使用以下命令绘制字符串(ASCII 或 Unicode 编码符号的形式)时Graphics.DrawString() https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.drawstring使用固定大小的字体,生成的图形似乎会生成某种网格,从而降低渲染的视觉质量。
解决方案是用 GDI 方法替换 GDI+ Graphics 方法,使用TextRenderer.MeasureText() https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.textrenderer.measuretext and TextRenderer.DrawText() https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.textrenderer.drawtext.
用于纠正问题并重现问题的示例代码。
使用默认的本地 CodePage 编码加载文本文件。源文本已保存,没有任何 Unicode 编码。如果使用不同的编码,Encoding.Default
必须替换为实际的编码(例如Encoding.Unicode
, Encoding.UTF8
...).
用于所有测试的固定大小字体是Lucida Console, 4em Regular
.
通常可用的其他候选人是Consolas
and Courier New
.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Text;
using System.Windows.Forms;
// Read the input text - assume UTF8 Encoding
string text = File.ReadAllText([Source text Path], Encoding.UTF8);
Font font = new Font("Lucida Console", 4, FontStyle.Regular, GraphicsUnit.Point);
// Use TextRenderer
using (var bitmap = ASCIIArtBitmap(text, font))
bitmap.Save(@"[FilePath1]", ImageFormat.Png);
// Use GDI+ Graphics
using (var bitmap = ASCIIArtBitmapGdiPlus(text, font))
bitmap.Save(@"[FilePath2]", ImageFormat.Png);
// Use GraphicsPath
using (var bitmap = ASCIIArtBitmapGdiPlusPath(text, font))
bitmap.Save(@"[FilePath3]", ImageFormat.Png);
font.Dispose();
TextRenderer
首先用于消除报告的视觉缺陷。
作为注释,两者TextRederer.MeasureText()
and Graphics.DrawString()
根据 MSDN 文档,应使用 来测量单行文本。
无论如何,众所周知,当文本由多行组成时,如果换行符分隔这些行,则可以正确测量文本。
它可以很容易地测试,将源文本与Environment.Newline
作为分隔符并将行数乘以单行的高度。结果总是一样的。
private Bitmap ASCIIArtBitmap(string text, Font font)
{
var flags = TextFormatFlags.Top | TextFormatFlags.Left |
TextFormatFlags.NoPadding | TextFormatFlags.NoClipping;
Size bitmapSize = TextRenderer.MeasureText(text, font, Size.Empty, flags);
var bitmap = new Bitmap(bitmapSize.Width, bitmapSize.Height, PixelFormat.Format24bppRgb)
using (var g = Graphics.FromImage(bitmap)) {
bitmapSize = TextRenderer.MeasureText(g, text, font, new Size(bitmap.Width, bitmap.Height), flags);
TextRenderer.DrawText(g, text, font, Point.Empty, Color.Black, Color.White, flags);
return bitmap;
}
}
低分辨率渲染(150 x 55 字符)。看不到网格效果.
Using Graphics.DrawString()
,重现所报告的行为。
TextRenderingHint.AntiAlias https://learn.microsoft.com/en-us/dotnet/api/system.drawing.text.textrenderinghint被指定为减少视觉缺陷的努力。合成质量.高速 https://learn.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.compositingquality看起来不合适,但实际上,在这种情况下,它的渲染效果比HighQuality
.
文字对比 https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.textcontrast= 1 使生成的图像稍微暗一些。默认设置太亮并且丢失细节(不过我的意见)。
private Bitmap ASCIIArtBitmapGdiPlus(string text, Font font)
{
using (var modelbitmap = new Bitmap(10, 10, PixelFormat.Format24bppRgb))
using (var modelgraphics = Graphics.FromImage(modelbitmap))
{
modelgraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
SizeF bitmapSize = modelgraphics.MeasureString(text, font, Point.Empty, StringFormat.GenericTypographic);
var bitmap = new Bitmap((int)bitmapSize.Width, (int)bitmapSize.Height, PixelFormat.Format24bppRgb);
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(Color.White);
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.TextContrast = 1;
g.DrawString(text, font, Brushes.Black, PointF.Empty, StringFormat.GenericTypographic);
return bitmap;
}
}
}
中低分辨率(300 x 110 个字符),网格效果可见。
Graphics.DrawString() TextRenderer.DrawText()
另一种方法,使用GraphicsPath.AddString() https://learn.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.graphicspath.addstring
生成的位图稍微好一点,但网格效果仍然存在。
真正可以注意到的是速度的差异。图形路径 https://learn.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.graphicspath is 慢得多比所有其他测试方法都要好。
private Bitmap ASCIIArtBitmapGdiPlusPath(string text, Font font)
{
using (var path = new GraphicsPath(FillMode.Alternate)) {
path.AddString(text, font.FontFamily, (int)font.Style, 4, Point.Empty, StringFormat.GenericTypographic);
var gpRect = Rectangle.Round(path.GetBounds());
var bitmap = new Bitmap(gpRect.Width, gpRect.Height);
using (var g = Graphics.FromImage(bitmap)) {
g.Clear(Color.White);
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; // Not used
g.PixelOffsetMode = PixelOffsetMode.Half; // <= for the Bitmap
g.SmoothingMode = SmoothingMode.AntiAlias; // <= for the GraphicsPath
g.FillPath(Brushes.Black, path);
return bitmap;
}
}
}
在这种情况下,为什么渲染质量如此不同?
一切都取决于 GDI+ 分辨率无关的网格拟合渲染的性质。
来自微软起源的一份不起眼的文档,在 WayBack Machine 上找到:
GDI+ 文本、分辨率独立性和渲染方法。 http://web.archive.org/web/20100116044507/http://windowsclient.net/articles/gdiptext.aspx
网格拟合,也称为提示,是调整网格的过程
渲染字形中像素的位置以使字形更容易
在较小的尺寸下也清晰可见。技术包括将字形词干对齐
整个像素并确保字形的相似特征受到影响
一样。
为了补偿网格拟合,尝试实现文本的最佳外观,印刷跟踪(通常称为字母间距 https://en.wikipedia.org/wiki/Letter-spacing),已修改。
当 GDI+ 显示一行短于网格的字形时
它们的设计宽度,遵循以下一般规则:
- 该线路允许收缩最多
em
字形间距没有任何变化。
- 剩余的收缩是通过将单词之间的任何空格的宽度增加到最大两倍来弥补的。
- 剩余的收缩是通过在字形之间引入空白像素来弥补的。
这种“努力”似乎已经到了修改kerning https://en.wikipedia.org/wiki/Kerning字形对。
在比例字体中,视觉渲染有一个好处,但是对于固定大小的字体,前面提到的计算会产生一种网格对齐,当相同的符号重复多次时清晰可见。
TextRenderer GDI 方法,基于清晰类型渲染 - 旨在屏幕上文本的视觉呈现- 使用字形的子像素表示。字母间距的计算完全不同。
Microsoft ClearType 概述。 https://learn.microsoft.com/en-us/typography/cleartype/
ClearType 的工作原理是访问各个垂直颜色条纹
LCD 屏幕每个像素中的元素。在 ClearType 之前,
计算机可以显示的最小细节级别是单个
像素,但通过在液晶显示器上运行 ClearType,我们现在可以
显示宽度小至像素的一小部分的文本特征。
额外的分辨率提高了微小细节的清晰度
文本显示,使长时间阅读变得更加容易。
缺点是这种计算字母间距的方法不适合从 WinForms 打印。 MSDN 文档反复说明了这一点。
关于该主题的其他有趣资源:
开发的艺术 - 文本渲染方法比较或 GDI 与 GDI+ https://theartofdev.com/2014/04/21/text-rendering-methods-comparison-or-gdi-vs-gdi-revised/
为什么我的文本在 GDI+ 和 GDI 中看起来不同? https://vinsontech.wordpress.com/2012/02/06/why-does-my-text-look-different-in-gdi-and-in-gdi/
GDI 与 GDI+ 文本渲染性能 https://blogs.msdn.microsoft.com/cjacks/2006/05/19/gdi-vs-gdi-text-rendering-performance/
堆栈溢出答案:
为什么 Graphics.MeasureString() 返回的数字高于预期数字? https://stackoverflow.com/questions/1203087/why-is-graphics-measurestring-returning-a-higher-than-expected-number
修改 System.Drawing.Graphics.DrawString() 中的字距调整 https://stackoverflow.com/questions/3235103/modifying-the-kerning-in-system-drawing-graphics-drawstring
用于生成 ASCII 艺术文本的应用程序:
SourceForge 上的 ASCII Generator 2(免费软件) https://ascgendotnet.jmsoftware.co.uk/