SDWebimage 源碼分析之SDImageIOCoder SDImageIOAnimatedCoder 源碼分析

1. SDImageIOCoder


/ * *
內(nèi)置編碼器埂奈,支持PNG, JPEG, TIFF,包括支持漸進(jìn)解碼较雕。

GIF
也支持靜態(tài)GIF(意思將只處理第一幀)结执。
如果完全支持GIF次氨,我們推薦“SDAnimatedImageView”來保持CPU和內(nèi)存的平衡蔽介。

HEIC
這個編碼器也支持HEIC格式,因為ImageIO本身就支持它煮寡。但是它取決于系統(tǒng)的能力虹蓄,所以它不能在所有的設(shè)備上工作,請參見:https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf
模擬器&& (iOS 11 || tvOS 11 || macOS 10.13)
模擬器&& (iOS 11 && A9Chip) || (macOS 10.13 && 6thGenerationIntelCPU))
編碼(軟件):macOS 10.13
編譯(硬件):模擬器&& (iOS 11 && A10FusionChip) || (macOS 10.13 && 6thGenerationIntelCPU))
* /

- (BOOL)canDecodeFromData:(nullable NSData *)data {
    switch ([NSData sd_imageFormatForImageData:data]) {
        case SDImageFormatWebP:
            // Do not support WebP decoding
            return NO;
        case SDImageFormatHEIC:
            // Check HEIC decoding compatibility
            return [SDImageHEICCoder canDecodeFromHEICFormat];
        case SDImageFormatHEIF:
            // Check HEIF decoding compatibility
            return [SDImageHEICCoder canDecodeFromHEIFFormat];
        default:
            return YES;
    }
}

不支持webp格式的解碼幸撕, HEIC 和 HEIF 格式薇组,使用子工廠來判斷是否可以進(jìn)行解碼

`

  • (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options這個是解碼的方法,核心的方法是調(diào)用SDImageIOAnimatedCoder這個類去做解碼,坐儿,包括下面的漸進(jìn)式解碼和編碼律胀,都是調(diào)用到這個類里面宋光,所以我們著重分析這個SDImageIOAnimatedCoder` 這個類

2. SDImageIOAnimatedCoder


+ (SDImageFormat)imageFormat {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)imageUTType {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)dictionaryProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)unclampedDelayTimeProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)delayTimeProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSString *)loopCountProperty {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

+ (NSUInteger)defaultLoopCount {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
}

下來看看這里是什么意思,其實SDImageIOAnimatedCoder相當(dāng)于父類炭菌,怎么說呢罪佳,也相當(dāng)于一個工廠,對外用戶是不知道內(nèi)部有 GIF 解碼器黑低,HEIC 解碼器等赘艳,接口只有一個解碼接口,用戶只需要調(diào)用一個解碼的接口投储,至于最后創(chuàng)建GIF工廠還是HEIC工廠第练,由這個父工廠來負(fù)責(zé)創(chuàng)建決定,所以你會發(fā)現(xiàn)

@interface SDImageGIFCoder : SDImageIOAnimatedCoder <SDProgressiveImageCoder, SDAnimatedImageCoder>

@property (nonatomic, class, readonly, nonnull) SDImageGIFCoder *sharedCoder;

@end
@interface SDImageHEICCoder : SDImageIOAnimatedCoder <SDProgressiveImageCoder, SDAnimatedImageCoder>

@property (nonatomic, class, readonly, nonnull) SDImageHEICCoder *sharedCoder;

@end

我們具體的解碼器都是集成自這個父類玛荞,那么差別在哪


+ (SDImageFormat)imageFormat {
    return SDImageFormatGIF;
}

+ (NSString *)imageUTType {
    return (__bridge NSString *)kUTTypeGIF;
}

+ (NSString *)dictionaryProperty {
    return (__bridge NSString *)kCGImagePropertyGIFDictionary;
}

+ (NSString *)unclampedDelayTimeProperty {
    return (__bridge NSString *)kCGImagePropertyGIFUnclampedDelayTime;
}

+ (NSString *)delayTimeProperty {
    return (__bridge NSString *)kCGImagePropertyGIFDelayTime;
}

+ (NSString *)loopCountProperty {
    return (__bridge NSString *)kCGImagePropertyGIFLoopCount;
}

+ (NSUInteger)defaultLoopCount {
    return 1;
}


+ (SDImageFormat)imageFormat {
    return SDImageFormatHEIC;
}

+ (NSString *)imageUTType {
    return (__bridge NSString *)kSDUTTypeHEIC;
}

+ (NSString *)dictionaryProperty {
    return kSDCGImagePropertyHEICSDictionary;
}

+ (NSString *)unclampedDelayTimeProperty {
    return kSDCGImagePropertyHEICSUnclampedDelayTime;
}

+ (NSString *)delayTimeProperty {
    return kSDCGImagePropertyHEICSDelayTime;
}

+ (NSString *)loopCountProperty {
    return kSDCGImagePropertyHEICSLoopCount;
}

+ (NSUInteger)defaultLoopCount {
    return 0;
}

就是你需要告訴我父工廠,你是什么類型的解碼器呕寝,當(dāng)前解碼器的一些屬性勋眯,這些父類不負(fù)責(zé)指定,延遲到子類下梢,下面繼續(xù)看代碼就明白了客蹋,比如循環(huán)次數(shù)時間的屬性獲取,每一種類型都有自己的屬性名字,看下面的兩個方法就明白了

// 獲取循環(huán)次數(shù)
+ (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
    // 默認(rèn)循環(huán)次數(shù)孽江,父類不指定讶坯,子類會指定一個值,默認(rèn)為1
    NSUInteger loopCount = self.defaultLoopCount;
    // 從source中獲取圖片屬性
    NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
    // 獲取具體的圖片屬性岗屏,比如png為kCGImagePropertyPNGDictionary辆琅,HEIC為kSDCGImagePropertyHEICSDictionary,由子類指定
    NSDictionary *containerProperties = imageProperties[self.dictionaryProperty];
    if (containerProperties) {
        // 從屬性中獲取loopcount
        NSNumber *containerLoopCount = containerProperties[self.loopCountProperty];
        if (containerLoopCount != nil) {
            loopCount = containerLoopCount.unsignedIntegerValue;
        }
    }
    return loopCount;
}
// 某一幀的幀時長
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
    NSDictionary *options = @{
        (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
        (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
    };
    NSTimeInterval frameDuration = 0.1;
    // 得到這一幀的屬性
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
    if (!cfFrameProperties) {
        return frameDuration;
    }
    NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
    // 獲取這一幀的屬性集合
    NSDictionary *containerProperties = frameProperties[self.dictionaryProperty];
    // 從屬性字典中獲取延遲時間
    NSNumber *delayTimeUnclampedProp = containerProperties[self.unclampedDelayTimeProperty];
    if (delayTimeUnclampedProp != nil) {
        frameDuration = [delayTimeUnclampedProp doubleValue];
    } else {
        NSNumber *delayTimeProp = containerProperties[self.delayTimeProperty];
        if (delayTimeProp != nil) {
            frameDuration = [delayTimeProp doubleValue];
        }
    }
    
    // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
    // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
    // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
    // for more information.
    
    if (frameDuration < 0.011) {
        frameDuration = 0.1;
    }
    
    CFRelease(cfFrameProperties);
    return frameDuration;
}

創(chuàng)建某一個位置的幀圖片

// 創(chuàng)建某一個位置的幀圖片
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
     //獲取CGImageSource類在CoreFundation框架中的id
     //CFTypeID CGImageSourceGetTypeID (void);
     //獲取所支持的圖片格式數(shù)組
     //CFArrayRef __nonnull CGImageSourceCopyTypeIdentifiers(void);
     //獲取CGImageSource對象的圖片格式
     //CFStringRef __nullable CGImageSourceGetType(CGImageSourceRef __nonnull isrc);
     //獲取CGImageSource中的圖片張數(shù) 不包括縮略圖
     //size_t CGImageSourceGetCount(CGImageSourceRef __nonnull isrc);
     //獲取CGImageSource的文件信息
     /*
     字典參數(shù)可配置的鍵值對與創(chuàng)建CGImageSource所傳參數(shù)意義一致
     返回的字典中的鍵值意義后面介紹
     */
     //CFDictionaryRef __nullable CGImageSourceCopyProperties(CGImageSourceRef __nonnull isrc, CFDictionaryRef __nullable options);
     //獲取CGImageSource中某個圖像的附加數(shù)據(jù)
     /*
     index參數(shù)設(shè)置獲取第幾張圖像 options參數(shù)可配置的鍵值對與創(chuàng)建CGImageSource所傳參數(shù)意義一致
     返回的字典中的鍵值意義后面介紹
     */
     //CFDictionaryRef __nullable CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
     //獲取圖片的元數(shù)據(jù)信息 CGImageMetadataRef類是圖像原數(shù)據(jù)的抽象
     //CGImageMetadataRef __nullable CGImageSourceCopyMetadataAtIndex (CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
     //獲取CGImageSource中的圖片數(shù)據(jù)
     //CGImageRef __nullable CGImageSourceCreateImageAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
     //刪除一個指定索引圖像的緩存
     //void CGImageSourceRemoveCacheAtIndex(CGImageSourceRef __nonnull isrc, size_t index);
     //獲取某一幀圖片的縮略圖
     //CGImageRef __nullable CGImageSourceCreateThumbnailAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
     //創(chuàng)建一個空的CGImageSource容器这刷,逐步加載大圖片
     //CGImageSourceRef __nonnull CGImageSourceCreateIncremental(CFDictionaryRef __nullable options);
     //使用新的數(shù)據(jù)更新CGImageSource容器
     //void CGImageSourceUpdateData(CGImageSourceRef __nonnull isrc, CFDataRef __nonnull data, bool final);
     //更新數(shù)據(jù)提供器來填充CGImageSource容器
     //void CGImageSourceUpdateDataProvider(CGImageSourceRef __nonnull isrc, CGDataProviderRef __nonnull provider, bool final);
     //獲取當(dāng)前CGImageSource的狀態(tài)
     /*
     CGImageSourceStatus枚舉意義:
     typedef CF_ENUM(int32_t, CGImageSourceStatus) {
         kCGImageStatusUnexpectedEOF = -5, //文件結(jié)尾出錯
         kCGImageStatusInvalidData = -4,   //數(shù)據(jù)無效
         kCGImageStatusUnknownType = -3,   //未知的圖片類型
         kCGImageStatusReadingHeader = -2, //讀標(biāo)題過程中
         kCGImageStatusIncomplete = -1,    //操作不完整
         kCGImageStatusComplete = 0        //操作完整
     };
     */
     //CGImageSourceStatus CGImageSourceGetStatus(CGImageSourceRef __nonnull isrc);
     //同上婉烟,獲取某一個圖片的狀態(tài)
     //CGImageSourceStatus CGImageSourceGetStatusAtIndex(CGImageSourceRef __nonnull isrc, size_t index);
  
    // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
    // Parse the image properties
    // 獲取圖片某一個索引位置的屬性
    NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
    // 獲取寬高
    NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue];
    NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue];
    // 獲取方向
    CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
    if (!exifOrientation) {
        exifOrientation = kCGImagePropertyOrientationUp;
    }
    // 獲取類型
    CFStringRef uttype = CGImageSourceGetType(source);
    // Check vector format
    BOOL isVector = NO;
    if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) {
        isVector = YES;
    }

    NSMutableDictionary *decodingOptions;
    if (options) {
        decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options];
    } else {
        decodingOptions = [NSMutableDictionary dictionary];
    }
    CGImageRef imageRef;
    if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
        if (isVector) {
            if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
                // Provide the default pixel count for vector images, simply just use the screen size
#if SD_WATCH
                thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size;
#elif SD_UIKIT
                thumbnailSize = UIScreen.mainScreen.bounds.size;
#elif SD_MAC
                thumbnailSize = NSScreen.mainScreen.frame.size;
#endif
            }
            CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
            NSUInteger DPIPerPixel = 2;
            NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel;
            decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI);
        }
        // 得到這個索引位置的圖片
        imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
    } else {
        // 設(shè)置縮略圖是否進(jìn)行變換
        decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
        CGFloat maxPixelSize;
        // 如果設(shè)置了變換
        if (preserveAspectRatio) {
            CGFloat pixelRatio = pixelWidth / pixelHeight;
            CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
            if (pixelRatio > thumbnailRatio) {
                maxPixelSize = thumbnailSize.width;
            } else {
                maxPixelSize = thumbnailSize.height;
            }
        } else {
            maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
        }
        // 設(shè)置縮略圖的最大尺寸
        decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
        // 無論如何都設(shè)置縮略圖
        decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES);
        // 創(chuàng)建某個位置的縮略圖
        imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
    }
    if (!imageRef) {
        return nil;
    }
    
    if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
        if (preserveAspectRatio) {
            // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
            exifOrientation = kCGImagePropertyOrientationUp;
        } else {
            // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size
            // CGImageSourceCreateThumbnailAtIndex 只是像素維度的縮小,我們需要真正的縮小圖片大小
            CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
            CGImageRelease(imageRef);
            imageRef = scaledImageRef;
        }
    }
    
#if SD_UIKIT || SD_WATCH
    UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
#else
    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
#endif
    CGImageRelease(imageRef);
    return image;
}

解碼圖片暇屋,解碼圖片其實就是從用戶傳的屬性中獲取縮放因子似袁,是否保持長寬比,縮略圖大小咐刨,是否只解碼第一幀昙衅,然后獲取當(dāng)前imageSouce所有的圖片數(shù)量,如果只解碼第一張圖片或者圖片數(shù)量就是一張定鸟,那么直接調(diào)用上面的創(chuàng)建一幀圖片就可以了而涉,如果是多張圖片如gif,那么就循環(huán)創(chuàng)建

// 解碼圖片
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
    if (!data) {
        return nil;
    }
    // 設(shè)置縮放因子仔粥,默認(rèn)為1
    CGFloat scale = 1;
    NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
    if (scaleFactor != nil) {
        scale = MAX([scaleFactor doubleValue], 1);
    }
    // 設(shè)置縮略圖大小
    CGSize thumbnailSize = CGSizeZero;
    NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
    if (thumbnailSizeValue != nil) {
#if SD_MAC
        thumbnailSize = thumbnailSizeValue.sizeValue;
#else
        thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
    }
    // 是否保持長寬比
    BOOL preserveAspectRatio = YES;
    // 根據(jù)用戶設(shè)置
    NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
    if (preserveAspectRatioValue != nil) {
        preserveAspectRatio = preserveAspectRatioValue.boolValue;
    }
    
#if SD_MAC
    // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
    // Which decode frames in time and reduce memory usage
    if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
        SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
        NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
        imageRep.size = size;
        NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
        [animatedImage addRepresentation:imageRep];
        return animatedImage;
    }
#endif
    // 用data創(chuàng)建source
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    if (!source) {
        return nil;
    }
    // 獲取圖片數(shù)量
    size_t count = CGImageSourceGetCount(source);
    UIImage *animatedImage;
    // 是否只解碼第一幀
    BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
    // 如果只解碼第一幀或者圖片數(shù)量<=1婴谱,那么就直接獲取第一幀圖片就可以了
    if (decodeFirstFrame || count <= 1) {
        // 創(chuàng)建一幀圖片蟹但,根據(jù)source,索引谭羔,縮放因子华糖,是否保持長寬比
        animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
    } else {
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
        
        for (size_t i = 0; i < count; i++) {
            UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
            if (!image) {
                continue;
            }
            
            NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
            // 創(chuàng)建每一幀圖片
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
            [frames addObject:frame];
        }
        
        NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
        // 根據(jù)幀組創(chuàng)建一個動圖
        animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
        animatedImage.sd_imageLoopCount = loopCount;
    }
    animatedImage.sd_imageFormat = self.class.imageFormat;
    CFRelease(source);
    
    return animatedImage;
}

下面是創(chuàng)建漸進(jìn)式圖片加載,圖片可以邊下載邊加載更新


- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
    return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}
// 創(chuàng)建一個漸進(jìn)式圖片加載
// https://cloud.tencent.com/developer/article/1186094
// https://juejin.im/post/5d9d7dbef265da5b9764be3c
// 在下載圖片的時候瘟裸,可以漸進(jìn)式下載圖片客叉,避免內(nèi)存高峰
- (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
    self = [super init];
    if (self) {
       
         /*
         設(shè)置一個預(yù)期的圖片文件格式,需要設(shè)置為字符串類型的值
         */
         //const CFStringRef kCGImageSourceTypeIdentifierHint;
         /*
         設(shè)置是否以解碼的方式讀取圖片數(shù)據(jù) 默認(rèn)為kCFBooleanTrue
         如果設(shè)置為true话告,在讀取數(shù)據(jù)時就進(jìn)行解碼 如果為false 則在渲染時才進(jìn)行解碼
         */
         //const CFStringRef kCGImageSourceShouldCache;
         /*
         返回CGImage對象時是否允許使用浮點值 默認(rèn)為kCFBooleanFalse
         */
         //const CFStringRef kCGImageSourceShouldAllowFloa;
         /*
         設(shè)置如果不存在縮略圖則創(chuàng)建一個縮略圖兼搏,縮略圖的尺寸受開發(fā)者設(shè)置影響,如果不設(shè)置尺寸極限沙郭,則為圖片本身大小
         默認(rèn)為kCFBooleanFalse
         */
         //const CFStringRef kCGImageSourceCreateThumbnailFromImageIfAbsent;
         /*
         設(shè)置是否創(chuàng)建縮略圖佛呻,無論原圖像有沒有包含縮略圖kCFBooleanFalse
         */
         //const CFStringRef kCGImageSourceCreateThumbnailFromImageAlways;
         /*
         //設(shè)置縮略圖的寬高尺寸 需要設(shè)置為CFNumber值
         */
         //const CFStringRef kCGImageSourceThumbnailMaxPixelSize;
         /*
         設(shè)置縮略圖是否進(jìn)行Transfrom變換
         */
         //const CFStringRef kCGImageSourceCreateThumbnailWithTransform;
     
        NSString *imageUTType = self.class.imageUTType;
        _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType});
        CGFloat scale = 1;
        NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
        if (scaleFactor != nil) {
            scale = MAX([scaleFactor doubleValue], 1);
        }
        _scale = scale;
        CGSize thumbnailSize = CGSizeZero;
        NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
        if (thumbnailSizeValue != nil) {
    #if SD_MAC
            thumbnailSize = thumbnailSizeValue.sizeValue;
    #else
            thumbnailSize = thumbnailSizeValue.CGSizeValue;
    #endif
        }
        _thumbnailSize = thumbnailSize;
        BOOL preserveAspectRatio = YES;
        NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
        if (preserveAspectRatioValue != nil) {
            preserveAspectRatio = preserveAspectRatioValue.boolValue;
        }
        _preserveAspectRatio = preserveAspectRatio;
#if SD_UIKIT
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
    }
    return self;
}
// 在下載的過程中,不斷的更新數(shù)據(jù)
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
    if (_finished) {
        return;
    }
    _imageData = data;
    _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
    // 向 _imageSource 更新數(shù)據(jù)病线,要注意的是這里需要每次傳入全部圖片數(shù)據(jù)吓著,而非增量圖片數(shù)據(jù)
    CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
//  如果 _width 和 _height 為0,則需要從 _imageSource 中獲取一次數(shù)據(jù)
    if (_width + _height == 0) {
        NSDictionary *options = @{
            (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
            (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
        };
        CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options);
        if (properties) {
            CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
            val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
            if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
            CFRelease(properties);
        }
    }
    
    // For animated image progressive decoding because the frame count and duration may be changed.
    [self scanAndCheckFramesValidWithImageSource:_imageSource];
}
// 圖片下載完成后送挑,通過此方法返回
- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
    UIImage *image;
    
    if (_width + _height > 0) {
        // Create the image
        CGFloat scale = _scale;
        NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
        if (scaleFactor != nil) {
            scale = MAX([scaleFactor doubleValue], 1);
        }
        // 創(chuàng)建圖片
        image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
        if (image) {
            image.sd_imageFormat = self.class.imageFormat;
        }
    }
    
    return image;
}

接下來是編碼圖片,首先將圖片轉(zhuǎn)化為一幀一幀绑莺,然后設(shè)置用戶傳進(jìn)來的編碼質(zhì)量參數(shù),介于0-1.0之間的雙精度值惕耕,表示生成圖像數(shù)據(jù)的編碼壓縮質(zhì)量纺裁。1.0不產(chǎn)生壓縮,0.0產(chǎn)生可能的最大壓縮司澎。如果沒有提供欺缘,使用1.0。(NSNumber)惭缰,然后設(shè)置用戶傳進(jìn)來的編碼背景顏色一個UIColor(NSColor)值用于非alpha圖像編碼當(dāng)輸入圖像有alpha通道時浪南,背景顏色將被用來組成alpha通道。如果沒有漱受,使用白色络凿。然后設(shè)置圖片下載完編碼的最大大小,NSUInteger值指定編碼后的最大輸出數(shù)據(jù)字節(jié)大小昂羡。一些有損格式絮记,如JPEG/HEIF支持提示編解碼器,以自動降低質(zhì)量和匹配的文件大小虐先,你想要怨愤。注意,這個選項將覆蓋' SDImageCoderEncodeCompressionQuality '蛹批,因為現(xiàn)在質(zhì)量是由編碼器決定的撰洗。(NSNumber)篮愉,由于壓縮算法的限制,不能保證輸出大小差导。這個選項對矢量圖像不起作用试躏。然后就是根據(jù)所有的這些屬性,去編碼一幀一幀圖片

- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
    if (!image) {
        return nil;
    }
    CGImageRef imageRef = image.CGImage;
    if (!imageRef) {
        // Earily return, supports CGImage only
        return nil;
    }
    
    if (format != self.class.imageFormat) {
        return nil;
    }
    
    NSMutableData *imageData = [NSMutableData data];
    // 獲取圖片原生類型字符串
    CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
    // 將圖片轉(zhuǎn)化為動圖數(shù)組
    NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
    
    // Create an image destination. Animated Image does not support EXIF image orientation TODO
    // The `CGImageDestinationCreateWithData` will log a warning when count is 0, use 1 instead.
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count ?: 1, NULL);
    if (!imageDestination) {
        // Handle failure.
        return nil;
    }
    NSMutableDictionary *properties = [NSMutableDictionary dictionary];
    // Encoding Options
    //介于0-1.0之間的雙精度值设褐,表示生成圖像數(shù)據(jù)的編碼壓縮質(zhì)量颠蕴。1.0不產(chǎn)生壓縮,0.0產(chǎn)生可能的最大壓縮助析。如果沒有提供犀被,使用1.0。(NSNumber)
    double compressionQuality = 1;
    if (options[SDImageCoderEncodeCompressionQuality]) {
        compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
    }
    properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
    // 一個UIColor(NSColor)值用于非alpha圖像編碼當(dāng)輸入圖像有alpha通道時外冀,背景顏色將被用來組成alpha通道寡键。如果沒有,使用白色锥惋。
    CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
    if (backgroundColor) {
        properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
    }
    CGSize maxPixelSize = CGSizeZero;
    NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
    if (maxPixelSizeValue != nil) {
#if SD_MAC
        maxPixelSize = maxPixelSizeValue.sizeValue;
#else
        maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
    }
    NSUInteger pixelWidth = CGImageGetWidth(imageRef);
    NSUInteger pixelHeight = CGImageGetHeight(imageRef);
    CGFloat finalPixelSize = 0;
    if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) {
        CGFloat pixelRatio = pixelWidth / pixelHeight;
        CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
        if (pixelRatio > maxPixelSizeRatio) {
            finalPixelSize = maxPixelSize.width;
        } else {
            finalPixelSize = maxPixelSize.height;
        }
        properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
    }
    // 圖片下載完編碼的最大大小
    // NSUInteger值指定編碼后的最大輸出數(shù)據(jù)字節(jié)大小昌腰。一些有損格式,如JPEG/HEIF支持提示編解碼器膀跌,以自動降低質(zhì)量和匹配的文件大小,你想要固灵。注意捅伤,這個選項將覆蓋' SDImageCoderEncodeCompressionQuality '两疚,因為現(xiàn)在質(zhì)量是由編碼器決定的炉爆。(NSNumber)
    // @note這是一個提示,由于壓縮算法的限制盖袭,不能保證輸出大小仍秤。這個選項對矢量圖像不起作用熄诡。
    NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
    if (maxFileSize > 0) {
        properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
        // Remove the quality if we have file size limit
        properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
    }
    BOOL embedThumbnail = NO;
    if (options[SDImageCoderEncodeEmbedThumbnail]) {
        embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
    }
    
    /*為JPEG和HEIF啟用或禁用縮略圖嵌入。
    *值應(yīng)為kCFBooleanTrue或kCFBooleanFalse诗力。默認(rèn)值為kCFBooleanFalse */
    properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
    
    BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
    if (encodeFirstFrame || frames.count == 0) {
        // for static single images
        CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
    } else {
        // for animated images
        NSUInteger loopCount = image.sd_imageLoopCount;
        NSDictionary *containerProperties = @{
            self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)}
        };
        // container level properties (applies for `CGImageDestinationSetProperties`, not individual frames)
        CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties);
        
        for (size_t i = 0; i < frames.count; i++) {
            SDImageFrame *frame = frames[i];
            NSTimeInterval frameDuration = frame.duration;
            CGImageRef frameImageRef = frame.image.CGImage;
            properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)};
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties);
        }
    }
    // Finalize the destination.
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        // Handle failure.
        imageData = nil;
    }
    
    CFRelease(imageDestination);
    
    return [imageData copy];
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凰浮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苇本,更是在濱河造成了極大的恐慌袜茧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓣窄,死亡現(xiàn)場離奇詭異笛厦,居然都是意外死亡,警方通過查閱死者的電腦和手機俺夕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門裳凸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贱鄙,“玉大人,你說我怎么就攤上這事姨谷《耗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵菠秒,是天一觀的道長疙剑。 經(jīng)常有香客問我,道長践叠,這世上最難降的妖魔是什么言缤? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮禁灼,結(jié)果婚禮上管挟,老公的妹妹穿的比我還像新娘。我一直安慰自己弄捕,他們只是感情好僻孝,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著守谓,像睡著了一般穿铆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斋荞,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天荞雏,我揣著相機與錄音,去河邊找鬼平酿。 笑死凤优,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜈彼。 我是一名探鬼主播筑辨,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幸逆!你這毒婦竟也來了棍辕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤秉颗,失蹤者是張志新(化名)和其女友劉穎痢毒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚕甥,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡哪替,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了菇怀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凭舶。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡晌块,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帅霜,到底是詐尸還是另有隱情匆背,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布身冀,位于F島的核電站钝尸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏搂根。R本人自食惡果不足惜珍促,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剩愧。 院中可真熱鬧猪叙,春花似錦、人聲如沸仁卷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锦积。三九已至芒帕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丰介,已是汗流浹背副签。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留基矮,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓冠场,卻偏偏與公主長得像家浇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碴裙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353