我正在使用长字符串绘制位图(超过一百万个字符),包括由\r\n
编写的多行字符StringBuilder
。
我的文本到位图代码如下:
public static Bitmap GetBitmap(string input, Font inputFont,
Color bmpForeground, Color bmpBackground) {
Image bmpText = new Bitmap(1, 1);
try {
// Create a graphics object from the image.
Graphics g = Graphics.FromImage(bmpText);
// Measure the size of the text when applied to image.
SizeF inputSize = g.MeasureString(input, inputFont);
// Create a new bitmap with the size of the text.
bmpText = new Bitmap((int)inputSize.Width,
(int)inputSize.Height);
// Instantiate graphics object, again, since our bitmap
// was modified.
g = Graphics.FromImage(bmpText);
// Draw a background to the image.
g.FillRectangle(new Pen(bmpBackground).Brush,
new Rectangle(0, 0,
Convert.ToInt32(inputSize.Width),
Convert.ToInt32(inputSize.Height)));
// Draw the text to the image.
g.DrawString(input, inputFont,
new Pen(bmpForeground).Brush, new PointF(0, 0));
} catch {
// Draw a blank image with background.
Graphics.FromImage(bmpText).FillRectangle(
new Pen(bmpBackground).Brush,
new Rectangle(0, 0, 1, 1));
}
return (Bitmap)bmpText;
}
通常,它按预期工作 - 但仅在用于单个字符时。但是,问题在于使用多个字符时。简而言之,当绘制到图像上时,额外的线条会垂直和水平显示。
此处演示此效果,放大1:1(参见https://i.imgur.com/ITGA9WN.png):
但是,我可以在Notepad ++中使用字符串的输出呈现相同的文本,它基本上与预期一致:
我可以在任何其他文本查看器中查看它;结果将是相同的。
那么如何以及为什么用这些“额外”线渲染Bitmap的程序呢?
答案 0 :(得分:4)
使用带有固定大小字体的Graphics.DrawString()绘制字符串(ASCII或Unicode编码符号形式)时,生成的图形似乎会生成一种网格,从而降低了渲染的视觉质量。
解决方案是使用TextRenderer.MeasureText()和TextRenderer.DrawText()将GDI + Graphics方法替换为GDI方法。
用于纠正问题并重现问题的示例代码。
使用默认的本地CodePage编码加载文本文件。源文本已保存,没有任何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;
using (FileStream stream = new FileStream(openfile.FileName, FileMode.Open, FileAccess.Read, FileShare.None))
using (StreamReader reader = new StreamReader(stream, Encoding.Default))
TextInput = reader.ReadToEnd();
Font font = new Font("Lucida Console", 4, FontStyle.Regular, GraphicsUnit.Point);
using (Bitmap bitmap = ASCIIArtBitmap(TextInput, font))
bitmap.Save(@"[FilePath1]", ImageFormat.Png);
using (Bitmap bitmap = ASCIIArtBitmapGdiPlus(TextInput, font))
bitmap.Save(@"[FilePath2]", ImageFormat.Png);
using (Bitmap bitmap = ASCIIArtBitmapGdiPlusPath(TextInput, font))
bitmap.Save(@"[FilePath3]", ImageFormat.Png);
font.Dispose();
TextRenderer
首先用于消除所报告的视觉缺陷。
请注意,根据MSDN文档,TextRederer.MeasureText
和Graphics.DrawString
都应用于衡量单行文字。
无论如何,还知道当文本由多行组成时,如果换行将这些行分开,则正确测量文本。
它可以很容易地测试,将源文本与Enviroment.Newline
分隔为分隔符,并将行数乘以单行的高度。结果总是一样的。
private Bitmap ASCIIArtBitmap(string text, Font font)
{
TextFormatFlags flags = TextFormatFlags.Top | TextFormatFlags.Left |
TextFormatFlags.NoPadding | TextFormatFlags.NoClipping;
Size BitmapSize = TextRenderer.MeasureText(text, font, Size.Empty, flags);
using (Bitmap bitmap = new Bitmap(BitmapSize.Width, BitmapSize.Height, PixelFormat.Format24bppRgb))
using (Graphics graphics = Graphics.FromImage(bitmap))
{
BitmapSize = TextRenderer.MeasureText(graphics, text, font, new Size(bitmap.Width, bitmap.Height), flags);
TextRenderer.DrawText(graphics, text, font, Point.Empty, Color.Black, Color.White, flags);
return (Bitmap)bitmap.Clone();
}
}
低分辨率渲染,(150 x 55个字符)。没有可见的网格效果。
使用Graphics.DrawString()
重现报告的行为。
TextRenderingHint.AntiAlias
,以减少视觉缺陷。 CompositingQuality.HighSpeed
似乎不合适,但在这种情况下,它实际上比HighQuality
更好
TextContrast = 1
会使得到的图像变暗。默认设置太亮并且丢失了细节(不过我的意见)。
private Bitmap ASCIIArtBitmapGdiPlus(string text, Font font)
{
using (Bitmap modelbitmap = new Bitmap(10, 10, PixelFormat.Format24bppRgb))
using (Graphics modelgraphics = Graphics.FromImage(modelbitmap))
{
modelgraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
SizeF BitmapSize = modelgraphics.MeasureString(text, font, Point.Empty, StringFormat.GenericTypographic);
using (Bitmap bitmap = new Bitmap((int)BitmapSize.Width, (int)BitmapSize.Height, PixelFormat.Format24bppRgb))
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.White);
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
graphics.TextContrast = 1;
graphics.DrawString(text, font, Brushes.Black, PointF.Empty, StringFormat.GenericTypographic);
return (Bitmap)bitmap.Clone();
}
}
}
中低分辨率(300 x 110个字符),网格效果可见。
Graphics.DrawString() TextRenderer.DrawText()
另一种方法,使用GraphicsPath.AddString()
生成的位图稍微好一些,但无论如何都会出现网格效应
可以真正注意到的是速度的差异。所有其他测试方法GraphicsPath
慢一点。
private Bitmap ASCIIArtBitmapGdiPlusPath(string text, Font font)
{
GraphicsPath GPath = new GraphicsPath(FillMode.Alternate);
GPath.AddString(text, font.FontFamily, (int)font.Style, 4, Point.Empty, StringFormat.GenericTypographic);
Rectangle GPathArea = Rectangle.Round(GPath.GetBounds());
using (Bitmap bitmap = new Bitmap(GPathArea.Width, GPathArea.Height))
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.White);
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.FillPath(Brushes.Black, GPath);
return (Bitmap)bitmap.Clone();
}
}
为什么渲染质量在这种情况下是如此不同?
所有这些都取决于GDI +与分辨率无关的网格拟合渲染的性质。
来自WayBack Machine上发现的微软文档的一篇晦涩难懂的文件:
GDI+ Text, Resolution Independence, and Rendering Methods.
网格拟合,也称为提示,是调整网格的过程 渲染字形中像素的位置,以便轻松制作字形 在较小的尺寸清晰可辨。技术包括对齐字形词干 整个像素并确保字形的相似特征受到影响 同样。
为了补偿网格拟合,尝试实现文本的最佳外观,印刷跟踪(通常称为letter-spacing)被修改。
当GDI +显示一行网格拟合的字形时,短于 它们的设计宽度遵循以下一般规则:
- 允许该行在不改变字形间距的情况下收缩直至em。
- 通过增加单词之间任何空格的宽度来弥补剩余的收缩,最多加倍。
- 通过在字形之间引入空白像素来弥补剩余的收缩。
醇>
这个"努力"似乎被推到了修改kerning对字形的点。
在比例字体中,视觉渲染有一个好处,但是使用固定大小的字体,前面提到的计算会产生一种网格对齐,当相同的符号重复多次时清晰可见。 / p>
TextRenderer GDI方法,基于清晰类型渲染 - 旨在在屏幕上显示文本的可视化渲染 - 使用字形的子像素表示。字母间距的计算完全不同。
ClearType通过访问单个垂直颜色条纹来工作 LCD屏幕的每个像素中的元素。在ClearType之前, 计算机可以显示的最小细节级别是单个 像素,但在LCD显示器上运行ClearType,我们现在可以 显示文本的特征,小到像素宽度的一小部分 额外的分辨率增加了微小细节的锐度 文本显示,使长时间阅读更容易。
缺点是这种计算字母间距的方法,使其不适合从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
回答:
Why is Graphics.MeasureString() returning a higher than expected number?
Modifying the kerning in System.Drawing.Graphics.DrawString()
用于生成ASCII艺术文本的应用程序: