Date: | June 14, 2006 / year-entry #199 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060614-00/?p=30873 |
Comments: | 41 |
Summary: | Windows provides a variety of technologies for rendering monochrome text on color displays, taking advantage of display characteristics to provide smoother results. These include grayscale anti-aliasing as well as the more advanced ClearType technique. Both of these methods read from the background pixels to decide what pixels to draw in the foreground. This means that... |
Windows provides a variety of technologies for rendering monochrome text on color displays, taking advantage of display characteristics to provide smoother results. These include grayscale anti-aliasing as well as the more advanced ClearType technique. Both of these methods read from the background pixels to decide what pixels to draw in the foreground. This means that rendering text requires extra attention. If you draw text with an opaque background, there is no problem because you are explicitly drawing the background pixels as part of the text-drawing call, so the results are consistent regardless of what the previous background pixels were. But if you draw text with a transparent background, then you must make sure the background pixels that you draw against are the ones you really want. The most common way people mess this up is by drawing text multiple times. I've seen programs which draw text darker and darker the longer you use it. We'll see here how this can happen and what you need to do to avoid it. Start with the scratch program and make these changes: HFONT g_hfAntialias; HFONT g_hfClearType; BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { g_hfAntialias = CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, TEXT("Tahoma")); g_hfClearType = CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, TEXT("Tahoma")); return g_hfAntialias && g_hfClearType; } void OnDestroy(HWND hwnd) { if (g_hfAntialias) DeleteObject(g_hfAntialias); if (g_hfClearType) DeleteObject(g_hfClearType); PostQuitMessage(0); } void MultiPaint(HDC hdc, int x, int y, int n) { LPCTSTR psz = TEXT("The quick brown fox jumps over the lazy dog."); int cch = lstrlen(psz); for (int i = 0; i < n; i++) { TextOut(hdc, x, y, psz, cch); } } void PaintContent(HWND hwnd, PAINTSTRUCT *pps) { int iModePrev = SetBkMode(pps->hdc, TRANSPARENT); HFONT hfPrev = SelectFont(pps->hdc, g_hfAntialias); MultiPaint(pps->hdc, 10, 0, 1); MultiPaint(pps->hdc, 10, 20, 2); MultiPaint(pps->hdc, 10, 40, 3); SelectFont(pps->hdc, g_hfClearType); MultiPaint(pps->hdc, 10, 80, 1); MultiPaint(pps->hdc, 10,100, 2); MultiPaint(pps->hdc, 10,120, 3); SelectFont(pps->hdc, hfPrev); SetBkMode(pps->hdc, iModePrev); } This program creates two fonts, one with anti-aliased (grayscale) quality and another with ClearType quality. (I have no idea why people claim that there is no thread-safe way to enable ClearType on an individual basis. We're doing it just fine here.) Run this program and take a close look at the results. Observe that in each set of three rows of text, the more times we overprint, the darker the text. In particular, notice that overprinting the anti-aliased font makes the result significantly uglier and uglier! What went wrong? The first time we drew the text, the background was a solid fill of the window background color. But when the text is drawn over itself, the background it sees is the previous text output. When the algorithm decides that "This pixel should be drawn by making the existing pixel 50% darker," it actually comes out 75% darker since the pixel is darkened twice. And if you draw it three times, the pixel comes out 88% darker. When you draw text, draw it exactly once, and draw it over the background you ultimately want. This allows the anti-aliasing and ClearType engines to perform their work with accurate information. The programs that darken the text are falling afoul of the overprinting problem. When the programs decide that some screen content needs to be redrawn (for example, if the focus rectangle needs to be added or removed), they "save time" by refraining from erasing the background and merely drawing the text again (but with/without the focus rectangle). Unfortunately, if you don't erase the background, then the text ends up drawn over a previous copy of itself, resulting in darkening. The solution is to draw text over the correct background. If you don't know what background is on the screen right now, then you need to erase it in order to set it to a known state. Otherwise, you will be blending text against an unknown quantity, which leads to inconsistent (and ugly) results.
If you keep your eagle eyes open, you can often spot another case
where people make the overprinting mistake:
When text
in a control (say, a check box)
becomes darker and darker the more times you tab through it.
This happens when programs
don't pay close attention to the flags passed in the
|
Comments (41)
Comments are closed. |
Sorry for the off-topic, but:
“I have no idea why people claim that there is no thread-safe way
to enable ClearType on an individual basis. We’re doing it just fine
here.”
According to the linked post, the person was not making the
non-thread-safe claim himself, but was instead claiming that the MSDN
docs authoritatively stated it was not thread-safe (although there is
no link to the alleged page).
In any case, how is saying “We’re doing it just fine here” any
different from yesterday’s post when one person’s way of doing
something (in this case, changing the monitor power settings) happened
to work for him, no matter what the documentation might have happened
to say broadcasting random messages all over the place?
_Especially_ for code that might have threading issues?!? Of all
the places where “it seems to work fine at the moment” can never be
an indicator of anything, multithreaded code is surely it.
The Windows Media Player 11 beta suffers pretty badly from this problem. Here’s hoping that the developers read your blog :)
OFF TOPIC:
Raymond, please do a write-up covering this:
http://www.wincustomize.com/articles.aspx?aid=117870&c=1
Just tried it and it works. Any idea what the back story is?
@eden
I get some sort of asian looking script (Chinese, Japanese, not sure). Looks like notepad has a hard time working out what kind of encoding to use with that series of bytes. Michael Kaplan’s blog had something about this (not this specific case) once if I recall correctly.
@Eden
I would guess that notepad is using IsTextUnicode to determine the encoding of the file in question, which is getting it wrong because there isn’t enough data to perform a reliable statistical analysis (it seems to think it’s UTF16 rather than ANSI).
If you load notepad, choose File->Open and select ANSI encoding before loading the file, it will display correctly.
An interesting question is how LVM_CREATEDRAGIMAGE does the text drawing for anti-aliased fonts (for ClearType it does not draw any text). Do they translate the colour blending to alpha blending?
For the search-impaired, here’s the article Raymond alluded to:
http://blogs.msdn.com/oldnewthing/archive/2004/03/24/95235.aspx
Adam: "not thread safe" refers to the dubious practice of enabling/disabling the global ClearType setting (using SystemParametersInfo) for the duration of the painting operation, which is a criminally lazy way to enforce ClearType without changing your CreateFont calls that use DEFAULT_QUALITY. I find it hard to believe Internet Explorer 7 does this
The wireless connection dialog is guilty of this too (at least when using Windows Classic), look at the links on the left, some of them become darker with time, but revert back to normal when hovered.
“(I have no idea why people claim that there is no thread-safe way to enable ClearType on an individual basis. We’re doing it just fine here.)”
My guess? They claim it because there is no thread-safe way to enable ClearType on an individual basis.
That there is a thread-safe way to USE cleartype, once it is enabled, was never in dispute.
But I dunno, maybe that’s not the rigth page. MSDN is an impenetrable mess in which I can never find what I want since it has no sensible index or navigable tree. I can’t prove the nonexistence of a solution in there any more than I can prove the nonexistence of an invisible pink unicorn.
This is why people try posting messages to every window they can find: because finding the Right Way in MSDN is not feasible.
From reading this, I can see why Apple went with Quartz. Should be interesting to see if the Vista stuff fixes these problems.
Raymond, I’m not disputing what you’re seeing, or what the docs say, but for some people it really doesn’t work. I spent a fair amount of time trying to get this to work the way it’s supposed to, and it consistently didn’t. Quality always seems to be the lower of the global setting and the CreateFont param; I’ve just reconfirmed this now. My plaintive query (unanswered, alas) on the topic is here with more details:
http://groups.google.com/group/microsoft.public.win32.programmer.gdi/msg/c70a073ac358a708
Maybe a daft question, but are you definitely seeing non-smoothed fonts elsewhere after turning off ClearType? It doesn’t take effect until you OK the whole Display Properties dialog, not just the Effects popup. Otherwise, I give up. SDK bug that was fixed in a later version? Driver bug?
[How can the operating system “fix” this problem? -Raymond]
Simple – offer better primitives and handle window compositing in the GUI rather than expect apps to do so. It also helps that Quartz is firly well designed, and the GDI is kind of awful (comes from supporting 15 years of legacy, I suppose).
My mistake – I thought this was an interaction between AA fonts and transparent windows.
BryanK – no, the issue is exactly the opposite of what you describe. We’re trying to get a ClearType-enabled HFONT when ClearType is disabled in the control panel.
Make sure you’re running this on XP as I believe ClearType is not supported on 2K.
As far as I can tell, WPF would allow you to create this annoying effect, but since it handles all painting it would prevent this from happening by accident. The new window manager may even prevent it from happening by accident with GDI apps.
Knowbody Nos: Yes, this is XP.
Raymond: I know it sounds that way, but this honestly isn’t a reading comprehension issue :-)
Some more info though: I tried your test app and it works as advertised. I tried vanilla TextOut to the window DC in my own paint handler (not something I normally do), and that’s fine too. What was not fine, and still isn’t fine even with the exact same CreateFont call, is TextOut to a memory DC created via CreateCompatibleDC, even though ClearType is manifestly possible on this DC because it works when enabled globally.
Ah well. Something new for me to chew on, at any rate.
Btw, I love your new yellow inline comments, Raymond. Thanks for being so patient with us!
Raymond,
The documentation is not a model of clarity.
DOCUMENTATION: “If neither ANTIALIASED_QUALITY nor NONANTIALIASED_QUALITY is selected, the font is antialiased only if the user chooses “smooth screen fonts” in Control Panel. ” [in CreateFont]
REALITY: if CLEARTYPE_QUALITY is chosen, the font will be antialised (if possible) despite the control panel.
WHY: because the documentation was not properly updated when XP came out. Cleartype is a type of antialiasing, but it still isn’t fully integrated into the documentation.
(also, the control panel doesn’t say “smooth screen fonts” — at least, not any more. It says, “smooth edges of screen fonts” and gives the choice of nothing, Standard, or ClearType.)
Raymond,
The model that Windows uses for drawing on the screen is not universally used by all windows system. The VAX windowing system, for example, had the very handy feature that once you drew on the screen once, you didn’t have to redraw it — ever. If you wanted to change the screen, you just changed it.
It’s not a change of primitives; it’s a change in who “owns” the job of handling overlapping wndows. Some systems decided that it was a job that could be done by the window system; others decided that it was too hard, and gave it back to the individual programs.
The VAX system, BTW, had a neat feature: if you had a mostly-bare window, it would remember all of the drawing commands you used to make it; as the screen got more complicated, the window system would automatically start saving your window as a bitmap instead.
IMHO, the VAX windowing model was better. In many Microsoft Windows programs, a lot of time is spent ‘remembering’ just what’s on the screen — thus, a basic cycle is to calculate a certain amount about what should happen, and store it in a unique-per-application structure. When the application gets a ‘paint’ message, it must then paw through that structure and paint it.
To me, it’s a pain. It means I have to chop up every action into two pieces, and keep around a bunch of context just to make things happen. The context, in turn, has to be managed. It’s a lot easier to ‘just draw something’ and move on.
Peter
@Dewi Morgan: you claim the following at your website:
"MorganAlley’s people bring decades of experience with websites and the Internet"
Decades is plural, making it no less than 20 years. You *really* designed websites back in 1986?
J
Eden, you’re reading the wrong blog. http://blogs.msdn.com/michkap/archive/2006/06/14/631016.aspx
Hi,
Is there any way to prevent this?
Hi,
Is there any way to prevent this?
BryanK: "(If I were writing a Win32 program, I’d just pass DEFAULT_QUALITY, and use the system ClearType setting… but that’s a separate issue.)"
It depends on why you are drawing. I have some library code which draws to a bitmap, and sometimes that bitmap goes directly onto the window (it is a back-buffer) and sometimes it gets exported as a .BMP file. In the latter case I don’t want ClearType. ClearType uses sub-pixel positioning on an LCD screen. The .BMP may end up on a CRT screen or a printer, and ClearType looks poor there.
Of course you don’t get anti-aliasing this way, because you’re doing all the rasterization yourself. Perhaps you need a bit of Anti Grain Geometry (http://www.antigrain.com).
Not sure if anybody still reads these comments a day later, but for the benefit of any future Googlers I tracked down the problem described earlier. And hey, it’s not as if the Microsoft blogging community has anything else to talk about this evening.
The problem turned out to be an (arguably) undocumented precondition of GetTextMetrics. When you call this function with a memory DC, you MUST have already selected a bitmap into that DC. Otherwise, antialiasing for the selected font is forced down to the worse of the font and global settings. Thus, with ClearType disabled globally, a memory HDC and a ClearType-enabled HFONT:
SelectObject(hdc, hfont);
SelectObject(hdc, hbitmap);
GetTextMetrics(hdc, &tm);
gives you ClearType text, and
SelectObject(hdc, hfont);
GetTextMetrics(hdc, &tm);
SelectObject(hdc, hbitmap);
doesn’t.
Quite why this should be, I’ve no idea. The CreateCompatibleDC docs talk about needing a bitmap for drawing ops, but GetTextMetrics is hardly a drawing op. The same docs mention that the default drawing surface is monochrome, but if that were the deciding factor I’d expect ClearType to be disabled regardless of the global setting, and it isn’t.
In any case, it’s trivial to work around. Mildly annoying, since I was using the results of GetTextMetrics to calculate the size of bitmap needed, but recreating and reselecting the font before drawing works fine. A dummy bitmap would probably also do the trick.
Raymond:
I think you misread my post. The problem was with GetTextMetrics, not TextOut. Obviously, I was selecting a bitmap before doing any drawing, or I wouldn’t have seen any text, antialiased or otherwise.
The questions are:
1) Why does GetTextMetrics care whether I have a bitmap selected? Heck, from the description it should be possible to fill a TEXTMETRIC from just an HFONT, without even an HDC.
2) Even if it does care, why is a “getter” function like GetTextMetrics actively changing the state of a font? Reselecting the same HFONT afterwards did not correct the issue; it took another call to CreateFont.
Ah, OK. Scratch question 1.
“All sizes are specified in logical units; that is, they depend on the current mapping mode of the display context.” (TEXTMETRIC docs)
No, that doesn’t fit. If that were the root cause, then the first sequence above wouldn’t have worked – select font, select bitmap, call GetTextMetrics – because the bitmap is still monochrome when the font is selected. And it does work; I’ve just checked, twice.
And recall that I was getting ClearType when it was enabled globally, even when selecting the bitmap after BOTH selecting the font and calling GetTextMetrics.
The page you linked was interesting; I hadn’t seen that material before. However, UnrealizeObject/SelectObject does not restore ClearType rendering. CreateFont/SelectObject does. There’s definitely something fishy going on.
(Clarification, since I think server lag is confusing matters. My "scratch question 1" comment was a followup to my 7:42 post, not a reply to your comment on it.)
Responding to your comment on my 8:39 post…
The page you linked includes the following passage:
"When an application changes the logical device mapping of a DC (by changing the mapping mode or the window or viewport definition), the system re-realizes the currently selected pen and font before they are used the next time."
If selecting a bitmap into a memory DC counts as changing the logical device mapping, this would explain why I could get ClearType even though the DC was monochrome when the font was originally selected.
I was already selecting a different font before trying UnrealizeObject/SelectObject, just to be on the safe side. Made no difference.
Probably not much else to be said here; it really does look as though there is something rotten in the state of GetTextMetrics. Might justify a note in the MSDN docs. In any case, many thanks: I wouldn’t have discovered the specific problem and the workaround without the encouraging knowledge that this stuff was working for somebody somewhere.
Avalon will generally fix this problem for new app developers who use it and I’m glad for that.
I really wish it had an unmanaged interface. Is there a good chance the shell will start using it in Vista++ (Vienna?)?
BTW, one reason that GetTextMetrics would care about the bitmap is that it may return different results if ClearType is on. The antialiasing allows it to space letters more precisely, so a run of text may not be the same length on a monochrome bitmap as on a device bitmap.
I had a lot of trouble getting Clear Type to work on my menu class, which draws to a memory DC. I remember searching thoroughly combing the documentation and not understanding why my ANTIALIASED did not work. Since system menus where properly antialiased, and my tear off menus were not, they stood out.
Now I look back at the file diff and I seems all I ended up doing was use PROOF_QUALITY for the antialiasing flag in the LOGFONT structure — instead of ANTIALIASED_QUALITY.
LOGFONT g_lfNormal ={ -11, 0, 0, 0, FW_NORMAL,FALSE, FALSE, FALSE,DEFAULT_CHARSET,
OUT_STROKE_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH|FF_DONTCARE, _T("Tahoma")};
With regards to extracting antialiasing font bitmaps with GetGlyphOutline, I have done this as well. I needed this to generate a font bitmap to use in OpenGL. But I am doing this ‘offline’ and generating a bitmap file, a mosaic of all ASCII character I need, which I ship with the app. I didn’t need to go through that trouble, but I investigated using that.
Using the bitmaps out of GetGlyphOutline is too much work guys, certainly don’t use use this if you want to draw on screen with it at runtime.
If you want do to something forced antialiasing offscreen like this, I suggest using GDI to draw whatever you need at twice the size, and sizing down the bitmap with a good filter. It will end up being a lot less image processing code than to apply that 64-shades-of-gray image onto your background.
Because once a filter is running and optimized, it runs fast and it scales well, no matter the complexity of the graphics elements or text you want to anti-alias.
@ping and pong
You write: "Decades is plural[…] You *really* designed websites back in 1986?"
Thanks, I’ll pass your comment to our copy-writer: I suspect he counted "man-decades". Both directors began webdev work in ’93, and 1.3 "decades" each isn’t really "decades", even to href="">one significant figure</a> (except in the gramatical sense that pluralises any fraction).
Back on topic:
Raymond is clearly correct, and I clearly wrong (I should have tested his code before speaking!).
Thank you for this post, Raymond: I’m writing a "transparent Java1.1 rich-text no-Swing textarea" at the moment (pointless, yes: so sue me!), and if Java respects the system ClearType settings, this "darkening" effect is definitely something I shall have to bear in mind.
Your article is prety nice. It’s a pity that i didn’t see it more later.
Wow, I’m absolutely astonished. One of the first things I do after installing Windows is turn off all the effects that just use up more CPU time and make it slower for the information I use to appear. Well, I turned on Clear Type today on my notebook (XP Pro SP2). WOW!!! I never realized how poorly the fonts looked before. The letters are nice and crisp. I was starting to have to squint at the letters, and was considering increasing my font size. There’s no need for that now. Even if this does take up some CPU time, I’m keeping it. Raymond, thank you so much for bringing up this topic.