A second spring of text rasterization

Albert Camus said that "autumn is a second spring when every leaf is a flower". Similarly, text rasterization on Mac OS X is a constant summer where every character is in permanent bloom.

The background for this blog post is a text rendering task which has been sitting in my Jira for a while, and which I recently got around to completing. The goal of the task was to make text rendering on Mac identical whether you used the native paint engine or the raster paint engine. One of the reasons why this is important to us, is that we've been considering making QRasterPaintEngine the default paint engine on Mac/Cocoa at some point. This is our reference paint engine, already the default on Windows, and it has proven to give better visual results than the native Mac OS X paint engine, as well as better performance in many key areas. Also, by unifying the default technologies across several different platforms, we reduce our own maintenance load and make it possible for developers to focus more on other types of improvements than fixing platform regressions (read: everybody wins.) Of course, before we can do this, we have to make sure that using the raster paint engine does not ruin Qt's native look and feel on the platform. Qt Bug 5053 was one of the obstacles standing in the way.

In addition, the task is part of a larger, ongoing project to provide typographically correct text rendering in Qt wherever possible, which I'll hopefully come back to in future blogs. Right now I'll focus on the changes to the raster engine.

To illustrate the original problem with using the raster paint engine as a replacement for the current one, let's look at some screenshots. The first image is from the TextEdit demo using the native paint engine to display text.

Text rendering with the native paint engine in Qt 4.7 on Mac OS X

Both Mac OS X and Windows use a form of antialiasing when rasterizing character shapes on screen which is called subpixel antialiasing. The trick behind this is to recognize that pixels on an LCD screen is divided into three separate components: Red, green and blue. These three colors are physically separate inside each of the pixels on screen. So where regular antialiasing would have you apply an opacity to the pixel you are drawing depending on how large part of the pixel the shape is actually covering, subpixel antialiasing will have you apply three separate opacities instead - one for the red component, one for the green component and one for the blue - depending on how large a part of each subcomponent of the pixel the shape covers.

The result is, as you can see from the zoomed area in the screenshot above, that the edges of the shapes are littered with pixels in all the colors of the rainbow, like someone sprayed your screen with tiny, tiny confetti.

One key difference, however, between text rendering on Mac and text rendering on Windows, is that Windows does horizontal hinting on each glyph before rasterizing it, while Mac does not. Hinting is the execution of small programs embedded in the font (or an automatic algorithm) to move the control points of the vector shapes around so that they are aligned to the current pixel grid and visually optimized to be displayed in a given size at a given resolution. This makes the text look clearer on a monitor, which typically has a very low resolution, but since the shapes defined by the font are altered, both in appearance and size, a layout applied to text rendered in the font will give you different results at different resolutions: Specifically, a layout defined for a document as it looks on screen, might not be correct for the same document when it's rendered on a printer. The unhinted approach is what I refer to as typographically correct above, and even on platforms where you want the UI to be rendered with hinted glyphs, there are use cases where the ability to switch this off would be very convenient.

One effect of the font hinting is that each glyph on Windows has a single unique rasterization for a given font and point size. Regardless of where the glyph's vector shape is placed on the screen, it will be altered to align with the pixel grid, thus it will cover each subpixel in the same degree, whether it's horizontally positioned at, say, 0.0, 0.5 or 0.986574.

On Mac, however, the characters can be placed at subpixel positions, affecting the coverage when they are rasterized.

Let's zoom in a little bit more on the previous example.

Look at e.g. the character i in this screenshot. In the word ipsum it's rendered with red and blue. In the words in and elit, however, it's different, rendered in black, light blue and beige. This is because of a difference in the subpixel positions of the glyphs, causing the subpixels they touch to be covered in different amounts.

Now let's look at how the same text is rendered using the raster engine in Qt 4.7.

Compare this to the screenshot made with the native paint engine. The first thing you might notice is in the unzoomed area of the image, where the characters look like they are spaced unevenly (click on the image to make it full size. A good example is the spacing of the s and u in the word posuere) If you look closer at the zoomed area, you will see that the reason for this is that the rasterization of each glyph is the same for all subpixel positions.

When the raster paint engine was written, the UI text presentation needs on Mac were not considered, as the Mac already had a functioning native paint engine to handle this. Later, the paint engine was extended to use glyph caching: The first time a given character in a given font and size is rasterized, the image is saved in a glyph atlas and then reused whenever the same character is rendered again. This gives a performance improvement, as the actual rasterization of the glyph is very expensive. Since the needs of Mac were not considered in the design of this, only a single rasterization is cached per set of font, glyph and size. On Mac, there can be several such rasterizations. The number of different rasterizations supported is arbitrary, depending on the font and font size. (Currently, Qt 4.8 attempts to detect this number by some experimental reverse engineering, drawing the same glyph at twelve different subpixel locations and counting the number of unique rasterizations we get back. Higher granularity would most likely not be visible unless you zoom in, but please let me know if anyone has a better idea on how to determine the number of unique rasterizations used in the native text rendering.)

In conclusion, Qt 4.8 will see the end of this gap, and rendering the example text presented above with the raster engine will yield identical results to the native engine (in fact it's so identical that I won't even bother showing a screenshot.) To make it even better, other font engines will be able to benefit from this when they are written to support subpixel positioning. The future on all the modern desktop platforms holds great things, including typographically correct text that, when zoomed, will look like a beautiful, lush garden of flowers, rather than the drab and uniform plasticity of hinted glyphs.


Blog Topics:

Comments