Saving optimal JPEGs on iOS
Conventional wisdom for creating a JPEG version of a UIImage
is first to turn it into an NSData
and immediately write it to disk like so:
NSData *jpegRepresentation = UIImageJPEGRepresentation(image, 0.94);
[jpegRepresentation writeToFile:outputURL.path
atomically:NO];
Most of the time this is exactly right. However, if file size is important, Image IO is a great alternative. It is a powerful system framework to read and write images, and produces smaller files at the same compression level.
Why Image IO?
A project I am working on requires uploading photos en masse. Low upload bandwidth makes file size a limiting factor, so I sought out ways to reduce it.
I put together a test project to find the differences between the two methods. The results are pretty interesting:
- Image IO files are on average 20% (but up to 30%) smaller1.
- Image IO takes about 2x longer.
The only discernible visual difference is the grain in the images, but even that is minor. Here’s a diff between two versions of the same original photo:
Using Image IO
First, you’ll need to add two new framework dependencies:
@import ImageIO; // to do the actual work
@import MobileCoreServices; // for the type defines
When creating your JPEG file, the first step is to create a CGImageDestinationRef
specifying where to write the result:
CGImageDestinationRef destinationRef =
CGImageDestinationCreateWithURL((__bridge CFURLRef)outputURL,
/* file type */ kUTTypeJPEG,
/* number of images */ 1,
/* reserved */ NULL);
Image IO is able to produce files of a few different types2 but my focus here is JPEGs. Next, we set up the properties of the output file, specifying a constant compression factor:
NSDictionary *properties = @{
(__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.94)
};
CGImageDestinationSetProperties(destinationRef,
(__bridge CFDictionaryRef)properties);
And, importantly, we specify what is to be written out:
CGImageDestinationAddImage(destinationRef,
/* image */ image.CGImage,
/* properties */ NULL);
And finally, we write it to disk and clean up the reference:
CGImageDestinationFinalize(destinationRef);
CFRelease(destinationRef);
-
The UIImage version has a color profile, while the Image IO version does not. However, running both files through Image Optim produces a 7% reduction on both, so I am choosing to ignore this difference. Afterall, you can’t remove the color profile anyway! ↩︎
-
The following are possible types you can use, from the documentation:
Constant UTI type kUTTypeImage
public.image kUTTypePNG
public.png kUTTypeJPEG
public.jpeg kUTTypeJPEG2000
public.jpeg-2000 (OS X only) kUTTypeTIFF
public.tiff kUTTypePICT
com.apple.pict (OS X only) kUTTypeGIF
com.compuserve.gif