该文章阅读的SDWebImage的版本为4.3.3。
这个是编解码器类,提供了对PNG、JPEG、TIFF类型图片的编解码,还支持逐行解码。
1.公共方法
+ (nonnull instancetype)sharedCoder;
复制代码
这个类除了实现SDWebImageProgressiveCoder
协议中方法外,对外暴露的只有一个获取单例对象的方法。
2.静态变量
定义每像素字节数为4
static const size_t kBytesPerPixel = 4;
复制代码
定义每位字节数为8
static const size_t kBitsPerComponent = 8;
复制代码
定义解码图像的最大大小为60MB
static const CGFloat kDestImageSizeMB = 60.0f;
复制代码
定义用于解码图像的最大大小为20MB
static const CGFloat kSourceImageTileSizeMB = 20.0f;
复制代码
定义1MB是1024*1024Bytes
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
复制代码
定义1MB的像素数是1MB的Bytes数量除以1像素的Bytes数
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
复制代码
定义解码完的图像的最大像素数是解码完的图像最大大小乘以1MB的像素数
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
复制代码
定义用于解码的图像的最大像素数是解码的图像的最大大小乘以1MB的像素数
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
复制代码
定义重叠像素大小为2像素
static const CGFloat kDestSeemOverlap = 2.0f;
复制代码
3.私有变量
定义了两个变量来保存图像的宽和高
size_t _width, _height;
复制代码
定义变量记录图像方向
UIImageOrientation _orientation;
复制代码
定义变量记录图像源
CGImageSourceRef _imageSource;
复制代码
4.生命周期方法
- (void)dealloc {
// 释放并置空图像源变量
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
复制代码
+ (instancetype)sharedCoder {
// 实例化SDWebImageImageIOCoder对象
static SDWebImageImageIOCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageImageIOCoder alloc] init];
});
return coder;
}
复制代码
5.私有方法
/**
判断图像是否需要解码
*/
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// 如果没传图像就直接返回NO
if (image == nil) {
return NO;
}
// 如果是动图也直接返回NO
if (image.images != nil) {
return NO;
}
// 获取到图像的位图图像
CGImageRef imageRef = image.CGImage;
// 判断图像是否有通明度
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
// 如果有透明度也直接返回NO
if (hasAlpha) {
return NO;
}
return YES;
}
复制代码
/**
判断是否支持HEIC类型图像的解码
*/
+ (BOOL)canDecodeFromHEICFormat {
// 1.先获取进程信息对象
// 2.通过进程信息对象获取操作系统版本
// 3.如果当前操作系统版本是iOS 11+就支持HEIC类型图像的解码
static BOOL canDecode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
#if TARGET_OS_SIMULATOR || SD_WATCH
canDecode = NO;
#elif SD_MAC
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
// macOS 10.13+
canDecode = processInfo.operatingSystemVersion.minorVersion >= 13;
} else {
canDecode = NO;
}
#elif SD_UIKIT
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
// iOS 11+ && tvOS 11+
canDecode = processInfo.operatingSystemVersion.majorVersion >= 11;
} else {
canDecode = NO;
}
#endif
#pragma clang diagnostic pop
});
return canDecode;
}
复制代码
/**
判断是否支持HEIC类型图像的编码
*/
+ (BOOL)canEncodeToHEICFormat {
// 如果能创建CGImageDestinationRef对象就支持HEIC类型图像的编码
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can not encode to HEIC
canEncode = NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
canEncode = YES;
}
});
return canEncode;
}
复制代码
/**
通过图像数据获取图像的EXIF方向
*/
+ (UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
// 定义临时变量保存方向,默认向上
UIImageOrientation result = UIImageOrientationUp;
// 获取图像源对象
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
// 如果能获取到图像源对象就进一步判断,否则就返回向上
if (imageSource) {
// 获取图像源中图像的属性字典
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
// 如果获取成功就继续获取图像方向
CFTypeRef val;
NSInteger exifOrientation;
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) {
// 如果获取到方向就转换成UIImageOrientation类型数据返回
CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation);
result = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
} // 如果没获取到方向就返回向上
CFRelease((CFTypeRef) properties);
} else {
// 如果获取失败就返回向上
}
CFRelease(imageSource);
}
return result;
}
复制代码
/**
判断是否缩小图片
*/
+ (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;
// 如果压缩比小于1,也就是总像素数大于最大像素数,就返回YES
// 否则就返回NO
if (imageScale < 1) {
shouldScaleDown = YES;
} else {
shouldScaleDown = NO;
}
return shouldScaleDown;
}
复制代码
/**
获取图像的色彩空间
*/
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
// 获取图像色彩空间模型
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
// 获取图像色彩空间
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
// 如果色彩空间模型是未知、单色彩、CMYK、索引类型的,就统一返回RGB类型。
// 其他的就返回原本获取到的类型
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = SDCGColorSpaceGetDeviceRGB();
}
return colorspaceRef;
}
复制代码
/**
解压图片对象
*/
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
// 如果不需要解码就不继续进行了了,直接返回
if (![[self class] shouldDecodeImage:image]) {
return image;
}
// 建立自动释放池,以帮助系统在收到内存警告时释放内存。
@autoreleasepool{
// 获取到图片对象的位图图像
CGImageRef imageRef = image.CGImage;
// 获取图片对象的色彩空间
CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:imageRef];
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
// 创建不带透明度的位图图形上下文
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
0,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// 如果创建失败就直接返回图片对象
if (context == NULL) {
return image;
}
// 绘制图像到上下文中
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
// 生成位图图像
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
// 根据位图图像生成图片对象
UIImage *imageWithoutAlpha = [[UIImage alloc] initWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];
// 释放掉上下文
CGContextRelease(context);
// 释放掉位图图像
CGImageRelease(imageRefWithoutAlpha);
// 返回生成的图片对象
return imageWithoutAlpha;
}
}
复制代码
/**
解压并缩小图片对象
*/
- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
// 如果不需要解码就不继续进行了,直接返回
if (![[self class] shouldDecodeImage:image]) {
return image;
}
// 如果不需要缩小就调用只解压的方法
if (![[self class] shouldScaleDownImage:image]) {
return [self sd_decompressedImageWithImage:image];
}
// 创建图像上下文
CGContextRef destContext;
// 建立自动释放池,以帮助系统在收到内存警告时释放内存。
@autoreleasepool {
// 获取到图片对象的位图图像
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;
// 计算缩小后的尺寸
CGSize destResolution = CGSizeZero;
destResolution.width = (int)(sourceResolution.width*imageScale);
destResolution.height = (int)(sourceResolution.height*imageScale);
// 获取色彩空间
CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:sourceImageRef];
// 创建图像上下文
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// 如果创建失败就直接返回图片对象
if (destContext == NULL) {
return image;
}
// 设置图像上下文的绘图质量
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
// 创建临时变量保存来源块大小
CGRect sourceTile = CGRectZero;
// 来源块的宽度就是原图的宽度
sourceTile.size.width = sourceResolution.width;
// 计算来源块的高度
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
// 设置来源块的横坐标
sourceTile.origin.x = 0.0f;
// 创建历史变量保存目标块大小
CGRect destTile;
// 目标块的宽是缩放后的宽
destTile.size.width = destResolution.width;
// 目标块的宽是来源块的高乘以缩放比
destTile.size.height = sourceTile.size.height * imageScale;
// 设置目标块的横坐标
destTile.origin.x = 0.0f;
// 计算来源块与目标块的重复区域
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
// 生成变量保存来源块图像位图
CGImageRef sourceTileImageRef;
// 计算需要绘制的次数
int iterations = (int)( sourceResolution.height / sourceTile.size.height );
// 计算剩余像素的高度
int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
if(remainder) {
// 如果有剩余像素就将绘制次数加1
iterations++;
}
// 创建变量保存来源块的高度,用来计算纵坐标的移动
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;
// 计算目标块的纵坐标:目标图像的高度减去要绘制的来源块的高度乘以压缩比,再减去重叠高度
// 这个地方,来源块的纵坐标是递增的,目标块的纵坐标是递减的,这是因为为UIKit的坐标系和CGContext是镜像关系
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;
}
}
复制代码
6.SDWebImageProgressiveCoder协议方法实现
- 解码
- (BOOL)canDecodeFromData:(nullable NSData *)data {
// 获取图像类型
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// 如果是WebP格式就返回NO
return NO;
case SDImageFormatHEIC:
// 如果是HEIC格式就检查当前系统是否支持
return [[self class] canDecodeFromHEICFormat];
default:
// 其他类型就返回YES
return YES;
}
}
复制代码
- (UIImage *)decodedImageWithData:(NSData *)data {
// 如果没传数据就返回空
if (!data) {
return nil;
}
// 生成图片对象
UIImage *image = [[UIImage alloc] initWithData:data];
#if SD_MAC
return image;
#else
// 如果生成失败就返回空
if (!image) {
return nil;
}
// 获取到图像的方向
UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
// 如果方向不为上,就根据原图片方向生成图片对象
if (orientation != UIImageOrientationUp) {
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:orientation];
}
return image;
#endif
}
复制代码
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
#if SD_MAC
return image;
#endif
#if SD_UIKIT || SD_WATCH
// 创建变量保存,默认是不缩小
BOOL shouldScaleDown = NO;
// 如果传入选项字典参数
if (optionsDict != nil) {
// 创建变量保存value值
NSNumber *scaleDownLargeImagesOption = nil;
// 如果SDWebImageCoderScaleDownLargeImagesKey对应的value值是NSNumber类型的,就用变量保存
if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
}
// 如果获取到的value值不为空,就转换成BOOL类型,并保存
if (scaleDownLargeImagesOption != nil) {
shouldScaleDown = [scaleDownLargeImagesOption boolValue];
}
}
if (!shouldScaleDown) {
// 如果不需要缩小就直接调用解压方法
return [self sd_decompressedImageWithImage:image];
} else {
// 如果需要缩小就调用解压并缩小方法
UIImage *scaledDownImage = [self sd_decompressedAndScaledDownImageWithImage:image];
if (scaledDownImage && !CGSizeEqualToSize(scaledDownImage.size, image.size)) {
// 如果处理成功,还要处理数据指针,调用压缩方法
SDImageFormat format = [NSData sd_imageFormatForImageData:*data];
NSData *imageData = [self encodedDataWithImage:scaledDownImage format:format];
if (imageData) {
// 通过参数回传处理后的图像数据
*data = imageData;
}
}
// 返回处理后的图片
return scaledDownImage;
}
#endif
}
复制代码
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
// 获取图像类型
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// 如果是WebP格式就返回NO
return NO;
case SDImageFormatHEIC:
// 如果是HEIC格式就检查当前系统是否支持
return [[self class] canDecodeFromHEICFormat];
default:
// 其他类型就返回YES
return YES;
}
}
复制代码
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
// 创建一个增量图像源
if (!_imageSource) {
_imageSource = CGImageSourceCreateIncremental(NULL);
}
UIImage *image;
// 更新数据源
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
// 获取到图像的宽、高和方向
if (_width + _height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = 1;
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in didCompleteWithError.) So save it here and pass it on later.
#if SD_UIKIT || SD_WATCH
_orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue];
#endif
}
}
if (_width + _height > 0) {
// 创建位图对象
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
#if SD_UIKIT || SD_WATCH
// iOS变形图像的解决方法
if (partialImageRef) {
// 获取图像的高
const size_t partialHeight = CGImageGetHeight(partialImageRef);
// 生成RGB色彩空间
CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
// 创建位图图形上下文
CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
if (bmContext) {
// 将图像绘制到位图图形上下文中
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
// 位图对象的引用计数-1
CGImageRelease(partialImageRef);
// 通过上下文获取图片
partialImageRef = CGBitmapContextCreateImage(bmContext);
// 位图对象的引用计数-1
CGContextRelease(bmContext);
}
else {
// 位图对象的引用计数-1
CGImageRelease(partialImageRef);
// 位图对象置空
partialImageRef = nil;
}
}
#endif
// 根据位图生成图片独享
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:1 orientation:_orientation];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif // 位图对象的引用计数-1
CGImageRelease(partialImageRef);
}
}
// 如果加载完成就释放掉位图对象,并置空
if (finished) {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
// 返回图片对象
return image;
}
复制代码
- 编码
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
// 获取图像类型
switch (format) {
case SDImageFormatWebP:
// 如果是WebP格式就返回NO
return NO;
case SDImageFormatHEIC:
// 如果是HEIC格式就检查当前系统是否支持
return [[self class] canEncodeToHEICFormat];
default:
// 其他类型就返回YES
return YES;
}
}
复制代码
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
// 如果图片对象为空就直接返回空
if (!image) {
return nil;
}
// 如果没有传图片类型
if (format == SDImageFormatUndefined) {
// 有透明度的就是PNG类型,否则就是JPEG类型
BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
// 创建变量保存图片编码后的数据
NSMutableData *imageData = [NSMutableData data];
// 获取CFStringRef格式的图片类型
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
// 创建CGImageDestinationRef对象
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
// 如果创建失败就直接返回空
if (!imageDestination) {
// Handle failure.
return nil;
}
// 创建字典对象保存编码参数
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
// 获取图片的方向
NSInteger exifOrientation = [SDWebImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
// 设置方向参数
[properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
#endif
// 将图像位图对象添加到CGImageDestinationRef对象中
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
// 如果编码失败就返回空
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
// 释放掉imageDestination对象
CFRelease(imageDestination);
// 返回编码后的数据
return [imageData copy];
}
复制代码
7.总结
这个类实现了SDWebImageProgressiveCoder
协议中的方法,提供了解码、编码、解压和缩小的功能,下面我们就梳理一下各个功能的逻辑。
7.1.解码
这个功能实现比较简单,主要是利用了UIImage
类的initWithData:
和initWithCGImage: scale: orientation:
这两个对象方法:
- 首先判断是否有图像数据;
- 接着利用图像数据生成图片对象;
- 然后判断图片对象是否生成成功;
- 再获取图像方向,如果方向不是向上,就根据方向生成图片对象;
- 最后返回生成的图片对象
7.2.编码
这个功能涉及到了对ImageIO
库中CGImageDestinationRef
的使用,但是我们通篇看下来,就是三个步骤:
-
把冰箱门打开:利用
CGImageDestinationCreateWithData()
函数创建图像目的地; -
把大象放进去:使用
CGImageDestinationAddImage()
函数将图片对象添加到图像目的地中; -
把冰箱门关上:调用
CGImageDestinationFinalize()
函数将图片对象写入到数据对象中;
具体的逻辑是:
- 首先判断是否有图片对象;
- 接着判断图片对象的格式;
- 然后创建图像目的地对象;
- 接着配置编码的属性,即图像的方向;
- 然后将图片对象和属性添加到图像目的地对象中编码;
- 最后返回编码后的数据;
7.3.解压
图片为什么要解压?具体可以看一下这篇文章:谈谈 iOS 中图片的解压缩。解压的步骤也是分为三步:
- 使用
CGBitmapContextCreate()
函数创建位图图像上下文; - 利用
CGContextDrawImage()
函数将图片绘制到位图图像上下文中; - 调用
CGBitmapContextCreateImage()
函数从位图图像上下文中获取位图图像;
具体逻辑是:
- 首先判断是否应该解码,如果图片对象不存在、是动图、有透明度就不解码;
- 接下来创建一个自动释放池,以便在内存不足时清除缓存;
- 然后创建一个位图图像上下文;
- 接着就把图像绘制到位图图像上下文中;
- 再从位图图像上下文中获取绘制好的位图对象;
- 最后利用位图对象生成图片对象并返回;
7.4.解压并缩小
具体逻辑:
- 首先判断是否应该解码,如果图片对象不存在、是动图、有透明度就不解码;
- 然后判断是否应该缩小,如果图像的像素数量不超过指定数量就只解压不缩小;
- 接下来创建一个自动释放池,以便在内存不足时清除缓存;
- 接着创建一个位图图像上下文;
- 然后创建一个循环,依次从原图像中获取一片宽度和原图相等,高度是指定数值,横坐标为0,纵坐标递增的图像,绘制到位图图像上下文的指定位置和大小,直至获取完原图。
- 再从位图图像上下文中获取绘制好的位图对象;
- 最后利用位图对象生成图片对象并返回;
其中缩小的功能实现的思想是,将原图分片缩小。每次获取原图中的一片进行缩小,直至全部缩小完成。
源码阅读系列:SDWebImage
源码阅读:SDWebImage(二)——SDWebImageCompat
源码阅读:SDWebImage(三)——NSData+ImageContentType
源码阅读:SDWebImage(四)——SDWebImageCoder
源码阅读:SDWebImage(五)——SDWebImageFrame
源码阅读:SDWebImage(六)——SDWebImageCoderHelper
源码阅读:SDWebImage(七)——SDWebImageImageIOCoder
源码阅读:SDWebImage(八)——SDWebImageGIFCoder
源码阅读:SDWebImage(九)——SDWebImageCodersManager
源码阅读:SDWebImage(十)——SDImageCacheConfig
源码阅读:SDWebImage(十一)——SDImageCache
源码阅读:SDWebImage(十二)——SDWebImageDownloaderOperation
源码阅读:SDWebImage(十三)——SDWebImageDownloader
源码阅读:SDWebImage(十四)——SDWebImageManager
源码阅读:SDWebImage(十五)——SDWebImagePrefetcher
源码阅读:SDWebImage(十六)——SDWebImageTransition
源码阅读:SDWebImage(十七)——UIView+WebCacheOperation
源码阅读:SDWebImage(十八)——UIView+WebCache
源码阅读:SDWebImage(十九)——UIImage+ForceDecode/UIImage+GIF/UIImage+MultiFormat
源码阅读:SDWebImage(二十)——UIButton+WebCache
源码阅读:SDWebImage(二十一)——UIImageView+WebCache/UIImageView+HighlightedWebCache