How to downscale a UIImage in IOS by the Data size

感情迁移 提交于 2019-11-27 17:53:56

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.

Besides the maximum size you also need to choose a minimum size as well as decide on performance. For example, you could check the size of UIImageJPEGRepresentation(image, 1.0). If too big, do you then check at 0.95 or 0.1?

One possible approach is to get the size of UIImageJPEGRepresentation(image, 1.0) and see by what percent it is too big. For example, say it is 600kB. You should then compute 500.0 / 600 which is roughly 0.83. So then do UIImageJPEGRepresentation(image, 0.83). That won't give exactly 500kB but it may be close enough.

Another approach would be to start with UIImageJPEGRepresentation(image, 1.0). It it's too big then do UIImageJPEGRepresentation(image, 0.5) If too big then go with 0.25 but if too small go with 0.75. Keep splitting the difference until you get within an acceptable range of your desired size.

This was my approach:

// 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);

    }
}

The resize image method is as follows:

// Returns a rescaled copy of the image, taking into account its orientation
// The image will be scaled disproportionately if necessary to fit the bounds specified by the parameter
- (UIImage *)resizedImage:(CGSize)newSize interpolationQuality:(CGInterpolationQuality)quality {
    BOOL drawTransposed;

    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            drawTransposed = YES;
            break;

        default:
            drawTransposed = NO;
    }

    return [self resizedImage:newSize
                    transform:[self transformForOrientation:newSize]
               drawTransposed:drawTransposed
         interpolationQuality:quality];
}

Followed by:

// Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size
// The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation
// If the new size is not integral, it will be rounded up
- (UIImage *)resizedImage:(CGSize)newSize
                transform:(CGAffineTransform)transform
           drawTransposed:(BOOL)transpose
     interpolationQuality:(CGInterpolationQuality)quality {
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
    CGRect transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width);
    CGImageRef imageRef = self.CGImage;

    // Build a context that's the same dimensions as the new size
    CGContextRef bitmap = CGBitmapContextCreate(NULL,
                                                newRect.size.width,
                                                newRect.size.height,
                                                CGImageGetBitsPerComponent(imageRef),
                                                0,
                                                CGImageGetColorSpace(imageRef),
                                                CGImageGetBitmapInfo(imageRef));

    // Rotate and/or flip the image if required by its orientation
    CGContextConcatCTM(bitmap, transform);

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(bitmap, quality);

    // Draw into the context; this scales the image
    CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef);

    // Get the resized image from the context and a UIImage
    CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef];

    // Clean up
    CGContextRelease(bitmap);
    CGImageRelease(newImageRef);

    return newImage;
}

// Additional methods for reference

// Returns an affine transform that takes into account the image orientation when drawing a scaled image
- (CGAffineTransform)transformForOrientation:(CGSize)newSize {
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (self.imageOrientation) {
        case UIImageOrientationDown:           // EXIF = 3
        case UIImageOrientationDownMirrored:   // EXIF = 4
            transform = CGAffineTransformTranslate(transform, newSize.width, newSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:           // EXIF = 6
        case UIImageOrientationLeftMirrored:   // EXIF = 5
            transform = CGAffineTransformTranslate(transform, newSize.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:          // EXIF = 8
        case UIImageOrientationRightMirrored:  // EXIF = 7
            transform = CGAffineTransformTranslate(transform, 0, newSize.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        default:
            break;
    }

    switch (self.imageOrientation) {
        case UIImageOrientationUpMirrored:     // EXIF = 2
        case UIImageOrientationDownMirrored:   // EXIF = 4
            transform = CGAffineTransformTranslate(transform, newSize.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:   // EXIF = 5
        case UIImageOrientationRightMirrored:  // EXIF = 7
            transform = CGAffineTransformTranslate(transform, newSize.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        default:
            break;
    }

    return transform;
}
-(NSData*)testData
{
    UIImage *imageToUpload=[UIImage imageNamed:@"images/lifestyle2.jpg"];
    NSData *imgData=UIImageJPEGRepresentation(imageToUpload,1.0);
    float compressionRate=10;
    while (imgData.length>1024)
    {
        if (compressionRate>0.5)
        {
            compressionRate=compressionRate-0.5;
            imgData=UIImageJPEGRepresentation(imageToUpload,compressionRate/10);
        }
        else
        {
            return imgData;
        }
    }
    return imgData;
}

It maintains image quality not lass than 1MB.

Call it with,

NSData *compressedImageData=[self testData];
NSLog(@"%lu KB",compressedImageData.length);

In your revised question, you clarified that your goal was to say within file size limitations while uploading images. In that case, playing around with JPEG compression options is fine as suggested by rmaddy.

The interesting question is that you have two variables to play around with, JPEG compression and image dimensions (there are others, too, but I'll keep it simple). How do you want to prioritize one over the other? For example, I don't think it makes sense to keep a full resolution, absurdly compressed image (e.g. 0.1 quality factor). Nor does it make sense to keep a tiny resolution, uncompressed image. Personally, I'd iteratively adjust quality as suggested by rmaddy, but set some reasonable floor (e.g. JPEG quality not less than, say 0.70). At that point, I might consider changing the image dimensions (and that changes file size pretty quickly, too), and altering the dimensions until the resulting NSData was an appropriate size.

Anyway, in my original answer, I focused on the memory consumption within the app (as opposed to file size). For posterity's sake, see that answer below:


If you are trying to control how much memory is used when you load the images into UIImage objects to be used in UIKit objects, then playing around with JPEG compression won't help you much, because the internal representation of the images once you load them into UIKit objects is uncompressed. Thus, in that scenario, JPEG compression options doesn't accomplish much (other than sacrificing image quality).

To illustrate the idea, I have an image that is 1920 x 1080. I have it in PNG format (the file is 629kb), a compressed JPEG format (217kb), and a minimally compressed JPEG format (1.1mb). But, when I load those three different images into UIImageView objects (even if they have a very small frame), Instrument's "Allocations" tool shows me that they're each taking up 7.91mb:

This is because when you load the image into an image view, the internal, uncompressed representation of these three images is four bytes per pixel (a byte for red, one for green, one for blue, and one for alpha). Thus a my 1920 x 1080 images take up 1920 x 1080 x 4 = 8,249,400 = 7.91mb.

So, if you don't want them to take up more than 500kb in memory when loading them into image view objects, that means that you want to resize them such that the product of the width times the height will be 128,000 or less (i.e. if square, less than 358 x 358 pixels).

But, if your concern is one of network bandwidth as you upload images or persistent storage capacity, then go ahead and play around with JPEG compression values as suggested by rmaddy's excellent answer. But if you're trying to address memory consumption issues while the images are loaded into UIKit objects, then don't focus on compression, but focus on resizing the image.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!