SDWebImage源码解读_之SDWebImageDecoder
第四篇
前言
首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗?
其实不解码也是可以使用的,假如说我们通过imageNamed:
来加载image,系统默认会在主线程立即进行图片的解码工作。这一过程就是把image解码成可供控件直接使用的位图。
当在主线程调用了大量的imageNamed:
方法后,就会产生卡顿了。为了解决这个问题我们有两种比较简单的处理方法:
- 我们不使用
imageNamed:
加载图片,使用其他的方法,比如imageWithContentsOfFile:
- 我们自己解码图片,可以把这个解码过程放到子线程
通过上边这两点小小的建议,我们知道了处理图片的一些小技巧。我们还需要知道图片的一些基础知识和如何解码图片。
图像存储
首先图像的存储是二维的,所以我们需要考虑如何表示图像中某个特定位置的值。然后,我们需要考虑具体的值应该如何量化。另外,根据我们捕捉图像的途径,也会有不同的方式来编码图形数据。一般来说,最直观的方式是将其存为位图数据,可如果你想处理一组几何图形,效率就会偏低。一个圆形可以只由三个值 (两个坐标值和半径) 来表示,使用位图会使文件更大,却只能做粗略的近似。
不同于位图把值存在阵列中,矢量格式存储的是绘图图像的指令。在处理一些可以被归纳为几何形状的简单图像时,这样做显然更有效率;但面对照片数据时矢量储存就会显得乏力了。建筑师设计房屋更倾向于使用矢量的方式,因为矢量格式并不仅仅局限于线条的绘制,也可以用渐变或图案的填充作为展示,所以利用矢量方式完全可以生成房屋的拟真渲染图。
用于填充的图案单元则更适合被储存为一个位图,在这种情况下,我们可能需要一个混合格式。一个非常普遍的混合格式的一个例子是 PostScript,(或者时下比较流行的衍生格式,PDF),它基本上是一个用于绘制图像的描述语言。上述格式主要针对印刷业,而 NeXT 和 Adobe 开发的 Display Postscript 则是进行屏幕绘制的指令集。PostScript 能够排布字母,甚至位图,这使得它成为了一个非常灵活的格式。
矢量图像
矢量格式的一大优点是缩放。矢量格式的图像其实是一组绘图指令,这些指令通常是独立于尺寸的。如果你想扩大一个圆形,只需在绘制前扩大它的半径就可以了。位图则没这么容易。最起码,如果扩大的比例不是二的倍数,就会涉及到重绘图像,并且各个元素都只是简单地增加尺寸,成为一个色块。由于我们不知道这图像是一个圆形,所以无法确保弧线的准确描绘,效果看起来肯定不如按比例绘制的线条那样好。也因此,在像素密度不同的设备中,矢量图像作为图形资源会非常有用。位图的话,同样的图标,在视网膜屏幕之前的 iPhone 上看起来并没有问题,在拉伸两倍后的视网膜屏幕上看起来就会发虚。就好像仅适配了 iPhone 的 App 运行在 iPad 的 2x 模式下就不再那么清晰了。
虽然 Xcode 6 已经支持了 PDF 格式,但迄今仍不完善,只是在编译时将其创建成了位图图像。最常见的矢量图像格式为 SVG,在 iOS 中也有一个渲染 SVG 文件的库,SVGKit。
位图
大部分图像都是以位图方式处理的,从这里开始,我们就将重点放在如何处理它们上。第一个问题,是如何表示两个维度。所有的格式都以一系列连续的行作为单元,而每一行则水平地按顺序存储了每个像素。大多数格式会按照行的顺序进行存储,但是这并不绝对,比如常见的交叉格式,就不严格按照行顺序。其优点是当图像被部分加载时,可以更好的显示预览图像。在互联网初期,这是一个问题,随着数据的传输速度提升,现在已经不再被当做重点。
表示位图最简单的方法是将二进制作为每个像素的值:一个像素只有开、关两种状态,我们可以在一个字节中存储八个像素,效率非常高。不过,由于每一位只有最多两个值,我们只能储存两种颜色。考虑到现实中的颜色数以百万计,上述方法听起来并不是很有用。不过有一种情况还是需要用到这样的方法:遮罩。比如,图像的遮罩可以被用于透明性,在 iOS 中,遮罩被应用在 tab bar 的图标上 (即便实际图标不是单像素位图)。
如果要添加更多的颜色,有两个基本的选择:使用一个查找表,或直接用真实的颜色值。GIF 图像有一个颜色表 (或色彩面板),可以存储最多 256 种颜色。存储在位图中的值是该查询列表中的索引值,对应着其相应的颜色。所以,GIF 文件仅限于 256 色。对于简单的线条图或纯色图,这是一种不错的解决方法。但对于照片来说,就会显示的不够真实,照片需要更精细的颜色深度。进一步的改进是 PNG 文件,这种格式可以使用一个预置的色板或者独立的通道,它们都支持可变的颜色深度。在一个通道中,每个像素的颜色分量 (红,绿,蓝,即 RGB,有时添加透明度值,即RGBA) 是直接指定的。
GIF 和 PNG 对于具有大面积相同颜色的图像是最好的选择,因为它们使用的 (主要是基于游程长度编码的) 压缩算法可以减少存储需求。这种压缩是无损的,这意味着图像质量不会被压缩过程影响。
一个有损压缩图像格式的例子是 JPEG。创建 JPEG 图像时,通常会指定一个与图像质量相关的压缩比值参数,压缩程度过高会导致图像质量恶化。JPEG 不适用于对比鲜明的图像 (如线条图),其压缩方式对类似区域的图像质量损害会相对严重。如果某张截图中包含了文本,且保存为 JPEG 格式,就可以清楚地看到:生成的图像中字符周围会出现杂散的像素点。在大部分照片中不存在这个问题,所以照片主要使用 JPEG 格式。
总结:就放大缩小而言,矢量格式 (如 SVG) 是最好的。对比鲜明且颜色数量有限的线条图最适合 GIF 或 PNG (其中 PNG 更为强大),而照片,则应该使用 JPEG。当然,这些都不是不可逾越的规则,不过通常而言,对一定的图像质量与图像尺寸而言,遵守规则会得到最好的结果。
做一些好玩的事
连接了上边的知识呢,就可以做一些好玩的事了。比如说给图像打马赛克,合并图像等等。再次就不介绍怎么实现了。有兴趣的同学可以自己网上去搜,例子很多。
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image
好了,言归正传,读完上边的内容,我们明白了为什么要解码图片,那么这个方法就是解码图片的实现过程。这给我们提供了一种思路:我们有时在优化代码的时候,可以考虑用这个方法来处理图像数据。
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
// 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 imageRef = image.CGImage;
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bytesPerRow = kBytesPerPixel * width;
// 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.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
我们一行一行的看:
static const size_t kBytesPerPixel = 4;
kBytesPerPixel
用来说明每个像素占用内存多少个字节,在这里是占用4个字节。(图像在iOS设备上是以像素为单位显示的)。
static const size_t kBitsPerComponent = 8;
kBitsPerComponent
表示每一个组件占多少位。这个不太好理解,我们先举个例子,比方说RGBA,其中R(红色)G(绿色)B(蓝色)A(透明度)是4个组件,每个像素由这4个组件组成,那么我们就用8位来表示着每一个组件,所以这个RGBA就是8*4 = 32位。
知道了kBitsPerComponent
和每个像素有多少组件组成就能计算kBytesPerPixel
了。计算公式是:(bitsPerComponent * number of components + 7)/8
.
判断要不要解码
if (![UIImage shouldDecodeImage:image]) {
return image;
}
并不是所有的image都要解码的。我们来看看shouldDecodeImage:
这个函数:
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
return NO;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
// do not decode images with alpha
if (anyAlpha) {
return NO;
}
return YES;
}
不适合解码的条件为:
- image为nil
- animated images 动图不适合
- 带有透明因素的图像不适合
获取核心数据
通过CGImageRef imageRef = image.CGImage
可以拿到和图像有关的各种参数。
- 颜色空间
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
- 宽
size_t width = CGImageGetWidth(imageRef);
- 高
size_t height = CGImageGetHeight(imageRef);
- 计算出每行的像素数
size_t bytesPerRow = kBytesPerPixel * width;
创建没有透明因素的bitmap graphics contexts
// 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.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (context == NULL) {
return image;
}
注意:这里创建的contexts是没有透明因素的。在UI渲染的时候,实际上是把多个图层按像素叠加计算的过程,需要对每一个像素进行 RGBA 的叠加计算。当某个 layer 的是不透明的,也就是 opaque 为 YES 时,GPU 可以直接忽略掉其下方的图层,这就减少了很多工作量。这也是调用 CGBitmapContextCreate 时 bitmapInfo 参数设置为忽略掉 alpha 通道的原因。
绘制图像
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.0f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
if (![UIImage shouldDecodeImage:image]) {
return image;
}
if (![UIImage shouldScaleDownImage:image]) {
return [UIImage decodedImageWithImage:image];
}
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);
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);
// current color space
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
// Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
// 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(destBitmapData,
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
if (destContext == NULL) {
free(destBitmapData);
return image;
}
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// 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 imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
return destImage;
}
}
......... 这个方法也真够长的,看了就头疼啊。不过我们还是会一点点分析。我们能够学会如何压缩一个图像。
最大支持压缩图像源的大小
static const CGFloat kDestImageSizeMB = 60.0f;
默认的单位是MB,这里设置了60MB。当我们要压缩一张图像的时候,首先就是要定义最大支持的源文件的大小,不能没有任何限制。下边是SDWebImage
的建议:
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
原图方块的大小
static const CGFloat kSourceImageTileSizeMB = 20.0f;
这个方块将会被用来分割原图,默认设置为20M。
1M有多少字节
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
1M有多少像素
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
目标总像素
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
原图放款总像素
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
重叠像素大小
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
重点来了,如何把一个很大的原图压缩成指定的大小?
原理: 首先定义一个大小固定的方块,然后把原图按照方块的大小进行分割,最后把每个方块中的数据画到目标画布上,这样就能得到目标图像了。接下来我们做出相信的解释。
检测图像能否解码
if (![UIImage shouldDecodeImage:image]) {
return image;
}
检查图像应不应该压缩,原则是:如果图像大于目标尺寸才需要压缩
if (![UIImage shouldScaleDownImage:image]) {
return [UIImage decodedImageWithImage:image];
} + (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
BOOL shouldScaleDown = YES; CGImageRef sourceImageRef = image.CGImage;
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
float imageScale = kDestTotalPixels / sourceTotalPixels;
if (imageScale < 1) {
shouldScaleDown = YES;
} else {
shouldScaleDown = NO;
} return shouldScaleDown;
}
拿到数据信息 sourceImageRef
CGImageRef sourceImageRef = image.CGImage;
计算原图的像素 sourceResolution
CGSize sourceResolution = CGSizeZero;
sourceResolution.width = CGImageGetWidth(sourceImageRef);
sourceResolution.height = CGImageGetHeight(sourceImageRef);
计算原图总像素 sourceTotalPixels
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
计算压缩比例 imageScale
// 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;
计算目标像素 destResolution
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
获取当前的颜色空间 colorspaceRef
// current color space
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef]; + (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef); BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
CFAutorelease(colorspaceRef);
}
return colorspaceRef;
}
计算并创建目标图像的内存 destBitmapData
size_t bytesPerRow = kBytesPerPixel * destResolution.width; // Allocate enough pixel data to hold the output image.
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
if (destBitmapData == NULL) {
return image;
}
创建目标上下文 destContext
// 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(destBitmapData,
destResolution.width,
destResolution.height,
kBitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); if (destContext == NULL) {
free(destBitmapData);
return image;
}
设置压缩质量
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
计算第一个原图方块 sourceTile,这个方块的宽度同原图一样,高度根据方块容量计算
// 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;
计算目标图像方块 destTile
// 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;
计算原图像方块与方块重叠的像素大小 sourceSeemOverlap
// 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);
计算原图像需要被分割成多少个方块 iterations
// 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 imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
总结
好了,这篇文章已经很长了 ,但是令人高兴的是,我们学到了很多关于图像的知识。其中比较重要的是图片的基础知识,还有就是把图片按照方块进行切割的思想了,目前我能想的使用场景就是当我们加载一个比较大的数据时,可以把数据切成一个一个的方块,然后显示。
由于个人知识有限,如有错误之处,还望各路大侠给予指出啊
发现一片文章讲解的也很有意思,一张图片引发的深思。
- SDWebImage源码解读 之 NSData+ImageContentType 简书 博客园
- SDWebImage源码解读 之 UIImage+GIF 简书 博客园
- SDWebImage源码解读 之 SDWebImageCompat 简书 博客园
SDWebImage源码解读_之SDWebImageDecoder的更多相关文章
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
- SDWebImage源码解读之SDWebImageDownloader
SDWebImage源码解读之SDWebImageDownloader 第八篇 前言 SDWebImageDownloader这个类非常简单,作者的设计思路也很清晰,但是我想在这说点题外话. 如果有人 ...
- SDWebImage源码解读之SDWebImageManager
第九篇 前言 SDWebImageManager是SDWebImage中最核心的类了,但是源代码确是非常简单的.之所以能做到这一点,都归功于功能的良好分类. 有了SDWebImageManager这个 ...
- SDWebImage源码解读之SDWebImagePrefetcher
> 第十篇 ## 前言 我们先看看`SDWebImage`主文件的组成模块: ![](http://images2015.cnblogs.com/blog/637318/201701/63731 ...
- SDWebImage源码解读之分类
第十一篇 前言 我们知道SDWebImageManager是用来管理图片下载的,但我们平时的开发更多的是使用UIImageView和UIButton这两个控件显示图片. 按照正常的想法,我们只需要在他 ...
- SDWebImage源码解读之干货大总结
这是我认为的一些重要的知识点进行的总结. 1.图片编码简介 大家都知道,数据在网络中是以二进制流的形式传播的,那么我们该如何把那些1和0解析成我们需要的数据格式呢? 说的简单一点就是,当文件都使用二进 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
随机推荐
- 【开源】.Net Api开放接口文档网站
开源地址:http://git.oschina.net/chejiangyi/ApiView 开源QQ群: .net 开源基础服务 238543768 ApiView .net api的接口文档查看 ...
- 通过AngularJS实现前端与后台的数据对接(一)——预备工作篇
最近,笔者在做一个项目:使用AngularJS,从而实现前端与后台的数据对接.笔者这是第一次做前端与后台的数据对接的工作,因此遇到了许多问题.笔者在这些问题中,总结了一些如何实现前端与后台的数据对接的 ...
- bzoj1079--记忆化搜索
题目大意:有n个木块排成一行,从左到右依次编号为1~n.你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块.所有油漆刚好足够涂满所有木块,即c1+c2+...+ck=n.相邻两个木块涂相同色显得 ...
- 浅谈JSP中include指令与include动作标识的区别
JSP中主要包含三大指令,分别是page,include,taglib.本篇主要提及include指令. include指令使用格式:<%@ include file="文件的绝对路径 ...
- css实现文本框和下拉框结合的案例
html 代码部分 <div id="list-name-input" class="list-name-input"> <select ty ...
- 【repost】JS错误类型的学习
SyntaxError是解析代码时发生的语法错误 // 变量名错误 var 1a; // 缺少括号 console.log 'hello'); (2)ReferenceError Referen ...
- .NET跨平台之旅:数据库连接字符串写法引发的问题
最近在一个ASP.NET Core站点中遇到一个奇怪问题.当用dotnet run命令启动站点后,开始的一段时间请求执行速度超慢,有时要超过20秒,有时甚至超过1分钟,日志中会记录这样的错误: Sys ...
- 您真的理解了SQLSERVER的日志链了吗?
您真的理解了SQLSERVER的日志链了吗? 先感谢宋沄剑给本人指点迷津,还有郭忠辉童鞋今天在QQ群里抛出的问题 这个问题跟宋沄剑讨论了三天,再次感谢宋沄剑 一直以来,SQLSERVER提供了一个非常 ...
- .NET跨平台之旅:在生产环境中上线第一个运行于Linux上的ASP.NET Core站点
2016年7月10日,我们在生产环境中上线了第一个运行于Linux上的ASP.NET Core站点,这是一个简单的提供后端服务的ASP.NET Core Web API站点. 项目是在Windows上 ...
- ReactNative入门 —— 动画篇(下)
在上篇动画入门文章中我们了解了在 React Native 中简单的动画的实现方式,本篇将作为上篇的延续,介绍如何使用 Animated 实现一些比较复杂的动画. 动画组合 在 Animated 中提 ...