美文网首页
iOS 图片压缩总结

iOS 图片压缩总结

作者: 西门吹水Jacky | 来源:发表于2020-06-06 16:57 被阅读0次

    一.压缩方式和方法

    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。

    相关文章

      网友评论

          本文标题:iOS 图片压缩总结

          本文链接:https://www.haomeiwen.com/subject/utdnzhtx.html