Smaller PNGs, and Android’s AAPT tool
Great question Dan. I hope that you were contemplating this while you took your profile picture ;)
And yes, it’s true, the android toolchain does in fact run some PNG optimization. Check out the official docs:
Image resources placed in res/drawable/ may be automatically optimized with lossless image compression by the aapt tool during the build process. 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.
Hmm.. so that’s nice.. Images with < 256 colors will get converted to indexed mode… but is that all? To find out, we need to dig into the source code.
What the AAPT tool does with PNGs
A nice thing about Android is that most of the tools are open source, so we can look directly at the AAPT source code, to see what it really does. Tracking down the analyze_image function inside of png.cpp we can see that it checks for three specific optimizations:
1. Every pixel has R == G == B (grayscale)
2. Every pixel has A == 255 (opaque)
3. There are no more than 256 distinct RGBA colors
So AAPT will load your PNG, test if it can convert it to grayscale, test if it’s fully opaque, or test if it can be converted to an indexed variant. That’s it.
These checks make a lot of sense when you consider the fact that most of the PNG assets inside of an APK typically are either grayscale, opaque, or limited in color uniqueness; Having the ability to take these known formats, and convert them to smaller PNG files, seems like a good deal.
But what if I want more than that?
Probably more interesting, is all the stuff that AAPT doesn’t do; which includes anything that A) will alter the visual perception of your image (potentially making it lower quality) and B) anything more advanced than color mode swapping.
This is the exact reason I discuss using more advanced tools in my previous article. Applying these to your pipeline can help reduce PNG file size in a way that the standard AAPT tool can’t.
It’s important to remember that with the modern data compression algorithms, recursive data compression isn’t possible. If you take a piece of data that you’ve compressed, and try to compress it again, you’ll usually end up making it larger and at best, just keep it the same size.
So, if you’ve applied an optimization process to PNGs, prior to them being tossed to AAPT, they may end up getting inflated, sometimes by 10+%. Which seems like a raw deal: If you’ve taken all this time to shrink your PNG files, they shouldn’t get inflated later :\
The solution to this issue is using the cruncherEnabled flag in your Gradle files to disable this process for PNG files:
cruncherEnabled = false
This will turn off the AAPT png optimization for non-9patch PNG files inside of your APK (I haven’t figured out what it does with 9Patches yet…)
The result, is that you can use your own custom PNG optimizer scripts, shrinking them as small as possible, and know that the AAPT tool won’t accidentally inflate them later.