iOS 图片压缩总结

2020-06-06  本文已影响0人  西门吹水Jacky

一.压缩方式和方法

1.压缩方式

1.1 质量压缩
1.2 尺寸压缩
1.3 质量和尺寸共同压缩

2.压缩方法

2.1质量压缩

public func jpegData(compressionQuality: CGFloat) -> Data? // return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)

2.2尺寸压缩

 var resultImage:UIImage? = nil
 let size: CGSize = CGSize(width: width, height: height)
 UIGraphicsBeginImageContext(size)
    originalImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
        if let image:UIImage = UIGraphicsGetImageFromCurrentImageContext(){
            resultImage = image
        }
 UIGraphicsEndImageContext()

2.3 使用ImageIO进行尺寸压缩(待实践,貌似可以避免在生成图片过程中产生的bitmap,这样可以极大程度的减少内存和cpu的消耗)

二.压缩到指定大小

1.压缩质量(二分法)

    /// 压缩图片
    /// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
    /// - Returns: 压缩后的图片data
    func compressQualityWithMaxLength(maxLength: Float) -> Data? {
        let maxValue:Float = maxLength * 1024 * 1024//最大字节数
        var compressionQuality:CGFloat = 1
        var compressData:Data? = self.jpegData(compressionQuality: compressionQuality)
        var max:CGFloat = 1
        var min:CGFloat = 0
        if let data = compressData, Float(data.count) < maxValue{
            return compressData//原图大小少于所需大小直接返回
        }
        for _ in 0 ... 6 {
            compressionQuality = (max + min) * 0.5
            if let data:Data = self.jpegData(compressionQuality: compressionQuality){
                compressData = data
                if Float(data.count) < maxValue * 0.9 {
                    min = compressionQuality  // 缩小压缩比例
                }else if Float(data.count) > maxValue {
                    max = compressionQuality  // 扩大压缩比例
                }else{
                    break
                }
            }
        }
        return compressData
    }
  • 缺点:图片的大小是由图片的宽高和像素决定的,而压质量其实只能决定部分图片大小。当图片的宽高过大时,是不能通过压质量来决定最优的图片大小
  • 解决方案:质量和尺寸都压缩

1.压缩尺寸

   /// 压缩图片
   ///
   /// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
   /// - Returns: 压缩后的图片data
   func compressSizeWithMaxLength(maxLength: Float) -> Data? {
       if let jpegData:Data = self.jpegData(compressionQuality: 1) {
           var resultImage: UIImage = self
           var resultData: NSData = NSData(data: jpegData)
           let maxValue:Int = Int(maxLength * 1024 * 1024)//所需大小
           var lastLength: Int = 0
           if resultData.length <= maxValue {
               return resultData as Data//原图片大小小于所需大小直接返回
           }
           
           //条件:生成的图片大小是否满足所需大小
           while resultData.length > maxValue, resultData.length != lastLength{
               lastLength = resultData.length
               let route: CGFloat = CGFloat(maxValue) / CGFloat(lastLength)
               let width: CGFloat = resultImage.size.width * CGFloat(sqrtf(Float(route)))
               let height: CGFloat = resultImage.size.height * CGFloat(sqrtf(Float(route)))
               let size: CGSize = CGSize(width: width, height: height)
               //压缩尺寸并生成新的图片
               UIGraphicsBeginImageContext(size)
               resultImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
               resultImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
               UIGraphicsEndImageContext()
               if let data = resultImage.jpegData(compressionQuality: 1) {
                    resultData = NSData(data: data)
               }
           }
           return resultData as Data
       }
       return nil
   }
  • 缺点:用UIGraphicsBeginImageContext去绘画新的图片产生临时的bitmap占用内存,draw会消耗cpu,如果绘画的次数过多,内存会爆增、app卡顿,最后crash。通过所需大小和总大小的比例来计算尺寸缩放,可能会造成图片尺寸太小达不到尺寸上的需求。
    有使用autoreleasepool来释放,但是效果并不理想,当压缩的图片一次性过多的时,可能autoreleasepool来不及释放
  • 解决方案:减少UIGraphicsBeginImageContext的绘画次数

3.质量尺寸结合压缩

    /// 压缩图片
    /// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
    /// - Returns: 压缩后的图片data
    func compressWithMaxLength(maxLength: Float) -> Data? {
        let maxValue:Int = Int(maxLength * 1024 * 1024)//最大字节数
        var compressionQuality:CGFloat = 1
        var compressData:Data? = self.jpegData(compressionQuality: compressionQuality)
        var max:CGFloat = 1
        var min:CGFloat = 0
        if let data = compressData, data.count < maxValue{
            return compressData//原图大小少于所需大小直接返回
        }
        for _ in 0 ... 6 {
            compressionQuality = (max + min) * 0.5
            if let data:Data = self.jpegData(compressionQuality: compressionQuality){
                compressData = data
                if Float(data.count) < Float(maxValue) * 0.9 {
                    min = compressionQuality  // 缩小压缩比例
                }else if data.count > maxValue {
                    max = compressionQuality  // 扩大压缩比例
                }else{
                    break
                }
            }
        }
        
        if let data = compressData, data.count > maxValue{//压缩质量后大小还是不符合再进行尺寸压缩
            var resultImage: UIImage = self
            var resultData: NSData = NSData(data: data)
            var lastLength: Int = 0
            //条件:生成的图片大小是否满足所需大小
            while resultData.length > maxValue, resultData.length != lastLength{
                lastLength = resultData.length
                let route: CGFloat = CGFloat(maxValue) / CGFloat(lastLength)
                let width: CGFloat = resultImage.size.width * CGFloat(sqrtf(Float(route)))
                let height: CGFloat = resultImage.size.height * CGFloat(sqrtf(Float(route)))
                let size: CGSize = CGSize(width: width, height: height)
                //压缩尺寸并生成新的图片
                UIGraphicsBeginImageContext(size)
                resultImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
                resultImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
                UIGraphicsEndImageContext()
                if let data = resultImage.jpegData(compressionQuality: 1) {
                     resultData = NSData(data: data)
                }
            }
            return resultData as Data
        }else{
            //压缩质量后大小符合 直接返回
            return compressData
        }
    }

普通场景下用这个方法应该足够了,如果一次性压4到8张大图,还是会造成crash,crash的原因就是因为UIGraphicsBeginImageContext的绘画次数太多内存爆增导致,当然这个也看手机,好一点的手机可能不会。

4.仿微信压缩图片

    /// 仿微信压缩图片  
    /// - Return: 压缩后的图片data
    func smartCompress() -> Data? {
        /** 仿微信算法 **/
        var tempImage = self
        let width:Int = Int(self.size.width)
        let height:Int = Int(self.size.height)
        var updateWidth = width
        var updateHeight = height
        let longSide = max(width, height)
        let shortSide = min(width, height)
        let scale:CGFloat = CGFloat(CGFloat(shortSide) / CGFloat(longSide))
        
        // 大小压缩
        if shortSide < 1080 || longSide < 1080 { // 如果宽高任何一边都小于 1080
            updateWidth = width
            updateHeight = height
        } else { // 如果宽高都大于 1080
            if width < height { // 说明短边是宽
                updateWidth = 1080
                updateHeight = Int(1080 / scale)
            } else { // 说明短边是高
                updateWidth = Int(1080 / scale)
                updateHeight = 1080
            }
        }
        
        let size: CGSize = CGSize(width: updateWidth, height: updateHeight)
        UIGraphicsBeginImageContext(size)
        tempImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
        if let image:UIImage = UIGraphicsGetImageFromCurrentImageContext(){
            tempImage = image
        }
        UIGraphicsEndImageContext()
        let resultData:Data? = tempImage.jpegData(compressionQuality:0.5)//质量压缩一半
        return resultData
    }

该方法是先压尺寸一次,再压质量一次,减少UIGraphicsBeginImageContext绘画次数为一次,避免了多操作,从而减少了内存的占用,cpu的使用率,而且图片清晰,避免了crash。

上一篇 下一篇

猜你喜欢

热点阅读