123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDImageIOAnimatedCoder.h"
- #import "NSImage+Compatibility.h"
- #import "UIImage+Metadata.h"
- #import "NSData+ImageContentType.h"
- #import "SDImageCoderHelper.h"
- #import "SDAnimatedImageRep.h"
- #import "UIImage+ForceDecode.h"
- // Specify DPI for vector format in CGImageSource, like PDF
- static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
- // Specify File Size for lossy format encoding, like JPEG
- static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
- @interface SDImageIOCoderFrame : NSObject
- @property (nonatomic, assign) NSUInteger index; // Frame index (zero based)
- @property (nonatomic, assign) NSTimeInterval duration; // Frame duration in seconds
- @end
- @implementation SDImageIOCoderFrame
- @end
- @implementation SDImageIOAnimatedCoder {
- size_t _width, _height;
- CGImageSourceRef _imageSource;
- NSData *_imageData;
- CGFloat _scale;
- NSUInteger _loopCount;
- NSUInteger _frameCount;
- NSArray<SDImageIOCoderFrame *> *_frames;
- BOOL _finished;
- BOOL _preserveAspectRatio;
- CGSize _thumbnailSize;
- }
- - (void)dealloc
- {
- if (_imageSource) {
- CFRelease(_imageSource);
- _imageSource = NULL;
- }
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- - (void)didReceiveMemoryWarning:(NSNotification *)notification
- {
- if (_imageSource) {
- for (size_t i = 0; i < _frameCount; i++) {
- CGImageSourceRemoveCacheAtIndex(_imageSource, i);
- }
- }
- }
- #pragma mark - Subclass Override
- + (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];
- }
- #pragma mark - Utils
- + (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
- NSUInteger loopCount = self.defaultLoopCount;
- NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
- NSDictionary *containerProperties = imageProperties[self.dictionaryProperty];
- if (containerProperties) {
- 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 {
- // 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 *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(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
- 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;
- }
- #pragma mark - Decode
- - (BOOL)canDecodeFromData:(nullable NSData *)data {
- return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
- }
- - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
- if (!data) {
- return nil;
- }
- 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
-
- CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
- if (!source) {
- return nil;
- }
- size_t count = CGImageSourceGetCount(source);
- UIImage *animatedImage;
-
- BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
- if (decodeFirstFrame || count <= 1) {
- 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;
- }
- #pragma mark - Progressive Decode
- - (BOOL)canIncrementalDecodeFromData:(NSData *)data {
- return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
- }
- - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
- self = [super init];
- if (self) {
- 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
- CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
-
- 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;
- }
- #pragma mark - Encode
- - (BOOL)canEncodeToFormat:(SDImageFormat)format {
- return (format == self.class.imageFormat);
- }
- - (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
- double compressionQuality = 1;
- if (options[SDImageCoderEncodeCompressionQuality]) {
- compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
- }
- properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
- 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 > 0 && pixelHeight > 0) {
- 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 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];
- }
- 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];
- }
- #pragma mark - SDAnimatedImageCoder
- - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options {
- if (!data) {
- return nil;
- }
- self = [super init];
- if (self) {
- CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
- if (!imageSource) {
- return nil;
- }
- BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource];
- if (!framesValid) {
- CFRelease(imageSource);
- return nil;
- }
- 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;
- _imageSource = imageSource;
- _imageData = data;
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- return self;
- }
- - (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
- if (!imageSource) {
- return NO;
- }
- NSUInteger frameCount = CGImageSourceGetCount(imageSource);
- NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
- NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray array];
-
- for (size_t i = 0; i < frameCount; i++) {
- SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
- frame.index = i;
- frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
- [frames addObject:frame];
- }
-
- _frameCount = frameCount;
- _loopCount = loopCount;
- _frames = [frames copy];
-
- return YES;
- }
- - (NSData *)animatedImageData {
- return _imageData;
- }
- - (NSUInteger)animatedImageLoopCount {
- return _loopCount;
- }
- - (NSUInteger)animatedImageFrameCount {
- return _frameCount;
- }
- - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
- if (index >= _frameCount) {
- return 0;
- }
- return _frames[index].duration;
- }
- - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
- if (index >= _frameCount) {
- return nil;
- }
- // Animated Image should not use the CGContext solution to force decode. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961
- NSDictionary *options = @{
- (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
- (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
- };
- UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:options];
- if (!image) {
- return nil;
- }
- image.sd_imageFormat = self.class.imageFormat;
- image.sd_isDecoded = YES;;
- return image;
- }
- @end
|