SDImageIOCoder 是内置的支持 PNG,JPEG,TIFF,GIF(仅支持解码第一帧)和 HEIC(仅支持某些系统),并支持渐进式加载,声明以下:html
/** Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding. GIF Also supports static GIF (meaning will only handle the 1st frame). For a full GIF support, we recommend `SDAnimatedImageView` to keep both CPU and memory balanced. HEIC This coder also supports HEIC format because ImageIO supports it natively. But it depends on the system capabilities, so it won't work on all devices, see: https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf Decode(Software): !Simulator && (iOS 11 || tvOS 11 || macOS 10.13) Decode(Hardware): !Simulator && ((iOS 11 && A9Chip) || (macOS 10.13 && 6thGenerationIntelCPU)) Encode(Software): macOS 10.13 Encode(Hardware): !Simulator && ((iOS 11 && A10FusionChip) || (macOS 10.13 && 6thGenerationIntelCPU)) */
@interface SDImageIOCoder : NSObject <SDProgressiveImageCoder>
@property (nonatomic, class, readonly, nonnull) SDImageIOCoder *sharedCoder;
@end
复制代码
判断图片是否支持编解码的方法以下所示:shell
- (BOOL)canDecodeFromData:(nullable NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) { // 匹配当前的图片数据是什么类型
case SDImageFormatWebP: // 不支持 WebP 格式的图片
// Do not support WebP decoding
return NO;
case SDImageFormatHEIC:
// Check HEIC decoding compatibility
return [[self class] canDecodeFromHEICFormat];
case SDImageFormatHEIF:
// Check HEIF decoding compatibility
return [[self class] canDecodeFromHEIFFormat];
default:
return YES;
}
}
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
case SDImageFormatWebP:
// Do not support WebP encoding
return NO;
case SDImageFormatHEIC:
// Check HEIC encoding compatibility
return [[self class] canEncodeToHEICFormat];
case SDImageFormatHEIF:
// Check HEIF encoding compatibility
return [[self class] canEncodeToHEIFFormat];
default:
return YES;
}
}
复制代码
获取图片数据格式的方法以下所示,声明在 NSData+ImageContentType 分类中:app
+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
if (!data) {
return SDImageFormatUndefined;
}
// File signatures table: http://www.garykessler.net/library/file_sigs.html
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return SDImageFormatJPEG;
case 0x89:
return SDImageFormatPNG;
case 0x47:
return SDImageFormatGIF;
case 0x49:
case 0x4D:
return SDImageFormatTIFF;
case 0x52: {
if (data.length >= 12) {
//RIFF....WEBP
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return SDImageFormatWebP;
}
}
break;
}
case 0x00: {
if (data.length >= 12) {
//....ftypheic ....ftypheix ....ftyphevc ....ftyphevx
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(4, 8)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"ftypheic"]
|| [testString isEqualToString:@"ftypheix"]
|| [testString isEqualToString:@"ftyphevc"]
|| [testString isEqualToString:@"ftyphevx"]) {
return SDImageFormatHEIC;
}
//....ftypmif1 ....ftypmsf1
if ([testString isEqualToString:@"ftypmif1"] || [testString isEqualToString:@"ftypmsf1"]) {
return SDImageFormatHEIF;
}
}
break;
}
}
return SDImageFormatUndefined;
}
复制代码
上面两个方法调用了 canEncodeToHEICFormat 和 canEncodeToHEIFFormat 两个方法,其实现以下所示:ide
+ (BOOL)canDecodeFromFormat:(SDImageFormat)format { // 判断是否支持某一个格式的编码
CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; // 获取该格式的 imageUTType
NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageSourceCopyTypeIdentifiers(); // 获取 Image I/O 支持的全部的格式
if ([imageUTTypes containsObject:(__bridge NSString *)(imageUTType)]) { // 判断是否包含当前格式的 imageUTType
return YES;
}
return NO;
}
+ (BOOL)canDecodeFromHEICFormat { // 判断是否支持 HEIC 格式的解码
static BOOL canDecode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
canDecode = [self canDecodeFromFormat:SDImageFormatHEIC];
});
return canDecode;
}
+ (BOOL)canDecodeFromHEIFFormat { // 判断是否支持 HEIF 格式的解码
static BOOL canDecode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
canDecode = [self canDecodeFromFormat:SDImageFormatHEIF];
});
return canDecode;
}
+ (BOOL)canEncodeToFormat:(SDImageFormat)format { // 判断是否支持某一个格式的编码,经过看是否能生成 CGImageDestination 来判断
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can't encode to HEIC
return NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
return YES;
}
}
+ (BOOL)canEncodeToHEICFormat { // 判断是否支持 HEIC 格式的编码
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
canEncode = [self canEncodeToFormat:SDImageFormatHEIC];
});
return canEncode;
}
+ (BOOL)canEncodeToHEIFFormat { // 判断是否支持 HEIF 格式的编码
static BOOL canEncode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
canEncode = [self canEncodeToFormat:SDImageFormatHEIF];
});
return canEncode;
}
复制代码
获取图片格式的对应 UTType 的方法声明在 NSData+ImageContentType 中,就是一个匹配的过程,实现以下:ui
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format {
CFStringRef UTType;
switch (format) {
case SDImageFormatJPEG:
UTType = kUTTypeJPEG;
break;
case SDImageFormatPNG:
UTType = kUTTypePNG;
break;
case SDImageFormatGIF:
UTType = kUTTypeGIF;
break;
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
break;
case SDImageFormatHEIC:
UTType = kSDUTTypeHEIC;
break;
case SDImageFormatHEIF:
UTType = kSDUTTypeHEIF;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;
break;
}
return UTType;
}
复制代码
SDWebImage 基本的解码方法以下所示:编码
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) {
return nil;
}
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1) ;
}
UIImage *image = [[UIImage alloc] initWithData:data scale:scale];
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
return image;
}
复制代码
很简单粗暴的经过 initWithData:scale 方法进行图片的解码。atom
SDWebImage 支持图片的渐进式解码,能够边下载图片编解码,以规避内存高峰。初始化相关方法以下所示:spa
- (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
self = [super init];
if (self) {
_imageSource = CGImageSourceCreateIncremental(NULL); // 建立 imageSource
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor]; // 获取解码 scale
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; // 监听内存警告通知并处理内存问题
#endif
}
return self;
}
- (void)dealloc {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification
{
if (_imageSource) {
CGImageSourceRemoveCacheAtIndex(_imageSource, 0);
}
}
复制代码
实际的解码方法分为两步,在下载数据的过程当中经过 updateIncrementalData:finished: 方法不断更新数据,以下所示:.net
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
_finished = finished;
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished); // 向 _imageSource 更新数据,要注意的是这里须要每次传入所有图片数据,而非增量图片数据
if (_width + _height == 0) { // 若是 _width 和 _height 为0,则须要从 _imageSource 中获取一次数据
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL); // 获取 properties
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.
_orientation = (CGImagePropertyOrientation)orientationValue; // 由于图片方向信息会丢失,因此先保存起来
}
}
}
复制代码
在图片下载完毕后,经过以下方法返回图片:code
- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
UIImage *image;
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
CGFloat scale = _scale; // 获取图片 scale
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
#if SD_UIKIT || SD_WATCH
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:_orientation]; // 转换方向类型
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:imageOrientation];
#else
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:_orientation];
#endif
CGImageRelease(partialImageRef); // 释放 CGImage
CFStringRef uttype = CGImageSourceGetType(_imageSource); // 获取图片的 uttype
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype]; // 转换成正常的格式
}
}
return image;
}
复制代码
uttype 转换图片格式的方法声明在 NSData+ImageContentType 中,以下所示:
+ (SDImageFormat)sd_imageFormatFromUTType:(CFStringRef)uttype {
if (!uttype) {
return SDImageFormatUndefined;
}
SDImageFormat imageFormat;
if (CFStringCompare(uttype, kUTTypeJPEG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatJPEG;
} else if (CFStringCompare(uttype, kUTTypePNG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPNG;
} else if (CFStringCompare(uttype, kUTTypeGIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatGIF;
} else if (CFStringCompare(uttype, kUTTypeTIFF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatTIFF;
} else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatWebP;
} else if (CFStringCompare(uttype, kSDUTTypeHEIC, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIC;
} else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIF;
} else {
imageFormat = SDImageFormatUndefined;
}
return imageFormat;
}
复制代码
编码图片的代码大同小异,以下所示:
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
if (!image) {
return nil;
}
if (format == SDImageFormatUndefined) { // 若是图片格式不能肯定,则根据 CGImage 中是否包含 Alpha 通道来判断是 PNG 仍是 JPEG
BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage];
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; // 获取图片格式的 uttype
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
#if SD_UIKIT || SD_WATCH
CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation]; // 将 iOS 图片方向转换为 CGImagePropertyOrientation 类型
#else
CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
#endif
properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
// Add your image to the destination.
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
// Handle failure.
imageData = nil;
}
CFRelease(imageDestination);
return [imageData copy];
}
复制代码