圖片壓縮算法

最近項目中要做圖片壓縮,由于之前沒有接觸過,所以在做的過程中遇到了幾個問題,在這做下整理,希望看到的同學遇到相似的問題可以有點啟發(fā)。

1.如何獲取圖片大小

我們一開始定的策略是上傳圖片時,20M以上不讓選擇,1-20M以內壓縮60%,1M以內不壓縮(由于之前都沒有接觸過,也沒有調查微信、微博等主流App的壓縮算法,所以暫時定了這個壓縮比例)。
想要做壓縮,首先需要獲取圖片的大小,我們知道,在iOS上有兩個獲取圖片大小的方法,UIImagePNGRepresentation和UIImageJPEGRepresentation。
UIImagePNGRepresentation我們在這里不過多贅述。

1.為什么不提UIImagePNGRepresentation?

回復:據(jù)說這個讀取圖片的大小會比較大,因為是png格式,讀取的內容會有多圖層的的問題導致讀取的會顯示比較大,而且比較耗時間。網(wǎng)上有人做過測試:同樣是讀取攝像頭拍攝的同樣景色的照片,UIImagePNGRepresentation() 返回的數(shù)據(jù)量大小為199K,而 UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的數(shù)據(jù)量大小只為 140KB,比前者少了50多KB。如果對圖片的清晰度要求不高,還可以通過設置 UIImageJPEGRepresentation 函數(shù)的第二個參數(shù),大幅度降低圖片數(shù)據(jù)量。
如果還有什么問題可以繼續(xù)百度,這里不進行過多贅述。

2.關于UIImageJPEGRepresentation,首先第一個參數(shù)是我們都知道的圖片image,但是第二個參數(shù)scale,一個0~1的浮點型比率,你以為0就是沒有,壓縮到0b大小,1.0就是原圖大?。看鸢甘??:錯,首先你的圖片的大小是根據(jù)(圖片的寬圖片的高每一個色彩的深度,這個和手機的系統(tǒng)有關,一般是4)。你的圖片只會按照你的手機像素的分辨率[UIScreen mainScreen].scale來讀取值。其次,第二個參數(shù)蘋果官方并沒有明確說明這個參數(shù)的具體意義。對于大圖片來說,即使你的scale選的很小,比如:0.0000000(n個0)001,但是得到的結果還是很大,這里做了一個實驗:一個10M左右的圖片,處理后大小為2M多。有點像是“壓不動”的感覺。當然如果是小圖片的話那就是沒問題,能滿足你的希望的壓縮到的大小。

既然兩種方式都不可行,那我們應該如何獲取。通過閱讀TZImagePicker的源碼,發(fā)現(xiàn)他是用以下方法獲取的。

- (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.

原來,它就是我們相冊中圖片、視頻的展現(xiàn)方式。所以我們可以通過上面的方法獲取到圖片的原始大小,做為對比,UIImageJPEGRepresentation(image, 1.0)獲取到的也是jpeg壓縮后的圖片大小。

所以,我們需要在相冊中獲取該圖片的大?。ㄈ绻窍鄼C拍照獲取的圖片,需要先保存到相冊,然后再通過該方式獲取大小)

- (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.拿到圖片大小,然后就可以進行壓縮了

關于壓縮,網(wǎng)絡上的說法也是很多(安卓好像有[Luban算法],iOS沒找到對應的)。這里只說純粹的質量壓縮。
一般采取的都是二分法壓縮。

imageData = UIImageJPEGRepresentation(image, compression);
    if (imageData.length < fImageBytes) {
        //二分最大10次,區(qū)間范圍精度最大可達0.00097657;最大6次,精度可達0.015625
        for (int i = 0; i < 6; ++i) {
            compression = (max + min) / 2;
            imageData = UIImageJPEGRepresentation(image, compression);
            //容錯區(qū)間范圍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張圖片進行壓縮的話,就會內存暴增,網(wǎng)上找到了別人寫的方式,其基本原理就是以下一句話:

使用ImageIO接口,避免在改變圖片大小的過程中產生臨時的bitmap,就能夠在很大程度上減少內存的占有從而避免由此導致的app閃退問題。

這里直接附原文地址:iOS優(yōu)秀的圖片壓縮處理方案

后來發(fā)現(xiàn)用這種方式壓縮的圖片,基本接近設置的壓縮比(誤差大約在10%以內,針對1-20M圖片),但是發(fā)現(xiàn)按40%壓縮后的圖片,上傳依然很慢。
這時,我們才想到研究下微信的壓縮,于是發(fā)現(xiàn)了別人的壓縮都是尺寸壓縮+質量壓縮,而質量壓縮,都是壓縮比例很大,于是,我們重新定義了一套壓縮策略,直接用UIImageJPEGRepresentation進行壓縮。

3.新的圖片壓縮策略:

圖片壓縮策略.png

以下是代碼實現(xiàn):

+(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();
    
    //進行圖像的畫面質量壓縮,統(tǒng)一按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);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容