# How to compress an image elegantly

Time：2022-10-17

In normal development, we often receive such a request: Before uploading an image, we need to compress the image to less than 100KB and make it as clear as possible.

As an intuitive developer, we immediately dividedcompression factor

``````NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
(Why use UIImageJPEGRepresentation instead of UIImagePNGRepresentation can be Googled, I will not explain it here)``````

It turns out that users may choose 10MB pictures, and the compression factor of 0.8 can only be compressed to 3MB, which is definitely not possible.

Then let’s have a loop! until you hit the target!

``````    NSData *imageData = UIImageJPEGRepresentation(image, imageCompressRate);;
while (imageData.length  > 100 * 1024) {
imageCompressRate -= 0.1;
imageData = UIImageJPEGRepresentation(image, imageCompressRate);
}``````

After you wrote the above code, tested a few pictures, and submitted the code to your heart’s content, but a few days later, you found that someone got stuck when uploading pictures! This is swollen and fat four!

When I checked, it turned out that the while loop I just wrote overwhelmed the loop! what? ! I reproduced the pictures fed back by users, and found that if it is a very large picture, no matter how small the compression factor is, it cannot be suppressed.

Let’s take a 11MB picture as an example to see the relationship between the compression result and the compression factor:

From this result we can draw two conclusions:

• It is not possible to infinitely press a picture to a size close to 0 just by pressing
• The change of the compression factor has a significant effect between 1 and 0.8, and the difference after 0.8 is not significant.

Good guy, no matter how the pressure is not 0, then there is a possibility of an infinite loop in our while loop, so what should we do?

The solution will definitely be said at the end. Before introducing the solution, let’s think about it, why does UIImageJPEGRepresentation appear “unpressible”?

The size of the picture is calculated by: width x height x bit depth. The compression method of UIImageJPEGRepresentation is actually to represent several pixels with one pixel, that is, to make a fuss about the bit depth without affecting the bitmap of the picture ( width x height),

So if the picture is relatively large, no matter how you use UIImageJPEGRepresentation, it will be pressed to a threshold and will not be close to 0.

The principle is clear, let’s think about how to compress a picture gracefully! Go directly to the code first:

``````/*
Get the image compression factor based on the image size
*/
+ (CGFloat)getCompressRateByImageSize:(CGFloat)imageSize targetSize:(CGFloat)targetSize {
NSUInteger rate = (NSUInteger)(imageSize / targetSize);
rate = (rate == 0) ? 1 : rate;

// default 0.8 compression factor
CGFloat maxCompressRate = 0.8;
CGFloat minCompressRate = 0.2;

// Inverse proportional compression function
CGFloat compressRate = 0.8 / rate;

compressRate = MIN(MAX(compressRate, minCompressRate), maxCompressRate);
return compressRate;
}

/*!
* @brief makes the image just smaller than the specified size after compression
*
* @param image The compressed size of maxLength of the current image to be compressed
*
* @return image object
*/
+ (NSData *)compressImageSize:(UIImage *)image toByte:(NSUInteger)maxLength {
// press
NSData *data = UIImageJPEGRepresentation(image, 1);
if (data.length < maxLength) {
return data;
}

CGFloat compressRate = [self.class getCompressRateByImageSize:data.length targetSize:maxLength];
data = UIImageJPEGRepresentation(image, compressRate);
if (data.length < maxLength) {
return data;
}

// shrink
UIImage *resultImage = [UIImage imageWithData:data];
NSUInteger lastDataLength = 0;
while (data.length > maxLength && data.length != lastDataLength) {
lastDataLength = data.length;
CGFloat ratio = (CGFloat)maxLength / data.length;
CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)), (NSUInteger)(resultImage.size.height * sqrtf(ratio)));
if (CGSizeEqualToSize(size, CGSizeZero) || size.width < 10 || size.height < 10) {
break;
}
UIGraphicsBeginImageContext(size);
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, compressRate);
}

return data;
}``````

The logic is not complicated. Since simple compression will cause it to be uncompressed, we will compress it first and then compress it. In view of the rapid loss of UIImageJPEGRepresentation compression (after reaching the threshold, the effect of pressing once and twice is the same) ,

So we directly build an inverse proportional function here`getCompressRateByImageSize:`, to calculate a compression factor that is more suitable for the current image compression, and only press it once. Of course, if the compression is still relatively large, we will compress the image until the target size is reached.