参考链接:
https://www.jianshu.com/p/4dcd6e4bdbf0
https://nshipster.com/image-resizing/
调研的压缩方案主要有三种 UIGraphicsBeginImageContextWithOptions、ImageIO、SDWebimage 大图压缩方案
压缩代码
(1) UIGraphicsGetImageFromCurrentImageContext&drawInRect
- (NSData *)compressImageData
{
CGRect rect = CGRectMake(0.0, 0.0, (int)100, (int)100);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1);
[self drawInRect:rect];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
NSData *imageData = UIImageJPEGRepresentation(img, 1);
UIGraphicsEndImageContext();
return imageData;
}
(2) SDWebimage 分段 循环绘制
@autoreleasepool {
CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
// Determine the scale ratio to apply to the input image
// that results in an output image of the defined size.
// see kDestImageSizeMB, and how it relates to destTotalPixels.
float imageScale = kDestTotalPixels / sourceTotalPixels;
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
// device color space
CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(sourceImageRef);
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (destContext == NULL) {
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationDefault);
// Now define the size of the rectangle to be used for the
// incremental blits from the input image to the output image.
// we use a source tile width equal to the width of the source
// image due to the way that iOS retrieves image data from disk.
// iOS must decode an image from disk in full width 'bands', even
// if current graphics context is clipped to a subrect within that
// band. Therefore we fully utilize all of the pixel data that results
// from a decoding opertion by achnoring our tile size to the full
// width of the input image.
CGRect sourceTile = CGRectZero;
sourceTile.size.width = sourceResolution.width;
// The source tile height is dynamic. Since we specified the size
// of the source tile in MB, see how many rows of pixels high it
// can be given the input image width.
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
sourceTile.origin.x = 0.0f;
// The output tile is the same proportions as the input tile, but
// scaled to image scale.
CGRect destTile;
destTile.size.width = destResolution.width;
destTile.size.height = sourceTile.size.height * imageScale;
destTile.origin.x = 0.0f;
// The source seem overlap is proportionate to the destination seem overlap.
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
CGImageRef sourceTileImageRef;
// calculate the number of read/write operations required to assemble the
// output image.
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// If tile height doesn't divide the image height evenly, add another iteration
// to account for the remaining pixels.
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
float sourceTileHeightMinusOverlap = sourceTile.size.height;
sourceTile.size.height += sourceSeemOverlap;
destTile.size.height += kDestSeemOverlap;
for( int y = 0; y < iterations; ++y ) {
@autoreleasepool {
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
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 );
}
}
CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
CGContextRelease(destContext);
if (destImageRef == NULL) {
return image;
}
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
(3)ImageIO
let string = Bundle.main.path(forResource: "VIIRS_3Feb2012_lrg", ofType: "jpg");
self.URL = NSURL.fileURL(withPath: string!) as NSURL
let date = Date.init(timeIntervalSinceNow: 0)
// Do any additional setup after loading the view, typically from a nib.
let size = CGSize.init(width: 100, height: 100)
// ImageIO
if let imageSource = CGImageSourceCreateWithURL(self.URL!, nil) {
let options: [NSString: NSObject] = [
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) as NSObject,
kCGImageSourceCreateThumbnailFromImageAlways: true as NSObject
]
let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary).flatMap { UIImage(cgImage: $0) }
let data = scaledImage?.jpegData(compressionQuality: 1)
print("\(Date.init(timeIntervalSinceNow: 0).timeIntervalSince(date))")
- 测试结果:
(1) iphoneXR 系统12.1.2
全景图 imageSize: 16382 × 3628 压缩至:1000*100
API | 压缩大小 | 内存消耗 | 耗时 |
---|---|---|---|
UI | 100* 100 | 峰值7.78 平稳4.78 | 0.354609 |
SD | 1M | 峰值134 平稳 4.43 | 0.672967 |
IO | 100* 100 | 峰值7.69 平稳4.21 | 0.339498 |
(2) iphone8p 系统12.0.1
imageSize:1242 × 6075 压缩至:100*100
API | 压缩大小 | 内存消耗 | 耗时 |
---|---|---|---|
UI | 100* 100 | 峰值14.6 平稳 11.3 | 0.06 |
SD | 100*100 | 峰值 21.7 平稳 11.2 | 0.09 |
IO | 100* 100 | 峰值 20.9 平稳 12.1 | 0.0938 |
(3)iphone7p 系统 11.2.1
imageSize:3024 × 4032 压缩至:100*100
API | 压缩大小 | 内存消耗 | 耗时 |
---|---|---|---|
UI | 100* 100 | 峰值50.2 平稳4.28 | 0.640203 |
SD | 100*100 | 峰值49.2 平稳3.4 | 0.791870 |
IO | 100* 100 | 峰值3.69 平稳3.2 | 0.1033 |
(4)iphone7p 系统 11.2.1
imageSize:12000 * 12000 压缩至:100*100
API | 压缩大小 | 内存消耗 | 耗时 |
---|---|---|---|
UI | 100* 100 | 峰值 11.82 平稳3.29 | 0.32567 |
SD | 100*100 | 峰值 157.52 3.16 | 3.420274 |
IO | 100* 100 | 峰值3.69 平稳3.46 | 0.14890503883361816 |
(5)iphone 6 系统 10.3.2
12000 * 12000 压缩至:100*100
API | 压缩大小 | 内存消耗 | 耗时 |
---|---|---|---|
UI | 100* 100 | 3.6 | 0.343466 |
SD | 100*100 | 峰值 156M 平稳 3.7 | 4.298185 |
IO | 100* 100 | 3.5 | 0.418 |
- 结论
从测试结果可以看出 图片压缩 imageIO,UIGraphic性能表现良好,UIGraphic需要占用主线程绘制,对于需要后台绘制imageIO表现处理很大的优势,imageIO在各种图片压缩上整体表现良好; SD针对超长大图有优势,其他方面表现很差,这跟本身循环绘制原理也有必然联系 ,SD用的CGImageCreateWithImageInRect和CGContenxtDrawImage本身压缩性能也不错,在图片剪裁方面优势较大 ;
代码地址:https://github.com/denghuihua/ImageScale
ImageIO 介绍:
Image I/O is a powerful, yet lesser-known framework for working with images. Independent of Core Graphics, it can read and write between many different formats, access photo metadata, and perform common image processing operations. The framework offers the fastest image encoders and decoders on the platform, with advanced caching mechanisms and even the ability to load images incrementally.
映像I/O是一个功能强大但不太为人所知的处理映像的框架。它独立于核心图形,可以在许多不同格式之间读写,访问照片元数据,并执行常见的图像处理操作。该框架提供了平台上最快的图像编码器和解码器,具有先进的缓存机制,甚至可以增量加载图像。