Discussion:
Drawing text to a Bitmap has horrible looking output
(too old to reply)
Lloyd Dupont
2006-05-27 14:13:32 UTC
Permalink
I tryed to do a bitmap export in my app by creating a bitmap, drawing to it
and saving the file.
Something like that

void SaveToFile(string pngfile)
{
using(Bitmap bmp = new Bitmap(200, 200);
{
using(Graphics g = Graphics.FromImage(bmp))
g.DrawString("Hello!", this.Font, Brushes.Black, 0, 50);
bmp.Save(pngfile, ImageFormat.Png);
}
}

But while this work, the resulting Bitmap, I mean the text quality on the
Bitmap is appaling.
Whatever the SmoothingMode or TextRenderingHit I use.

Any tips to improve that?

Also, in fact, I'm doing a lot of GDI drawing, and the text output with HDC
/ ExtTextOut suffer from the same problem, whatever quality hint I used in
CreatFont(), any other GDI hint?
Kellie Fitton
2006-05-27 19:36:09 UTC
Permalink
Hi,

You can use the following GDI+ methods to improve the output:

Graphics::SetInterpolationMode
Graphics::SetSmoothingMode
Graphics::SetPixelOffsetMode

You can use the GDI graphic drawing to create a bitmap image,
then use the following APIs to produce an antialias effect:

SetStretchBltMode() using HALFTONE
StretchBlt()

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Classes/GraphicsClass/GraphicsMethods/SetInterpolationMode.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusreference/classes/graphicsclass/graphicsmethods/setsmoothingmode.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Classes/GraphicsClass/GraphicsMethods/SetPixelOffsetMode.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6cth.asp

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_9cok.asp

Hope these suggestions helps,

Kellie.
Lloyd Dupont
2006-05-28 02:09:10 UTC
Permalink
Thanks for all your links!

Unfortunatly none them help me fix the quality of the output of DrawString()
on a Bitmap.

I did some more test and discovered that if I paint the Background of my
Bitmap first, or use a 24bpp bitmap g.DrawString() yield much better result.

However my problem lies mainly with GDI drawing (ExtTextOut) as it is the
API I should use.

After doing some more test I come to realize that, perhaps, a Bitmap's HDC
doesn't support the same quality hint as the screen HDC.
it's why CreateFont(... CLEARTYPE_QUALITY ...) doesn't work for the Bitmap
HDC?
(while CreateFont(... ANTIALIASED_QUALITY ...) does)

Do you have any tip on how to test, wether or not, the current device
support the given font quality hint?
So that I could gracefully drop one level?
Ben Voigt
2006-05-29 14:55:25 UTC
Permalink
Post by Lloyd Dupont
Thanks for all your links!
Unfortunatly none them help me fix the quality of the output of
DrawString() on a Bitmap.
I did some more test and discovered that if I paint the Background of my
Bitmap first, or use a 24bpp bitmap g.DrawString() yield much better result.
I accidentally replied to your previous post before reading this one. This
statement confirms my suspicion concerning losing the alpha channel.

When your bitmap has an alpha channel (32-bit), the GDI+ text routines use
transparency to implement anti-aliasing. GDI doesn't understand the alpha
channel information... it uses only grayscale blends with the background
color.

If you draw the background first, it's opaque and you get blends with the
background with an opaque result.
Post by Lloyd Dupont
However my problem lies mainly with GDI drawing (ExtTextOut) as it is the
API I should use.
After doing some more test I come to realize that, perhaps, a Bitmap's HDC
doesn't support the same quality hint as the screen HDC.
it's why CreateFont(... CLEARTYPE_QUALITY ...) doesn't work for the Bitmap
HDC?
(while CreateFont(... ANTIALIASED_QUALITY ...) does)
Do you have any tip on how to test, wether or not, the current device
support the given font quality hint?
So that I could gracefully drop one level?
Lloyd Dupont
2006-05-28 02:19:35 UTC
Permalink
I solved it!

In my MC++ wrapper around HDC, when it's created from a Bitmap, I maximize
the Font quality to ANTIALIASED_QUALITY and it all works nicely :-)
Lloyd Dupont
2006-05-28 02:37:28 UTC
Permalink
disregard the previous post ..
grmrmbll......

still don't work..
sigh....
Kellie Fitton
2006-05-28 05:02:28 UTC
Permalink
Hi,

The GDI and the Win32 APIs such as ExtTextOut() does not provide
any antialias/text smoothing, only GDI+ support font smoothing,
you might want to use the following API to enable smoothing mode:

SystemParametersInfo() using SPI_SETFONTSMOOTHING,
SPI_SETFONTSMOOTHINGCONTRAST,
SPI_SETFONTSMOOTHINGTYPE

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/systemparametersinfo.asp

Kellie.
Lloyd Dupont
2006-05-28 06:37:19 UTC
Permalink
Thanks ever Vigilent Kellie ;-)
Post by Kellie Fitton
Hi,
The GDI and the Win32 APIs such as ExtTextOut() does not provide
any antialias/text smoothing, only GDI+ support font smoothing,
SystemParametersInfo() using SPI_SETFONTSMOOTHING,
SPI_SETFONTSMOOTHINGCONTRAST,
SPI_SETFONTSMOOTHINGTYPE
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/systemparametersinfo.asp
Kellie.
James Brown [MVP]
2006-05-28 11:06:37 UTC
Permalink
Standard Win32 CreateFont allows you to specify font-smoothing options with
no problem - certainly, I've never had any issue with creating a 'cleartype'
font, and then drawing with this font to an off-screen DC - font smoothing
works as expected in this case. Not tried doing this from GDI+ -> normal GDI
though.

James
--
Microsoft MVP - Windows SDK
www.catch22.net
Free Win32 Source and Tutorials
Post by Lloyd Dupont
Thanks ever Vigilent Kellie ;-)
Post by Kellie Fitton
Hi,
The GDI and the Win32 APIs such as ExtTextOut() does not provide
any antialias/text smoothing, only GDI+ support font smoothing,
SystemParametersInfo() using SPI_SETFONTSMOOTHING,
SPI_SETFONTSMOOTHINGCONTRAST,
SPI_SETFONTSMOOTHINGTYPE
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/systemparametersinfo.asp
Kellie.
Lloyd Dupont
2006-05-28 11:20:25 UTC
Permalink
Post by James Brown [MVP]
Standard Win32 CreateFont allows you to specify font-smoothing options
with no problem - certainly, I've never had any issue with creating a
'cleartype' font, and then drawing with this font to an off-screen DC -
font smoothing works as expected in this case. Not tried doing this from
GDI+ -> normal GDI though.
I think that's the issue.
Here is a simple weird test I have done.

I create HFONT with CreateFont(... CLEARTYPE_QUALITY ..)
I also SetMapMode(MM_TEXT) and SetWorldTransform(rotated and scaled world)

If I do
Image img = new Bitmap(width, height, ImageFormat.24bpp); <<== GDI work
better with 24bpp
HDC hdc = Graphics.FromImage(img).GetHdc();
DrawText(hdc, ....)

this produce nice text output, however if I do

Image img = new Bitmap(width, height, ImageFormat.24bpp);
Graphics g = Graphics.FromImage(img);
g.FillRect(widt, height, Brushes.Color) <<=== new
HDC hdc = g.GetHdc();
DrawText(hdc, ....)

the text is blurred ... :-(

Somehow some GDI+ operation have bad impact on my output.
And I can't removethe g.FillRect() as I have huge recursive mixed GDI/GDI+
code ....

However If I use CreateFont(.. ANTIALIAS_QUALITY ..) it works (I do get the
ANTIALIAS quality, not as nice as CLEARTYPE, but NOT blurred!)
Mick Doherty
2006-05-28 12:34:34 UTC
Permalink
When drawing to an offscreen bitmap using GDI you need to use a
CompatibleDC.

Bitmap bm = new Bitmap(width, height, ImageFormat.24bpp);
Graphics g = Graphics.FromImage(bm);
g.FillRect(width, height, Brushes.Color)
HDC hdc = g.GetHdc();

HDC cDC = CreateCompatibleDC(hdc);
HBitmap bmOriginal = SelectObject(cDC, bm.GetHBitmap());
DrawText(cDC, ....);
BitBlt(hdc, 0, 0, width, height, cDC, 0, 0, SRCCOPY);
SelectObject(cDC, bmOriginal);
DeleteDC(cDC);
...
--
Mick Doherty
http://dotnetrix.co.uk/nothing.html
Post by Lloyd Dupont
Post by James Brown [MVP]
Standard Win32 CreateFont allows you to specify font-smoothing options
with no problem - certainly, I've never had any issue with creating a
'cleartype' font, and then drawing with this font to an off-screen DC -
font smoothing works as expected in this case. Not tried doing this from
GDI+ -> normal GDI though.
I think that's the issue.
Here is a simple weird test I have done.
I create HFONT with CreateFont(... CLEARTYPE_QUALITY ..)
I also SetMapMode(MM_TEXT) and SetWorldTransform(rotated and scaled world)
If I do
Image img = new Bitmap(width, height, ImageFormat.24bpp); <<== GDI work
better with 24bpp
HDC hdc = Graphics.FromImage(img).GetHdc();
DrawText(hdc, ....)
this produce nice text output, however if I do
Image img = new Bitmap(width, height, ImageFormat.24bpp);
Graphics g = Graphics.FromImage(img);
g.FillRect(widt, height, Brushes.Color) <<=== new
HDC hdc = g.GetHdc();
DrawText(hdc, ....)
the text is blurred ... :-(
Somehow some GDI+ operation have bad impact on my output.
And I can't removethe g.FillRect() as I have huge recursive mixed GDI/GDI+
code ....
However If I use CreateFont(.. ANTIALIAS_QUALITY ..) it works (I do get
the ANTIALIAS quality, not as nice as CLEARTYPE, but NOT blurred!)
Lloyd Dupont
2006-05-28 15:06:17 UTC
Permalink
I tried that, which seems reasonable though a tad acrobatic but, quite
strangely, I had no GDI output at all when doing that ?!?!

Anyway, I dropped this issue as it is quite marginal and I could make sure
to use ANTIALIAS_QUALITY in such case.

Regards,
Lloyd

PS: I upgraded to the latest version of Nothing and it really works just as
expected!
Without all these annoying spywares and adwares you find in Anything
nowadays!

"Mick Doherty"
Post by Mick Doherty
When drawing to an offscreen bitmap using GDI you need to use a
CompatibleDC.
Bitmap bm = new Bitmap(width, height, ImageFormat.24bpp);
Graphics g = Graphics.FromImage(bm);
g.FillRect(width, height, Brushes.Color)
HDC hdc = g.GetHdc();
HDC cDC = CreateCompatibleDC(hdc);
HBitmap bmOriginal = SelectObject(cDC, bm.GetHBitmap());
DrawText(cDC, ....);
BitBlt(hdc, 0, 0, width, height, cDC, 0, 0, SRCCOPY);
SelectObject(cDC, bmOriginal);
DeleteDC(cDC);
...
--
Mick Doherty
http://dotnetrix.co.uk/nothing.html
Post by Lloyd Dupont
Post by James Brown [MVP]
Standard Win32 CreateFont allows you to specify font-smoothing options
with no problem - certainly, I've never had any issue with creating a
'cleartype' font, and then drawing with this font to an off-screen DC -
font smoothing works as expected in this case. Not tried doing this from
GDI+ -> normal GDI though.
I think that's the issue.
Here is a simple weird test I have done.
I create HFONT with CreateFont(... CLEARTYPE_QUALITY ..)
I also SetMapMode(MM_TEXT) and SetWorldTransform(rotated and scaled world)
If I do
Image img = new Bitmap(width, height, ImageFormat.24bpp); <<== GDI work
better with 24bpp
HDC hdc = Graphics.FromImage(img).GetHdc();
DrawText(hdc, ....)
this produce nice text output, however if I do
Image img = new Bitmap(width, height, ImageFormat.24bpp);
Graphics g = Graphics.FromImage(img);
g.FillRect(widt, height, Brushes.Color) <<=== new
HDC hdc = g.GetHdc();
DrawText(hdc, ....)
the text is blurred ... :-(
Somehow some GDI+ operation have bad impact on my output.
And I can't removethe g.FillRect() as I have huge recursive mixed
GDI/GDI+ code ....
However If I use CreateFont(.. ANTIALIAS_QUALITY ..) it works (I do get
the ANTIALIAS quality, not as nice as CLEARTYPE, but NOT blurred!)
Mick Doherty
2006-05-28 18:06:44 UTC
Permalink
Post by Lloyd Dupont
I tried that, which seems reasonable though a tad acrobatic but, quite
strangely, I had no GDI output at all when doing that ?!?!
Sounds like you either missed out the BitBlt, or the SelectObject
afterwards.

I am currently doing something very similar (in VB.Net though) and it works
exactly as expected.
Post by Lloyd Dupont
Anyway, I dropped this issue as it is quite marginal and I could make sure
to use ANTIALIAS_QUALITY in such case.
Personally I always like to know why something didn't work, even if I have
an acceptable workaround.
So in case it helps here are the exact methods that I am using along with an
example of their usage:
(Interop declarations not included)

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
'You sometimes need to use GDI since the control whos surface
'you're drawing to uses GDI to measure the rectangle and the GDI+
'DrawString method results in a string that's too long to fit.
Private Sub DrawGDIText(ByVal hdc As IntPtr, ByVal text As String, _
ByVal font As Font, ByVal bounds As Rectangle, _
ByVal backColor As Color, _
ByVal foreColor As Color, _
ByVal alignment As ContentAlignment)

SetTextColor(hdc, ColorTranslator.ToWin32(foreColor))

If backColor.Equals(Color.Transparent) Then
SetBkMode(hdc, TRANSPARENT)
Else
SetBkMode(hdc, OPAQUE)
SetBkColor(hdc, ColorTranslator.ToWin32(backColor))
End If

Dim hFont As IntPtr = font.ToHfont
Dim hOldFont As IntPtr = SelectObject(hdc, hFont)

Dim flags As Int32 = DT_SINGLELINE
Select Case alignment
Case ContentAlignment.TopLeft
flags += DT_TOP Or DT_LEFT
Case ContentAlignment.TopCenter
flags += DT_TOP Or DT_CENTER
Case ContentAlignment.TopRight
flags += DT_TOP Or DT_RIGHT
Case ContentAlignment.MiddleLeft
flags += DT_VCENTER Or DT_LEFT
Case ContentAlignment.MiddleCenter
flags += DT_VCENTER Or DT_CENTER
Case ContentAlignment.MiddleRight
flags += DT_VCENTER Or DT_RIGHT
Case ContentAlignment.BottomLeft
flags += DT_BOTTOM Or DT_LEFT
Case ContentAlignment.BottomCenter
flags += DT_BOTTOM Or DT_CENTER
Case ContentAlignment.BottomRight
flags += DT_BOTTOM Or DT_RIGHT
End Select

DrawText(hdc, text, -1, New RECT(bounds), flags)

SelectObject(hdc, hOldFont)
DeleteObject(hFont)

End Sub

'A bitmap needs to pass a CompatibleDC to the DrawText method.
Friend Sub DrawGDIText(ByVal bm As Bitmap, ByVal text As String, _
ByVal font As Font, ByVal bounds As Rectangle, _
ByVal backColor As Color, _
ByVal foreColor As Color, _
ByVal alignment As ContentAlignment)

Dim g As Graphics = Graphics.FromImage(bm)

If Not backColor.Equals(Color.Transparent) Then
Dim backBrush As New SolidBrush(backColor)
g.FillRectangle(backBrush, bounds)
backBrush.Dispose()
End If

Dim hDC As IntPtr = g.GetHdc
Dim cDC As IntPtr = CreateCompatibleDC(hDC)
Dim hBitmap As IntPtr = SelectObject(cDC, bm.GetHbitmap)

DrawGDIText(cDC, text, font, bounds, Color.Transparent, _
foreColor, alignment)

BitBlt(hDC, bounds.Left, bounds.Top, bounds.Width, bounds.Height, _
cDC, bounds.Left, bounds.Top, SRCCOPY)

SelectObject(cDC, hBitmap)
DeleteDC(cDC)

g.ReleaseHdc(hDC)
g.Dispose()

End Sub

Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click

Dim bm As New Bitmap(someImage)

Dim testFont As New Font("Times New Roman", 36, _
FontStyle.Italic, GraphicsUnit.Point)

DrawGDIText(bm, "Sample", testFont, _
New Rectangle(Point.Empty, bm.Size), _
Color.FromArgb(200, 255, 128, 0), Color.White, _
ContentAlignment.MiddleCenter)

testFont.Dispose()
bm.Save("c:\Testing.png", Imaging.ImageFormat.Png)
bm.Dispose()

End Sub
/////////////////////////////////////////////////////////////////////////////
Post by Lloyd Dupont
Regards,
Lloyd
PS: I upgraded to the latest version of Nothing and it really works just
as expected!
Without all these annoying spywares and adwares you find in Anything
nowadays!
:-) I'll add that to my feedback
--
Mick Doherty
http://dotnetrix.co.uk/nothing.html
Lloyd Dupont
2006-06-20 00:44:35 UTC
Permalink
Well I solved it.
Instead this, which would have required too many bitmap copy anyway (As I
switch between GDI & GDI+ graphics all the time) I simply use the 'standart'
double buffering class (BufferedGraphics, which seems to works well in
windows forms) to draw to Bitmap too.
And it worked!
--
Regards,
Lloyd Dupont

NovaMind development team
NovaMind Software
Mind Mapping Software
<www.nova-mind.com>
"Mick Doherty"
Post by Mick Doherty
Post by Lloyd Dupont
I tried that, which seems reasonable though a tad acrobatic but, quite
strangely, I had no GDI output at all when doing that ?!?!
Sounds like you either missed out the BitBlt, or the SelectObject
afterwards.
I am currently doing something very similar (in VB.Net though) and it
works exactly as expected.
Post by Lloyd Dupont
Anyway, I dropped this issue as it is quite marginal and I could make
sure to use ANTIALIAS_QUALITY in such case.
Personally I always like to know why something didn't work, even if I have
an acceptable workaround.
So in case it helps here are the exact methods that I am using along with
(Interop declarations not included)
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
'You sometimes need to use GDI since the control whos surface
'you're drawing to uses GDI to measure the rectangle and the GDI+
'DrawString method results in a string that's too long to fit.
Private Sub DrawGDIText(ByVal hdc As IntPtr, ByVal text As String, _
ByVal font As Font, ByVal bounds As Rectangle, _
ByVal backColor As Color, _
ByVal foreColor As Color, _
ByVal alignment As ContentAlignment)
SetTextColor(hdc, ColorTranslator.ToWin32(foreColor))
If backColor.Equals(Color.Transparent) Then
SetBkMode(hdc, TRANSPARENT)
Else
SetBkMode(hdc, OPAQUE)
SetBkColor(hdc, ColorTranslator.ToWin32(backColor))
End If
Dim hFont As IntPtr = font.ToHfont
Dim hOldFont As IntPtr = SelectObject(hdc, hFont)
Dim flags As Int32 = DT_SINGLELINE
Select Case alignment
Case ContentAlignment.TopLeft
flags += DT_TOP Or DT_LEFT
Case ContentAlignment.TopCenter
flags += DT_TOP Or DT_CENTER
Case ContentAlignment.TopRight
flags += DT_TOP Or DT_RIGHT
Case ContentAlignment.MiddleLeft
flags += DT_VCENTER Or DT_LEFT
Case ContentAlignment.MiddleCenter
flags += DT_VCENTER Or DT_CENTER
Case ContentAlignment.MiddleRight
flags += DT_VCENTER Or DT_RIGHT
Case ContentAlignment.BottomLeft
flags += DT_BOTTOM Or DT_LEFT
Case ContentAlignment.BottomCenter
flags += DT_BOTTOM Or DT_CENTER
Case ContentAlignment.BottomRight
flags += DT_BOTTOM Or DT_RIGHT
End Select
DrawText(hdc, text, -1, New RECT(bounds), flags)
SelectObject(hdc, hOldFont)
DeleteObject(hFont)
End Sub
'A bitmap needs to pass a CompatibleDC to the DrawText method.
Friend Sub DrawGDIText(ByVal bm As Bitmap, ByVal text As String, _
ByVal font As Font, ByVal bounds As Rectangle, _
ByVal backColor As Color, _
ByVal foreColor As Color, _
ByVal alignment As ContentAlignment)
Dim g As Graphics = Graphics.FromImage(bm)
If Not backColor.Equals(Color.Transparent) Then
Dim backBrush As New SolidBrush(backColor)
g.FillRectangle(backBrush, bounds)
backBrush.Dispose()
End If
Dim hDC As IntPtr = g.GetHdc
Dim cDC As IntPtr = CreateCompatibleDC(hDC)
Dim hBitmap As IntPtr = SelectObject(cDC, bm.GetHbitmap)
DrawGDIText(cDC, text, font, bounds, Color.Transparent, _
foreColor, alignment)
BitBlt(hDC, bounds.Left, bounds.Top, bounds.Width, bounds.Height, _
cDC, bounds.Left, bounds.Top, SRCCOPY)
SelectObject(cDC, hBitmap)
DeleteDC(cDC)
g.ReleaseHdc(hDC)
g.Dispose()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim bm As New Bitmap(someImage)
Dim testFont As New Font("Times New Roman", 36, _
FontStyle.Italic, GraphicsUnit.Point)
DrawGDIText(bm, "Sample", testFont, _
New Rectangle(Point.Empty, bm.Size), _
Color.FromArgb(200, 255, 128, 0), Color.White, _
ContentAlignment.MiddleCenter)
testFont.Dispose()
bm.Save("c:\Testing.png", Imaging.ImageFormat.Png)
bm.Dispose()
End Sub
/////////////////////////////////////////////////////////////////////////////
Post by Lloyd Dupont
Regards,
Lloyd
PS: I upgraded to the latest version of Nothing and it really works just
as expected!
Without all these annoying spywares and adwares you find in Anything
nowadays!
:-) I'll add that to my feedback
--
Mick Doherty
http://dotnetrix.co.uk/nothing.html
Loading...