源码阅读:SDWebImage(八)——SDWebImageGIFCoder

SDWebImage GIF 解码
本文详细解析了 SDWebImage 中 SDWebImageGIFCoder 类的实现原理,介绍了如何处理 GIF 图像的逐帧解码及循环播放。通过源码分析,展示了 GIF 动图的加载、解码和编码过程。

该文章阅读的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值