压缩大图片方法decodedAndScaledDownImageWithImage
这篇博文里有指出压缩算法里存在的一些问题 SDWebImage 图片压缩方法我的见解
做一些条件判断,以及一些总像素的最小单位
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
// 是否能解码
if (![self shouldDecodeImage:image]) {
return image;
}
// 是否需要缩放
if (![self shouldScaleDownImage:image limitBytes:bytes]) {
return [self decodedImageWithImage:image];
}
// 目标返回图像的总像素
CGFloat destTotalPixels;
// 分成每一个小图像的总像素,一个小图像是一行,所以在后续的计算中,宽度是一
CGFloat tileTotalPixels;
// byte 是限制的大小
if (bytes == 0) {
bytes = kDestImageLimitBytes; // 默认最大是 1024*1024*60
}
// kBytesPerPixel = 4 每个像素占用四个字节
destTotalPixels = bytes / kBytesPerPixel;
//
tileTotalPixels = destTotalPixels / 3;
// 目标图像的画布
CGContextRef destContext;
接下去这部分主要是对原图片和压缩后的图片进行计算,包括总像素,宽,高
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool {
// 图片源的信息
CGImageRef sourceImageRef = image.CGImage;
// 原图片的宽高
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
// 原图片的总像素
CGFloat 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.
// 利用像素比较缩小的比例
CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
// 根据比例获取目标图片的宽高,最小为 1 像素
CGSize destResolution = CGSizeZero;
destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale));
destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale));
// 设备的色彩空间
// device color space
CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
// 是否含有透明度
BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
// 图像位图的位深常数,按主机字节序排列。有 16 和 32 两种
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
// 两个枚举分别代表
// alpha分量存储在每个像素的最高有效位,并且颜色分量已经乘以该alpha值。
// 没有Alpha通道。如果像素的总大小大于颜色空间中颜色分量数目所需的空间,则忽略最高有效位。
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// 初始化目标图像的画布,参数作用
// 指向要在其中渲染图形的内存中目标的指针。该存储块的大小至少应为(bytesPerRow * height)个字节。
// 如果要此函数为位图分配内存,则传递NULL。这使您不必管理自己的内存,从而减少了内存泄漏问题。
// 宽 ,高 ,每个像素占位(8)
// 位图的每一行要使用的内存字节数。如果 data 参数为NULL,则传递0值将导致自动计算该值
// 色彩空间
// 用于指定位图是否应包含Alpha通道
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst
// 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, kCGInterpolationHigh);
// Now define the size of the rectangle to be used for the
// incremental bits 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 operation by anchoring 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 = MAX(1, (int)(tileTotalPixels / 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 output 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.
// 如果有余数,也就是还有小于 sourceTile.size.height 的部分,多一次循环
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
iterations++;
}
需要计算图片块的位置,以及将图像 draw 到画布上。这个重叠像素高度 kDestSeemOverlap 还不知道是什么作用
// 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 {
// 计算源图片块的 Y 坐标
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
// 计算目标图片块的 Y 坐标(?和原图的顺序是相反的)
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;
}
根据压缩后得到的 cgImage 得到目标图片
#if SD_MAC
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
#endif
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
SDImageCopyAssociatedObject(image, destImage);
destImage.sd_isDecoded = YES;
return destImage;
}
}