Resizing High Resolution Images on iOS Without Memory Issues

If you have ever worked with user generated images in an iOS app, you would know that it is very easy to quickly spend all the memory you have which would then result in your app being terminated by the OS. In order to support high resolution images in an app, I searched around for ways to resize images on device. The easiest and the most common way is to use CoreGraphics to draw the image to a smaller rect and then convert it back to a UIImage. The problem with this approach is that it isn’t gaining you anything because for drawing the image, you are decoding it anyway which requires reading the full sized image and might crash the app.

To correctly resize the image without decoding it first is to drop down a level and use the ImageIO framework. The first thing you need with the ImageIO framework is to create an image source from an image (the image can be an existing UIImage, path to a file or even an ALAsset). Let’s see how we can resize an image when we know the local path to the image. If you have the image in some other form, all you need to change is this step (see this for details).

The next step is to use the CGImageSourceCreateThumbnailAtIndex method to actually generate the thumbnail image from this source. We need to pass some options here to configure the thumbnail generation (e.g. the max pixel size). kCGImageSourceCreateThumbnailWithTransform option automatically rotates the image to the correct orientation and kCGImageSourceCreateThumbnailFromImageAlways asks the OS to create the thumbnail even if it is already present. This option is important as without it, the OS might use an existing thumbnail which can be much smaller than what you asked for.

- (void)resizeImageAtPath:(NSString *)imagePath {
	// Create the image source
    CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef) [NSURL fileURLWithPath:imagePath], NULL);
    // Create thumbnail options
    CFDictionaryRef options = (__bridge CFDictionaryRef) @{
            (id) kCGImageSourceCreateThumbnailWithTransform : @YES,
            (id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
            (id) kCGImageSourceThumbnailMaxPixelSize : @(640)
    };
    // Generate the thumbnail
    CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
    CFRelease(src);
    // Write the thumbnail at path
    CGImageWriteToFile(thumbnail, imagePath);
}

Now that we have the CGImageRef for the small image, let’s see how we can save the image back to the path. We will use the CGImageDestinationAddImage function to write the image.

void CGImageWriteToFile(CGImageRef image, NSString *path) {
    CFURLRef url = (__bridge CFURLRef) [NSURL fileURLWithPath:path];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL);
    CGImageDestinationAddImage(destination, image, nil);

    if (!CGImageDestinationFinalize(destination)) {
        NSLog(@"Failed to write image to %@", path);
    }
}

This is all you need to load larger images in form of something that you can safely understand. We even tried this with a 6019×6019 image from NASA without any memory problems.

Published 31 Aug 2014

I build mobile and web applications. Full Stack, Rails, React, Typescript, Kotlin, Swift
Pulkit Goyal on Twitter