How to downscale a UIImage in IOS by the Data size

StuartM picture StuartM · Dec 5, 2013 · Viewed 17.2k times · Source

I am looking to downscale a UIImage in iOS.

I have seen other questions below and their approach on how to downscale the image by size. Resizing Images Objective-C
How to resize the image programmatically in objective-c in iphone
The simplest way to resize an UIImage?

These questions are all based on re-sizing the image to a specific size. In my case I am looking to re-size/downscale the image based on a maximum size.

As an example, I would like to set a maximum NSData size to be 500 KB. I know that I can get the size of the image like this:// Check the size of the image returned

NSData *imageData = UIImageJPEGRepresentation(image, 0.5);

// Log out the image size
NSLog(@"%lu KB",(imageData.length/1024));

What I would like to do is some form of loop here. If the size is greater than the maximum size that I set, I would like to scale down the image slightly, then check the size. If the size is still too large scale down again slightly then check again, until it is lower than the maximum set size.

I am not sure what the best approach for this is. Ideally I do not want to scale down the image to a specific size all the time, but only slightly scale down the image each time. That way I can have the largest (size w/h) of the image itself and at its maximum size (bytes). If I scale down slightly only at a time, what would be the best way to accomplish this?

EDIT To confirm, I am looking to re-size the actual image but re-size the image so that it is smaller than the maximum NSData Length. For example:
-Check the NSData Length
-If above the maximum I want to pass the UIImage into a method
-Then loop through this method slightly re-sizing the actual image size each time
-Until it is under the maximum NSData length, then return the image?

Answer

Rob picture Rob · Dec 11, 2013

Right now, you have a routine that says:

// Check if the image size is too large
if ((imageData.length/1024) >= 1024) {

    while ((imageData.length/1024) >= 1024) {
        NSLog(@"While start - The imagedata size is currently: %f KB",roundf((imageData.length/1024)));

        // While the imageData is too large scale down the image

        // Get the current image size
        CGSize currentSize = CGSizeMake(image.size.width, image.size.height);

        // Resize the image
        image = [image resizedImage:CGSizeMake(roundf(((currentSize.width/100)*80)), roundf(((currentSize.height/100)*80))) interpolationQuality:kMESImageQuality];

        // Pass the NSData out again
        imageData = UIImageJPEGRepresentation(image, kMESImageQuality);

    }
}

I wouldn't advise recursively resizing the image. Every time you resize, you lose some quality (often manifesting itself as a "softening" of the image with loss of detail, with cumulative effects). You always want to go back to original image and resize that smaller and smaller. (As a minor aside, that if statement is redundant, too.)

I might suggest the following:

NSData  *imageData    = UIImageJPEGRepresentation(image, kMESImageQuality);
double   factor       = 1.0;
double   adjustment   = 1.0 / sqrt(2.0);  // or use 0.8 or whatever you want
CGSize   size         = image.size;
CGSize   currentSize  = size;
UIImage *currentImage = image;

while (imageData.length >= (1024 * 1024))
{
    factor      *= adjustment;
    currentSize  = CGSizeMake(roundf(size.width * factor), roundf(size.height * factor));
    currentImage = [image resizedImage:currentSize interpolationQuality:kMESImageQuality];
    imageData    = UIImageJPEGRepresentation(currentImage, kMESImageQuality);
}

Note, I'm not touching image, the original image, but rather assigning currentImage by doing a resize from the original image each time, by a decreasing scale each time.

BTW, if you're wondering about my cryptic 1.0 / sqrt(2.0), I was trying to draw a compromise between your iterative 80% factor and my desire to favor resizing by a power of 2 where I can (because a reduction retains more sharpness when done by a power of 2). But use whatever adjustment factor you want.

Finally, if you're doing this on huge images, you might think about using @autoreleasepool blocks. You'll want to profile your app in Allocations in Instruments and see where your high water mark is, as in the absence of autorelease pools, this may constitute a fairly aggressive use of memory.