Android 2.3 is finally out! I recently mentioned changes to default bitmaps and windows formats we made in Gingerbread and I would like to explain why these changes were made. I will also show you the difference between Android’s various bitmap formats and why you must use them carefully.
Early Android devices had limited memory and computing power and to optimize the system we chose to draw opaque applications, by default, on 16 bits surfaces. The encoding format we use for 16 bits opaque pixels is called
RGB_565: red and blue components have 5 bits of precision, and green has 6 bits of precision. This work relatively well but can lead to severe visual artifacts when drawing with bitmaps encoded in other formats. The two other formats supported by Android are
ARGB_4444. Both formats allow the encoding of an alpha channel for translucency and only differ in the precision of the components.
ARGB_8888 requires twice the amount of memory, as each pixel is encoded with 32 bits, but provides the best quality. It is also the format we use automatically when loading bitmaps with an alpha channel (PNG resources for instance.) You should ignore
ARGB_4444, unless you really know what you are doing, because it looks awful.
What does it all mean for your application? To make it easier to understand, I wrote a simple application you can run on your device, or on the emulator, to compare the quality and performance of various source and destination formats. The application contains two activities, one running in 16 bits, and one running in 32 bits. When launched, each activity lets you switch between bitmaps loaded in the 3 formats I described earlier. You can also enable dithering and profiling.
Let’s start by comparing the three bitmap formats as they appear when drawn on a 16 bits window (click on the image for a full-size version that will make the differences more obvious):
To better compare the three formats, take a close look at the gray gradient on the left side of each screenshot:
You can see three types of artifacts in this image: banding, low precision and dithering. The three bitmaps are loaded from the same resource and converted at load time using BitmapFactory.Options.inPreferredConfig. The first bitmap,
ARGB_8888, shows weird bands of colors, particularly visible in the gray gradient. You can also see that the bands have a slight green shift. This artifact appears when the 8 bits of precision are clamped to 5 or 6 bits of precision.
ARGB_4444 exhibits a similar behavior because of the upscale from 4 bits of precision to 5 or 6 bits. You can also notice a dithering pattern. Finally, the
RGB_565 shows a clear dithering pattern.
These atrocities are unfortunately the default behavior on Android before 2.3. Fortunately, In most situations the user will not notice these artifacts. These artifact show mostly on subtle gradients or very specific types of images. There are also ways to hide these artifacts. For instance, opaque images that will be loaded in
RGB_565 can be pre-dithered in a tool like Adobe Photoshop, with more powerful and higher quality filters than the one used by Android. I would however not recommend this approach because it forces you to bake noise in your image, making it harder to reuse in the future. Another solution is apply dithering on 32 bits images. The following screenshot shows an
ARGB_8888 bitmap drawn on a 16 bits window with dithering enabled:
The terrible banding artifact we saw earlier is now gone! The only difference is enabling the dither flag on the paint (it also works on drawables.) Of course, because of its very nature, dithering is not perfect. A much better solution is simply to render 32 bits bitmaps on 32 bits surfaces, which Android 2.3 now does by default. The following screenshot shows the result of this new behavior:
The following image shows the difference between the previous approach, a dithered 32 bits bitmap drawn onto a 16 bits surface, and the new approach, a 32 bits bitmap drawn onto a 32 bits surface:
This image clearly shows that dithering introduces a very special type of noise used to trick your eye into seeing a smooth gradient. However, the 32 bits surface shows no artifact whatsoever and clearly provides the best quality. This is why we changed the behavior of Android 2.3 to loads bitmaps and windows in 32 bits by default.
You should also remember that choosing the appropriate bitmap format can lead to better runtime performance. For instance, drawing a 32 bits bitmap onto a 16 bits surface requires a specific conversion that costs CPU time. The following table compares the time taken to draw bitmaps in different formats, with or without dithering, on both 16 and 32 bits windows:
This simple performance test shows very clearly that using a compatible format (32 bits bitmap on a 32 bits window or 16 bits/565 bitmap on a 16 bits window) is the most efficient way to draw bitmaps. For this reason, you should always check the format of your bitmaps and windows and try to make them compatible with each other. You can choose a bitmap’s format when creating an empty bitmap or when decoding a resource or stream. You can of course check the format of an existing bitmap using Bitmap.getConfig(). You can choose and query the format of the window by using getWindow().getAttributes().format from your
onCreate() method. You can also refer to this article’s example source code to see how to choose a particular format.