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];
}
複製代碼