SDWebImage 内部实现了一个图片解码/编码的图片编码器,用于图片的编解码、压缩。其中涉及到编解码管理(SDWebImageCodersManager)、编解码协议(SDWebImageCoderSDWebImageProgressiveCoder )、PNG/JPEG/TIFF编解码,解压缩,显示大图(SDWebImageImageIOCoder)、GIF编解码(SDWebImageGIFCoder)、WebP编解码(SDWebImageWebPCoder)以及辅助类(SDWebImageCoderHelper)。

SDWebImageImageIOCoder :内置编码器,该类遵守了 SDWebImageCoder协议,但并没有遵守 SDWebImageProgressiveCoder协议,说明该类仅针对git图片的编解码,并不支持git图片的逐行解码功能。

编码

判断图片格式是否支持编码,该类仅针对动图的操作,所以仅判断图片的格式是否为GIF格式,如果是返回YES,否则返回NO

1
2
3
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return (format == SDImageFormatGIF);
}

根据给定的图片格式将图片进行编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
//如果image不存在,返回nil
if (!image) {
return nil;
}

//该类只支持动图的编解码,如果image的格式不是GIF,返回nil
if (format != SDImageFormatGIF) {
return nil;
}

//临时变量imageData,用来保存图片数据
NSMutableData *imageData = [NSMutableData data];
// 获取GIF图像格式的CFStringRef格式字符串
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
// 生成图片对象的SDWebImageFrame类型元素的数组
NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];

// 创建图像目标。 GIF不支持EXIF图像方向
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);

//如果创建失败,返回nil
if (!imageDestination) {
// Handle failure.
return nil;
}
if (frames.count == 0) {
// 用于静态单个GIF图片(如果是单帧的动图就直接将图片添加到imageDestination中)
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
} else {
// 用于动画的GIF图片
//// 获取到动图的循环次数
NSUInteger loopCount = image.sd_imageLoopCount;

// 创建一个动图属性字典保存循环次数
NSDictionary *gifProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary: @{(__bridge 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 NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};

// 将位图和其对应的属性添加到imageDestination中
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
// 如果编码失败就返回nil
if (CGImageDestinationFinalize(imageDestination) == NO) {

imageData = nil;
}

// 释放imageDestination对象
CFRelease(imageDestination);

返回图片数据
return [imageData copy];
}

解码

判断是否支持图片数据的解码
该类仅针对动图的操作,所以仅判断图片的格式是否为GIF格式,如果是支持该图片数据解码,否则返回NO

1
2
3
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
}

将图片数据解码为图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
- (UIImage *)decodedImageWithData:(NSData *)data {
//如果数据为空,返回nil
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);
//如果图片源不存在,返回nil
if (!source) {
return nil;
}

// 获取子图片数量
size_t count = CGImageSourceGetCount(source);

// 创建临时变量,用来保存动图对象
UIImage *animatedImage;

//如果子图片个数不大于2,直接将数据转化成图片
if (count <= 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 NSString *)kCGImagePropertyGIFDictionary];

如果gifProperties存在
if (gifProperties) {
//获取GIF循环次数
NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge NSString *)kCGImagePropertyGIFLoopCount];

//如果可以转换成NSNumber类型,就将获取到的GIF循环次数赋值给loopCount
if (gifLoopCount != nil) {
loopCount = gifLoopCount.unsignedIntegerValue;
}
}

// 利用封装好的SDWebImageFrame对象数组生成动图对象
animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
// 设置动图对象的循环次数
animatedImage.sd_imageLoopCount = loopCount;
//设置动图的图片格式
animatedImage.sd_imageFormat = SDImageFormatGIF;
}

//释放图片源source
CFRelease(source);

//返回动图
return animatedImage;
#endif
}

如果执行动图的解压操作,就直接返回该图片,动图不支持解压

1
2
3
4
5
6
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
// GIF do not decompress
return image;
}

私有方法

获取动图每一帧的显示时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
//临时变量每一帧的显示时间,默认为0.1f
float frameDuration = 0.1f;

// 获取图片源中指定位置的图片属性
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
//如果获取失败,就返回默认每一帧的显示时间
if (!cfFrameProperties) {
return frameDuration;
}

获取图片属性字典
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
//从图片属性中获取gif属性字典
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];

//从git属性中获取当前帧的显示时间
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];

//如果当前帧的显示时间不为nil,赋值给frameDuration
if (delayTimeUnclampedProp != nil) {
frameDuration = [delayTimeUnclampedProp floatValue];
} else {
//如果通过key:kCGImagePropertyGIFUnclampedDelayTime 从gifProperties字典中获取不到当前帧的显示时间,则通过另一个key:kCGImagePropertyGIFDelayTime获取
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];

//如果不为空,赋值给赋值给frameDuration
if (delayTimeProp != nil) {
frameDuration = [delayTimeProp floatValue];
}
}

//许多烦人的广告指定0持续时间,以使图像尽快闪现。 我们遵循Firefox的行为,并为指定持续时间<= 10 ms的任何帧使用100 ms的持续时间。

如果当前帧显示的时间实现小于11ms,就重新设置为100ms
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}

//释放cfFrameProperties
CFRelease(cfFrameProperties);

//返回当前帧显示时间
return frameDuration;
}