Monday, May 10, 2010

Pixel Considerations

WARNING: This blog entry was imported from my old blog on (which used different blogging software), so formatting and links may not be correct.

Antialiasing makes lines and shapes look smooth - though sometimes at the expense of sharpness. What if you're trying to draw a horizontal or vertical line where you don't need antialiasing? You might be under the impression that if you position your shapes at round integer positions, you will avoid antialiasing.
But it's not quite that simple - so avoid trying to be smart with layout code like this:

label.layoutX = (width - labelWidth) / 2 as Integer;

The idea here is that when you're placing something in the middle you might end up with a fractional value, say 42.5, and so you added the "as Integer" to round things to a whole number to avoid introducing antialiasing.

Well, this may have exactly the opposite effect! Take a look at the following picture, which shows two rectangles. Both rectangles have a stroke width of 1.0, and one of them is positioned at a round integer, and the other one is positioned at 0.5.

Here's a zoomed in view which makes things clearer:

Obviously, the rectangle on the left is blurry because antialiasing is attempting to show the line as being somewhere in the middle between them. The rectangle
on the right on the other hand is clear and crisp because the lines overlap EXACTLY with the pixel grid the line is rendered into.

Here's the thing though: The rectangle on the left is the one that was positioned at round integers, and the rectangle on the right is the one positioned
at round integer + 0.5 !

So should you run out and ensure that all your horizontal and vertical edges are positioned at 0.5? No. The key here is the stroke width. Take a look at the following figure, where I have position rectangles with different stroke widths (1, 2, 3) and different pixel positions (0, 0.25, 0.5).

Zoomed in:

As you can see, whether you match the pixel grid perfectly depends on the stroke width and the pixel positions. This actually makes sense. Think of your pixel grid as having x coordinates at the boundaries of each pixel. In other words, "0.0" is the left edge of the first pixel, and 1.0 is the right edge of the first pixel. The line position has to be the center of the stroke. So if you want to have a line of thickness 1, then that line will run exactly through the pixel, so we must position its center at x=0.5. When the stroke width increases to 2 however, the center will be in the middle (e.g. 1), and so we should position it at a round number. And so on.

When you're dealing with large shapes this isn't a big deal. But if you're trying to paint a grid (like the one below), a pattern, or small controls (like disclosure arrows - which is how I came to look into this), it can pay off.

By the way -- on OSX there's a nice screen zoom (hold the Option key and then do a two-fingered drag on the trackpad up or down) which makes it easy to zoom in and look at the pixels for anything on the screen. But unfortunately it doesn't show pixels as square, it does more blending, so it's much harder to tell what's going on at the individual pixel level. Get an image editor which lets you zoom in with clear pixel boundaries, or even a screen magnifying lens. Here's how the builtin screen zoom looks - as you can see it's not as clear as the pixel zooms above:

UPDATE: Marius taught me in the comments that you can turn off the OSX zoom smoothing in the Universal Access options. Sweet! I can now instantly check the pixels without going to an intermediate screenshot! Thanks!

Finally: Jonathan Giles from the JavaFX controls team has been doing a great job aggregating and summarizing interesting FX articles each week -- highly recommended if you're doing anything with JavaFX.

No comments:

Post a Comment