diff --git a/SDWebImage/SDWebImageCodersManager.h b/SDWebImage/SDWebImageCodersManager.h index 0feabd96..cef4ba76 100644 --- a/SDWebImage/SDWebImageCodersManager.h +++ b/SDWebImage/SDWebImageCodersManager.h @@ -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 ------ diff --git a/SDWebImage/SDWebImageCodersManager.m b/SDWebImage/SDWebImageCodersManager.m index 2416169f..23bc4c42 100644 --- a/SDWebImage/SDWebImageCodersManager.m +++ b/SDWebImage/SDWebImageCodersManager.m @@ -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 diff --git a/SDWebImage/SDWebImageGIFCoder.m b/SDWebImage/SDWebImageGIFCoder.m index 49669fe2..72162148 100644 --- a/SDWebImage/SDWebImageGIFCoder.m +++ b/SDWebImage/SDWebImageGIFCoder.m @@ -10,6 +10,7 @@ #import "NSImage+WebCache.h" #import #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 and + // 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*)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) { diff --git a/SDWebImage/SDWebImageImageIOCoder.m b/SDWebImage/SDWebImageImageIOCoder.m index 62d7d367..ade5b26e 100644 --- a/SDWebImage/SDWebImageImageIOCoder.m +++ b/SDWebImage/SDWebImageImageIOCoder.m @@ -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: diff --git a/SDWebImage/UIImage+GIF.h b/SDWebImage/UIImage+GIF.h index 479d5723..a3a66465 100755 --- a/SDWebImage/UIImage+GIF.h +++ b/SDWebImage/UIImage+GIF.h @@ -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;