iOS 图片压缩总结

一.压缩方式和方法

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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343