原始碼閱讀:SDWebImage(七)——SDWebImageImageIOCoder

堯少羽發表於2018-06-11

該文章閱讀的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的使用,但是我們通篇看下來,就是三個步驟:

  1. 把冰箱門開啟:利用CGImageDestinationCreateWithData()函式建立影像目的地;

  2. 把大象放進去:使用CGImageDestinationAddImage()函式將圖片物件新增到影像目的地中;

  3. 把冰箱門關上:呼叫CGImageDestinationFinalize()函式將圖片物件寫入到資料物件中;

具體的邏輯是:

  • 首先判斷是否有圖片物件;
  • 接著判斷圖片物件的格式;
  • 然後建立影像目的地物件;
  • 接著配置編碼的屬性,即影像的方向;
  • 然後將圖片物件和屬性新增到影像目的地物件中編碼;
  • 最後返回編碼後的資料;

7.3.解壓

圖片為什麼要解壓?具體可以看一下這篇文章:談談 iOS 中圖片的解壓縮。解壓的步驟也是分為三步:

  1. 使用CGBitmapContextCreate()函式建立點陣圖影像上下文;
  2. 利用CGContextDrawImage()函式將圖片繪製到點陣圖影像上下文中;
  3. 呼叫CGBitmapContextCreateImage()函式從點陣圖影像上下文中獲取點陣圖影像;

具體邏輯是:

  • 首先判斷是否應該解碼,如果圖片物件不存在、是動圖、有透明度就不解碼;
  • 接下來建立一個自動釋放池,以便在記憶體不足時清除快取;
  • 然後建立一個點陣圖影像上下文;
  • 接著就把影像繪製到點陣圖影像上下文中;
  • 再從點陣圖影像上下文中獲取繪製好的點陣圖物件;
  • 最後利用點陣圖物件生成圖片物件並返回;

7.4.解壓並縮小

具體邏輯:

  • 首先判斷是否應該解碼,如果圖片物件不存在、是動圖、有透明度就不解碼;
  • 然後判斷是否應該縮小,如果影像的畫素數量不超過指定數量就只解壓不縮小;
  • 接下來建立一個自動釋放池,以便在記憶體不足時清除快取;
  • 接著建立一個點陣圖影像上下文;
  • 然後建立一個迴圈,依次從原影像中獲取一片寬度和原圖相等,高度是指定數值,橫座標為0,縱座標遞增的影像,繪製到點陣圖影像上下文的指定位置和大小,直至獲取完原圖。
  • 再從點陣圖影像上下文中獲取繪製好的點陣圖物件;
  • 最後利用點陣圖物件生成圖片物件並返回;

其中縮小的功能實現的思想是,將原圖分片縮小。每次獲取原圖中的一片進行縮小,直至全部縮小完成。

原始碼閱讀系列:SDWebImage

原始碼閱讀: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

相關文章