7

I am trying to draw some text using TextRenderer (since this is favorable to using Graphics.DrawString) to a Bitmap, however it is having some very undesirable effects.

Example Code

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

Result

enter image description here

I'm not sure exactly how to fix this...help!

I do NOT want to use Graphics.DrawString as I want correct GDI (as opposed to GDI+) rendering.

NOTE: I've just realized, I've left a gaping hole in this question. Some people have pointed out that rendering ClearType text is working fine on their machines...

I'm trying to render the text to a transparent (Color.Transparent) bitmap. If I do it with a solid color, everything works fine! (but it is imperative that I render to a transparent Bitmap).

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • Try to set the proper `Graphics.InterpolationMode` and `Graphics.SmoothingMode`. – Hamlet Hakobyan Sep 16 '13 at 22:06
  • @HamletHakobyan, I've tried that at least, but neither solve the problem. These seem to work when using Graphics.DrawString, however since TextRenderer is a GDI text drawing system, these properties do not seem to work. – Matthew Layton Sep 16 '13 at 22:13

3 Answers3

4

Specify a BackColor in your call to DrawText():

TextRenderer.DrawText(graphics, "Hello World", this.Font, this.ClientRectangle, Color.Black, this.BackColor);
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • 1
    That works, but the background for the text is actually an image, therefore, the BackColor for the text should be transparent. Unfortunately since GDI isn't alpha aware, using Color.Transparent doesn't blend ClearType text properly. – Matthew Layton Sep 17 '13 at 07:54
4

You can try setting TextRenderingHint for your Image Graphics:

using (Graphics graphics = Graphics.FromImage(buffer))
{
    graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
    TextRenderer.DrawText(graphics, "Hello World", this.Font, this.ClientRectangle, Color.Black);
}
King King
  • 61,710
  • 16
  • 105
  • 130
  • Works OK for AntiAliased text, but not for ClearType. It is the latter that I would like. – Matthew Layton Sep 17 '13 at 07:55
  • @series0ne why do you need `ClearType` here? What do you mean by `not for ClearType`? you should update the code to show your try. – King King Sep 17 '13 at 07:59
  • By default (on my systems at least), the TextRenderingHint is ClearType. Therefore, my code omits the TextRenderingHint, since the default is already ClearType, therefore there is no need to explicitly define the TextRenderingHint. – Matthew Layton Sep 17 '13 at 08:11
  • I think this might solve the issue...http://stackoverflow.com/questions/2691543/how-do-i-fix-the-alpha-value-after-calling-gdi-text-functions – Matthew Layton Sep 17 '13 at 08:25
  • @series0ne it's just you don't want to use `TextRenderingHint`, I don't know why your `Graphics.TextRenderingHint` has default value of `ClearType`, I tested to print the default value and it's `SystemDefault`, `TextRenderingHint = TextRenderingHint.ClearTypeGridFit` also works. – King King Sep 17 '13 at 08:32
  • I really couldn't care less for using TextRenderingHint, or not. Either way, it IS being used in the background regardless of whether I set it explicitly. - See my updated code example. – Matthew Layton Sep 17 '13 at 08:41
  • To answer this comment: "I don't know why your Graphics.TextRenderingHint has default value of ClearType, I tested to print the default value and it's SystemDefault" - Yes It's SystemDefault on my machine too. My point was that the default text rendering mechanism on my machine is ClearType, therefore, On my machine, SystemDefault and ClearTypeGridFit are the same. - On an older machine (say Win2k), the SystemDefault would likely be AntiAlias or AntiAliasGridFit. Hope that makes sense. – Matthew Layton Sep 17 '13 at 08:43
4

The issue is that TextRenderer uses GDI rendering that uses ClearType to render text, clear-type uses special anti-aliasing algorithm to smooth the text, unfortunately it doesn't work when you try to draw on bitmap device.

To make it work you have to use a trick, draw into in-memory and then copy to bitmap:

  1. Create in-memory bitmap buffer that is compatible with display device context (IntPtr.Zero handle)
  2. Fill the buffer background with solid color or image
  3. Render the text into the memory bitmap
  4. Copy from in-memory bitmap to image device context (BitBlt)

See this blog for details: GDI text rendering to image.

Sample code, sorry its a bit long:

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;
    }
}
Arthur
  • 1,484
  • 1
  • 16
  • 18
  • Hi, I'm getting an exception from the BitBlt() call. I tried to add CallingConvention = CallingConvention.Cdecl to the import, but it made no difference. Message: A call to PInvoke function 'BitmapRecognition!BitmapRecognition.Text::BitBlt' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature. – Ondrej Sotolar Jun 30 '14 at 18:10
  • whoops, BitBlt has incorrect signature, change the last argument (dwRop) from long to int. sorry. – Arthur Jul 01 '14 at 14:15