美文网首页iOS架构
iOS图片压缩方案

iOS图片压缩方案

作者: 愤怒小鸟飞呀飞 | 来源:发表于2019-07-09 18:02 被阅读0次

    一、概述

    “压” 是指文件体积变小,但是像素数不变,长宽尺寸不变,那么质量可能下降。(图片压完之后,占用内存的bitMap不变)
    “缩” 是指文件的尺寸变小,也就是像素数减少,而长宽尺寸变小,文件体积同样会减小。

    二、“压”

    1、图片质量压缩原理
    采用一些算法,将几个像素点用一个像素点的数据表示
    2、图片压完之后,占用内存的bitMap不变论证:

    UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
        NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
        NSLog(@"imgData:%ld",imgData.length);
        
        ;
        CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
        NSLog(@"rawData.length:%ld",(long)CFDataGetLength(rawData));
        
        NSLog(@"----------------------我是分割线---------------------");
        
        NSData *compressData = UIImageJPEGRepresentation(image, 0.1);
        UIImage *compressImg = [UIImage imageWithData:compressData];
        
        
        NSString *newPath = [NSString stringWithFormat:@"%@/%@",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject],@"compressImg.jpeg"];
       BOOL a =  [compressData writeToFile:newPath atomically:YES];
        NSLog(@"imgData:%ld",compressData.length);
        CFDataRef compressRawData = CGDataProviderCopyData(CGImageGetDataProvider(compressImg.CGImage));
        NSLog(@"rawData.length:%ld",(long)CFDataGetLength(compressRawData));
    

    打印结果:

    2019-07-09 18:01:38.172504+0800 Img[47267:15114761] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
    2019-07-09 18:01:38.263858+0800 Img[47267:15114761] [framework] CUIThemeStore: No theme registered with id=0
    2019-07-09 18:01:38.330603+0800 Img[47267:15114761] imgData:10983
    2019-07-09 18:01:38.331847+0800 Img[47267:15114761] rawData.length:480000
    2019-07-09 18:01:38.331955+0800 Img[47267:15114761] ----------------------我是分割线---------------------
    2019-07-09 18:01:38.334699+0800 Img[47267:15114761] imgData:5560
    2019-07-09 18:01:38.335806+0800 Img[47267:15114761] rawData.length:480000
    

    如上,图片"压"后,占用内存的bitmap没有改变

    三、“缩”
    1、原理
    图片尺寸压缩,减小体积,图片bitmap减小
    2、方案
    使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap,就能够在很大程度上减少内存的占有从而避免由此导致的app闪退问题。

        UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
        NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
        NSLog(@"imgData:%ld",imgData.length);
        
        ;
        CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
        NSLog(@"rawData.length:%ld",(long)CFDataGetLength(rawData));
        
        NSLog(@"----------------------我是分割线---------------------");
        
        UIImage *sizeCompressImg = [self thumbnailForData:imgData maxPixelSize:image.size.width * 0.25];
        CFDataRef sizeCompressRawData = CGDataProviderCopyData(CGImageGetDataProvider(sizeCompressImg.CGImage));
        NSLog(@"rawData.length:%ld",(long)CFDataGetLength(sizeCompressRawData));
    

    打印结果

    2019-07-09 20:10:04.300806+0800 Img[48279:15418157] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
    2019-07-09 20:10:04.378632+0800 Img[48279:15418157] [framework] CUIThemeStore: No theme registered with id=0
    2019-07-09 20:10:04.448211+0800 Img[48279:15418157] imgData:10983
    2019-07-09 20:10:04.449632+0800 Img[48279:15418157] rawData.length:480000
    2019-07-09 20:10:04.449805+0800 Img[48279:15418157] ----------------------我是分割线---------------------
    2019-07-09 20:10:04.450898+0800 Img[48279:15418157] rawData.length:30000
    

    分析:图片bitmap占用从 480000 减小到 30000减小了16倍


    image.png

    四、图片质量压缩方案探索
    1、分次循环压缩

        ///  对图片进行质量压缩
        ///
        /// - Parameter targetByte: 压缩目标字节数
        /// - Returns: 压缩之后的data 对象 
        func compressByQulity(toTargetByte targetByte:NSInteger)->NSData?{
            let bigImage = self
            
            let imgData = bigImage.dataFromImage()
            guard imgData != nil else{
                return nil
            }
            var targetImageData:Data = imgData!
            print("lalala compress before:%d",targetImageData.count)
            //分次循环压缩
            //当前imgData 大小
            var targetImageDataLength:Int = targetImageData.count
            //上次压缩imgData 大小
            var lastTargetImageDataLength : Int = 0
            //png jpeg应机器学习同学要求,同时采用jpeg压缩格式
            while(targetImageDataLength > targetByte ){
                //上次压缩图片大小赋值
                lastTargetImageDataLength = targetImageDataLength;
                
                //压缩
                let img =  UIImage.init(data: targetImageData)
                guard img != nil else{
                    break
                }
                let compressImgData = img!.jpegData(compressionQuality: CGFloat(UIImage.qualityPerCompressRate))
                guard compressImgData != nil else{
                    break
                }
                targetImageData = compressImgData!
                targetImageDataLength = targetImageData.count
                guard targetImageDataLength < lastTargetImageDataLength else {
                    break
                }
            }
            print("lalala compress after:%d",targetImageData.count)
            return targetImageData as NSData
        }
    
    lalala =-------我是分割线-----------------
    lalala compress before:%d 1811323
    lalala compress after:%d 543473
    lalala compress after:%d 544901
    lalala =-------我是分割线-----------------
    lalala compress before:%d 1559291
    lalala compress after:%d 526630
    lalala compress after:%d 527984
    lalala =-------我是分割线-----------------
    lalala compress before:%d 2522415
    lalala compress after:%d 766597
    lalala compress after:%d 767281
    lalala =-------我是分割线-----------------
    lalala compress before:%d 2287580
    lalala compress after:%d 753524
    lalala compress after:%d 754047
    

    以上算法存在以下问题
    (1)、第二次一定比第一次大,所以只能跑一遍
    (2)、UIImage 与 data互转 来回互转 data数据会改变即 UIImageJPEGRepresentation 1.0 从本地读取到data数据也会改变,图片的质量也会受到影响

      UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
        NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
        NSLog(@"imgData:%ld",imgData.length);
        
        NSData *jpegData = UIImageJPEGRepresentation(image, 1.0);
        NSLog(@"jpegData:%ld",jpegData.length);
        
        UIImage *newImage = [UIImage imageWithData:jpegData];
        NSData *newData = UIImageJPEGRepresentation(newImage, 1.0);
        NSLog(@"newData:%ld",newData.length);
    

    打印结果:

    2019-07-09 20:56:55.355619+0800 Img[48690:15554037] imgData:10983
    2019-07-09 20:56:55.359325+0800 Img[48690:15554037] jpegData:47681
    2019-07-09 20:56:55.363122+0800 Img[48690:15554037] newData:51046
    

    2、 二分法寻找最佳压缩率
    (1)UIImageJPEGRepresentation(image, scale) API调研
    a、图片压缩测试 原图大小:7801942

    scale 实际 正常比例计算
    0.1 114823 780192
    0.2 127503 1560384
    0.3 156226 2340577
    0.4 204067
    0.5 261299
    0.6 331804
    0.7 438994
    0.8 508924
    0.9 640913

    从上图得出 实际压缩率不是传入的值,实际压缩率一定大于压缩结果
    b、压缩极限测试
    原图大小:193737
    压缩比率
    0.01 113710
    对上面压缩之后的值再次压缩
    0.01 113710
    压缩到极限值之后,压缩之后的数据,将不在压缩
    (2)、压缩目标
    压缩 目标 物理大小 500k,容错范围 5000.8 ~~5001.2
    (3)、UIImageJPEGRepresentation能力之外数据处理方案
    a、图片压缩0.01之后还是不能到500k,直接返回压缩最小值
    b、循环到第6次数,压缩率为0.984,已经压缩为最大值,还没有进入理想数据范围,将不在进行压缩,直接返回UIImageJPEGRepresentation 1.0压缩数值
    c、本来就是有损压缩,无论png还是jpeg 统一使用jpeg压缩方案与UIImage->data转化方法

     func compressByQulity(toTargetByte targetByte:NSInteger)->NSData?{
            let bigImage = self
            
            //本来就是有损压缩,无论png还是jpeg 统一使用jpeg 注意:如果为png格式,转化之后filesize会比源文件小很多,这里将进行一次有损压缩
            let imgData = bigImage.jpegData(compressionQuality: 1.0)
            guard imgData != nil else{
                return nil
            }
            
            var targetImageData:Data = imgData!
            print("lalala =-------我是分割线-----------------")
            print("lalala compress before---",targetImageData.count)
            guard targetImageData.count > targetByte else {
                print("lalala compress after:%d",targetImageData.count)
                return targetImageData as NSData
            }
            
            //分次循环寻找compressRate大小
            //png jpeg应机器学习同学要求,同时采用jpeg压缩格式
            var compressionRate:CGFloat = 1.0
            var max:CGFloat = 1
            var min:CGFloat = 0
            //指数二分处理,s首先计算最小值
            compressionRate = CGFloat(pow(2.0, -6.0))
            let smallestImgData = bigImage.jpegData(compressionQuality: compressionRate)
            guard smallestImgData != nil else {
                return nil
            }
            
            if smallestImgData!.count < targetByte{
                for _ in 0..<6 {
                    compressionRate = (max + min)/2.0
                    let compressData = bigImage.jpegData(compressionQuality: compressionRate)
                    guard compressData != nil else {
                        break
                    }
                    
                    //容错区间范围0.8~1.2 409600 614400
                    if compressData!.count < Int(Double(targetByte) * 0.8){
                        min = compressionRate
                    }else if compressData!.count > Int(Double(targetByte) * 1.2){
                        max = compressionRate
                    }else{
                        //找到压缩最佳值
                        targetImageData = compressData!
                        print("lalala ---0.9:%d~~~1.1:%d",Int(Double(targetByte) * 0.8),Int(Double(targetByte) * 1.2))
                        break
                    }
                    print("lalala ---\(compressionRate)----compressDatalenth:%d",compressData?.count)
                }
            }
            print("lalala compress after:%d",targetImageData.count)
            return targetImageData as NSData
        }
    
    

    原因:image data 相互转换 data忽大忽小
    结果:可能是300k,但是已经是jpeg能提供的最佳压缩率了

    lalala compress before--- 1006180
    lalala ---0.5----compressDatalenth:%d Optional(156856)
    lalala ---0.75----compressDatalenth:%d Optional(266978)
    lalala ---0.875----compressDatalenth:%d Optional(321495)
    lalala ---0.9375----compressDatalenth:%d lalala ---0.96875----compressDatalenth:%d Optional(352076)
    lalala ---0.984375----compressDatalenth:%d Optional(359699)
    

    此方案基于 UIImageJPEGRepresentation 传入参数值 由小到大,压缩值 也 由小变大规律
    规律论证如下:

    参考链接:iOS图片压缩处理
    为什么实际大小和占用空间不一样

    iOS优秀的图片压缩处理方案(系统UIimagejpeg和uikit以及imageI/处理)
    图像压缩原理
    图片有损压缩原理

    相关文章

      网友评论

        本文标题:iOS图片压缩方案

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