SDWebImage - 压缩大图片源码阅读

1,540 阅读4分钟

压缩大图片方法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;
    }
}