1. SDImageIOCoder
/ * *
内置编码器,支持PNG, JPEG, TIFF,包括支持渐进解码。
GIF
也支持静态GIF(意思将只处理第一帧)。
如果完全支持GIF,我们推荐“SDAnimatedImageView”来保持CPU和内存的平衡。
HEIC
这个编码器也支持HEIC格式,因为ImageIO本身就支持它。但是它取决于系统的能力,所以它不能在所有的设备上工作,请参见: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 格式,使用子工厂来判断是否可以进行解码
`
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options
这个是解码的方法,核心的方法是调用
SDImageIOAnimatedCoder这个类去做解码,,包括下面的渐进式解码和编码,都是调用到这个类里面,所以我们着重分析这个
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
相当于父类,怎么说呢,也相当于一个工厂,对外用户是不知道内部有 GIF 解码器,HEIC 解码器等,接口只有一个解码接口,用户只需要调用一个解码的接口,至于最后创建GIF工厂还是HEIC工厂,由这个父工厂来负责创建决定,所以你会发现
@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;
}
就是你需要告诉我父工厂,你是什么类型的解码器,当前解码器的一些属性,这些父类不负责指定,延迟到子类,下面继续看代码就明白了,比如循环次数时间的属性获取,每一种类型都有自己的属性名字,看下面的两个方法就明白了
// 获取循环次数
+ (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
// 默认循环次数,父类不指定,子类会指定一个值,默认为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;
}
创建某一个位置的帧图片
// 创建某一个位置的帧图片
+ (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);
//获取所支持的图片格式数组
//CFArrayRef __nonnull CGImageSourceCopyTypeIdentifiers(void);
//获取CGImageSource对象的图片格式
//CFStringRef __nullable CGImageSourceGetType(CGImageSourceRef __nonnull isrc);
//获取CGImageSource中的图片张数 不包括缩略图
//size_t CGImageSourceGetCount(CGImageSourceRef __nonnull isrc);
//获取CGImageSource的文件信息
/*
字典参数可配置的键值对与创建CGImageSource所传参数意义一致
返回的字典中的键值意义后面介绍
*/
//CFDictionaryRef __nullable CGImageSourceCopyProperties(CGImageSourceRef __nonnull isrc, CFDictionaryRef __nullable options);
//获取CGImageSource中某个图像的附加数据
/*
index参数设置获取第几张图像 options参数可配置的键值对与创建CGImageSource所传参数意义一致
返回的字典中的键值意义后面介绍
*/
//CFDictionaryRef __nullable CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//获取图片的元数据信息 CGImageMetadataRef类是图像原数据的抽象
//CGImageMetadataRef __nullable CGImageSourceCopyMetadataAtIndex (CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//获取CGImageSource中的图片数据
//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);
//创建一个空的CGImageSource容器,逐步加载大图片
//CGImageSourceRef __nonnull CGImageSourceCreateIncremental(CFDictionaryRef __nullable options);
//使用新的数据更新CGImageSource容器
//void CGImageSourceUpdateData(CGImageSourceRef __nonnull isrc, CFDataRef __nonnull data, bool final);
//更新数据提供器来填充CGImageSource容器
//void CGImageSourceUpdateDataProvider(CGImageSourceRef __nonnull isrc, CGDataProviderRef __nonnull provider, bool final);
//获取当前CGImageSource的状态
/*
CGImageSourceStatus枚举意义:
typedef CF_ENUM(int32_t, CGImageSourceStatus) {
kCGImageStatusUnexpectedEOF = -5, //文件结尾出错
kCGImageStatusInvalidData = -4, //数据无效
kCGImageStatusUnknownType = -3, //未知的图片类型
kCGImageStatusReadingHeader = -2, //读标题过程中
kCGImageStatusIncomplete = -1, //操作不完整
kCGImageStatusComplete = 0 //操作完整
};
*/
//CGImageSourceStatus CGImageSourceGetStatus(CGImageSourceRef __nonnull isrc);
//同上,获取某一个图片的状态
//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 {
// 设置缩略图是否进行变换
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
CGFloat maxPixelSize;
// 如果设置了变换
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);
}
// 设置缩略图的最大尺寸
decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
// 无论如何都设置缩略图
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES);
// 创建某个位置的缩略图
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;
}
解码图片,解码图片其实就是从用户传的属性中获取缩放因子,是否保持长宽比,缩略图大小,是否只解码第一帧,然后获取当前imageSouce所有的图片数量,如果只解码第一张图片或者图片数量就是一张,那么直接调用上面的创建一帧图片就可以了,如果是多张图片如gif,那么就循环创建
// 解码图片
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) {
return nil;
}
// 设置缩放因子,默认为1
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
// 设置缩略图大小
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
// 是否保持长宽比
BOOL preserveAspectRatio = YES;
// 根据用户设置
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创建source
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
// 获取图片数量
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
// 是否只解码第一帧
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
// 如果只解码第一帧或者图片数量<=1,那么就直接获取第一帧图片就可以了
if (decodeFirstFrame || count <= 1) {
// 创建一帧图片,根据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];
// 创建每一帧图片
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
// 根据帧组创建一个动图
animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
animatedImage.sd_imageFormat = self.class.imageFormat;
CFRelease(source);
return animatedImage;
}
下面是创建渐进式图片加载,图片可以边下载边加载更新
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}
// 创建一个渐进式图片加载
// https://cloud.tencent.com/developer/article/1186094
// https://juejin.im/post/5d9d7dbef265da5b9764be3c
// 在下载图片的时候,可以渐进式下载图片,避免内存高峰
- (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
self = [super init];
if (self) {
/*
设置一个预期的图片文件格式,需要设置为字符串类型的值
*/
//const CFStringRef kCGImageSourceTypeIdentifierHint;
/*
设置是否以解码的方式读取图片数据 默认为kCFBooleanTrue
如果设置为true,在读取数据时就进行解码 如果为false 则在渲染时才进行解码
*/
//const CFStringRef kCGImageSourceShouldCache;
/*
返回CGImage对象时是否允许使用浮点值 默认为kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceShouldAllowFloa;
/*
设置如果不存在缩略图则创建一个缩略图,缩略图的尺寸受开发者设置影响,如果不设置尺寸极限,则为图片本身大小
默认为kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceCreateThumbnailFromImageIfAbsent;
/*
设置是否创建缩略图,无论原图像有没有包含缩略图kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceCreateThumbnailFromImageAlways;
/*
//设置缩略图的宽高尺寸 需要设置为CFNumber值
*/
//const CFStringRef kCGImageSourceThumbnailMaxPixelSize;
/*
设置缩略图是否进行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;
}
// 在下载的过程中,不断的更新数据
- (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 更新数据,要注意的是这里需要每次传入全部图片数据,而非增量图片数据
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
// 如果 _width 和 _height 为0,则需要从 _imageSource 中获取一次数据
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);
}
// 创建图片
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;
}
接下来是编码图片,首先将图片转化为一帧一帧,然后设置用户传进来的编码质量参数,介于0-1.0之间的双精度值,表示生成图像数据的编码压缩质量。1.0不产生压缩,0.0产生可能的最大压缩。如果没有提供,使用1.0。(NSNumber),然后设置用户传进来的编码背景颜色一个UIColor(NSColor)值用于非alpha图像编码当输入图像有alpha通道时,背景颜色将被用来组成alpha通道。如果没有,使用白色。然后设置图片下载完编码的最大大小,NSUInteger值指定编码后的最大输出数据字节大小。一些有损格式,如JPEG/HEIF支持提示编解码器,以自动降低质量和匹配的文件大小,你想要。注意,这个选项将覆盖' SDImageCoderEncodeCompressionQuality ',因为现在质量是由编码器决定的。(NSNumber),由于压缩算法的限制,不能保证输出大小。这个选项对矢量图像不起作用。然后就是根据所有的这些属性,去编码一帧一帧图片
- (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];
// 将图片转化为动图数组
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之间的双精度值,表示生成图像数据的编码压缩质量。1.0不产生压缩,0.0产生可能的最大压缩。如果没有提供,使用1.0。(NSNumber)
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
// 一个UIColor(NSColor)值用于非alpha图像编码当输入图像有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值指定编码后的最大输出数据字节大小。一些有损格式,如JPEG/HEIF支持提示编解码器,以自动降低质量和匹配的文件大小,你想要。注意,这个选项将覆盖' SDImageCoderEncodeCompressionQuality ',因为现在质量是由编码器决定的。(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启用或禁用缩略图嵌入。
*值应为kCFBooleanTrue或kCFBooleanFalse。默认值为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];
}