热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在位图上绘制长字符串会导致绘制问题

如何解决《在位图上绘制长字符串会导致绘制问题》经验,为你挑选了1个好方法。

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

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.MeasureTextGraphics.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(免费软件)


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
author-avatar
呀yuan-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有