在位图上绘制长字符串会导致绘制问题 [英] Drawing a Long String on to a Bitmap results in Drawing Issues
问题描述
我正在将一个长字符串绘制到位图(通话超过一百万个字符),包括由StringBuilder
编写的多行字符\r\n
.
我的文本到位图"代码如下:
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放大显示了这种效果(请参见完整图像) :
但是,我可以仅使用字符串的输出在Notepad ++中呈现相同的文本,并且基本上可以预期:
我可以在任何其他文本查看器中查看它;结果将是相同的.
那么程序如何以及为什么用那些额外"的行渲染位图?
使用 TextRenderer.MeasureText()和 请注意,根据MSDN文档, 低分辨率渲染(150 x 55个字符).没有可见的网格效果. 使用 中低分辨率(300 x 110个字符),可见网格效果. 网格拟合,也称为提示,是调整
渲染字形中像素的位置以使字形容易
较小的尺寸清晰可见.技术包括对齐字形词干
整个像素并确保字形的相似特征受到影响
同样. 为了补偿网格拟合,尝试使文本尽可能达到最佳外观,即印刷跟踪(通常称为字母间距). 当GDI +显示的网格拟合字形的行短于
它们的设计宽度,遵循以下一般规则:
此努力"似乎已被推到修改字距调整对的地步字形. 在比例字体中,视觉呈现是有好处的,但是使用固定大小的字体,前面提到的计算会产生一种网格对齐,当同一符号重复多次时,该网格对齐清晰可见. TextRenderer GDI方法基于清晰类型的呈现(旨在屏幕上文本的视觉呈现),使用字形的亚像素表示形式.字母间距的计算完全不同. ClearType通过访问各个垂直色带来工作
LCD屏幕每个像素中的元素.在ClearType之前,
一台计算机可以显示的最小细节级别是一个
像素,但是在LCD监视器上运行ClearType之后,我们现在可以
显示宽度仅为像素的一小部分的文本特征. 缺点是这种计算字母间距的方法使其不适合从WinForms打印. MSDN文档反复指出这一点. 关于该主题的其他有趣资源: SO答案: 为什么Graphics.MeasureString()返回一个比预期的数字高? 在System.Drawing.Graphics.DrawString()中修改字距 用于生成ASCII文字的应用程序: I'm working with drawing a long string to a bitmap (talking more than a million characters), including multiline characters My Text to Bitmap code is as follows: Normally, it works as expected -- but only when used for single characters. However, the issue lies when a multitude of characters are used. Simply put, extra lines appear both vertically and horizontally when drawn on to the image. This effect is demonstrated here, zoomed in 1:1 (see the full Image): However, I can render this same text in Notepad++ with just the output of the string and it's essentially as expected: I can view it in any other text viewer; the result will be the same. So how, and why, is the program rendering the Bitmap with those 'extra' lines? When drawing a string of characters (ASCII or a form of Unicode encoded symbols) using Graphics.DrawString() with a fixed sized Font, the resulting graphics appear to generate a sort of grid, degrading the visual quality of the rendering. A solution is to substitute the GDI+ Graphics methods with the GDI methods, using TextRenderer.MeasureText() and TextRenderer.DrawText(). Sample code to correct the problem and to reproduce it. Load a text file using the default local CodePage encoding. The source text has been saved without any Unicode encoding. If a different encoding is used, the The fixed size Font used for all tests is As a note, both Low resolution rendering, (150 x 55 chars). No grid effect visible. Using Medium-low resolution (300 x 110 chars), the grid-effect is visible. The resulting bitmap is a somewhat better, but the grid effect is there anyway. All depends on the nature of the GDI+ resolution-independent grid-fitted rendering. From an obscure document, of Microsoft's origin, found on the WayBack Machine: GDI+ Text, Resolution Independence, and Rendering Methods. Grid Fitting, also known as hinting, is the process of adjusting the
position of pixels in a rendered glyph to make the glyph easily
legible at smaller sizes. Techniques include aligning glyph stems on
whole pixels and ensuring similar features of a glyph are affected
equally. In an effort to compensates for Grid Fitting, trying to achieve the best appearance possible for a text, the typographic tracking (usually called letter-spacing), is modified. When GDI+ displays a line of grid fitted glyphs that are shorter than
their design width, it follows these general rules:
This "effort" seems to be pushed to the point of modifying the kerning pairs of the glyphs. In a proportional Font, the visual rendering has a benefit, but with a fixed size Font, the previously mentioned calculation produces a sort of grid-alignment, clearly visible when the same symbol is repeated multiple times. TextRenderer GDI methods, based on clear-type rendering - aimed to the visual rendering of text on screen - uses a sub-pixel representation of glyphs. The calculation of letter-spacing is completely different. ClearType works by accessing the individual vertical color stripe
elements in every pixel of an LCD screen. Before ClearType, the
smallest level of detail that a computer could display was a single
pixel, but with ClearType running on an LCD monitor, we can now
display features of text as small as a fraction of a pixel in width. The drawback is that this method of calculating the letter-spacing, makes it unsuited for printing from WinForms. MSDN documentation repeatedly states this. Other interesting resources on the subject: 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 SO answers: Why is Graphics.MeasureString() returning a higher than expected number? Modifying the kerning in System.Drawing.Graphics.DrawString() Application used to generate the ASCII-art text: ASCII Generator 2 on SourceForge (free software) 这篇关于在位图上绘制长字符串会导致绘制问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!TextRenderer
首先用于消除报告的视觉缺陷.
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();
}
}
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();
}
}
}
Graphics.DrawString() TextRenderer.DrawText()
另一种方法,使用 GDI +文本,分辨率独立性和渲染方法.
>
额外的分辨率提高了细微细节的清晰度
文本显示,使长时间阅读变得更加容易.
\r\n
, written by 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;
}
Encoding.Default
must be substituted with the actual Encoding (e.g. Encoding.Unicode
, Encoding.UTF8
...).Lucida Console, 4em Regular
.
Other candidates, usually available, are 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;
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
is first used to eliminate the visual defect reported.TextRederer.MeasureText
and Graphics.DrawString
, per the MSDN documentation, should be used to measure a single line of text.
Anyway, it's also known that the text is correctly measured when the text is composed of multiple lines, if a line feed separates those lines.
It can be easily tested, splitting the source text with Enviroment.Newline
as separator and multiplying the number of lines by the height of a single line. The result is always the same.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();
}
}
Graphics.DrawString()
, to reproduce the reported behaviour.TextRenderingHint.AntiAlias
is specified, as an effort to reduce the visual defect. CompositingQuality.HighSpeed
seems out of place, but it actually, in this case, renders better than HighQuality
.
TextContrast = 1
makes the resulting image a little darker. With the default setting is too bright and loses details (my opinion, though).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();
}
}
}
Graphics.DrawString() TextRenderer.DrawText()
Another approach, using GraphicsPath.AddString()
What can be really noticed, is the difference in speed. GraphicsPath
is way slower that all other methods tested.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();
}
}
Why the rendering quality is, in this case, so different?
The extra resolution increases the sharpness of the tiny details in
text display, making it much easier to read over long durations.