我正在将一个长字符串绘制到位图(通话超过一百万个字符),包括\r\n
由a编写的多行字符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 ++中仅使用字符串的输出来呈现相同的文本,并且基本上是如预期的那样:
我可以在任何其他文本查看器中查看它;结果将是相同的。
那么程序如何以及为什么用那些“额外”的行渲染位图呢?
1> Jimi..:
当使用具有固定大小的Font的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
首先用于消除报告的视觉缺陷。
作为一个说明,无论是TextRederer.MeasureText
和Graphics.DrawString
每MSDN文档,应该被用来测量单个文本行。
无论如何,如果由多行组成的文本由换行符分隔,则还可以正确测量文本。
它很容易测试,使用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上发现的,来自Microsoft的晦涩文档中:
GDI +文本,分辨率独立性和渲染方法。
网格拟合,也称为提示,是调整渲染字形中像素位置以使字形在较小尺寸下易于辨认的过程。技术包括在整个像素上对齐字形茎,并确保字形的相似特征受到同等影响。
为了弥补网格拟合的不足,试图使文本达到最佳外观,对印刷跟踪(通常称为字母间距)进行了修改。
当GDI +显示的是一行比其设计宽度短的网格拟合字形时,它遵循以下一般规则:
允许该行最多收缩一个em,而字形间距不变。
剩余的收缩是通过增加单词之间的任何空格的宽度(最大为两倍)来弥补的。
剩余的收缩是通过在字形之间引入空白像素来弥补的。
这种“努力”似乎已被推到修改字形紧缩对的地步。
在比例字体中,视觉渲染是有好处的,但是对于固定大小的字体,前面提到的计算会产生某种网格对齐,当同一符号重复多次时,该网格对齐清晰可见。
基于透明类型呈现的TextRenderer GDI方法(旨在在屏幕上视觉呈现文本)使用字形的子像素表示。字母间距的计算完全不同。
Microsoft ClearType概述。
ClearType通过访问LCD屏幕的每个像素中的各个垂直色带元素来工作。在使用ClearType之前,计算机可以显示的最小细节级别为单个像素,但是在LCD监视器上运行ClearType的情况下,我们现在可以显示宽度仅为像素的一小部分的文本。
额外的分辨率提高了文本显示中微小细节的清晰度,使长时间阅读变得更加容易。
缺点是这种计算字母间距的方法使其不适合从WinForms打印。MSDN文档反复说明了这一点。
关于此主题的其他有趣资源:
开发的艺术-文本呈现方法比较或GDI与GDI +
为什么我的文字在GDI +和GDI中看起来不同?
GDI与GDI +文本渲染性能
所以答案:
为什么Graphics.MeasureString()返回的数字比预期的高?
在System.Drawing.Graphics.DrawString()中修改字距调整
用于生成ASCII文字的应用程序:
SourceForge上的ASCII生成器2(免费软件)