最近项目中要做图片压缩,由于之前没有接触过,所以在做的过程中遇到了几个问题,在这做下整理,希望看到的同学遇到相似的问题可以有点启发。
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.新的图片压缩策略:
以下是代码实现:
+(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);
}