【问题标题】:Drawing a Long String on to a Bitmap results in Drawing Issues在位图上绘制长字符串会导致绘制问题
【发布时间】:2018-10-01 07:07:20
【问题描述】:

我正在为位图绘制一个长字符串(超过一百万个字符),包括由StringBuilder 编写的多行字符\r\n

我的 Text to Bitmap 代码如下:

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(参见full Image):

但是,我可以在 Notepad++ 中仅使用字符串的输出来呈现相同的文本,它基本上与预期的一样:

我可以在任何其他文本查看器中查看它;结果是一样的。

那么,程序如何以及为什么使用这些“额外”行来渲染位图?

【问题讨论】:

  • 重现该问题的输入将帮助任何希望在本地尝试它的人...
  • 嗯,我可以通过选择
  • @TaW 使用 TextRenderer.MeasureText/TextRenderer.DrawText 而不是 Graphics.DrawString,我看不到那种工件。使用 Consolas 4em,TextFormatFlags 只是设置为 (Top | Left | NoPadding)。位图是使用Bitmap bitmap = new Bitmap([Width], [Height], PixelFormat.Format24bppRgb); 创建的
  • 一定要调查一下。 this is an interesting write-up
  • 你有很棒的模式! -- 我想我有足够的文档来解释为什么结果如此不同。我一有时间就会把它写下来。

标签: c# .net text bitmap gdi+


【解决方案1】:

当使用具有固定大小字体的Graphics.DrawString() 绘制字符串(ASCII 或某种形式的 Unicode 编码符号)时,生成的图形似乎会生成一种网格,从而降低渲染的视觉质量。

一种解决方案是用 GDI 方法替换 GDI+ 图形方法,使用 TextRenderer.MeasureText()TextRenderer.DrawText()

纠正问题并重现问题的示例代码。

使用默认的本地 CodePage 编码加载文本文件。源文本已在没有任何 Unicode 编码的情况下保存。如果使用不同的编码,Encoding.Default 必须替换为实际的编码(例如Encoding.UnicodeEncoding.UTF8...)。

用于所有测试的固定大小字体是Lucida Console, 4em Regular
通常可用的其他候选人是ConsolasCourier 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首先用于消除报告的视觉缺陷。

请注意,根据 MSDN 文档,TextRederer.MeasureText()Graphics.DrawString() 都应该用于测量单行文本。
无论如何,当文本由多行组成时,如果换行符将这些行分开,则文本被正确测量也是众所周知的。
它可以很容易地测试,将源文本以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 字符)。没有可见的网格效果

使用Graphics.DrawString(),重现报告的行为。

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 Machine 上的一份不知名的 Microsoft 文档:

GDI+ Text, Resolution Independence, and Rendering Methods.

Grid Fitting,也称为hinting,是调整网格的过程 渲染字形中像素的位置以轻松制作字形 在较小的尺寸下清晰易读。技术包括对齐字形词干 整个像素并确保字形的相似特征受到影响 平等。

为了补偿 Grid Fitting,试图为文本实现最佳外观,修改了印刷跟踪(通常称为 letter-spacing)。

当 GDI+ 显示一排比 它们的设计宽度,它遵循以下一般规则:

  1. 在不改变字形间距的情况下,该行最多可以收缩 em
  2. 剩余的收缩是通过增加单词之间任何空格的宽度来弥补的,最多加倍。
  3. 剩余的收缩是通过在字形之间引入空白像素来弥补的。

这种“努力”似乎被推到了修改kerning字形对的地步。

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

TextRenderer GDI 方法,基于清晰类型的渲染 - 旨在在屏幕上视觉渲染文本 - 使用字形的亚像素表示。字母间距的计算完全不同。

Microsoft ClearType overview.

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

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-13
    • 1970-01-01
    • 2011-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多