Merge pull request #2064 from dreampiggy/feature_gif_category_support_animate_again

Give back our GIF category animation support since we now use plugin coders
This commit is contained in:
Bogdan Poplauschi 2017-10-18 12:03:21 +03:00 committed by GitHub
commit 81c7108706
7 changed files with 124 additions and 33 deletions

View File

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

View File

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

View File

@ -10,7 +10,10 @@
#import "SDWebImageCoder.h" #import "SDWebImageCoder.h"
/** /**
Built in coder that supports GIF Built in coder using ImageIO that supports GIF encoding/decoding
@note `SDWebImageIOCoder` supports GIF but only as static (will use the 1st frame).
@note Use `SDWebImageGIFCoder` for fully animated GIFs - less performant than `FLAnimatedImage`
@note The recommended approach for animated GIFs is using `FLAnimatedImage`
*/ */
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder> @interface SDWebImageGIFCoder : NSObject <SDWebImageCoder>

View File

@ -10,6 +10,7 @@
#import "NSImage+WebCache.h" #import "NSImage+WebCache.h"
#import <ImageIO/ImageIO.h> #import <ImageIO/ImageIO.h>
#import "NSData+ImageContentType.h" #import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"
@implementation SDWebImageGIFCoder @implementation SDWebImageGIFCoder
@ -40,35 +41,90 @@
size_t count = CGImageSourceGetCount(source); size_t count = CGImageSourceGetCount(source);
UIImage *staticImage; UIImage *animatedImage;
if (count <= 1) { if (count <= 1) {
staticImage = [[UIImage alloc] initWithData:data]; animatedImage = [[UIImage alloc] initWithData:data];
} else { }
// we will only retrieve the 1st frame. the full GIF support is available via the FLAnimatedImageView category. else {
// this here is only code to allow drawing animated images as static ones NSMutableArray *images = [NSMutableArray array];
#if SD_WATCH
CGFloat scale = 1;
scale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat scale = 1;
scale = [UIScreen mainScreen].scale;
#endif
CGImageRef CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL); NSTimeInterval duration = 0.0f;
#if SD_UIKIT || SD_WATCH
UIImage *frameImage = [UIImage imageWithCGImage:CGImage scale:scale orientation:UIImageOrientationUp]; for (size_t i = 0; i < count; i++) {
staticImage = [UIImage animatedImageWithImages:@[frameImage] duration:0.0f]; 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 #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); CFRelease(source);
return staticImage; return animatedImage;
#endif #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 - (UIImage *)decompressedImageWithImage:(UIImage *)image
data:(NSData *__autoreleasing _Nullable *)data data:(NSData *__autoreleasing _Nullable *)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict { options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
@ -92,16 +148,36 @@
NSMutableData *imageData = [NSMutableData data]; NSMutableData *imageData = [NSMutableData data];
CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format]; CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:format];
NSUInteger frameCount = 1;
if (image.images) {
frameCount = image.images.count;
}
// Create an image destination. // Create an image destination. GIF does not support EXIF image orientation
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL); CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
if (!imageDestination) { if (!imageDestination) {
// Handle failure. // Handle failure.
return nil; return nil;
} }
// Add your image to the destination. if (!image.images) {
CGImageDestinationAddImage(imageDestination, image.CGImage, nil); // 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. // Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) { if (CGImageDestinationFinalize(imageDestination) == NO) {

View File

@ -11,6 +11,8 @@
/** /**
Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding Built in coder that supports PNG, JPEG, TIFF, includes support for progressive decoding
Also supports static GIF (meaning will only handle the 1st frame).
For a full GIF support, we recommend `FLAnimatedImage` or our less performant `SDWebImageGIFCoder`
*/ */
@interface SDWebImageImageIOCoder : NSObject <SDWebImageProgressiveCoder> @interface SDWebImageImageIOCoder : NSObject <SDWebImageProgressiveCoder>

View File

@ -66,8 +66,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#pragma mark - Decode #pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data { - (BOOL)canDecodeFromData:(nullable NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) { switch ([NSData sd_imageFormatForImageData:data]) {
// Do not support GIF and WebP decoding // Do not support WebP decoding
case SDImageFormatGIF:
case SDImageFormatWebP: case SDImageFormatWebP:
return NO; return NO;
default: default:
@ -91,6 +90,17 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
} }
UIImage *image = [[UIImage alloc] initWithData:data]; 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 #if SD_UIKIT || SD_WATCH
UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data]; UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) { if (orientation != UIImageOrientationUp) {
@ -372,8 +382,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#pragma mark - Encode #pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format { - (BOOL)canEncodeToFormat:(SDImageFormat)format {
switch (format) { switch (format) {
// Do not support GIF and WebP encoding // Do not support WebP encoding
case SDImageFormatGIF:
case SDImageFormatWebP: case SDImageFormatWebP:
return NO; return NO;
default: default:

View File

@ -12,12 +12,13 @@
@interface UIImage (GIF) @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; + (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; - (BOOL)isGIF;