Mahir Mahir - 17 days ago 5
Objective-C Question

Calling imageWithData:UIImageJPEGRepresentation() multiple times only compresses image the first time

In order to prevent lagging in my app, I'm trying to compress images larger than 1 MB (mostly for pics taken from iphone's normal camera.

UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageSize = UIImageJPEGRepresentation(image, 1);
NSLog(@"original size %u", [imageSize length]);

UIImage *image2 = [UIImage imageWithData:UIImageJPEGRepresentation(image, 0)];
NSData *newImageSize = UIImageJPEGRepresentation(image2, 1);
NSLog(@"new size %u", [newImageSize length]);
UIImage *image3 = [UIImage imageWithData:UIImageJPEGRepresentation(image2, 0)];
NSData *newImageSize2 = UIImageJPEGRepresentation(image3, 1);
NSLog(@"new size %u", [newImageSize2 length]);

picView = [[UIImageView alloc] initWithImage:image3] ;

However, the NSLog I get outputs something along the lines of

original size 3649058
new size 1835251
new size 1834884

The difference between the 1st and 2nd compression is almost negligible. My goal is to get the image size below 1 MB. Did I overlook something/is there an alternative approach to achieve this?

EDIT: I want to avoid scaling the image's height and width, if possible.

Rob Rob

A couple of thoughts:

  1. The UIImageJPEGRepresentation function does not return the "original" image. For example, if you employ a compressionQuality of 1.0, it does not, technically, return the "original" image, but rather it returns a JPEG rendition of the image with compressionQuality at its maximum value. This can actually yield an object that is larger than the original asset (at least if the original image is a JPEG). You're also discarding all of the metadata (information about where the image was taken, the camera settings, etc.) in the process.

  2. If you want the original asset, you should use PHImageManager:

    NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
    PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
    PHAsset *asset = [result firstObject];
    PHImageManager *manager = [PHImageManager defaultManager];
    [manager requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
        NSString *filename = [(NSURL *)info[@"PHImageFileURLKey"] lastPathComponent];
        // do what you want with the `imageData`

    In iOS versions prior to 8, you'd have to use assetForURL of the ALAssetsLibrary class:

    NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library assetForURL:url resultBlock:^(ALAsset *asset) {
        ALAssetRepresentation *representation = [asset defaultRepresentation];
        NSLog(@"size of original asset %llu", [representation size]);
        // I generally would write directly to a `NSOutputStream`, but if you want it in a
        // NSData, it would be something like:
        NSMutableData *data = [NSMutableData data];
        // now loop, reading data into buffer and writing that to our data strea
        NSError *error;
        long long bufferOffset = 0ll;
        NSInteger bufferSize = 10000;
        long long bytesRemaining = [representation size];
        uint8_t buffer[bufferSize];
        NSUInteger bytesRead;
        while (bytesRemaining > 0) {
            bytesRead = [representation getBytes:buffer fromOffset:bufferOffset length:bufferSize error:&error];
            if (bytesRead == 0) {
                NSLog(@"error reading asset representation: %@", error);
            bytesRemaining -= bytesRead;
            bufferOffset   += bytesRead;
            [data appendBytes:buffer length:bytesRead];
        // ok, successfully read original asset; 
        // do whatever you want with it here
    } failureBlock:^(NSError *error) {
        NSLog(@"error=%@", error);

    Please note that this assetForURL runs asynchronously.

  3. If you want a NSData with compression, you can use UIImageJPEGRepresentation with a compressionQuality less than 1.0. Your code actually does this with a compressionQuality of 0.0, which should offer maximum compression. But you don't save that NSData, but rather use it to create a UIImage and you then get a new UIImageJPEGRepresentation with a compressionQuality of 1.0, thus losing much of the compression you originally achieved.

    Consider the following code:

    // a UIImage of the original asset (discarding meta data)
    UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
    // this may well be larger than the original asset
    NSData *jpgDataHighestCompressionQuality = UIImageJPEGRepresentation(image, 1.0);
    [jpgDataHighestCompressionQuality writeToFile:[docsPath stringByAppendingPathComponent:@"imageDataFromJpeg.jpg"] atomically:YES];
    NSLog(@"compressionQuality = 1.0; length = %u", [jpgDataHighestCompressionQuality length]);
    // this will be smaller, but with some loss of data
    NSData *jpgDataLowestCompressionQuality = UIImageJPEGRepresentation(image, 0.0);
    NSLog(@"compressionQuality = 0.0; length = %u", [jpgDataLowestCompressionQuality length]);
    UIImage *image2 = [UIImage imageWithData:jpgDataLowestCompressionQuality];
    // ironically, this will be larger than jpgDataLowestCompressionQuality
    NSData *newImageSize = UIImageJPEGRepresentation(image2, 1.0);
    NSLog(@"new size %u", [newImageSize length]);
  4. In addition to the JPEG compression quality outlined the prior point, you could also just resize the image. You can also marry this with the JPEG compressionQuality, too.