How to Downscale a Uiimage in iOS by the Data Size

How to downscale a UIImage in IOS by the Data size

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.

The simplest way to resize an UIImage?

The simplest way is to set the frame of your UIImageView and set the contentMode to one of the resizing options.

Or you can use this utility method, if you actually need to resize an image:

+ (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
//UIGraphicsBeginImageContext(newSize);
// In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution).
// Pass 1.0 to force exact pixel size.
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

Example usage:

#import "MYUtil.h"

UIImage *myIcon = [MYUtil imageWithImage:myUIImageInstance scaledToSize:CGSizeMake(20, 20)];

How do I resize the UIImage to reduce upload image size

Swift 5.4 & Xcode 13

I was not satisfied with the solutions here, which generate an image based on a given KB size, since most of them used .jpegData(compressionQuality: x). This method won't work with large images, since even with compression quality set to 0.0, the large image will remain large, e.g. a 10 MB produced by portrait mode of a newer iPhone still will be above 1 MB with compressionQuality set to 0.0.

Therefore I used some answers here and rewrote a Helper Struct which converts an image in a background que:

import UIKit

struct ImageCompressor {
static func compress(image: UIImage, maxByte: Int,
completion: @escaping (UIImage?) -> ()) {
DispatchQueue.global(qos: .userInitiated).async {
guard let currentImageSize = image.jpegData(compressionQuality: 1.0)?.count else {
return completion(nil)
}

var iterationImage: UIImage? = image
var iterationImageSize = currentImageSize
var iterationCompression: CGFloat = 1.0

while iterationImageSize > maxByte && iterationCompression > 0.01 {
let percantageDecrease = getPercantageToDecreaseTo(forDataCount: iterationImageSize)

let canvasSize = CGSize(width: image.size.width * iterationCompression,
height: image.size.height * iterationCompression)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, image.scale)
defer { UIGraphicsEndImageContext() }
image.draw(in: CGRect(origin: .zero, size: canvasSize))
iterationImage = UIGraphicsGetImageFromCurrentImageContext()

guard let newImageSize = iterationImage?.jpegData(compressionQuality: 1.0)?.count else {
return completion(nil)
}
iterationImageSize = newImageSize
iterationCompression -= percantageDecrease
}
completion(iterationImage)
}
}

private static func getPercantageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
switch dataCount {
case 0..<3000000: return 0.05
case 3000000..<10000000: return 0.1
default: return 0.2
}
}
}

Compress an image to max 1 MB:

        ImageCompressor.compress(image: image, maxByte: 1000000) { image in
guard let compressedImage = image else { return }
// Use compressedImage
}
}

Change NSData Size of a UIImage - Compress And Resize UIImage With a Maximum Megabyte Mb limit

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.

}
}

UIImage resizing scale

For Swift 4 And Swift 3 use below UIImage extension for resizing image. It's calculated height according to given width.

extension UIImage {
func resized(toWidth width: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
}

How to resize an UIImage based on device size and scale?

Since the scale factor is specified in UIGraphicsBeginImageContextWithOptions, it is not needed to multiply the width and height by the screen scale when scaledRect is calculated.

And If you specify a value of 0.0 in UIGraphicsBeginImageContextWithOptions, the scale factor is automatically set to the scale factor of the device’s main screen.

- (UIImage *)hResizeImage:(UIImage *)image withTargetSize:(CGSize)targetSize {
CGSize size = image.size;
CGFloat widthRatio = targetSize.width / image.size.width;
CGFloat heightRatio = targetSize.height / image.size.height;

// Figure out what our orientation is, and use that to form the rectangle
CGSize newSize;
if(widthRatio > heightRatio) {
newSize = CGSizeMake(size.width * heightRatio, size.height * heightRatio);
} else {
newSize = CGSizeMake(size.width * widthRatio, size.height * widthRatio);
}

// This is the rect that we've calculated out and this is what is actually used below
CGRect scaledRect = CGRectMake(0, 0, newSize.width, newSize.height);

// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(scaledRect.size, false, 0.0f);
[image drawInRect:scaledRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

Reduce UIImage size to a manageable size (reduce bytes)

If you wish to simply compress your UIImage, you can use

NSData *dataForPNGFile = UIImagePNGRepresentation(yourImage);

to generate an NSData version of your image encoded as a PNG (easily inserted into an NSDictionary or written to disk), or you can use

NSData *dataForPNGFile = UIImageJPEGRepresentation(yourImage, 0.9f);

to do the same, only in a JPEG format. The second parameter is the image quality of the JPEG. Both of these should produce images that are smaller, memory-wise, than your UIImage.

Resizing a UIImage to create a smaller thumbnail (pixels-wise) using published methods is a little trickier. _imageScaledToSize is from the private API, and I'd highly recommend you not use it. For a means that works within the documented methods, see this post.

How to resize UIImageView based on UIImage's size/ratio in Swift 3?

It looks like you want to resize an ImageView according to the image ratio and the container view's size, here is the example in Swift (Sorry,the former answer with a bug, I fixed it):

   let containerView = UIView(frame: CGRect(x:0,y:0,width:320,height:500))
let imageView = UIImageView()

if let image = UIImage(named: "a_image") {
let ratio = image.size.width / image.size.height
if containerView.frame.width > containerView.frame.height {
let newHeight = containerView.frame.width / ratio
imageView.frame.size = CGSize(width: containerView.frame.width, height: newHeight)
}
else{
let newWidth = containerView.frame.height * ratio
imageView.frame.size = CGSize(width: newWidth, height: containerView.frame.height)
}
}


Related Topics



Leave a reply



Submit