該文章閱讀的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