Give back our GIF category animation support since we now use plugin coders. This will not affect FLAnimatedImageView which just create the first frame.

1. Change default coders to [IOCoder, WebPCoder]
2. Add back our previous GIF decoding code, with loop count support
3. Add GIF encoding code for animation
4. Modify IOCoder for GIF format because FLAnimatedImageView need just use the first frame
This commit is contained in:
DreamPiggy 2017-10-18 02:25:05 +08:00
parent d12484a762
commit 17386829c1
5 changed files with 116 additions and 30 deletions

View File

@ -17,10 +17,10 @@
Note: the `coders` getter will return the coders in their reversed order
Example:
- by default we internally set coders = `IOCoder`, `GIFCoder`, `WebPCoder`
- calling `coders` will return `@[WebPCoder, GIFCoder, IOCoder]`
- by default we internally set coders = `IOCoder`, `WebPCoder`
- calling `coders` will return `@[WebPCoder, IOCoder]`
- call `[addCoder:[MyCrazyCoder new]]`
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, GIFCoder, IOCoder]`
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, IOCoder]`
Coders
------

View File

@ -34,7 +34,7 @@
- (instancetype)init {
if (self = [super init]) {
// initialize with default coders
_mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder], [SDWebImageGIFCoder sharedCoder]] mutableCopy];
_mutableCoders = [@[[SDWebImageImageIOCoder sharedCoder]] mutableCopy];
#ifdef SD_WEBP
[_mutableCoders addObject:[SDWebImageWebPCoder sharedCoder]];
#endif

View File

@ -10,6 +10,7 @@
#import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
@implementation SDWebImageGIFCoder
@ -40,35 +41,90 @@
size_t count = CGImageSourceGetCount(source);
UIImage *staticImage;
UIImage *animatedImage;
if (count <= 1) {
staticImage = [[UIImage alloc] initWithData:data];
} else {
// we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category.
// this here is only code to allow drawing animated images as static ones
#if SD_WATCH
CGFloat scale = 1;
scale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat scale = 1;
scale = [UIScreen mainScreen].scale;
#endif
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
NSMutableArray *images = [NSMutableArray array];
CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL);
#if SD_UIKIT || SD_WATCH
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp];
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
continue;
}
duration += [self sd_frameDurationAtIndex:i source:source];
#if SD_WATCH
CGFloat scale = 1;
scale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat scale = 1;
scale = [UIScreen mainScreen].scale;
#endif
CGImageRelease(CGImage);
[images addObject:[UIImage imageWithCGImage:image scale:scale orientation:UIImageOrientationUp]];
CGImageRelease(image);
}
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
NSUInteger loopCount = 0;
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
if (gifProperties) {
NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount];
if (gifLoopCount) {
loopCount = gifLoopCount.unsignedIntegerValue;
}
}
animatedImage.sd_imageLoopCount = loopCount;
}
CFRelease(source);
return staticImage;
return animatedImage;
#endif
}
- (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
}
else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
// 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.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
}
- (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
@ -92,16 +148,36 @@
NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
NSUInteger frameCount = 1;
if (image.images) {
frameCount = image.images.count;
}
// Create an image destination.
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
// Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
if (!imageDestination) {
// Handle failure.
return nil;
}
// Add your image to the destination.
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
if (!image.images) {
// for static single GIF images
CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
} else {
// for animated GIF images
NSUInteger loopCount = image.sd_imageLoopCount;
NSTimeInterval totalDuration = image.duration;
NSTimeInterval frameDuration = totalDuration / frameCount;
NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
for (size_t i = 0; i < frameCount; i++) {
@autoreleasepool {
NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFUnclampedDelayTime : @(frameDuration)}};
CGImageRef frameImageRef = image.images[i].CGImage;
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
}
}
}
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {

View File

@ -67,7 +67,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
- (BOOL)canDecodeFromData:(nullable NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
// Do not support GIF and WebP decoding
case SDImageFormatGIF:
case SDImageFormatWebP:
return NO;
default:
@ -91,6 +90,17 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
}
UIImage *image = [[UIImage alloc] initWithData:data];
if (!image) {
return nil;
}
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
if (format == SDImageFormatGIF) {
// static single GIF need to be created animated for FLAnimatedImageView logic
// GIF does not support EXIF image orientation
image = [UIImage animatedImageWithImages:@[image] duration:image.duration];
return image;
}
#if SD_UIKIT || SD_WATCH
UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
@ -373,7 +383,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) {
// Do not support GIF and WebP encoding
case SDImageFormatGIF:
case SDImageFormatWebP:
return NO;
default:

View File

@ -12,12 +12,13 @@
@interface UIImage (GIF)
/**
* Compatibility method - creates an animated UIImage from an NSData, it will only contain the 1st frame image
* Creates an animated UIImage from an NSData.
* For static GIF, will create an UIImage with `images` array set to nil. For animated GIF, will create an UIImage with valid `images` array.
*/
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
/**
* Checks if an UIImage instance is a GIF. Will use the `images` array
* Checks if an UIImage instance is a GIF. Will use the `images` array.
*/
- (BOOL)isGIF;