1

I have been looking through every stack overflow post and video online and I can't find a solution that works correctly. Right now the user selects a photo that I want to compress before uploading to an AWS S3 bucket. The upload works perfectly but for some reason, the compressed image is larger than the original image! For example, if the user selects a 9KB photo, when I upload to S3 the photo is 28.5KB. I tried a different photo and it's 48KB and after "compression" on S3 its 378.9KB! (I am using the latest software version of everything and compiling with the simulator)

I want to compress the original image as much as I can before uploading.

This is what I have so far:

How I "compress" the image:

UIImage *compressedProductImage;
NSData *NSproductImage;
NSUInteger productImageSize;

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    self.productImage = info[UIImagePickerControllerOriginalImage];
    [self.productImageImageView setImage:self.productImage];
    [self dismissViewControllerAnimated:YES completion:nil];

    NSproductImage = UIImageJPEGRepresentation(self.productImage, 0.5f);
    productImageSize = [NSproductImage length];

    compressedProductImage = [UIImage imageWithData: NSproductImage];

How I upload the photo:

//Convert product UIImage
NSArray *productImagePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *productImageFilePath = [[productImagePaths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@".png"]];
[UIImagePNGRepresentation(compressedProductImage) writeToFile:productImageFilePath atomically:YES];
NSURL *productImageFileUrl = [NSURL fileURLWithPath:productImageFilePath];

uploadRequest.body = productImageFileUrl;//Needs to be a NSURL
uploadRequest.bucket = AWS_BUCKET_NAME;
uploadRequest.key = productImageKey;
uploadRequest.contentType = @"image/png";
uploadRequest.ACL = AWSS3BucketCannedACLPublicRead;

[[transferManager upload:uploadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor] withBlock:^id(AWSTask *task) {
    if (task.error != nil) {
        NSLog(@"%s %@","Error uploading (product image):", uploadRequest.key);
    }else{
        NSLog(@"Product image upload completed");
    }
    return nil;
}];
kb920
  • 3,039
  • 2
  • 33
  • 44
Lazar Kukolj
  • 696
  • 3
  • 15
  • 43

2 Answers2

4

As rmaddy points out, you're taking the picked image, converting it to JPEG, converting back to a UIImage (losing any benefit of the JPEG compression), and then converting it to a PNG, which offers modest compression, generally far less compression than the original JPEG from the users photo library.

You have a few options.

  1. You can retrieve the original imageData from the asset in your photos library as shown in https://stackoverflow.com/a/32845656/1271826, thereby avoiding the round-tripping it through a UIImage at all. Thus, you preserve the quality of the original image, preserve the meta data associated with this image, and enjoy the decent compression of the original asset.

  2. You could take the picked image as a UIImage and do a combination of:

    • reduce the dimensions of the image before you call UIImageJPEGRepresentation (see https://stackoverflow.com/a/10491692/1271826 for sample algorithm); and/or

    • use UIImageJPEGRepresentation with a quality less than 1.0, where the smaller the number, the more compression but the more image quality loss.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • So it looks like it either to complicated or not worth it at all to compress the image before upload. I guess I'll deal with the larger images and work on a better caching system then, thank you fo the insightful response! – Lazar Kukolj Aug 12 '16 at 19:26
  • 1
    I'm not sure what you mean by too complicated. When `didFinishPickingMediaWithInfo` is called, just grab the `UIImagePickerControllerReferenceURL`, get the original asset from the `PHImageManager`, and then send that. It's super simple. You get original asset, it's nice and small, no image quality or metadata loss, and you avoid all of that complicated conversion to JPEG and another conversion to PNG. But to each his own. – Rob Aug 12 '16 at 19:33
3

You don't actually compress anything. You start with a UIImage. This is a full pixel by pixel representation that takes (typically) width x height x 4 bytes.

You then convert that to a JPG with some compression. So NSproductImage is a much smaller representation, in memory, of the JPG version of the image. This is the supposed smaller size you see where you think it is now compressed.

But then you convert that JPG data back into a UIImage as compressedProductImage. This new UIImage still has the same width and heigh of the original UIImage. As a result, it still takes the same width x height x 4 bytes as the original. It's just of lower quality than the original due to the JPG compression.

Now you convert the updated UIImage into a PNG. Since PNG is lossless, it doesn't compress nearly as much as the JPG attempt. You then send this larger PNG version of the image to Amazon.

You should first remove the pointless code that first converts to JPG and then back to UIImage.

At this point you should either live with the size of the PNG or use JPG instead and send the smaller JPG to Amazon.

Another option would be to scale the image before sending it to Amazon.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • So it looks like it either to complicated or not worth it at all to compress the image before upload. I guess I'll deal with the larger images and work on a better caching system then, thank you fo the insightful response! – Lazar Kukolj Aug 12 '16 at 19:26