使用TextRenderer将文本绘制到位图上

7
我将尝试使用TextRenderer(因为这比使用Graphics.DrawString更好)在Bitmap上绘制一些文本,但是它会产生一些非常不良的效果。 示例代码
using (Bitmap buffer = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
    using (Graphics graphics = Graphics.FromImage(buffer))
    {
        // Produces the result below
        graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
        // Produces clean text, but I'd really like ClearType!
        graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
        TextRenderer.DrawText(graphics, "Hello World", this.Font, this.ClientRectangle, Color.Black);
    }
    e.Graphics.DrawImageUnscaled(buffer, this.ClientRectangle);
}

结果

enter image description here

我不确定如何解决这个问题...请帮忙!

我不想使用Graphics.DrawString,因为我想要正确的GDI(而不是GDI+)渲染。

注意:我刚意识到,我在这个问题中留下了一个巨大的漏洞。有些人指出,在他们的计算机上渲染ClearType文本没有问题...

我试图将文本渲染到透明(Color.Transparent)位图上。如果我用纯色来做,一切都正常!(但是我必须渲染到透明的位图上)。


尝试设置适当的 Graphics.InterpolationModeGraphics.SmoothingMode - Hamlet Hakobyan
@HamletHakobyan,我至少尝试过了,但都没有解决问题。这些属性似乎在使用Graphics.DrawString时有效,但由于TextRenderer是一个GDI文本绘制系统,所以这些属性似乎不起作用。 - Matthew Layton
3个回答

4

在调用 DrawText() 函数时指定 BackColor:

TextRenderer.DrawText(graphics, "Hello World", this.Font, this.ClientRectangle, Color.Black, this.BackColor);

1
可以这样做,但是文本的背景实际上是一张图片,因此文本的BackColor应该是透明的。不幸的是,由于GDI不支持alpha通道,使用Color.Transparent无法正确混合ClearType文本。 - Matthew Layton

4
你可以尝试设置文本呈现提示来处理图像图形
using (Graphics graphics = Graphics.FromImage(buffer))
{
    graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
    TextRenderer.DrawText(graphics, "Hello World", this.Font, this.ClientRectangle, Color.Black);
}

适用于反锯齿文本,但不适用于ClearType。我想要的是后者。 - Matthew Layton
@series0ne,你为什么需要在这里使用ClearType?你所说的“不适用于ClearType”是什么意思?你应该更新代码以展示你的尝试。 - King King
默认情况下(至少在我的系统上),TextRenderingHint是ClearType。因此,我的代码省略了TextRenderingHint,因为默认值已经是ClearType,因此没有必要显式定义TextRenderingHint。 - Matthew Layton
我认为这可能会解决问题...http://stackoverflow.com/questions/2691543/how-do-i-fix-the-alpha-value-after-calling-gdi-text-functions - Matthew Layton
回答这条评论:“我不知道为什么你的Graphics.TextRenderingHint默认值是ClearType,我测试了打印默认值,它是SystemDefault” - 是的,在我的机器上也是SystemDefault。我的意思是,在我的机器上,默认的文本呈现机制是ClearType,因此,在我的机器上,SystemDefault和ClearTypeGridFit是相同的。- 在旧机器上(比如Win2k),SystemDefault可能是AntiAlias或AntiAliasGridFit。希望这样说有意义。 - Matthew Layton
显示剩余2条评论

4
问题在于TextRenderer使用GDI渲染,该渲染使用ClearType来呈现文本,ClearType使用特殊的抗锯齿算法使文本平滑,但是当您尝试在位图设备上绘制时,它无法工作。
为使其正常工作,您需要使用一个技巧,即先绘制到内存中,然后再复制到位图中:
  1. 创建与显示设备上下文(IntPtr.Zero句柄)兼容的内存位图缓冲区
  2. 用纯色或图像填充缓冲区背景
  3. 将文本渲染到内存位图上
  4. 从内存位图复制到图像设备上下文(BitBlt)
有关详细信息,请参见此博客:GDI text rendering to image
示例代码,很抱歉它有点长:
public static class Test
{
    public static Image Render()
    {
        // create the final image to render into
        var image = new Bitmap(190, 30, PixelFormat.Format32bppArgb);

        // create memory buffer from desktop handle that supports alpha channel
        IntPtr dib;
        var memoryHdc = CreateMemoryHdc(IntPtr.Zero, image.Width, image.Height, out dib);
        try
        {
            // create memory buffer graphics to use for HTML rendering
            using (var memoryGraphics = Graphics.FromHdc(memoryHdc))
            {
                // must not be transparent background 
                memoryGraphics.Clear(Color.White);

                // execute GDI text rendering
                TextRenderer.DrawText(memoryGraphics, "Test string 1", new Font("Arial", 12), new Point(5, 5), Color.Red, Color.Wheat);
                TextRenderer.DrawText(memoryGraphics, "Test string 2", new Font("Arial", 12), new Point(100, 5), Color.Red);
            }

            // copy from memory buffer to image
            using (var imageGraphics = Graphics.FromImage(image))
            {
                var imgHdc = imageGraphics.GetHdc();
                BitBlt(imgHdc, 0, 0, image.Width, image.Height, memoryHdc, 0, 0, 0x00CC0020);
                imageGraphics.ReleaseHdc(imgHdc);
            }
        }
        finally
        {
            // release memory buffer
            DeleteObject(dib);
            DeleteDC(memoryHdc);
        }

        return image;
    }

    private static IntPtr CreateMemoryHdc(IntPtr hdc, int width, int height, out IntPtr dib)
    {
        // Create a memory DC so we can work off-screen
        IntPtr memoryHdc = CreateCompatibleDC(hdc);
        SetBkMode(memoryHdc, 1);

        // Create a device-independent bitmap and select it into our DC
        var info = new BitMapInfo();
        info.biSize = Marshal.SizeOf(info);
        info.biWidth = width;
        info.biHeight = -height;
        info.biPlanes = 1;
        info.biBitCount = 32;
        info.biCompression = 0; // BI_RGB
        IntPtr ppvBits;
        dib = CreateDIBSection(hdc, ref info, 0, out ppvBits, IntPtr.Zero, 0);
        SelectObject(memoryHdc, dib);

        return memoryHdc;
    }

    [DllImport("gdi32.dll")]
    public static extern int SetBkMode(IntPtr hdc, int mode);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    private static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateDIBSection(IntPtr hdc, [In] ref BitMapInfo pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset);

    [DllImport("gdi32.dll")]
    public static extern int SelectObject(IntPtr hdc, IntPtr hgdiObj);

    [DllImport("gdi32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);

    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    public static extern bool DeleteDC(IntPtr hdc);

    [StructLayout(LayoutKind.Sequential)]
    internal struct BitMapInfo
    {
        public int biSize;
        public int biWidth;
        public int biHeight;
        public short biPlanes;
        public short biBitCount;
        public int biCompression;
        public int biSizeImage;
        public int biXPelsPerMeter;
        public int biYPelsPerMeter;
        public int biClrUsed;
        public int biClrImportant;
        public byte bmiColors_rgbBlue;
        public byte bmiColors_rgbGreen;
        public byte bmiColors_rgbRed;
        public byte bmiColors_rgbReserved;
    }
}

嗨,我从BitBlt()调用中得到了一个异常。我尝试将CallingConvention = CallingConvention.Cdecl添加到导入中,但没有任何区别。消息:对PInvoke函数'BitmapRecognition!BitmapRecognition.Text :: BitBlt'的调用已经使堆栈不平衡。这很可能是因为托管的PInvoke签名与非托管目标签名不匹配。请检查PInvoke签名的调用约定和参数是否与目标非托管签名匹配。 - Ondrej Sotolar
糟糕,BitBlt的签名不正确,请将最后一个参数(dwRop)从long更改为int。抱歉。 - Arthur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接