The wonders of text rendering and GDI

My research into text rendering in .NET/Windows started with HTML Renderer, I have noticed text issues in the rendered HTML: fonts were pixelated, letter were too close, words were either too close or too far apart (Figure 1). Although minor issues they were bugging me, also I was hoping it will lead to some performance improvements as from my last optimization session text draw/measure were the most time consuming operations.
The resulted improvement exceeded my wildest expectations and worth a post on.

GDI vs. GDI+

There are two ways of drawing/measuring text in .NET using two libraries:

  1. GDI+ ( Graphics.DrawString , Graphics.MeasureString and Graphics.MeasureCharacterRanges )
  2. GDI ( TextRenderer.DrawText and TextRenderer.MeasureText )

 
clip_image001.png
Figure 1: GDI+ vs. GDI text rendering, note the issues in GDI+ rendered text.
 
Apparently until .NET 2.0 the framework used GDI+ text rendering methods but in .NET 2.0 GDI text rendering was introduced to be used by all WinForms control, because of backward compatibility it was added side-by-side with legacy GDI+ methods so today we have both options (see SetCompatibleTextRenderingDefault).
 
There are 3 issues with GDI+ that makes GDI text rendering better:

  1. GDI+ resolution-independent grid-fitted rendering may cause glyphs to increase in width tightening up the text and in some cases removing spaces between letters resulting in worse looking text (see here and here ).
  2. The shaping engines for international text have been updated many times for Windows/Uniscribe and for WPF, but have not been updated for GDI+, which causes international rendering support for new languages not to have the same level of quality (see here ).
  3. There are some performance issues caused by the somewhat stateless nature of GDI+, where device contexts would be set and then the original restored after each call (see here ).

 
Hence there is no reason using any of the GDI+ draw/measure methods so I won't go into the difference between Graphics.MeasureString and Graphics.MeasureCharacterRanges or the effect of StringFormat.GenericTypographic, if you need to render text the GDI+ methods are a mess you better avoid.
 

TextRenderer

Introduced in .NET 2.0 TextRenderer is the recommended text rendering method, using GDI library under the hood it provided the best looking text, accurate measurement and proper international support.
Using TextRenderer.DrawText and TextRenderer.MeasureText is straightforward and well documented so I won't go into details. The only annoying issue is the padding added to the left (1/6 em) and right (1/4 em) of the drawn/measured text as described here. To have exact draw/measure of text you need the subtract (1/6font.GetHeight()) from left corner during draw and (2.5/6font.GetHeight()) from the measured width (Figure 2).
 
clip_image002.png
Figure 2: Using TextRenderer draw/measure without and with adjustments.
 

Performance

While reading on the GDI+ vs. GDI stuff the consensus was that GDI is about 10x faster than GDI+. But switching HTML Renderer from GDI+ to GDI using TextRenderer resulted in ~30% slowdown, obviously performance research is in order.
 
I set up a quick performance test to draw/measure the same string ("Hello World") using the same font (Arial 10p) 5000 times, comparing each draw/measure method to GDI+ Graphics.DrawString and Graphics.MeasureCharacterRanges as they were originally used in HTML Renderer (Figure 3, Figure 4).
 

Method Avg. time msec Compared
Graphics.DrawString 0.1312 100%
TextRenderer.DrawText 0.1908 145%
DrawText (Native) 0.0844 64%
TextOut (Native) 0.0192 15%

Figure 3: Performance comparison for drawing text.
 

Method Avg. time msec Compared
Graphics.MeasureCharacterRanges 0.0460 100%
Graphics.MeasureString 0.0010 2%
TextRenderer.MeasureText 0.0190 41%
GetTextExtentPoint32 (Native) 0.0003 0.6%

Figure 4: Performance comparison for measuring text.
 
Although TextRenderer.MeasureText is 59% faster, TextRenderer.DrawText is 45% slower and it's much more expansive so that explains HTML Renderer result. There isn't much room to improve TextRenderer performance using the different overloads and flags it can receive so a different approach is needed.
Note, although the performance of Graphics.MeasureString is significantly better (98%) than Graphics.MeasureCharacterRanges its measurement data is extremely inaccurate to the drawn text so it is useless for any practical use.
 

Going native

The next step was to get the hands dirty and call GDI functions directly using p/invoke. It's much more complicated than using TextRenderer so I will dedicate my next post to the details and present a utility class to simplify it.
The important thing to know for now is that DrawText is the function used under-the-hood by TextRenderer and it allows the same rich set of capabilities as TextRenderer. TextOut is very close to the device driver's own TextOut so the call is quicker but also much less powerful (see here), and there is more details here about measuring text.
 
The performance of native draw/measure is amazing, 56% – 85% faster draw and 99.4% faster measure!
I haven't figured-out why TextRenderer is so much slower than native DrawText (225% slower) as the .NET source code doesn't seems very complicated, but the numbers don't lie.
 

HTML Renderer performance

Because HTML Renderer has all the logic for text align, line wrapping, etc. the actual displayed text is split into words and each word is measured and drawn separately so the most straightforward text draw/measure functions are required, GetTextExtentPoint32 and TextOut fit that requirement perfectly.
 
The final result is 60%-70% performance improvement, text measuring takes less than 1% and text drawing takes 25%-30% of all HTML Renderer time, a considerable improvement that switches the focus to other areas of the code for next time (and there will be a next time).
 
It is worth mentioning that to get the most out of using native GDI I added code to cache the native fonts, minimize the number of times HDC is created and font and color is selected. If to initialize and release everything for each string draw/measure the performance will actually be worse than using TextRenderer.
 

Wrap it up

 

Good reading

Why text appears different when drawn with GDI+ versus GDI
GDI vs. GDI+ Text Rendering Performance
MSDN: About Text Output
A Guide to WIN32 Clipping Regions
Why is Graphics.MeasureString() returning a higher than expected number?
How to get the exact text margins used by TextRenderer

12 comments on “The wonders of text rendering and GDI

  1. […] complete my previous post on text rendering in .NET I will present here the pitfalls I encountered migrating HTML Renderer to […]

  2. […] changing HTML Renderer to GDI text rendering (see previous post and this one) I have encountered another issue: GDI doesn’t support alpha channel, which […]

  3. yc says:

    Is GDI available on Mono? Would be great if your package will also work on Mono.

    • Arthur says:

      Frankly I don’t know, I will be surprised if it is.
      I’m currently working on v1.5 making the Html Renderer cross platform WPF, Silverlight, Metro so it will be also possible to have Mono as well.

  4. […] decided to revise my original findings (see: The wonders of text rendering and GDI) and do a comprehensive comparison for three […]

  5. […] The wonders of text rendering and GDI. […]

  6. […] complete my previous post on text rendering in .NET I will present here the pitfalls I encountered migrating HTML Renderer to […]

  7. […] changing HTML Renderer to GDI text rendering (see previous post and this one) I have encountered another issue: GDI doesn't support alpha channel, which means […]

  8. […] application for standard resolution devices2.   I decided to revise my original findings (see: The wonders of text rendering and GDI) and do a comprehensive comparison for three […]

  9. […] The wonders of text rendering and GDI . […]

  10. […] The wonders of text rendering and GDI […]

  11. […] halfway built. If you have nothing better to do, read up on the differences on The Art of Dev, here and here. In nearly all respects GDI+ appears to be inferior, but it can do one thing that GDI […]

Leave a comment