Saving Optimal JPEGs on iOS

Published Jul 18, 2017
Saving Optimal JPEGs on iOS

The conventional way to create a JPEG version of an UIImage is to first turn it into an NSData and immediately write it to the disk like so:

NSData *jpegRepresentation = UIImageJPEGRepresentation(image, 0.94);
[jpegRepresentation writeToFile:outputURL.path

This is right most of the time. However, if the size of the file is important, then 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. I sought out ways to reduce the size of the files as low upload bandwidth makes file size a limiting factor.

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%) smaller[1].
  • Image IO takes about 2x longer.

The only discernable visual difference is the grain in the images, but even that’s minor. Here's the difference between the two versions of the same original:


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 a few different file types[2] but I am focusing on JPEGs. Next, we set up the properties of the output file, specifying a constant compression factor:

NSDictionary *properties = @{
  (__bridge NSString *)kCGImageDestinationLossyCompressionQuality: @(0.94)

                                (__bridge CFDictionaryRef)properties);

The next step is mportant, we need to specify what has to be written out:

                           /* image */ image.CGImage,
                           /* properties */ NULL);

Finally, we write it to the disk and clean up the reference:


(This is from my blog.)

  1. The UIImage version has a color profile, while the Image IO version does not. However, as there is a 7% reduction on both files when running them through Image Optim, I am choosing to ignore this difference. Afterall, you can't remove the color profile anyway! ↩︎

  2. The following are possible types you can use:

    Constant UTI type
    kUTTypeImage public.image
    kUTTypePNG public.png
    kUTTypeJPEG public.jpeg
    kUTTypeJPEG2000 public.jpeg-2000 (OS X only)
    kUTTypeTIFF public.tiff
    kUTTypePICT (OS X only)
    kUTTypeGIF com.compuserve.gif
Discover and read more posts from Zachary West
get started