使用绘制字符串(ASCII或Unicode编码符号形式)时
Graphics.DrawString()
使用固定大小的字体,生成的图形似乎会生成一种网格,从而降低渲染的视觉质量。
解决方案是用GDI方法替换GDI+图形方法,使用
TextRenderer.MeasureText()
和
TextRenderer.DrawText()
。
用于更正问题并再现问题的示例代码。
使用默认的本地代码页编码加载文本文件。保存的源文本没有任何Unicode编码。如果使用不同的编码,则
Encoding.Default
必须用实际编码替换(例如。
Encoding.Unicode
,则,
Encoding.UTF8
…)。
用于所有测试的固定大小字体为
Lucida Console, 4em Regular
。
通常可用的其他候选人包括
Consolas
和
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()
和
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个字符)。无可见栅格效果
。
使用
图样抽绳()
,以复制报告的行为。
TextRenderingHint.AntiAlias
是为了减少视觉缺陷。
CompositingQuality.HighSpeed
看起来不合适,但实际上,在这种情况下,渲染效果比
HighQuality
。
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()
生成的位图稍好一些,但网格效果仍然存在。
真正可以注意到的是速度上的差异。
GraphicsPath
是
慢多了
比所有其他测试方法。
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;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.FillPath(Brushes.Black, path);
return bitmap;
}
}
}
在这种情况下,为什么渲染质量如此不同?
所有这些都取决于GDI+分辨率独立的网格拟合渲染的性质。
从WayBack机器上发现的一份来源于微软的模糊文档中:
GDI+ Text, Resolution Independence, and Rendering Methods.
网格拟合,也称为提示,是调整
渲染图示符中像素的位置,以轻松创建图示符
尺寸较小时清晰可见。技术包括在屏幕上对齐字形柄
整个像素并确保图示符的类似特征受到影响
同样地。
为了补偿网格拟合,尝试实现文本的最佳外观,排版跟踪(通常称为
letter-spacing
),已修改。
当GDI+显示一行短于
其设计宽度遵循以下一般规则:
-
该线路最多可通过
em
不更改图示符间距。
-
剩余的收缩是通过增加单词之间任何空格的宽度来弥补的,最大值为两倍。
-
剩余的收缩通过在轮廓之间引入空白像素来弥补。
这种“努力”似乎被推到了修改
kerning
图示符对。
在比例字体中,视觉渲染有好处,但对于固定大小的字体,前面提到的计算会产生一种网格对齐方式,当同一符号重复多次时,这种对齐方式清晰可见。
TextRenderer GDI方法,基于清除类型渲染-针对
屏幕上文本的视觉呈现
-使用图示符的亚像素表示。字母间距的计算完全不同。
Microsoft ClearType overview.
ClearType通过访问单个垂直色条工作
LCD屏幕每个像素中的元素。在ClearType之前
计算机能够显示的最小细节级别是单个
像素,但随着ClearType在LCD显示器上运行,我们现在可以
显示宽度仅为一个像素分数的文本特征。
额外的分辨率提高了图像中细微细节的清晰度
文本显示,使长时间阅读更加容易。
缺点是,这种计算字母间距的方法不适合从WinForms打印。MSDN文档反复说明了这一点。
关于该主题的其他有趣资源:
The Art of dev - Text rendering methods comparison or GDI vs. GDI+
Why does my text look different in GDI+ and in GDI?
GDI vs. GDI+ Text Rendering Performance
StackOverflow答案:
Why is Graphics.MeasureString() returning a higher than expected number?
Modifying the kerning in System.Drawing.Graphics.DrawString()
用于生成ASCII艺术文本的应用程序:
ASCII Generator 2 on SourceForge (free software)