图片压缩算法

作者: zgsddzwj | 来源:发表于2019-08-16 16:01 被阅读0次

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

    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);
    }
    

    相关文章

      网友评论

        本文标题:图片压缩算法

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