diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h index eeaeb695..17b7e546 100644 --- a/SDWebImage/Core/SDImageCoder.h +++ b/SDWebImage/Core/SDImageCoder.h @@ -9,6 +9,7 @@ #import #import "SDWebImageCompat.h" #import "NSData+ImageContentType.h" +#import "SDImageFrame.h" typedef NSString * SDImageCoderOption NS_STRING_ENUM; typedef NSDictionary SDImageCoderOptions; @@ -171,7 +172,8 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext /** Encode the image to image data. - @note This protocol may supports encode animated image frames. You can use `+[SDImageCoderHelper framesFromAnimatedImage:]` to assemble an animated image with frames. + @note This protocol may supports encode animated image frames. You can use `+[SDImageCoderHelper framesFromAnimatedImage:]` to assemble an animated image with frames. But this consume time is not always reversible. In 5.15.0, we introduce `encodedDataWithFrames` API for better animated image encoding. Use that instead. + @note Which means, this just forward to `encodedDataWithFrames([SDImageFrame(image: image, duration: 0], image.sd_imageLoopCount))` @param image The image to be encoded @param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible @@ -182,6 +184,21 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options; +#pragma mark - Animated Encoding +@optional +/** + Encode the animated image frames to image data. + + @param frames The animated image frames to be encoded, should be at least 1 element, or it will fallback to static image encode. + @param loopCount The final animated image loop count. 0 means infinity loop. This config ignore each frame's `sd_imageLoopCount` + @param format The image format to encode, you should note `SDImageFormatUndefined` format is also possible + @param options A dictionary containing any encoding options. Pass @{SDImageCoderEncodeCompressionQuality: @(1)} to specify compression quality. + @return The encoded image data + */ +- (nullable NSData *)encodedDataWithFrames:(nonnull NSArray*)frames + loopCount:(NSUInteger)loopCount + format:(SDImageFormat)format + options:(nullable SDImageCoderOptions *)options; @end #pragma mark - Progressive Coder diff --git a/SDWebImage/Core/SDImageCodersManager.m b/SDWebImage/Core/SDImageCodersManager.m index 00653d1c..0abb962e 100644 --- a/SDWebImage/Core/SDImageCodersManager.m +++ b/SDWebImage/Core/SDImageCodersManager.m @@ -127,4 +127,19 @@ return nil; } +- (NSData *)encodedDataWithFrames:(NSArray *)frames loopCount:(NSUInteger)loopCount format:(SDImageFormat)format options:(SDImageCoderOptions *)options { + if (!frames || frames.count < 1) { + return nil; + } + NSArray> *coders = self.coders; + for (id coder in coders.reverseObjectEnumerator) { + if ([coder canEncodeToFormat:format]) { + if ([coder respondsToSelector:@selector(encodedDataWithFrames:loopCount:format:options:)]) { + return [coder encodedDataWithFrames:frames loopCount:loopCount format:format options:options]; + } + } + } + return nil; +} + @end diff --git a/SDWebImage/Core/SDImageFrame.h b/SDWebImage/Core/SDImageFrame.h index a93fd9c8..41f39655 100644 --- a/SDWebImage/Core/SDImageFrame.h +++ b/SDWebImage/Core/SDImageFrame.h @@ -24,6 +24,11 @@ */ @property (nonatomic, readonly, assign) NSTimeInterval duration; +/// Create a frame instance with specify image and duration +/// @param image current frame's image +/// @param duration current frame's duration +- (nonnull instancetype)initWithImage:(nonnull UIImage *)image duration:(NSTimeInterval)duration; + /** Create a frame instance with specify image and duration @@ -31,6 +36,9 @@ @param duration current frame's duration @return frame instance */ -+ (instancetype _Nonnull)frameWithImage:(UIImage * _Nonnull)image duration:(NSTimeInterval)duration; ++ (nonnull instancetype)frameWithImage:(nonnull UIImage *)image duration:(NSTimeInterval)duration; + +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; @end diff --git a/SDWebImage/Core/SDImageFrame.m b/SDWebImage/Core/SDImageFrame.m index f035af54..bd207aee 100644 --- a/SDWebImage/Core/SDImageFrame.m +++ b/SDWebImage/Core/SDImageFrame.m @@ -17,11 +17,17 @@ @implementation SDImageFrame +- (instancetype)initWithImage:(UIImage *)image duration:(NSTimeInterval)duration { + self = [super init]; + if (self) { + _image = image; + _duration = duration; + } + return self; +} + + (instancetype)frameWithImage:(UIImage *)image duration:(NSTimeInterval)duration { - SDImageFrame *frame = [[SDImageFrame alloc] init]; - frame.image = image; - frame.duration = duration; - + SDImageFrame *frame = [[SDImageFrame alloc] initWithImage:image duration:duration]; return frame; } diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m index 49bdc422..b4bb06b7 100644 --- a/SDWebImage/Core/SDImageIOAnimatedCoder.m +++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m @@ -552,6 +552,23 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) { } - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options { + if (!image) { + return nil; + } + if (format != self.class.imageFormat) { + return nil; + } + + NSArray *frames = [SDImageCoderHelper framesFromAnimatedImage:image]; + if (!frames || frames.count == 0) { + SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:0]; + frames = @[frame]; + } + return [self encodedDataWithFrames:frames loopCount:image.sd_imageLoopCount format:format options:options]; +} + +- (NSData *)encodedDataWithFrames:(NSArray *)frames loopCount:(NSUInteger)loopCount format:(SDImageFormat)format options:(SDImageCoderOptions *)options { + UIImage *image = frames.firstObject.image; // Primary image if (!image) { return nil; } @@ -561,13 +578,8 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) { return nil; } - if (format != self.class.imageFormat) { - return nil; - } - NSMutableData *imageData = [NSMutableData data]; CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format]; - NSArray *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. @@ -630,12 +642,11 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) { properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail); BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue]; - if (encodeFirstFrame || frames.count == 0) { + if (encodeFirstFrame || frames.count <= 1) { // 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)} };