Guest Writers: Anthony & Miles are Developers on the WPF 2D Graphics team. Anthony specializes on our Geometry, Miles focuses on the Brushes codepath.
We in WPF-land have been fielding a lot questions from folks concerned that bitmaps viewed in WPF tend to look a little soft — to be precise, that crisp lines in bitmaps tend to smear across pixels. As a lot of you have surmised, the central reason for this is that WPF is a resolution independent framework: instead of talking about pixels, we talk about inches(or more precisely, DIPs, which are defined to be 1/96th of an inch). The precise location of the pixels gets abstracted out as an implementation detail.
In order for bitmaps to look crisp in WPF, the centers of the bitmap pixels have to align precisely with the centers of the device (monitor) pixels. This essentially means two things: the DPI of the bitmap has to be the same as the DPI of the device and the top-left-hand corner of the bitmap needs to be an integral number of pixels from the top-left-hand corner of the device. Because layout doesn’t know anything about pixels (either on the bitmap or on the device), though, there is no easy way to satisfy these two conditions.
What can I do to fix this?
In V1, we strongly urge developers to stay away from bitmaps with crisp edges (also known as “high-frequency bitmaps). As a general rule-of-thumb, you should only use bitmaps that you’d be comfortable compressing with Jpeg – if you need the visual quality of a lossless codec, it’s probably not going to look good in WPF.
If you absolutely must use crisp bitmaps (and we fully understand that there are a few scenarios where this is necessary), there are a couple of things that you can try doing:
Always use bitmaps that are the same DPI as the device on which you’re displaying. Since you don’t know at compile time what DPI your device is, in practice this means creating at least 3 copies of every bitmap: one for 96 DPI (the most common today), one for 120 DPI (the up-and-coming standard), and one for 144 DPI (common on some laptops). Of course, this still doesn’t handle every case: users are free to set whatever DPI they like on their machine and bitmaps might still look blurry in accessibility scenarios as well. You also have the problem of determining what the device DPI is in the first place – something that may not be possible in certain scenarios (e.g. Partial Trust).
Ensure that the bitmap is placed an integral number of pixels from the top-left-hand corner of the device. This one is more difficult. If all your elements are fixed-layout (e.g. Canvases), you can precisely position your content relative to the window (and in V1 windows are always pixel-aligned). If you make use of any of the more complicated layout elements (Grids, DockPanels, FlowDocuments, or the like), though, you must walk up the visual tree to calculate your offset from the window origin, and you have to do this every time layout changes (for instance, every time the user scrolls through the document).
What about pixel snapping?
Even though layout doesn’t know anything about pixels, WPF does in fact offer a mechanism for making vector content look crisp. This is the much talked about SnapsToDevicePixels property that WPF designers have grown to know and love (okay, “love” may be an exaggeration J). This works wonders on things like Controls and Shapes, but surprisingly it does nothing to improve the crispness of bitmaps. To understand why, recall how pixel-snapping works: Snapping defines a set of guidelines that which edges should be made crisp. Then, deep in our rasterizer (deep enough that we’re interacting with device pixels), we nudge vector content that lies along these edges to be pixel-aligned. Portions of geometry that don’t lie along the edges are stretched ever-so-slightly the corresponding amount.
The key point in all of this is that it’s geometry that’s pixel-snapped, not what’s used to fill that geometry (in this case, bitmaps). And geometry is really the only thing we can snap: if we “snapped” the fill, too, we’d end up stretching it by a couple of pixels, and any amount of stretching will cause the bitmap pixels to be misaligned with the device pixels – exactly what makes bitmaps look soft in the first place.
Is there ever going to be a solution?
While we weren’t able to solve this problem in V1, we fully appreciate its importance among designers, and it is a high priority item for us in the future -- both because of the crispness issue and because our current approach effectively halves the fidelity of the bitmap (do an internet search for “Nyquist frequency” for more info on this). When we do introduce a fix, though, it probably won’t be part of pixel-snapping. As I hope I’ve conveyed, pixel-snapping is a fundamentally different operation than what’s desired (despite the name).
In any case, thank you all very much for your feedback on this issue, and be rest assured that we are investigating this for the future.
 Actually, if a crisp bitmap is stretched by only a few pixels, it’ll look worse than soft. Because the bitmap and device will be almost “in phase” the bitmap will “beat”, causing parts of the bitmap to look crisp and other parts to look blurry. In many cases, this looks worse than if the bitmap is uniformly blurry:
Offset by .5 pixels (uniformly blurry):
Scaled by .99375 (two pixels on a 120 dpi screen):