在位图上绘制长字符串会导致绘制问题 [英] Drawing a Long String on to a Bitmap results in Drawing Issues

查看:117
本文介绍了在位图上绘制长字符串会导致绘制问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将一个长字符串绘制到位图(通话超过一百万个字符),包括由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()

TextRenderer首先用于消除报告的视觉缺陷.

请注意,根据MSDN文档,TextRederer.MeasureTextGraphics.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()


另一种方法,使用
GDI +文本,分辨率独立性和渲染方法.

网格拟合,也称为提示,是调整 渲染字形中像素的位置以使字形容易 较小的尺寸清晰可见.技术包括对齐字形词干 整个像素并确保字形的相似特征受到影响 同样.

为了补偿网格拟合,尝试使文本尽可能达到最佳外观,即印刷跟踪(通常称为字母间距).

当GDI +显示的网格拟合字形的行短于 它们的设计宽度,遵循以下一般规则:

  1. 该行最多可收缩一个em,而字形间距不变.
  2. 剩余的收缩是通过增加单词之间任何空格的宽度来实现的,最大程度加倍.
  3. 剩余的收缩是通过在字形之间引入空白像素来实现的.

此努力"似乎已被推到修改字距调整对的地步字形.

在比例字体中,视觉呈现是有好处的,但是使用固定大小的字体,前面提到的计算会产生一种网格对齐,当同一符号重复多次时,该网格对齐清晰可见.

TextRenderer GDI方法基于清晰类型的呈现(旨在屏幕上文本的视觉呈现),使用字形的亚像素表示形式.字母间距的计算完全不同.

Microsoft ClearType概述.

ClearType通过访问各个垂直色带来工作 LCD屏幕每个像素中的元素.在ClearType之前, 一台计算机可以显示的最小细节级别是一个 像素,但是在LCD监视器上运行ClearType之后,我们现在可以 显示宽度仅为像素的一小部分的文本特征.
额外的分辨率提高了细微细节的清晰度 文本显示,使长时间阅读变得更加容易.

缺点是这种计算字母间距的方法使其不适合从WinForms打印. MSDN文档反复指出这一点.

关于该主题的其他有趣资源:

艺术开发-文本呈现方法比较或GDI与GDI +

GDI与.GDI +文本渲染性能

SO答案:

为什么Graphics.MeasureString()返回一个比预期的数字高?

在System.Drawing.Graphics.DrawString()中修改字距

用于生成ASCII文字的应用程序:

SourceForge上的ASCII生成器2(免费软件)

I'm working with drawing a long string to a bitmap (talking more than a million characters), including multiline characters \r\n, written by a StringBuilder.

My Text to Bitmap code is as follows:

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;
}

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 Encoding.Default must be substituted with the actual Encoding (e.g. Encoding.Unicode, Encoding.UTF8...).

The fixed size Font used for all tests is 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.

As a note, both 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();
    }
}

Low resolution rendering, (150 x 55 chars). No grid effect visible.


Using 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();
        }
    }
}

Medium-low resolution (300 x 110 chars), the grid-effect is visible.


            Graphics.DrawString()                    TextRenderer.DrawText()


Another approach, using GraphicsPath.AddString()

The resulting bitmap is a somewhat better, but the grid effect is there anyway.
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?

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:

  1. The line is allowed to contract by up to an em without any change of glyph spacing.
  2. Remaining contraction is made up by increasing the width of any spaces between words, to a maximum of doubling.
  3. Remaining contraction is made up by introducing blanks pixels between glyphs.

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.

Microsoft ClearType overview.

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 extra resolution increases the sharpness of the tiny details in text display, making it much easier to read over long durations.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆