Lean Universal Apps ✿Franz Bruckhoff — July 28, 2014✿
Customers love universal apps that run on both iPhone and iPad. Because universal apps bundle all iPhone and iPad resources together, they may require much more space. If your app is graphics-intensive like Windy, you’ll want to figure out how to reduce its bursting download size by 50% or more without an impact on quality or performance.
The magic spell: “Lean Universal Apps”, Ladies and Gentlemen!
A lean universal app reuses as many resources as possible across all supported devices, and makes clever choices about the image formats and resolutions based on visual perception principles.
Windy reuses nearly all iPad graphics on the iPhone and iPod touch, which brings the download size from roughly 200 MB down to under 100 MB. Darker and less prominent graphics are included at @1x resolution for the iPad. In addition, nearly all PNG images are decomposed into pairs of opaque JPEG images. One for RGB and one for alpha. Windy scales them down and combines them at runtime into a single image with transparency.
Impact on Performance
“Wait a minute”, the skeptic says. “This seems like a lot of extra work for the iPhone and iPod touch. Imagine these poor little devices, with their tiny processors running hot like the sun, frantically cramming hundreds of colossal iPad @2x graphics into their overstuffed memory. And how many of them escape this unbearable imposition by terminating your app with low memory warnings.”
It’s not nearly as bad in reality. JPEG decoding times are multiple times larger than loading times of large files. Because JPEG decoding happens in the frequency domain, and the CPU is smart about decoding large files for thumbnails efficiently, the additional performance hit of using @2x iPad images on iPhone or iPod touch is very acceptable.
While the fast GPU decodes images at their original resolution, the slower CPU only decodes images at their target display size. The GPU is literally faster, and dumber. It’s a bit like Scrat, the Ice Age squirrel. Very quick at gathering all the nuts, but not too strategic about it.
The trick is to obtain a UIImage for the RGB JPEG and one for the alpha mask JPEG, and let Core Graphics scale them down into thumbnails on the slow but smart CPU, rather than the fast but dumb GPU:
UIGraphicsPushContext(ctx); CGContextDrawImage(ctx, CGRectMake(0, 0, dstWidth, dstHeight), cgImage); UIGraphicsPopContext();
In this case, “slow and smart” turns out to be roughly 3 times faster in average than “fast and dumb”.
Once the image pair is scaled down, Windy combines them into one image, which is then used by CALayer. Both CALayer’s contents and UIImageView’s image involve the GPU for fast drawing. So the rule of thumb for lean universal apps is to decompress and downscale on the CPU, rather than the GPU.
JPEG 2000 (JPF)
JPEG 2000 is JPEG with alpha transparency. When I heard about it, I was almost as excited as Scrat discovering a nut. Until reality showed up. While a test revealed that compressed JPF files were round about 40% smaller than 24-bit PNGs, they were still round about 6 times larger than pairs of two JPEGs for RGB + A. In addition, JPF decoding is round about 5 times slower than JPEG decoding.
PVRTC’s main advantage is that it doesn’t require decoding, and therefore loads and displays almost instantly. This is especially great for fast-paced games. However, they’re even larger than PNGs, which makes them a bad fit for lean universal apps.
ImageMagick for gray-scale JPEG
Logic demands that JPEGs in the gray-scale color space must shine with dramatic file size reduction over RGB JPEGs, right?
Be aware that Photoshop’s “Save for Web & Devices” saves JPEGs in the RGB color space regardless of the document’s color mode setting. To get a true gray-scale JPEG, you’ll want to “Save as…”.
As far as my experiments are concerned, true gray-scale JPEG images exported via Photoshop offer no file size advantages over RGB.
The good news is that
CGImageMaskCreate() happily takes an RGB pseudo-gray-scale JPEG and converts it into the proper format of a Core Graphics mask image without a noteworthy performance impact.
Another way to get true gray-scale images is to convert them via ImageMagick – a powerful command-line utility which can convert your RGB JPEG images into the gray-scale color space. You can install it via MacPorts by running
$ sudo port install ImageMagick
in Terminal. You can then convert your RGB JPEGs into gray-scale JPEGs using this Terminal command:
convert -type Gray-scale rgb.JPEG gray-scale.JPEG
However, JPEG does not lend itself very well to true gray-scale representations. Because we’re more sensitive to variations in brightness than hue, JPEG can apply more aggressive compression factors to color. The reduction in file size is fairly minimal. For example, a 42 kb RGB JPEG results in 37 kb gray-scale using ImageMagick’s conversion capabilities. One explanation might be that I simply used the wrong tools or settings, though I feel I have tried everything under the sun.
This is where ImageOptim comes to the rescue.
Further weight loss with ImageOptim
ImageOptim is a tool that finds the best compression algorithms and settings to squeeze the last drop of needless data out of your images.
Interestingly, optimized gray-scale JPEGs show no file size advantages over optimized RGB JPEGs, while pseudo-gray-scale JPEGs in the RGB color space remain in the RGB color space. Gray-scale JPEGs optimized with ImageOptim had the same file sizes as the RGB JPEGs converted to gray-scale color space via ImageMagick, which means we can probably skip ImageMagick and enjoy ImageOptim.
Xcode’s PNG Compression
Xcode has a built-in PNG compression which is enabled by default. If you’re using carefully optimized PNGs though, you’ll probably want to disable Xcode’s PNG compression, because it can make your PNGs larger than they were before.
COMPRESS_PNG_FILES = NO;
From my observation, ImageOptim outperforms Xcode’s PNG compression.
Want your app to be smaller with the least minimal effort? Use ImageOptim and disable Xcode’s PNG compression.
Want your universal app to be lean like Windy? Reuse most of your iPad @2x images on iPhone and iPod touch, and split your PNGs into optimized RGB + alpha pairs. Then scale them down and combine them at runtime as needed.
If I sense enough interest, I’ll throw in some extra hours to open-source the image loading and processing code used in Windy.