首先要知道为啥压缩大图片会内存暴增,然后降下来?(UIImageJPEGRepresentation,UIImagePNGRepresentation)
图片的压缩会瞬间消耗很大内存,apple官方文档对CGBitmapContextCreate函数的注解:
bitsPerComponent 表示存入内存中的每个像素中的每一个组件所占的位数;
bytesPerRow 表示存入内存中的位图的每一行所占的字节数;
解压缩操作中,每一个像素点都会分配一个空间来存储相关值,那么分辨率越高的图片,就意味着更多数量的像素点,也就意味着需要分配更多的空间!所以对于高分辨率图来说,解压缩操作的确会造成内存飙升,即使是几M的图片,解压缩过程中也是有可能消耗上G的内存!可以根据苹果官方的加载大图片例子得出结论,例子中有一句注释:
#define kImageFilename @"large_leaves_70mp.jpg" // 7033x10110 image, 271 MB uncompressed
large_leaves_70mp.jpg图片是7033x10110(占用磁盘大小8.3MB),分辨率 = 7033 x 10110 x 4(ARGB),对应位图占用大小 = 分辨率 x 1024 x 1024 ( = 271MB),解压会把图片转成位图,也就意味着会占用271MB内存,所以解压过程内存会瞬间消耗很大,等转成NSData后位图的内存就会被回收掉,内存就降下来,这时候NSData占用的大小即是图片的实际大小,该过程中由于会转成位图,而位图的大小是比图片的实际的大小大很多的,内存暴增的点就在位图。位图的内存大小计算是根据图片的分辨率而来(分辨率(width x heigth) x 1024 x 1024 x 4 (ARGB)),所以一般来说图片分辨率越高转成的位图占用的内存空间越大。
其次得知道压缩图片的目的,用于做啥?
这么一说感觉都用不到压缩了?一般的小图片当然还是要用到压缩,显示或上传,那如果非要压缩大图片呢?压缩大图片低设备肯定要挂,参考了苹果官方加载大图片的例子,大图片分成很多小片,利用GPU来处理每个小分片,最后显示,那么,大图片的压缩是不是也可以分片压缩呢?
答案是可以的,整体思路和图片分片显示一样的:
-
获取到某一图片分片(如果太大可以改变分片的大小到合适大小)
-
对获取到的图片分片压缩,压缩得到的data可以直接使用NSFilehandle断点写入磁盘中(大图片相当于大文件,大文件的断点写入可以参考iOS大文件下载,断点下载),然后释放data,最好不要保存到内存中.
-
等所有的图片分片都压缩完,自然也就压缩完。这时图片的data已经全部获取,接下来改干嘛就干嘛。
代码其实没多大变化,直接在苹果官方的加载图片的例子中修改,因为要用到苹果官方加载大图片例子中的图片分片
/** 循环获取到大图片的每个小分片*/
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
NSLog(@"iteration %d of %d",y+1,iterations);
/** sourceTile大图片每个分片大小*/
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = ( destResolution.height ) - ( ( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + destSeemOverlap );
/** 大图片的一个小分片sourceTileImageRef*/
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImage.CGImage, sourceTile );
/** 这边是保存到内存中,图片大小45MB左右,最后iphone4s内存在65MB左右,还是可以接受的,但是根据业务的需求,最好是把data写入磁盘中去,而不是保存到内存,我这边是偷懒了一下。*/
// [UIImagePNGRepresentation([[UIImage alloc] initWithCGImage:sourceTileImageRef]) writeToFile:path atomically:YES];
[bigData appendData:UIImagePNGRepresentation([[UIImage alloc] initWithCGImage:sourceTileImageRef])];
if( y == iterations - 1 && remainder ) {
float dify = destTile.size.height;
destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
dify -= destTile.size.height;
destTile.origin.y += dify;
}
CGContextDrawImage( destContext, destTile, sourceTileImageRef );
CGImageRelease( sourceTileImageRef );
}
if( y < iterations - 1 ) {
sourceImage = [[UIImage alloc] initWithContentsOfFile:imagePath];
[self performSelectorOnMainThread:@selector(updateScrollView:) withObject:nil waitUntilDone:YES];
}
}
注意
上述压缩方式,会导致图片错乱,以为每次压缩生成的NSData时候一张分片图片,下一张分片图的NSData直接追加在上一张的后面iOS直接识别不了,我猜测一张完整的图片有开始标记和结尾标记,iOS系统在识别时只发现了第一张的开始标记和第一张的结束标记,这样iOS认为已经加载好了图片,就不会往下加载,故图片会错乱,只显示第一张分片的图片。这个是一个问题,还没解决。目前想到的是,每次压缩一个图片分片就保存到磁盘,最后把所有的图片合成一张。