原始碼閱讀:SDWebImage(八)——SDWebImageGIFCoder

堯少羽發表於2018-06-22

該文章閱讀的SDWebImage的版本為4.3.3。

這個類和SDWebImageImageIOCoder類一樣,都是編解碼器類,只不過這個類是針對GIF格式的影像。

首先可以看到,這個類只遵守並實現了SDWebImageCoder協議,說明這個類不支援GIF格式的影像的逐行解碼功能。

1.公共方法

/**
 獲取該類的單例物件
 */
+ (nonnull instancetype)sharedCoder;
複製程式碼
+ (instancetype)sharedCoder {
    static SDWebImageGIFCoder *coder;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coder = [[SDWebImageGIFCoder alloc] init];
    });
    return coder;
}
複製程式碼

2.私有方法

/**
 獲取動圖每一幀的持續時間
 */
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
    // 建立變數儲存持續時間,預設0.1秒
    float frameDuration = 0.1f;
    // 獲取影像源中指定位置的影像屬性
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
    if (!cfFrameProperties) {
        // 如果沒有獲取到就直接返回預設持續時間
        return frameDuration;
    }
    
    // 獲取影像屬性中的GIF屬性字典
    NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
    NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
    
    // 從GIF屬性字典中獲取當幀圖片的持續時間
    NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
    if (delayTimeUnclampedProp != nil) {
        frameDuration = [delayTimeUnclampedProp floatValue];
    } else {
        // 如果通過上面那個key沒獲取到,就通過下面這個key獲取當幀圖片的持續時間
        NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
        if (delayTimeProp != nil) {
            frameDuration = [delayTimeProp floatValue];
        }
    }
    
    // 如果獲取到的持續時間小於11毫秒,就設定成100毫秒
    if (frameDuration < 0.011f) {
        frameDuration = 0.100f;
    }
    
    // 釋放屬性物件
    CFRelease(cfFrameProperties);
    // 返回持續時間
    return frameDuration;
}
複製程式碼

3.SDWebImageCoder協議方法實現

  • 解碼
- (BOOL)canDecodeFromData:(nullable NSData *)data {
    // 如果是GIF格式的圖片就返回YES
    return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
}
複製程式碼
- (UIImage *)decodedImageWithData:(NSData *)data {
    // 如果沒有資料就返回空
    if (!data) {
        return nil;
    }
    
#if SD_MAC
    SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
    NSImage *animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
    [animatedImage addRepresentation:imageRep];
    return animatedImage;
#else
    
    // 生成影像源
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
    // 如果沒生成就返回空
    if (!source) {
        return nil;
    }
    // 獲取子圖片數量
    size_t count = CGImageSourceGetCount(source);
    
    // 建立變數儲存動圖物件
    UIImage *animatedImage;
    
    if (count <= 1) {
        // 如果子圖片數量不超過1張就直接生成圖片物件
        animatedImage = [[UIImage alloc] initWithData:data];
    } else {
        // 建立可變陣列儲存SDWebImageFrame物件
        NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
        
        // 遍歷子圖片物件,並將其包裝成SDWebImageFrame物件
        for (size_t i = 0; i < count; i++) {
            // 獲取指定幀數的相點陣圖
            CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
            // 如果沒獲取到就跳過進入下次迴圈
            if (!imageRef) {
                continue;
            }
            
            // 獲取指定幀數的持續時間
            float duration = [self sd_frameDurationAtIndex:i source:source];
            // 根據相點陣圖生成圖片物件
            UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
            // 釋放相點陣圖
            CGImageRelease(imageRef);
            
            // 將一幀的資訊封裝成SDWebImageFrame物件
            SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration];
            // 將封裝好的SDWebImageFrame物件新增到陣列中儲存
            [frames addObject:frame];
        }
        
        // 建立變數儲存迴圈次數
        NSUInteger loopCount = 1;
        // 獲取到影像屬性
        NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
        // 獲取到GIF相關的影像屬性
        NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
        if (gifProperties) {
            // 獲取GIF迴圈次數
            NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount];
            if (gifLoopCount != nil) {
                // 儲存迴圈次數
                loopCount = gifLoopCount.unsignedIntegerValue;
            }
        }
        
        // 利用封裝好的SDWebImageFrame物件陣列生成動圖物件
        animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
        // 設定動圖物件的迴圈次數
        animatedImage.sd_imageLoopCount = loopCount;
    }
    
    // 釋放影像源
    CFRelease(source);
    
    // 返回動圖物件
    return animatedImage;
#endif
}
複製程式碼
- (UIImage *)decompressedImageWithImage:(UIImage *)image
                                   data:(NSData *__autoreleasing  _Nullable *)data
                                options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
    // GIF型別圖片物件不解壓
    return image;
}
複製程式碼
  • 編碼
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
    // 如果是GIF格式的圖片就返回YES
    return (format == SDImageFormatGIF);
}
複製程式碼
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
    // 如果沒有圖片物件就返回空
    if (!image) {
        return nil;
    }
    
    // 如果圖片型別不是GIF格式就返回空
    if (format != SDImageFormatGIF) {
        return nil;
    }
    
    // 建立變數儲存影像資料
    NSMutableData *imageData = [NSMutableData data];
    // 獲取GIF影像格式的CFStringRef格式字串
    CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
    // 生成圖片物件的SDWebImageFrame型別元素的陣列
    NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
    
    // 建立影像目的地
    CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
    // 如果建立失敗就直接返回空
    if (!imageDestination) {
        return nil;
    }
    if (frames.count == 0) {
        如果是單幀的動圖就直接將圖片新增到影像目的地中
        CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
    } else {
        // 獲取到動圖的迴圈次數
        NSUInteger loopCount = image.sd_imageLoopCount;
        // 建立一個動圖屬性字典儲存迴圈次數
        NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
        // 為影像目的地設定屬性
        CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
        
        // 遍歷每一幀圖片
        for (size_t i = 0; i < frames.count; i++) {
            // 獲取SDWebImageFrame物件
            SDWebImageFrame *frame = frames[i];
            // 獲取每一幀的展示時間
            float frameDuration = frame.duration;
            // 獲取每一幀的點陣圖
            CGImageRef frameImageRef = frame.image.CGImage;
            // 建立一個屬性字典儲存每一幀的展示時間
            NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
            // 將點陣圖和其對應的屬性新增到影像目的地中
            CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
        }
    }
    
    // 如果編碼失敗就返回空
    if (CGImageDestinationFinalize(imageDestination) == NO) {
        imageData = nil;
    }
    
    // 釋放掉imageDestination物件
    CFRelease(imageDestination);
    
    // 返回編碼後的資料
    return [imageData copy];
}
複製程式碼

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

相關文章