图片压缩算法

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

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 *)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.新的图片压缩策略:

图片压缩算法_第1张图片
图片压缩策略.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);
}

你可能感兴趣的:(图片压缩算法)