4

I have a UIImage that the user has uploaded.

If the image data is more than 10Mb how can I resize it to a maximum of 10 Mb? So far the closest thing I've found for data resizing is this:

NSData *imageData = UIImageJPEGRepresentation(theUploadedImage.image, 0.5f);

But I don't seem to have control over the Mb of file size... just over the second parameter in the JPG representation (image quality float)

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • possible duplicate of [Is it possible to resize images to specific size?](http://stackoverflow.com/questions/4023375/is-it-possible-to-resize-images-to-specific-size) – mico Dec 08 '13 at 20:45
  • See http://stackoverflow.com/questions/20403805/how-to-downscale-a-uiimage-in-ios-by-the-data-size/20404131#20404131 – rmaddy Dec 08 '13 at 20:45
  • @mico That "duplicate" is about setting the width/height of the image. This is about changing the resulting file size of the image's data. – rmaddy Dec 08 '13 at 20:47
  • Well, then it is duplicate of the another you linked. Duplicate still... – mico Dec 08 '13 at 20:48
  • @rmaddy So I should convert to NSData, take the `.length` of the NSData... divide by 1,048,576 (to convert to MB)... and if it is greater than 10 I should resize the image, re-capture it, and check again? Or I should use the JPEGRepresentation code with a lower float parameter (compressing?) and check again? – Albert Renshaw Dec 08 '13 at 20:55
  • I have a 2.4Mb image that I uploaded into the app and tried to convert to Mb with NSData and it said it was over 15 Mb :o Here is the code. `NSData *imageData = UIImagePNGRepresentation(theUploadedImage.image); NSLog(@"Image is %f Mb", (float)(((float)imageData.length)/(1048576.000000f)));//converts bytes to Mb` – Albert Renshaw Dec 08 '13 at 21:06
  • Is that just because I used PNG representation? :o I changed it to JPG with 1.0f parameter and it brought it down to a little over 6mB but the image file itself when I upload it to my computer and analyze is only 2.4Mb – Albert Renshaw Dec 08 '13 at 21:08

1 Answers1

2

Had to create my own function that compresses an image as small as it can get, and if it's still over my "max size" then it resizes, reserves and begins the compression iteration again. This does a fairly good job of getting the image as close as possible to target "max image file size" if it's over the file size. Also includes a failsafe after 1024 iterations. (Which should never last longer than a minute (but that's a scenario that would never occour... who uses images that are gigabytes on an iPhone? Haha))...

-(void)shrinkImage {


    //IMPORTANT!!! THIS CODE WAS CREATED WITH "ARC" IN MIND... DO NOT USE WITHOUT ARC UNLESS YOU ALTER THIS CODE TO MANAGE MEMORY


    float compressionVal = 1.0;
    float maxVal = 9.7;//MB

    UIImage *compressedImage = theUploadedImage.image; //get UIImage from imageView

    int iterations = 0;
    int totalIterations = 0;

    float initialCompressionVal = 0.00000000f;

    while (((((float)(UIImageJPEGRepresentation(compressedImage, compressionVal).length))/(1048576.000000000f)) > maxVal) && (totalIterations < 1024)) {

        NSLog(@"Image is %f MB", (float)(((float)(UIImageJPEGRepresentation(compressedImage, compressionVal)).length)/(1048576.000000f)));//converts bytes to MB

        compressionVal = (((compressionVal)+((compressionVal)*((float)(((float)maxVal)/((float)(((float)(UIImageJPEGRepresentation(compressedImage, compressionVal).length))/(1048576.000000000f)))))))/(2));
        compressionVal *= 0.97;//subtracts 3% of it's current value just incase above algorithm limits at just above MaxVal and while loop becomes infinite.

        if (initialCompressionVal == 0.00000000f) {
            initialCompressionVal = compressionVal;
        }

        iterations ++;

        if ((iterations >= 3) || (compressionVal < 0.1)) {
            iterations = 0;
            NSLog(@"%f", compressionVal);

            compressionVal = 1.0f;


            compressedImage = [UIImage imageWithData:UIImageJPEGRepresentation(compressedImage, compressionVal)];



            float resizeAmount = 1.0f;
            resizeAmount = (resizeAmount+initialCompressionVal)/(2);//percentage
            resizeAmount *= 0.97;//3% boost just incase image compression algorithm reaches a limit.
            resizeAmount = 1/(resizeAmount);//value
            initialCompressionVal = 0.00000000f;


            UIView *imageHolder = [[UIView alloc] initWithFrame:CGRectMake(0,0,(int)floorf((float)(compressedImage.size.width/(resizeAmount))), (int)floorf((float)(compressedImage.size.height/(resizeAmount))))];//round down to ensure frame isnt larger than image itself

            UIImageView *theResizedImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,(int)ceilf((float)(compressedImage.size.width/(resizeAmount))), (int)ceilf((float)(compressedImage.size.height/(resizeAmount))))];//round up to ensure image fits
            theResizedImage.image = compressedImage;


            [imageHolder addSubview:theResizedImage];


            UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageHolder.frame.size.width, imageHolder.frame.size.height), YES, 1.0f);
            CGContextRef resize_context = UIGraphicsGetCurrentContext();
            [imageHolder.layer renderInContext:resize_context];
            compressedImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();


            //after 3 compressions, if we still haven't shrunk down to maxVal size, apply the maximum compression we can, then resize the image (90%?), then re-start the process, this time compressing the compressed version of the image we were checking.

            NSLog(@"resize");
        }

        totalIterations ++;

    }

    if (totalIterations >= 1024) {
        NSLog(@"Image was too big, gave up on trying to re-size");//too many iterations failsafe. Gave up on trying to resize.
    } else {

        NSData *imageData = UIImageJPEGRepresentation(compressedImage, compressionVal);
        NSLog(@"FINAL Image is %f MB ... iterations: %i", (float)(((float)imageData.length)/(1048576.000000f)), totalIterations);//converts bytes to MB

        theUploadedImage.image = [UIImage imageWithData:imageData];//save new image to UIImageView.

    }
}
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • 4
    Gosh darn, Albert! All those parentheses are simply a nightmare to read. So much unnecessary code. Plus, you keep repeating calculations like `((float)(UIImageJPEGRepresentation(compressedImage, compressionVal).length))/(1048576.000000000f)`; do it once and store the result in a variable. There's line in there with 7, yes, __SEVEN__, closing parentheses. I implore you, __for the sake of your own sanity__, refactor your code, sir ;) – neilco Dec 09 '13 at 00:09
  • @neilco Hahaha! The parens make it easier on me, that's why I add them haha (Guess my brain just works differently haha)! I would heed your advice about making a variable for repeat calculations, except the only time I see that calculation repeated is in my NSLogs which I have removed from my production code :) – Albert Renshaw Dec 09 '13 at 00:19
  • Take at look at the `compressionVal` calculation. That can definitely be made more readable. It might be readable to you, but if someone wants to learn something (which is why they've come to SO) from your code, they may have a hard time comprehending it. Enjoy the rest of your weekend.000000000f ;) – neilco Dec 09 '13 at 00:36
  • +1 for nice answer. it works like charm and exactly what i wanted. – Maheta Dhaval K Jun 09 '14 at 06:18
  • @MahetaDhavalK Thanks! Also, I just edited it, the code you saw said "Mb" all throughout when it was really "MB" I had mega bits not mega bytes, but that was only NSLog and comment stuff anyways so it won't affect the code at all, just makes it look less sloppy :) – Albert Renshaw Jun 10 '14 at 00:13