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

SDWebImageImageIOCoder :内置编码器,支持PNG,JPEG,TIFF,支持逐行解码。该类遵守 SDWebImageProgressiveCoder协议。该协议也遵守SDWebImageCoder协议,所以这个类要实现这两个协议的方法。

GIF
还支持静态GIF(意思是只处理第一帧)。
要获得完整的GIF支持,我们建议使用FLAnimatedImage或性能较差的SDWebImageGIFCoder

HEIC
该编码器还支持HEIC格式,因为ImageIO本身支持它。但这取决于系统功能,因此无法在所有设备上运行。

私有静态常量

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
//每像素字节数为4
static const size_t kBytesPerPixel = 4;
//每位字节数为8
static const size_t kBitsPerComponent = 8;

/*
*设置标志“SDWebImageScaleDownLargeImages”时,解码图像的最大大小(以MB为单位)
* iPad1和iPhone 3GS的建议值:60。
* iPad2和iPhone 4的建议值:120。
* iPhone 3G和iPod 2及早期设备的建议值:30。
*/
static const CGFloat kDestImageSizeMB = 60.0f;

/*
*当设置标志“SDWebImageScaleDownLargeImages”时,定义用于解码图像的图块的最大大小(以MB为单位)
* iPad1和iPhone 3GS的建议值:20。
* iPad2和iPhone 4的建议值:40。
* iPhone 3G和iPod 2及早期设备的建议值:10。
*/
static const CGFloat kSourceImageTileSizeMB = 20.0f;

//定义 1M = 1024.0f * 1024.0f
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;

//定义1MB的像素数是1MB的Bytes数量除以1像素的Bytes数
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
//定义解码完的图像的最大像素总数数是解码完的图像最size乘以1MB的像素数
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;

//定义用于解码的图像的最大像素总数是解码的图像的size乘以1MB的像素数
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;

//定义重叠像素大小为2像素
static const CGFloat kDestSeemOverlap = 2.0f;

私有变量

1
2
3
4
5
6
7
8
        //定义了两个变量来保存图像的宽和高
size_t _width, _height;
#if SD_UIKIT || SD_WATCH
//定义变量记录图片方向
UIImageOrientation _orientation;
#endif
//定义变量记录图片源
CGImageSourceRef _imageSource;

life cycle

单例方法和dealloc方法

C 语言不能够调用OC中的retain与release,一般的C 语言接口都提供了release函数(比如CGContextRelease(context c))来管理内存。ARC不会自动调用这些C接口的函数,所以这还是需要我们自己来进行管理的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)dealloc {
//如果CGImageSourceRef类型的_imageSource存在,在dealloc方法中手动释放掉(CGXXX 和CFXXX方法,ARC并不会自动释放,需要手动去释放掉)

if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}

//单例方法
+ (instancetype)sharedCoder {
static SDWebImageImageIOCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageImageIOCoder alloc] init];
});
return coder;
}

解码

SDWebImageCoder协议方法

判断是否支持图片数据的解码

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)canDecodeFromData:(nullable NSData *)data {
//通过NSData中的扩展方法:sd_imageFormatForImageData 获取图片的格式
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// 不支持WebP格式图片解码
return NO;
case SDImageFormatHEIC:
// 检查是否支持HEIC格式图片解码
return [[self class] canDecodeFromHEICFormat];
default:
return YES;
}
}

通过图片数据(ImageData)解码获取图片

1
2
3
4
5
6
7
8
9
10
11
12
13
- (UIImage *)decodedImageWithData:(NSData *)data {
//如果data为空,返回nil
if (!data) {
return nil;
}

//将imagedata转为图片
UIImage *image = [[UIImage alloc] initWithData:data];
//设置图片的格式
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];

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
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
使用原始图片和图片数据解压缩图片。

@param image 需要压缩的原图片
@param data 指向原始图像数据的指针。 指针本身是非空的,但图像数据可以为空。 如果需要,此数据将设置为缓存。 如果您不需要同时修改数据,请忽略此参数。
@param optionsDict 一个包含任何解压缩选项的字典。 通过{SDWebImageCoderScaleDownLargeImagesKey:@(YES)}缩小大图像
@return 解压缩后的图片
*/
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {

//如果是mac,直接返回图片
#if SD_MAC
return image;
#endif

//如果是iOS 、osTV、osWatch
#if SD_UIKIT || SD_WATCH
// 创建变量保存,默认是不缩小
BOOL shouldScaleDown = NO;

// 如果传入选项字典参数
if (optionsDict != nil) {
//创建临时变量保存选项值
NSNumber *scaleDownLargeImagesOption = nil;
//SDWebImageCoderScaleDownLargeImagesKey对应的value类型为NSNumber,则保存value
if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
}
//如果选项值不为nil,转换成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];
//通过缩小的图片和图片格式编码获取imagedata
NSData *imageData = [self encodedDataWithImage:scaledDownImage format:format];
if (imageData) {
// 通过参数回传处理后的图像数据
*data = imageData;
}
}
// 返回处理后的图片
return scaledDownImage;
}
#endif
}

SDWebImageProgressiveCoder 协议方法

判断是否支持增量解码某个data数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
//获取图片的格式
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// 不支持WebP逐行解码
return NO;
case SDImageFormatHEIC:
// 检查HEIC解码兼容性
return [[self class] canDecodeFromHEICFormat];
default:
// 其他类型就返回YES
return YES;
}
}

将图片数据增量解码为图片

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
- (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);

//当我们绘制到Core Graphics时,我们会丢失方向信息,这意味着initWithCGIImage生成的图像有时会导致错误定向。 (与didCompleteWithError中的initWithData生成的图像不同。)因此将其保存在此处并稍后传递。
#if SD_UIKIT || SD_WATCH
_orientation = [SDWebImageCoderHelper imageOrientationFromEXIFOrientation:orientationValue];
#endif
}
}

if (_width + _height > 0) {
// 创建位图对象
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);

//如果存在,则根据该位图生成图片
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);
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
}
}
// 如果加载完成就释放掉位图对象,并置空
if (finished) {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}

//返回该图片
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
46
47
48
49
50
51
52
53
54
55
56
#if SD_UIKIT || SD_WATCH
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {

//如果图片不需要解码,直接返回image
if (![[self class] shouldDecodeImage:image]) {
return image;
}

//自动释放位图上下文和所有变量以帮助系统在存在内存警告时释放内存。
//在iOS7上,别忘了调用[[SDImageCache sharedImageCache] clearMemory];

// 建立自动释放池,以帮助系统在收到内存警告时释放内存。
@autoreleasepool{

//获取图片对象的位图图片
CGImageRef imageRef = image.CGImage;
// 获取图片对象的色彩空间
CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
// iOS 显示 Alpha(透明)信息 (BRGA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;

size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);


// CGBitmapContextCreate不支持kCGImageAlphaNone。由于此处的原始图像没有alpha信息,因此使用kCGImageAlphaNoneSkipLast创建没有alpha信息的位图图形上下文。
//创建位图图形上下文
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
//如果创建失败,直接返回image
if (context == NULL) {
return image;
}

// 将图像绘制到上下文中并检索没有alpha的新位图图像
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;
}
}

//解压并按比例缩小图片

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
//如果图片不需要解码,直接返回image
if (![[self class] shouldDecodeImage:image]) {
return image;
}

//如果图片不需要缩小,直接执行解压图片操作
if (![[self class] shouldScaleDownImage:image]) {
return [self sd_decompressedImageWithImage:image];
}

// 创建图像上下文
CGContextRef destContext;

//自动释放位图上下文和所有变量以帮助系统在存在内存警告时释放内存。
//在iOS7上,别忘了调用[[SDImageCache sharedImageCache] clearMemory];

// 建立自动释放池,以帮助系统在收到内存警告时释放内存
@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 = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(sourceImageRef);
// iOS 显示 Alpha(透明)信息 (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;

// CGBitmapContextCreate不支持kCGImageAlphaNone。由于此处的原始图像没有alpha信息,因此使用kCGImageAlphaNoneSkipLast创建没有alpha信息的位图图形上下文。
//创建位图图形上下文
destContext = CGBitmapContextCreate(NULL,
destResolution.width,
destResolution.height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);

//如果创建失败,直接返回image
if (destContext == NULL) {
return image;
}
// 设置图像上下文的绘图质量
CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);

//现在定义用于从输入图像到输出图像的增量blit的矩形的大小。由于iOS从磁盘检索图像数据的方式,我们使用的源块宽度等于源图像的宽度。 iOS必须以全宽“波段”从磁盘解码图像,即使当前图形上下文被剪切为该波段内的子图形也是如此。 因此,我们通过将我们的图块大小设置为输入图像的整个宽度来充分利用由解码操作产生的所有像素数据。

// 创建临时变量保存来源块大小
CGRect sourceTile = CGRectZero;
// 来源块的宽度就是原图的宽度
sourceTile.size.width = sourceResolution.width;

// 源图块高度是动态的。 由于我们以MB为单位指定了源块的大小,因此请查看输入图像宽度可以为多少像素行。
// 计算来源块的高度
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
// 设置来源块的X坐标
sourceTile.origin.x = 0.0f;

//输出图块与输入图块的比例相同,但缩放为图像比例。
// 创建历史变量保存目标块大小
CGRect destTile;

// 目标块的宽是缩放后的宽
destTile.size.width = destResolution.width;

// 目标块的宽是来源块的高乘以缩放比
destTile.size.height = sourceTile.size.height * imageScale;

// 设置目标块的X坐标
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;

// 如果有剩余像素就将绘制次数加1
if(remainder) {
iterations++;
}
// 添加似乎与切片重叠,但保存原始切片高度以进行y坐标计算。
// 创建变量保存来源块的高度,用来计算纵坐标的移动
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;
}
}
#endif

编码

判断某个图片格式是否支持编码

1
2
3
4
5
6
7
8
9
10
11
12
13
//如果是WebP格式,则不支持编码;HEIC格式:根据canEncodeToHEICFormat方法判断是否支持编码;其他格式都支持编码
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
case SDImageFormatWebP:
// 不支持WebP格式编码
return NO;
case SDImageFormatHEIC:
// 检查HEIC编码兼容性
return [[self class] canEncodeToHEICFormat];
default:
return YES;
}
}

将图片根据给定的格式解码成图片数据(imageData)

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
- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
//如果图片不存在,返回nil
if (!image) {
return nil;
}

//如果格式类型未识别,判断图片是否含有Alpha通道,如果有则定义为PNG格式,否则定义为JPEG格式
if (format == SDImageFormatUndefined) {
BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}

NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];

// 根据imageData 和 CFStringRef 创建 CGImageDestinationRef对象,如果创建失败,返回nil
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 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];
}

私有方法(辅助方法)

  1. 是否需要解压图片
    (如果图片为动图或不存在,不解压图片,否则解压图片)
1
2
3
4
5
6
7
8
9
10
11
12
13
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
//如果图片为空,返回NO
if (image == nil) {
return NO;
}

// 如果图片为动图,返回NO
if (image.images != nil) {
return NO;
}
否则返回YES
return YES;
}
  1. 是否支持HEIC格式类型的图片解码
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
+ (BOOL)canDecodeFromHEICFormat {
//临时静态变量 canDecode 为 NO
static BOOL canDecode = NO;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

//忽略-Wunguarded-availability警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"

//如果是iWatch 或者 SIMULATOR ,则不支持
#if TARGET_OS_SIMULATOR || SD_WATCH //iWatch
canDecode = NO;

#elif SD_MAC //mac

//获取进程信息对象
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
//通过进程信息对象获取操作系统版本。判断macOS系统版本10.13+,则支持HEIC类型图像的解码
canDecode = processInfo.operatingSystemVersion.minorVersion >= 13;
} else {
canDecode = NO;
}
#elif SD_UIKIT //iOS 和 tvOS
//获取进程信息对象
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
//通过进程信息对象获取操作系统版本。判断操作系统版本是iOS 11+就支持HEIC类型图像的解码
canDecode = processInfo.operatingSystemVersion.majorVersion >= 11;
} else {
canDecode = NO;
}
#endif
#pragma clang diagnostic pop
});
return canDecode;
}
  1. 是否支持HEIC格式类型的图片编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ (BOOL)canEncodeToHEICFormat {
//临时静态变量 canDecode 为 NO
static BOOL canEncode = NO;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatHEIC];

// 创建一个CGImageDestinationRef对象,如果该对象可以创建成功,则需要编码,否则不需要
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
if (!imageDestination) {
// Can't encode to HEIC
canEncode = NO;
} else {
// Can encode to HEIC
CFRelease(imageDestination);
canEncode = YES;
}
});
return canEncode;
}
  1. 是否应该缩小图片
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
#if SD_UIKIT || SD_WATCH
+ (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;
}
#endif