1. SDImageIOCoder
/ * *
內(nèi)置編碼器埂奈,支持PNG, JPEG, TIFF,包括支持漸進(jìn)解碼较雕。
GIF
也支持靜態(tài)GIF(意思將只處理第一幀)结执。
如果完全支持GIF次氨,我們推薦“SDAnimatedImageView”來保持CPU和內(nèi)存的平衡蔽介。
HEIC
這個編碼器也支持HEIC格式,因為ImageIO本身就支持它煮寡。但是它取決于系統(tǒng)的能力虹蓄,所以它不能在所有的設(shè)備上工作,請參見: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 格式薇组,使用子工廠來判斷是否可以進(jìn)行解碼
`
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options
這個是解碼的方法,核心的方法是調(diào)用
SDImageIOAnimatedCoder這個類去做解碼,坐儿,包括下面的漸進(jìn)式解碼和編碼律胀,都是調(diào)用到這個類里面宋光,所以我們著重分析這個
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
相當(dāng)于父類炭菌,怎么說呢罪佳,也相當(dāng)于一個工廠,對外用戶是不知道內(nèi)部有 GIF 解碼器黑低,HEIC 解碼器等赘艳,接口只有一個解碼接口,用戶只需要調(diào)用一個解碼的接口投储,至于最后創(chuàng)建GIF工廠還是HEIC工廠第练,由這個父工廠來負(fù)責(zé)創(chuàng)建決定,所以你會發(fā)現(xiàn)
@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;
}
就是你需要告訴我父工廠,你是什么類型的解碼器呕寝,當(dāng)前解碼器的一些屬性勋眯,這些父類不負(fù)責(zé)指定,延遲到子類下梢,下面繼續(xù)看代碼就明白了客蹋,比如循環(huán)次數(shù)時間的屬性獲取,每一種類型都有自己的屬性名字,看下面的兩個方法就明白了
// 獲取循環(huán)次數(shù)
+ (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
// 默認(rèn)循環(huán)次數(shù)孽江,父類不指定讶坯,子類會指定一個值,默認(rèn)為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;
}
創(chuàng)建某一個位置的幀圖片
// 創(chuàng)建某一個位置的幀圖片
+ (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);
//獲取所支持的圖片格式數(shù)組
//CFArrayRef __nonnull CGImageSourceCopyTypeIdentifiers(void);
//獲取CGImageSource對象的圖片格式
//CFStringRef __nullable CGImageSourceGetType(CGImageSourceRef __nonnull isrc);
//獲取CGImageSource中的圖片張數(shù) 不包括縮略圖
//size_t CGImageSourceGetCount(CGImageSourceRef __nonnull isrc);
//獲取CGImageSource的文件信息
/*
字典參數(shù)可配置的鍵值對與創(chuàng)建CGImageSource所傳參數(shù)意義一致
返回的字典中的鍵值意義后面介紹
*/
//CFDictionaryRef __nullable CGImageSourceCopyProperties(CGImageSourceRef __nonnull isrc, CFDictionaryRef __nullable options);
//獲取CGImageSource中某個圖像的附加數(shù)據(jù)
/*
index參數(shù)設(shè)置獲取第幾張圖像 options參數(shù)可配置的鍵值對與創(chuàng)建CGImageSource所傳參數(shù)意義一致
返回的字典中的鍵值意義后面介紹
*/
//CFDictionaryRef __nullable CGImageSourceCopyPropertiesAtIndex(CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//獲取圖片的元數(shù)據(jù)信息 CGImageMetadataRef類是圖像原數(shù)據(jù)的抽象
//CGImageMetadataRef __nullable CGImageSourceCopyMetadataAtIndex (CGImageSourceRef __nonnull isrc, size_t index, CFDictionaryRef __nullable options);
//獲取CGImageSource中的圖片數(shù)據(jù)
//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);
//創(chuàng)建一個空的CGImageSource容器这刷,逐步加載大圖片
//CGImageSourceRef __nonnull CGImageSourceCreateIncremental(CFDictionaryRef __nullable options);
//使用新的數(shù)據(jù)更新CGImageSource容器
//void CGImageSourceUpdateData(CGImageSourceRef __nonnull isrc, CFDataRef __nonnull data, bool final);
//更新數(shù)據(jù)提供器來填充CGImageSource容器
//void CGImageSourceUpdateDataProvider(CGImageSourceRef __nonnull isrc, CGDataProviderRef __nonnull provider, bool final);
//獲取當(dāng)前CGImageSource的狀態(tài)
/*
CGImageSourceStatus枚舉意義:
typedef CF_ENUM(int32_t, CGImageSourceStatus) {
kCGImageStatusUnexpectedEOF = -5, //文件結(jié)尾出錯
kCGImageStatusInvalidData = -4, //數(shù)據(jù)無效
kCGImageStatusUnknownType = -3, //未知的圖片類型
kCGImageStatusReadingHeader = -2, //讀標(biāo)題過程中
kCGImageStatusIncomplete = -1, //操作不完整
kCGImageStatusComplete = 0 //操作完整
};
*/
//CGImageSourceStatus CGImageSourceGetStatus(CGImageSourceRef __nonnull isrc);
//同上婉烟,獲取某一個圖片的狀態(tài)
//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 {
// 設(shè)置縮略圖是否進(jìn)行變換
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
CGFloat maxPixelSize;
// 如果設(shè)置了變換
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);
}
// 設(shè)置縮略圖的最大尺寸
decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
// 無論如何都設(shè)置縮略圖
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES);
// 創(chuàng)建某個位置的縮略圖
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;
}
解碼圖片暇屋,解碼圖片其實就是從用戶傳的屬性中獲取縮放因子似袁,是否保持長寬比,縮略圖大小咐刨,是否只解碼第一幀昙衅,然后獲取當(dāng)前imageSouce所有的圖片數(shù)量,如果只解碼第一張圖片或者圖片數(shù)量就是一張定鸟,那么直接調(diào)用上面的創(chuàng)建一幀圖片就可以了而涉,如果是多張圖片如gif,那么就循環(huán)創(chuàng)建
// 解碼圖片
- (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
if (!data) {
return nil;
}
// 設(shè)置縮放因子仔粥,默認(rèn)為1
CGFloat scale = 1;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
// 設(shè)置縮略圖大小
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
// 是否保持長寬比
BOOL preserveAspectRatio = YES;
// 根據(jù)用戶設(shè)置
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創(chuàng)建source
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
// 獲取圖片數(shù)量
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
// 是否只解碼第一幀
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
// 如果只解碼第一幀或者圖片數(shù)量<=1婴谱,那么就直接獲取第一幀圖片就可以了
if (decodeFirstFrame || count <= 1) {
// 創(chuàng)建一幀圖片蟹但,根據(jù)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];
// 創(chuàng)建每一幀圖片
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
}
NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
// 根據(jù)幀組創(chuàng)建一個動圖
animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
animatedImage.sd_imageLoopCount = loopCount;
}
animatedImage.sd_imageFormat = self.class.imageFormat;
CFRelease(source);
return animatedImage;
}
下面是創(chuàng)建漸進(jìn)式圖片加載,圖片可以邊下載邊加載更新
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
}
// 創(chuàng)建一個漸進(jìn)式圖片加載
// https://cloud.tencent.com/developer/article/1186094
// https://juejin.im/post/5d9d7dbef265da5b9764be3c
// 在下載圖片的時候瘟裸,可以漸進(jìn)式下載圖片客叉,避免內(nèi)存高峰
- (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
self = [super init];
if (self) {
/*
設(shè)置一個預(yù)期的圖片文件格式,需要設(shè)置為字符串類型的值
*/
//const CFStringRef kCGImageSourceTypeIdentifierHint;
/*
設(shè)置是否以解碼的方式讀取圖片數(shù)據(jù) 默認(rèn)為kCFBooleanTrue
如果設(shè)置為true话告,在讀取數(shù)據(jù)時就進(jìn)行解碼 如果為false 則在渲染時才進(jìn)行解碼
*/
//const CFStringRef kCGImageSourceShouldCache;
/*
返回CGImage對象時是否允許使用浮點值 默認(rèn)為kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceShouldAllowFloa;
/*
設(shè)置如果不存在縮略圖則創(chuàng)建一個縮略圖兼搏,縮略圖的尺寸受開發(fā)者設(shè)置影響,如果不設(shè)置尺寸極限沙郭,則為圖片本身大小
默認(rèn)為kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceCreateThumbnailFromImageIfAbsent;
/*
設(shè)置是否創(chuàng)建縮略圖佛呻,無論原圖像有沒有包含縮略圖kCFBooleanFalse
*/
//const CFStringRef kCGImageSourceCreateThumbnailFromImageAlways;
/*
//設(shè)置縮略圖的寬高尺寸 需要設(shè)置為CFNumber值
*/
//const CFStringRef kCGImageSourceThumbnailMaxPixelSize;
/*
設(shè)置縮略圖是否進(jìn)行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;
}
// 在下載的過程中,不斷的更新數(shù)據(jù)
- (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 更新數(shù)據(jù)病线,要注意的是這里需要每次傳入全部圖片數(shù)據(jù)吓著,而非增量圖片數(shù)據(jù)
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
// 如果 _width 和 _height 為0,則需要從 _imageSource 中獲取一次數(shù)據(jù)
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);
}
// 創(chuàng)建圖片
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;
}
接下來是編碼圖片,首先將圖片轉(zhuǎn)化為一幀一幀绑莺,然后設(shè)置用戶傳進(jìn)來的編碼質(zhì)量參數(shù),介于0-1.0之間的雙精度值惕耕,表示生成圖像數(shù)據(jù)的編碼壓縮質(zhì)量纺裁。1.0不產(chǎn)生壓縮,0.0產(chǎn)生可能的最大壓縮司澎。如果沒有提供欺缘,使用1.0。(NSNumber)惭缰,然后設(shè)置用戶傳進(jìn)來的編碼背景顏色一個UIColor(NSColor)值用于非alpha圖像編碼當(dāng)輸入圖像有alpha通道時浪南,背景顏色將被用來組成alpha通道。如果沒有漱受,使用白色络凿。然后設(shè)置圖片下載完編碼的最大大小,NSUInteger值指定編碼后的最大輸出數(shù)據(jù)字節(jié)大小昂羡。一些有損格式絮记,如JPEG/HEIF支持提示編解碼器,以自動降低質(zhì)量和匹配的文件大小虐先,你想要怨愤。注意,這個選項將覆蓋' SDImageCoderEncodeCompressionQuality '蛹批,因為現(xiàn)在質(zhì)量是由編碼器決定的撰洗。(NSNumber)篮愉,由于壓縮算法的限制,不能保證輸出大小差导。這個選項對矢量圖像不起作用试躏。然后就是根據(jù)所有的這些屬性,去編碼一幀一幀圖片
- (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];
// 將圖片轉(zhuǎn)化為動圖數(shù)組
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之間的雙精度值设褐,表示生成圖像數(shù)據(jù)的編碼壓縮質(zhì)量颠蕴。1.0不產(chǎn)生壓縮,0.0產(chǎn)生可能的最大壓縮助析。如果沒有提供犀被,使用1.0。(NSNumber)
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
// 一個UIColor(NSColor)值用于非alpha圖像編碼當(dāng)輸入圖像有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值指定編碼后的最大輸出數(shù)據(jù)字節(jié)大小昌腰。一些有損格式,如JPEG/HEIF支持提示編解碼器膀跌,以自動降低質(zhì)量和匹配的文件大小,你想要固灵。注意捅伤,這個選項將覆蓋' SDImageCoderEncodeCompressionQuality '两疚,因為現(xiàn)在質(zhì)量是由編碼器決定的炉爆。(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啟用或禁用縮略圖嵌入。
*值應(yīng)為kCFBooleanTrue或kCFBooleanFalse诗力。默認(rèn)值為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];
}