PNG compression in Android… (You have got to be kidding)

Over the last few weeks, I’ve been learning the Android SDK in an effort to bring Layers to Android devices. It’s going pretty well, but every once and a while I run into a truly WTF moment.

Tonight I was importing some images from the iPhone version of Layers when I noticed that Android seems to visibly reduce the quality of PNG files at compile time. In images with fine gradients, smooth color transitions, or very light shadows you tend to get banding. It almost looks like the image were being converted to a GIF file.

I figured it’d be easy to fix. Go into Eclipse, right click on everything, look in menus… repeat… profit! Unfortunately, it seems there’s no way to turn off compression for specific file or choose a non-lossy compression method. However, I found this gem of a solution in the Android Widget Design Guidelines:

“To reduce banding when exporting a widget, apply the following Photoshop Add Noise setting to your graphic.”

Um… what now?

It turns out, you can get around the compression algorithm by adding a small amount of pixel noise to your images. It’s mostly invisible, and it prevents the compression from producing obvious bands.

It’s an instant fix, but I almost laughed out loud. Seriously? This is the documented solution? *sigh*.

  1. Hi Ben,

    Glad to see that you’re taking Layers to other platforms.

  2. Nice article ! Unfortunately, you’re not understanding the problem in the correct way. Actually, Android is not compressing graphic resources at all. The problem comes from the fact most (I think all for now :( ) Android devices are based on a display that uses a 16-bits color palette to render colors. As a result, you get banding effects with simple gradient.

    I’ve wrote an article dealing with that problem (http://android.cyrilmottier.com/?p=196) : it’s in French but I’m sure Google Translate will help you :) .

    Good luck and welcome to the Android world !

  3. lailai

    Android fail haha

  4. Jay

    You might want to try inserting images into the res/raw/ directory.

    http://developer.android.com/guide/topics/graphics/2d-graphics.html

  5. Andrew Lundin

    Hi! I’m just getting into Android development, and I happened to run across your article yesterday. This morning I read about something that may provide a better solution to this problem. The following “Note” comes from this page:

    http://developer.android.com/guide/topics/graphics/2d-graphics.html

    Note: Image resources placed in res/drawable/ may be automatically optimized with lossless image compression by the aapt tool. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette. This will result in an image of equal quality but which requires less memory. So be aware that the image binaries placed in this directory can change during the build. If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in the res/raw/ folder instead, where they will not be optimized.

    I guess the noise they suggested to add is probably chromatic noise, which would expand the palette beyond the 256 colors eligible for 8-bit compression, meaning it probably won’t be recompressed at all. But if you don’t mind the hassle of reading the image as a bit stream, you can prevent the recompression without altering the image. I have not tried this, but it looks promising. I hope it helps!

  6. Hey,

    The real solution is here for in apps :

    http://stuffthathappens.com/blog/2010/06/04/android-color-banding/

    Tried it just now on my app and it works like a charm except I had to put it in the oncreate and not in the attachedwindow.

  7. Kevin

    Cyril is absolutely correct. This is neither Android-specific (the iPhone is 6-6-6 so 18 bits of color all in all) nor does the dithering stop any “optimiziations”. Most smart phone displays can only display 16 bits of color. Newer smartphones can show more but still like to use a color depth of 16 bits because it saves memory and cpu. You can tell the window manager to go to 24 or 32 bits if you want to, but your bitmaps will (obviously) need more ram.

  8. dori

    You can just set

    getWindow().setFormat(PixelFormat.RGBA_8888);

    before setting the contentView in an activity…solved!

  9. Alex Berg

    I suggest using the excellent program DamageControl. The program applies dithering to images, and can support dithering to 16 colors. Though new warned that if you resale the images on the device before displaying the image you will likely loose the careful dithering created by Damage Control and banding will reappear.

  10. I found a great trick for my game. I use the drawable-no-dpi folder.
    This way, nothing happens to my graphics.

    I use photoshop only now and create plenty of nice 9 patch PNGs.

    Feel free to drop by and grab them on my blog:
    android9patch.blogspot.com

    Free to use for any apps, including commercial.

    They can be editing with photoshop or the android 9 patch tool.

  11. I found a great trick for my game. I use the drawable-no-dpi folder.
    This way, nothing happens to my graphics.

    I use photoshop only now and create plenty of nice 9 patch PNGs.

    Feel free to drop by and grab them on my blog:
    android9patch.blogspot.com

  12. @Richard – Minor correction: the name of the folder needs to be “drawable-nodpi” (you had an extra “-”).

    BTW, thanks for sharing your 9-patch files – some good stuff there.

  13. Kat

    We’re having a slightly different problem… there’s no banding, but the colours are appearing completely wrong. We’re trying to get a sandy coloured png to appear, but it turns blue on android :/ and we can’t seem to find any fix for it, or even anyone with the same problem.

Reply to 'PNG compression in Android… (You have got to be kidding)'