最近項目中要做圖片壓縮,由于之前沒有接觸過,所以在做的過程中遇到了幾個問題,在這做下整理,希望看到的同學遇到相似的問題可以有點啟發(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.新的圖片壓縮策略:

以下是代碼實現(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);
}