大家好,好久没有更新博客了。一个早9晚5点半的硬是上成了996。悲剧的加班狗!

背景:

最近遇到一个图片压缩的问题,项目需求压缩图片500k以内上传服务器,还要求图片要清晰一点。还有证明是图片500k已经确实很清晰了,那就没办法,做呗~~!(不喜欢听bb的可以直接去下面撸代码)

思路

本来以为很简单的问题,自己随意写了一个UIImageJPEGRepresentation的方法进行一个循环压缩不就搞定了?,后来事实证明这个玩意儿很坑,有太多东西不是你想当然的。

1.为什么不提UIImagePNGRepresentation(<#UIImage * _Nonnull image#>)?

回复:据说这个读取图片的大小会比较大,因为是png格式,读取的内容会有多图层的的问题导致读取的会显示比较大,而且比较耗时间。网上有人做过测试:同样是读取摄像头拍摄的同样景色的照片,UIImagePNGRepresentation() 返回的数据量大小为199K,而 UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的数据量大小只为 140KB,比前者少了50多KB。如果对图片的清晰度要求不高,还可以通过设置 UIImageJPEGRepresentation 函数的第二个参数,大幅度降低图片数据量。
如果还有什么问题可以继续百度,这里不进行过多赘述。

2.首先第一个参数是我们都知道的图片image,但是第二个参数scale,一个0~1的浮点型比率,你以为0就是没有,压缩到0b大小,1.0就是原图大小?答案是?:错,首先你的图片的大小是根据(图片的宽*图片的高*每一个色彩的深度,这个和手机的系统有关,一般是4)。你的图片只会按照你的手机像素的分辨率[UIScreen mainScreen].scale来读取值。其次,第二个参数苹果官方并没有明确说明这个参数的具体意义。对于大图片来说,即使你的scale选的很小,比如:0.0000000(n个0)001,但是得到的结果还是很大,这里做了一个实验:一个10M左右的图片,处理后大小为2M多。有点像是“压不动”的感觉。当然如果是小图片的话那就是没问题,能满足你的希望的压缩到的大小。

既然是循环压,那么就要说一下算法,考虑到递归,二分法,后来发现网上也是有的,二分法处理。更快一点压缩图片到指定的大小。先看一段代码:

//二分最大10次,区间范围精度最大可达0.00097657;最大6次,精度可达0.015625for (int i = 0; i < 10; ++i) {compression = (max + min) / 2;imageData = UIImageJPEGRepresentation(image, compression);//容错区间范围0.9~1.0if (imageData.length < fImageBytes * 0.9) {min = compression;} else if (imageData.length > fImageBytes) {max = compression;} else {break;}}

上面就是使用二分法进行处理,比for循环依次递减“高效”很多,而且也合理很多。

但是你也会问,压缩“压不动”怎么办?

这样压缩到“极致”(一般我们不用进行太多的for循环,个人觉得参数到0.05已经可以了如果还是比你想要的大很多那就不要用UIImageJPEGRepresentation了),劳民伤财,劳的是cpu的高速运转,伤的是手机老化加快。哈哈,皮一下!

然后我们其实可以换一个方式,进行尺寸压缩:

提到尺寸压缩,你会不会很失望,看你的文章,原来也是使用UIGraphicsBeginImageContextWithOptions然后drawInRect绘制一个图片,大小。代码类似如下:

/* 根据 dWidth dHeight 返回一个新的image**/
- (UIImage *)drawWithWithImage:(UIImage *)imageCope Size:(CGSize)size {
//这里设置为0,意为自动设置清晰度,图片可以是别的传过来的图片信息UIGraphicsBeginImageContextWithOptions(size, NO,0);[imageCope drawInRect:CGRectMake(0, 0, size.width, size.height)];imageCope = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return imageCope;
}

首先我需要说一下这个绘制很耗内存性能的,[UIImage drawInRect:]在绘制时,先解码图片,再生成原始分辨率大小的bitmap,这是很耗内存的,并且还有位数对齐等耗时操作。如果在一个方法中循环压缩比例进行代码的比例压缩,那么这种使用UIKit类进行图片绘制的话是需要先把图片读入内存然后在进行绘制,那么势必会给内存中占用大量的临时内存bitmap,而这个如果再加上循环,那么内存占有将是不可估量的。

你可能会说,我加一个自动释放池@autoreleasepool,不就好了?

错:首先这个自动释放池@autoreleasepool不要放在循环的外面,包着这个循环,原因就不过多说明,可以自行百度。然后放在for循环内部包着这个绘制的方法,你的内存并不是画完就得到了释放,内存占有的情况可以得到缓解,但是还是不能解决内存突然暴增的问题。尤其是大图片的压缩尤其明显。

然后你会想换一个方式,这里我也亲测试了,使用Image I/O相关的处理方式,使用相关的生成缩略图的形式压缩图片文件。直接上代码如下:

记得导入相关的头文件
#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>static size_t getAssetBytesCallback(void *info, void *buffer, off_t position, size_t count{ALAssetRepresentation *rep = (__bridge id)info;NSError *error = nil;size_t countRead = [rep getBytes:(uint8_t *)buffer fromOffset:position length:count error:&error];if (countRead == 0 && error) {// We have no way of passing this info back to the caller, so we log it, at least.NSLog(@"thumbnailForAsset:maxPixelSize: got an error reading an asset: %@", error);}return countRead;
}static void releaseAssetCallback(void *info) {// The info here is an ALAssetRepresentation which we CFRetain in thumbnailForAsset:maxPixelSize:.// This release balances that retain.CFRelease(info);
}- (UIImage *)thumbnailForAsset:(ALAsset *)asset maxPixelSize:(NSUInteger)size {NSParameterAssert(asset != nil);NSParameterAssert(size > 0);ALAssetRepresentation *rep = [asset defaultRepresentation];CGDataProviderDirectCallbacks callbacks = {.version = 0,.getBytePointer = NULL,.releaseBytePointer = NULL,.getBytesAtPosition = getAssetBytesCallback,.releaseInfo = releaseAssetCallback,};CGDataProviderRef provider = CGDataProviderCreateDirect((void *)CFBridgingRetain(rep), [rep size], &callbacks);CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef) @{(NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES,(NSString *)kCGImageSourceThumbnailMaxPixelSize : @(size),(NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES,});CFRelease(source);CFRelease(provider);if (!imageRef) {return nil;}UIImage *toReturn = [UIImage imageWithCGImage:imageRef];CFRelease(imageRef);return toReturn;
}

这个是网上的,说的不清楚,(某两个人以及阿里云文档)只管代码补上,很反感。而且还有人搞了两个voidvoid,都是什么,,,,,自己搞了一下,这个东西,可以结合从数据库获取的info返回使用。这里代码如下:

#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {NSLog(@"info:\n%@", info);UIImage *image = info[UIImagePickerControllerOriginalImage];NSData *imgData = UIImageJPEGRepresentation(image, 1.0);NSLog(@"length1: %lu", (unsigned long)imgData.length);NSURL *imageURL = info[UIImagePickerControllerReferenceURL];ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];[assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {image = [self thumbnailForAsset:asset maxPixelSize:600];imgData = UIImageJPEGRepresentation(image, 1.0);NSLog(@"length2: %lu", (unsigned long)imgData.length);NSArray * paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"new/ceshi.jpg"];// 保存文件的名称BOOL result = [imgData writeToFile: filePath atomically:YES]; // 保存成功会返回YESNSLog(@"文件保存成功?%d",result);} failureBlock:nil];[picker dismissViewControllerAnimated:YES completion:^{}];
}

如果要是问我怎么打开相册?这里也配给你们:

 //初始化UIImagePickerController类UIImagePickerController * picker = [[UIImagePickerController alloc] init];//判断数据来源为相册picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;//设置代理picker.delegate = self;//打开相册[self presentViewController:picker animated:YES completion:nil];

记得声明一下代理。

使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap,就能够在很大程度上减少内存的占有从而避免由此导致的app闪退问题。

在这之前我也直接有过另外一种方式压缩图片(直接靠图片尺寸压缩绘制图片):

+ (void)compressedImageFiles:(UIImage *)image imageKB:(CGFloat)fImageKBytes imageBlock:(ReturnCompressImage)block {__block UIImage *imageCope = image;CGFloat fImageBytes = fImageKBytes * 1024;//需要压缩的字节Byte__block NSData *uploadImageData = nil;//        uploadImageData = UIImagePNGRepresentation(imageCope);uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);
//    NSLog(@"图片压前缩成 %fKB",uploadImageData.length/1024.0);
//    CGFloat value1 = uploadImageData.length/1024.0;CGSize size = imageCope.size;CGFloat imageWidth = size.width;CGFloat imageHeight = size.height;if (uploadImageData.length > fImageBytes && fImageBytes >0) {dispatch_async(dispatch_queue_create("CompressedImage", DISPATCH_QUEUE_SERIAL), ^{/* 宽高的比例 **/CGFloat ratioOfWH = imageWidth/imageHeight;/* 压缩率 **/CGFloat compressionRatio = fImageBytes/uploadImageData.length;/* 宽度或者高度的压缩率 **/CGFloat widthOrHeightCompressionRatio = sqrt(compressionRatio);CGFloat dWidth   = imageWidth *widthOrHeightCompressionRatio;CGFloat dHeight  = imageHeight*widthOrHeightCompressionRatio;if (ratioOfWH >0) { /* 宽 > 高,说明宽度的压缩相对来说更大些 **/dHeight = dWidth/ratioOfWH;}else {dWidth  = dHeight*ratioOfWH;}imageCope = [self drawWithWithImage:imageCope width:dWidth height:dHeight];//            uploadImageData = UIImagePNGRepresentation(imageCope);uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);//            NSLog(@"当前的图片已经压缩成 %fKB",uploadImageData.length/1024.0);//微调NSInteger compressCount = 0;/* 控制在 1M 以内**/while (fabs(uploadImageData.length - fImageBytes) > 1024) {/* 再次压缩的比例**/CGFloat nextCompressionRatio = 0.9;if (uploadImageData.length > fImageBytes) {dWidth = dWidth*nextCompressionRatio;dHeight= dHeight*nextCompressionRatio;}else {dWidth = dWidth/nextCompressionRatio;dHeight= dHeight/nextCompressionRatio;}imageCope = [self drawWithWithImage:imageCope width:dWidth height:dHeight];//                uploadImageData = UIImagePNGRepresentation(imageCope);uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);/*防止进入死循环**/compressCount ++;if (compressCount == 10) {break;}}//            NSLog(@"图片已经压缩成 %fKB",uploadImageData.length/1024.0);
//            CGFloat value2 = uploadImageData.length/1024.0;imageCope = [[UIImage alloc] initWithData:uploadImageData];dispatch_sync(dispatch_get_main_queue(), ^{if (block) {block(imageCope);}});});} else{if (block) {block(imageCope);}}
}/* 根据 dWidth dHeight 返回一个新的image**/
+ (UIImage *)drawWithWithImage:(UIImage *)imageCope width:(CGFloat)dWidth height:(CGFloat)dHeight{UIGraphicsBeginImageContext(CGSizeMake(dWidth, dHeight));[imageCope drawInRect:CGRectMake(0, 0, dWidth, dHeight)];imageCope = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return imageCope;}

这种方式极耗手机的cpu,而且绘制也是使用uikit进行绘制,内存占用也是比较严重的。

总结:

综合上面的所有情况现在我的最终处理方案如下:

1.首先使用UIImageJPEGRepresentation进行尽可能的压缩,这里我使用二分法(考虑到手机性能问题,这里二分法设置10次(能精确到0.00097657)以内即可)处理压缩的比率参数,

2.首先根据我设置的二分法的最小可能压缩一下原图片信息,比对一下最小的二分法能处理的最大限度得到的最小图片信息能否满足条件(在你设定的目标大小以内)。以减少不必要的循环,保护cpu处理。

3.然后对处理后的图片信息,保留最大压缩比(即上面的最小二分法的scale结果),然后再进行和最终目标的大小比值,求根,然后对图像的宽和高等比压缩处理。然后再次根据最小二分法的scale以UIImageJPEGRepresentation读取结果再和你的目标大小比对,然后以此循环。直到大小小于目标大小。

这样得到的图片几乎就能够在你设定的大小以内的附近,而且图片的信息肉眼几乎看不出来多大的区别。亲自试了3M,4M,6M,10M的大图片没有发现内存消耗有太大的波动。而且压缩出来的图片清晰度很高。

这里上代码如下:

- (void)compressedImageFiles:(UIImage *)imageimageKB:(CGFloat)fImageKBytes imageBlock:(void(^)(NSData *imageData))block{//二分法压缩图片CGFloat compression = 1;NSData *imageData = UIImageJPEGRepresentation(image, compression);NSUInteger fImageBytes = fImageKBytes * 1000;//需要压缩的字节Byte,iOS系统内部的进制1000if (imageData.length <= fImageBytes){block(imageData);return;}CGFloat max = 1;CGFloat min = 0;//指数二分处理,s首先计算最小值compression = pow(2, -6);imageData = UIImageJPEGRepresentation(image, compression);if (imageData.length < fImageBytes) {//二分最大10次,区间范围精度最大可达0.00097657;最大6次,精度可达0.015625for (int i = 0; i < 6; ++i) {compression = (max + min) / 2;imageData = UIImageJPEGRepresentation(image, compression);//容错区间范围0.9~1.0if (imageData.length < fImageBytes * 0.9) {min = compression;} else if (imageData.length > fImageBytes) {max = compression;} else {break;}}block(imageData);return;}// 对于图片太大上面的压缩比即使很小压缩出来的图片也是很大,不满足使用。//然后再一步绘制压缩处理UIImage *resultImage = [UIImage imageWithData:imageData];while (imageData.length > fImageBytes) {@autoreleasepool {CGFloat ratio = (CGFloat)fImageBytes / imageData.length;//使用NSUInteger不然由于精度问题,某些图片会有白边NSLog(@">>>>>>>>>>>>>>>>>%f>>>>>>>>>>>>%f>>>>>>>>>>>%f",resultImage.size.width,sqrtf(ratio),resultImage.size.height);CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)),(NSUInteger)(resultImage.size.height * sqrtf(ratio)));
//            resultImage = [self drawWithWithImage:resultImage Size:size];
//            resultImage = [self scaledImageWithData:imageData withSize:size scale:resultImage.scale orientation:UIImageOrientationUp];resultImage = [self thumbnailForData:imageData maxPixelSize:MAX(size.width, size.height)];imageData = UIImageJPEGRepresentation(resultImage, compression);}}//   整理后的图片尽量不要用UIImageJPEGRepresentation方法转换,后面参数1.0并不表示的是原质量转换。block(imageData);}
- (UIImage *)thumbnailForData:(NSData *)data maxPixelSize:(NSUInteger)size {CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef) @{(NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES,(NSString *)kCGImageSourceThumbnailMaxPixelSize : @(size),(NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES,});CFRelease(source);CFRelease(provider);if (!imageRef) {return nil;}UIImage *toReturn = [UIImage imageWithCGImage:imageRef];CFRelease(imageRef);return toReturn;
}

demo地址:https://github.com/KirstenDunst/CSXImageCompress

demo里面我是做了批量压缩处理,对于多个大图处理,内存也是没有什么太大的波动的。这里附上demo中的批量压缩的图片存储路径:

如有问题,欢迎指正!

这里也奉献一些大图(6M,10M)以供测试。:https://pan.baidu.com/s/13eexiBPy_lyJxBLXIddnZw

后续补充:

之前的测试中有发现使用上面demo中的方法有遇到iphone手机内存不足的手机拍的照片没有问题,再进行compressedImageFiles压缩处理的时候,会得到  “糊掉的”  图片,之后经过处理,在图片进行二分法压缩前,进行了一次图片的重绘操作解决了这个问题。以上的demo工具中的另一个方法resetSizeOfImage:能够解决这个问题。

扩展:

其实上面的demo中提到的Quartz2D或者UIKit的类中对图片的压缩,水印,剪切等操作,当看过CoreGraphics之后觉得图片原来也可以这么玩。它是iOS的核心图形库,包含Quartz2D绘图API接口,常用的是point,size,rect等这些图形,都定义在这个框架中,类名以CG开头的都属于CoreGraphics框架,它提供的都是C语言函数接口,是可以在iOS和mac OS 通用的。刚接触,这里的了解并不是很深入,但是是更接近底层的图像处理,操作处理上面也是有着很大的灵活性,也有可能会解答iphone内存不足遇到的压缩图片需要重绘问题。之后有时间我会再次整理一篇CoreGraphics的图片处理文章,敬请期待吧!

iOS优秀的图片压缩处理方案相关推荐

  1. (0031) iOS 开发之图片压缩

    1. Aspect单词的, 都会按照图片的宽高比来拉伸.这样会显示不全照片 2. Scale单词的,都会对图片进行拉伸(缩放); 3. 没有出现Scale单词的,都不会对图片进行拉伸; UIViewC ...

  2. (原创)介绍一个优秀的图片压缩库Compressor

    我们在做项目的时候,有时候需要在界面展示一张较大的图片 这时候我们应该想到两点 1图片是否能够缓存 2图片是否能够压缩 做到了缓存和压缩,才能尽可能低减少内存的负荷,增强app的流畅度 最近在了解这方 ...

  3. java jpeg压缩解码_图片压缩(iOS)

    场景很简单,上传图片前压缩图片,节省流量和发图时间.最近看了看 iOS 的静态图片压缩,这里记个笔记.本人之前没学过 iOS 和 Swift,本文是一篇入门文章,描述不到位之处请大家多多批评斧正. ̄ω ...

  4. iOS代码质量要求_图片压缩(iOS)

    场景很简单,上传图片前压缩图片,节省流量和发图时间.最近看了看 iOS 的静态图片压缩,这里记个笔记.本人之前没学过 iOS 和 Swift,本文是一篇入门文章,描述不到位之处请大家多多批评斧正. ̄ω ...

  5. 图片压缩利器Image Optimizer,一键压缩图像体积和尺寸清晰无损

    随着科技的发展,手机和数码相机的性能越来越强,拍出的照片质量越来越好,高品质的图片体积非常大,不利于保存和传输,因此,我们就希望能把图片进行压缩,这样既保证了图片质量,又节约了保存空间. 一.图片压缩 ...

  6. 每个人都要学的图片压缩终极奥义,有效解决 Android 程序 OOM

    由来 在我们编写 Android 程序的时候,几乎永远逃避不了图片压缩的难题.除了应用图标之外,我们所要显示的图片基本上只有两个来源: 来自网络下载 本地相册中加载 不管是网上下载下来的也好,还是从系 ...

  7. 浅谈移动端图片压缩(iOS Android)

    在 App 中,如果分享.发布.上传功能涉及到图片,必不可少会对图片进行一定程度的压缩.笔者最近在公司项目中恰好重构了双端(iOS&Android)的图片压缩模块.本文会非常基础的讲解一些图片 ...

  8. 浅谈移动端图片压缩(iOS Android)

    在 App 中,如果分享.发布.上传功能涉及到图片,必不可少会对图片进行一定程度的压缩.笔者最近在公司项目中恰好重构了双端(iOS&Android)的图片压缩模块.本文会非常基础的讲解一些图片 ...

  9. JS 图片压缩上传并在iOS中矫正方向

    JS 图片压缩上传并在iOS中矫正方向 最近在项目中,用到图片上传.如果不进行压缩再上传的话,动辄34兆的图片,上传起来会相当漫长.还有一点就是,在iOS中所拍摄的图片在本地显示是没有问题的,但是上传 ...

最新文章

  1. php函数多个参数_php中,用函数,如果有很多个参数,只使用最后一个参数,有什么优雅的写法?...
  2. jetson nano 安装 onnx
  3. Android事件传递(分发)机制
  4. cacti监控添加thold插件
  5. 怎么设置某个用户生成hdfs文件的权限_HDFS简明入门教程
  6. 详解linux的initrd
  7. Android中build target,minSdkVersion,targetSdkVersion,maxSdkVersion概念区分
  8. 【翻译】.NET 5 Preview2发布
  9. python concat_python-pd.concat()不合并在同一索引上
  10. Hadoop集群安装部署_伪分布式集群安装_01
  11. Linux: mv, rename单次及批次修改档案名称及后缀(批量修改文件名)
  12. 片段中的findViewById
  13. mtk刷机报错4032专业维修教程(图文)
  14. H3C AC:短信认证配置
  15. 夜神模拟器报错 daemon still not running error: cannot connect to daemon
  16. Linux如何使用mail命令给outlook邮箱发送邮件
  17. 七缸发动机预热,docker swarm + .net core 高速飙车成功
  18. 离散随机变量和连续随机变量_随机变量深度崩溃课程
  19. 图片处理系列一Android照片墙应用实现(绝对不崩溃)
  20. 在UE中创建配置文件

热门文章

  1. 萌新小白学3D建模需要什么软件,十年经验建模师为你解答,速看
  2. source insight的查找功能
  3. chinese-ocr-lite(pytorch) 转 caffe
  4. OKHTTP系列(九)---http请求头(header)作用
  5. 数据挖掘 顶级期刊_数据挖掘顶级期刊简介_47209.doc
  6. 小白算法学习 凸包 graham
  7. Numpy:np.isin()
  8. 取消开机CHKDSK磁盘检查
  9. JS实现RGB,HSL,HSB相互转换
  10. arm7c语言编程实例,ARM芯片嵌式系统C语言编程…….pdf