Two Methods of Image Compression by iOS and Image Compression Upload Function
- 2021-10-24 23:57:24
- OfStack
Two methods of compressing pictures: compressed picture quality (Quality) and compressed picture size (Size).
Compressed picture quality
NSData *data = UIImageJPEGRepresentation(image, compression);
UIImage *resultImage = [UIImage imageWithData:data];
Through the conversion of UIImage and NSData, the image quality of JPEG is reduced to compress the image. UIImageJPEGRepresentation:: The value of the second parameter compression is 0.0 ~ 1.0. The smaller the value, the lower the picture quality and the smaller the picture file.
Compressed picture size
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Given the required picture size size, resultImage is a picture drawn from the original image to the size of size.
Compress the picture so that the picture file is smaller than the specified size
If the picture definition requirements are not high, and the uploading and downloading speed of the picture is fast, the picture needs to be compressed before uploading the picture. To what extent it is compressed depends on the specific situation, but 1 will generally set the maximum value of 1 picture file, such as 100 KB. There are two ways to compress pictures by appealing. Assuming that the NSData object converted from the picture is data, the byte size of the picture can be obtained by data. length.
Compressed picture quality
It is easy to think of a way to gradually reduce the picture quality through a loop until the picture is slightly smaller than the specified size (maxLength).
+ (UIImage *)compressImageQuality:(UIImage *)image toByte:(NSInteger)maxLength {
CGFloat compression = 1;
NSData *data = UIImageJPEGRepresentation(image, compression);
while (data.length > maxLength && compression > 0) {
compression -= 0.02;
data = UIImageJPEGRepresentation(image, compression); // When compression less than a value, this code dose not work
}
UIImage *resultImage = [UIImage imageWithData:data];
return resultImage;
}
In this way, there are many cycles, low efficiency and long time consumption.
It can be optimized by 2-point method.
+ (UIImage *)compressImageQuality:(UIImage *)image toByte:(NSInteger)maxLength {
CGFloat compression = 1;
NSData *data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength) return image;
CGFloat max = 1;
CGFloat min = 0;
for (int i = 0; i < 6; ++i) {
compression = (max + min) / 2;
data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength * 0.9) {
min = compression;
} else if (data.length > maxLength) {
max = compression;
} else {
break;
}
}
UIImage *resultImage = [UIImage imageWithData:data];
return resultImage;
}
When the image size is smaller than maxLength and larger than maxLength * 0.9, no further compression is performed. Compress up to 6 times, 1/(2 ^ 6) = 0.015625 < 0.02, and the effect of reducing compression by 0.02 per cycle can also be achieved. This compression number is less than the cyclic reduction compression, and it takes less time. It should be noted that when the picture quality is lower than 1 certain level, continuous compression has no effect. That is to say, compression continues to decrease, and data does not continue to decrease. The advantage of compressed picture quality is that the picture clarity is preserved as much as possible, and the picture will not be obviously blurred; The disadvantage is that there is no guarantee that the picture will be smaller than the specified size after compression.
Compressed picture size
As before, it is easier to think of a way to gradually reduce the size of the picture through a loop until the picture is slightly smaller than the specified size (maxLength). Specific code is omitted. The same problem is that there are many cycles, low efficiency and long time consumption. You can use the 2-point method to improve efficiency, and the specific code is omitted. Here is another method, which is better than the 2-point method, has less compression times, and can make the picture just smaller than the specified size after compression (not only < maxLength, > maxLength * 0.9).
+ (UIImage *)compressImageSize:(UIImage *)image toByte:(NSUInteger)maxLength {
UIImage *resultImage = image;
NSData *data = UIImageJPEGRepresentation(resultImage, 1);
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))); // Use NSUInteger to prevent white blank
UIGraphicsBeginImageContext(size);
// Use image to draw (drawInRect:), image is larger but more compression time
// Use result image to draw, image is smaller but less compression time
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, 1);
}
return resultImage;
}
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
It is drawn with the new image resultImage, or it can be drawn with the original image image. Drawing with the original image, the compressed picture is closer to the specified size, but the compression times are more and it takes a long time. 1 picture with size of 6064 KB, compressed picture size, original drawing and new drawing results are as follows
指定大小(KB) | 原图绘制压缩后大小(KB) | 原图绘制压缩次数 | 新图绘制压缩后大小(KB) | 新图绘制压缩次数 |
---|---|---|---|---|
500 | 498 | 6 | 498 | 3 |
300 | 299 | 4 | 296 | 3 |
100 | 99 | 5 | 98 | 3 |
50 | 49 | 6 | 48 | 3 |
The compressed size of the two rendering methods is very close to the specified size, but the compression times of the original rendering can reach twice that of the new rendering. It is recommended to use new drawing to reduce the number of compressions. The compressed picture is obviously blurred than the compressed picture.
It is important to note that the code for drawing dimensions
CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)), (NSUInteger)(resultImage.size.height * sqrtf(ratio)));,
For each drawing size size, the width width and the height height should be converted into integers to prevent the drawn picture from having white edges.
Compressed picture size can make the picture smaller than the specified size, but it will make the picture obviously blurred (blurred by the quality of compressed picture).
Combination of two image compression methods
If you want to ensure the clarity of the picture, it is recommended to choose compressed picture quality. If you want to make Picture 1 smaller than the specified size, the compressed picture size can be satisfied. For the latter one, you can also compress the picture quality first. If it is less than the specified size, you can get a clear picture, otherwise, you can compress the picture size again.
+ (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength {
// Compress by quality
CGFloat compression = 1;
NSData *data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength) return image;
CGFloat max = 1;
CGFloat min = 0;
for (int i = 0; i < 6; ++i) {
compression = (max + min) / 2;
data = UIImageJPEGRepresentation(image, compression);
if (data.length < maxLength * 0.9) {
min = compression;
} else if (data.length > maxLength) {
max = compression;
} else {
break;
}
}
UIImage *resultImage = [UIImage imageWithData:data];
if (data.length < maxLength) return resultImage;
// Compress by size
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))); // Use NSUInteger to prevent white blank
UIGraphicsBeginImageContext(size);
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, compression);
}
return resultImage;
}
Let's look at the implementation method of iOS picture compression and uploading
Demand
Most of the time, we often encounter some problems when uploading pictures, either the picture quality deteriorates, or the picture is too big and so on. Here, I found a solution that is more in line with the needs at present. On the basis of the original, the dynamic compression coefficient is added and rewritten into Swift version.
Realization thought
Adjust the resolution first. The resolution can be set by yourself. If it is greater than this resolution, it will be reduced to this resolution, and if it is small, it will keep the original resolution. Then set the compression ratio according to the final size of the picture, for example, pass in maxSize = 30KB, and finally calculate the compression ratio of about this size. Basically, according to the current resolution, the final picture data can keep the same size and not be too fuzzy, which should be similar to the final effect of WeChat and Weibo, and the code still needs to be optimized!
Implementation code
Old version compression mode before Swift 3.0
// MARK: - Reduce quality
func resetSizeOfImageData(source_image: UIImage, maxSize: Int) -> NSData {
// Adjust the resolution first
var newSize = CGSize(width: source_image.size.width, height: source_image.size.height)
let tempHeight = newSize.height / 1024
let tempWidth = newSize.width / 1024
if tempWidth > 1.0 && tempWidth > tempHeight {
newSize = CGSize(width: source_image.size.width / tempWidth, height: source_image.size.height / tempWidth)
}
else if tempHeight > 1.0 && tempWidth < tempHeight {
newSize = CGSize(width: source_image.size.width / tempHeight, height: source_image.size.height / tempHeight)
}
UIGraphicsBeginImageContext(newSize)
source_image.drawAsPatternInRect(CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// First, judge whether the current quality meets the requirements, and then compress if it does not meet the requirements
var finallImageData = UIImageJPEGRepresentation(newImage,1.0)
let sizeOrigin = Int64((finallImageData?.length)!)
let sizeOriginKB = Int(sizeOrigin / 1024)
if sizeOriginKB <= maxSize {
return finallImageData!
}
// Save compressibility
let compressionQualityArr = NSMutableArray()
let avg = CGFloat(1.0/250)
var value = avg
for var i = 250; i>=1; i-- {
value = CGFloat(i)*avg
compressionQualityArr.addObject(value)
}
// Resize
// Explanation: Compression coefficient array compressionQualityArr It is stored from large to small.
// Idea: Calculate in half, if the intermediate compression coefficient still does not drop to the target value maxSize The compressibility is found from the second half; On the contrary, find the compressibility from the first half
finallImageData = UIImageJPEGRepresentation(newImage, CGFloat(compressionQualityArr[125] as! NSNumber))
if Int(Int64((UIImageJPEGRepresentation(newImage, CGFloat(compressionQualityArr[125] as! NSNumber))?.length)!)/1024) > maxSize {
// Get the original size
finallImageData = UIImageJPEGRepresentation(newImage, 1.0)
// Start with the second half
for idx in 126..<250 {
let value = compressionQualityArr[idx]
let sizeOrigin = Int64((finallImageData?.length)!)
let sizeOriginKB = Int(sizeOrigin / 1024)
print(" Current reduced quality: \(sizeOriginKB)")
if sizeOriginKB > maxSize {
print("\(idx)----\(value)")
finallImageData = UIImageJPEGRepresentation(newImage, CGFloat(value as! NSNumber))
} else {
break
}
}
} else {
// Get the original size
finallImageData = UIImageJPEGRepresentation(newImage, 1.0)
// Start with the first half
for idx in 0..<125 {
let value = compressionQualityArr[idx]
let sizeOrigin = Int64((finallImageData?.length)!)
let sizeOriginKB = Int(sizeOrigin / 1024)
print(" Current reduced quality: \(sizeOriginKB)")
if sizeOriginKB > maxSize {
print("\(idx)----\(value)")
finallImageData = UIImageJPEGRepresentation(newImage, CGFloat(value as! NSNumber))
} else {
break
}
}
}
return finallImageData!
}
Swift version 3.0 2-point compression mode
// MARK: - Reduce quality
func resetSizeOfImageData(source_image: UIImage!, maxSize: Int) -> NSData {
// First, judge whether the current quality meets the requirements, and then compress if it does not meet the requirements
var finallImageData = UIImageJPEGRepresentation(source_image,1.0)
let sizeOrigin = finallImageData?.count
let sizeOriginKB = sizeOrigin! / 1024
if sizeOriginKB <= maxSize {
return finallImageData! as NSData
}
// Adjust the resolution first
var defaultSize = CGSize(width: 1024, height: 1024)
let newImage = self.newSizeImage(size: defaultSize, source_image: source_image)
finallImageData = UIImageJPEGRepresentation(newImage,1.0);
// Save compressibility
let compressionQualityArr = NSMutableArray()
let avg = CGFloat(1.0/250)
var value = avg
var i = 250
repeat {
i -= 1
value = CGFloat(i)*avg
compressionQualityArr.add(value)
} while i >= 1
/*
Resize
Explanation: Compression coefficient array compressionQualityArr It is stored from large to small.
*/
// Thoughts: Use 2 Partition search
finallImageData = self.halfFuntion(arr: compressionQualityArr.copy() as! [CGFloat], image: newImage, sourceData: finallImageData!, maxSize: maxSize)
// If it still fails to compress to the specified size, the resolution is reduced
while finallImageData?.count == 0 {
// Every drop 100 Resolution
if defaultSize.width-100 <= 0 || defaultSize.height-100 <= 0 {
break
}
defaultSize = CGSize(width: defaultSize.width-100, height: defaultSize.height-100)
let image = self.newSizeImage(size: defaultSize, source_image: UIImage.init(data: UIImageJPEGRepresentation(newImage, compressionQualityArr.lastObject as! CGFloat)!)!)
finallImageData = self.halfFuntion(arr: compressionQualityArr.copy() as! [CGFloat], image: image, sourceData: UIImageJPEGRepresentation(image,1.0)!, maxSize: maxSize)
}
return finallImageData! as NSData
}
// MARK: - Adjust the resolution of the picture / Dimensions (Equal Scale)
func newSizeImage(size: CGSize, source_image: UIImage) -> UIImage {
var newSize = CGSize(width: source_image.size.width, height: source_image.size.height)
let tempHeight = newSize.height / size.height
let tempWidth = newSize.width / size.width
if tempWidth > 1.0 && tempWidth > tempHeight {
newSize = CGSize(width: source_image.size.width / tempWidth, height: source_image.size.height / tempWidth)
} else if tempHeight > 1.0 && tempWidth < tempHeight {
newSize = CGSize(width: source_image.size.width / tempHeight, height: source_image.size.height / tempHeight)
}
UIGraphicsBeginImageContext(newSize)
source_image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
// MARK: - 2 Partition method
func halfFuntion(arr: [CGFloat], image: UIImage, sourceData finallImageData: Data, maxSize: Int) -> Data? {
var tempFinallImageData = finallImageData
var tempData = Data.init()
var start = 0
var end = arr.count - 1
var index = 0
var difference = Int.max
while start <= end {
index = start + (end - start)/2
tempFinallImageData = UIImageJPEGRepresentation(image, arr[index])!
let sizeOrigin = tempFinallImageData.count
let sizeOriginKB = sizeOrigin / 1024
print(" Current reduced quality: \(sizeOriginKB)\n\(index)----\(arr[index])")
if sizeOriginKB > maxSize {
start = index + 1
} else if sizeOriginKB < maxSize {
if maxSize-sizeOriginKB < difference {
difference = maxSize-sizeOriginKB
tempData = tempFinallImageData
}
end = index - 1
} else {
break
}
}
return tempData
}