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
}

Related articles: