图片压缩算法

最近项目中要做图片压缩,由于之前没有接触过,所以在做的过程中遇到了几个问题,在这做下整理,希望看到的同学遇到相似的问题可以有点启发。

1.如何获取图片大小

我们一开始定的策略是上传图片时,20M以上不让选择,1-20M以内压缩60%,1M以内不压缩(由于之前都没有接触过,也没有调查微信、微博等主流App的压缩算法,所以暂时定了这个压缩比例)。
想要做压缩,首先需要获取图片的大小,我们知道,在iOS上有两个获取图片大小的方法,UIImagePNGRepresentation和UIImageJPEGRepresentation。
UIImagePNGRepresentation我们在这里不过多赘述。

1.为什么不提UIImagePNGRepresentation?

回复:据说这个读取图片的大小会比较大,因为是png格式,读取的内容会有多图层的的问题导致读取的会显示比较大,而且比较耗时间。网上有人做过测试:同样是读取摄像头拍摄的同样景色的照片,UIImagePNGRepresentation() 返回的数据量大小为199K,而 UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的数据量大小只为 140KB,比前者少了50多KB。如果对图片的清晰度要求不高,还可以通过设置 UIImageJPEGRepresentation 函数的第二个参数,大幅度降低图片数据量。
如果还有什么问题可以继续百度,这里不进行过多赘述。

2.关于UIImageJPEGRepresentation,首先第一个参数是我们都知道的图片image,但是第二个参数scale,一个0~1的浮点型比率,你以为0就是没有,压缩到0b大小,1.0就是原图大小?答案是?:错,首先你的图片的大小是根据(图片的宽图片的高每一个色彩的深度,这个和手机的系统有关,一般是4)。你的图片只会按照你的手机像素的分辨率[UIScreen mainScreen].scale来读取值。其次,第二个参数苹果官方并没有明确说明这个参数的具体意义。对于大图片来说,即使你的scale选的很小,比如:0.0000000(n个0)001,但是得到的结果还是很大,这里做了一个实验:一个10M左右的图片,处理后大小为2M多。有点像是“压不动”的感觉。当然如果是小图片的话那就是没问题,能满足你的希望的压缩到的大小。

既然两种方式都不可行,那我们应该如何获取。通过阅读TZImagePicker的源码,发现他是用以下方法获取的。

- (void)getOriginalDataFromSource:(PHAsset *)source completion:(void (^)(NSData *data))completion{
    PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
    options.resizeMode = PHImageRequestOptionsResizeModeFast;
    options.networkAccessAllowed = YES;
    
    [[PHImageManager defaultManager] requestImageDataForAsset:source options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
        completion(imageData);
    }];
}

那这个PHAsset又是什么东西?

查看它的定义:

A representation of an image, video, or Live Photo in the Photos library.

原来,它就是我们相册中图片、视频的展现方式。所以我们可以通过上面的方法获取到图片的原始大小,做为对比,UIImageJPEGRepresentation(image, 1.0)获取到的也是jpeg压缩后的图片大小。

所以,我们需要在相册中获取该图片的大小(如果是相机拍照获取的图片,需要先保存到相册,然后再通过该方式获取大小)

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    [picker dismissViewControllerAnimated:YES completion:nil];
    UIImage *image = nil;
    if (_isEditedImage) {
        image = [info objectForKey:UIImagePickerControllerEditedImage];
    } else {
        image = [info objectForKey:UIImagePickerControllerOriginalImage];
    }
    
    WS(weakSelf)
    [[TZImageManager manager]savePhotoWithImage:image completion:^(PHAsset *asset, NSError *error) {
        [weakSelf getOriginalDataFromSource:asset completion:^(NSData *data) {
            if (weakSelf.imageBlock) {
                GKTImageModel *model = [GKTImageModel new];
                model.image = image;
                model.assert = asset;
                model.imageData = data;
                model.isOriginal = YES;
                weakSelf.imageBlock(@[model]);
            }
        }];
 
        
    }];
}

保存的代码如下:

- (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion {
    [self savePhotoWithImage:image location:nil completion:completion];
}

- (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion {
    __block NSString *localIdentifier = nil;
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
        localIdentifier = request.placeholderForCreatedAsset.localIdentifier;
        if (location) {
            request.location = location;
        }
        request.creationDate = [NSDate date];
    } completionHandler:^(BOOL success, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (success && completion) {
                PHAsset *asset = [[PHAsset fetchAssetsWithLocalIdentifiers:@[localIdentifier] options:nil] firstObject];
                completion(asset, nil);
            } else if (error) {
                NSLog(@"保存照片出错:%@",error.localizedDescription);
                if (completion) {
                    completion(nil, error);
                }
            }
        });
    }];
}

2.拿到图片大小,然后就可以进行压缩了

关于压缩,网络上的说法也是很多(安卓好像有[Luban算法],iOS没找到对应的)。这里只说纯粹的质量压缩。
一般采取的都是二分法压缩。

imageData = UIImageJPEGRepresentation(image, compression);
    if (imageData.length < fImageBytes) {
        //二分最大10次,区间范围精度最大可达0.00097657;最大6次,精度可达0.015625
        for (int i = 0; i < 6; ++i) {
            compression = (max + min) / 2;
            imageData = UIImageJPEGRepresentation(image, compression);
            //容错区间范围0.9~1.0
            if (imageData.length < fImageBytes * 0.9) {
                min = compression;
            } else if (imageData.length > fImageBytes) {
                max = compression;
            } else {
                break;
            }
        }
        
        block(imageData);
        return;
    }

以上方法可以正常压缩图片,但是如果用户一下选取了9张图片进行压缩的话,就会内存暴增,网上找到了别人写的方式,其基本原理就是以下一句话:

使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap,就能够在很大程度上减少内存的占有从而避免由此导致的app闪退问题。

这里直接附原文地址:iOS优秀的图片压缩处理方案

后来发现用这种方式压缩的图片,基本接近设置的压缩比(误差大约在10%以内,针对1-20M图片),但是发现按40%压缩后的图片,上传依然很慢。
这时,我们才想到研究下微信的压缩,于是发现了别人的压缩都是尺寸压缩+质量压缩,而质量压缩,都是压缩比例很大,于是,我们重新定义了一套压缩策略,直接用UIImageJPEGRepresentation进行压缩。

3.新的图片压缩策略:

图片压缩策略.png

以下是代码实现:

+(void)zipNSDataWithImage:(UIImage *)sourceImage imageBlock:(ImageBlock)block{
    //进行图像尺寸的压缩
    CGSize imageSize = sourceImage.size;//取出要压缩的image尺寸
    CGFloat width = imageSize.width;    //图片宽度
    CGFloat height = imageSize.height;  //图片高度
    
    CGFloat scale = height/width;
    //0.宽高比例大于8
    if (scale > 8.0 || scale < 1/8.) {
        if (scale > 8.0) {
            if (width > 1080) {
                width = 1080;
                height = width * scale;
            }else {
                //不压缩
            }
        }else {
            if (height > 1080.) {
                height = 1080;
                width = height / scale;
            }else {
                //不压缩
            }
        }
        //1.宽高大于1080(宽高比不按照2来算,按照1来算)
    }else if (width>1080 && height>1080) {
        if (height > width) {
            CGFloat scale = height/width;
            width = 1080;
            height = width*scale;
        }else{
            CGFloat scale = width/height;
            height = 1080;
            width = height*scale;
        }
        //2.宽大于1080高小于1080
    }else if(width>1080 && height<1080){
        CGFloat scale = height/width;
        width = 1080;
        height = width*scale;
        //3.宽小于1080高大于1080
    }else if(width<1080 && height>1080){
        CGFloat scale = width/height;
        height = 1080;
        width = height*scale;
        //4.宽高都小于1080
    }else{
    }
    UIGraphicsBeginImageContext(CGSizeMake(width, height));
    [sourceImage drawInRect:CGRectMake(0,0,width,height)];
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    //进行图像的画面质量压缩,统一按0.5压缩
    NSData *data=UIImageJPEGRepresentation(newImage, 0.5);
//    if (data.length>100*1024) {
//        if (data.length>1024*1024) {//1M以及以上
//            data=UIImageJPEGRepresentation(newImage, 0.5);
//        }else if (data.length>512*1024) {//0.5M-1M
//            data=UIImageJPEGRepresentation(newImage, 0.8);
//        }else {
//            //0.25M-0.5M
//            data=UIImageJPEGRepresentation(newImage, 0.9);
//        }
//    }
    block(data);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,835评论 6 534
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,676评论 3 419
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,730评论 0 380
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,118评论 1 314
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,873评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,266评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,330评论 3 443
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,482评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,036评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,846评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,025评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,575评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,279评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,684评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,953评论 1 289
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,751评论 3 394
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,016评论 2 375

推荐阅读更多精彩内容