iOS图片压缩方案

一、概述

“压” 是指文件体积变小,但是像素数不变,长宽尺寸不变,那么质量可能下降。(图片压完之后,占用内存的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图片压缩方案)