New file |
| | |
| | | /* |
| | | * 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 "SDWebImageGIFCoder.h" |
| | | #import "NSImage+WebCache.h" |
| | | #import <ImageIO/ImageIO.h> |
| | | #import "NSData+ImageContentType.h" |
| | | #import "UIImage+MultiFormat.h" |
| | | #import "SDWebImageCoderHelper.h" |
| | | #import "SDAnimatedImageRep.h" |
| | | |
| | | @implementation SDWebImageGIFCoder |
| | | |
| | | + (instancetype)sharedCoder { |
| | | static SDWebImageGIFCoder *coder; |
| | | static dispatch_once_t onceToken; |
| | | dispatch_once(&onceToken, ^{ |
| | | coder = [[SDWebImageGIFCoder alloc] init]; |
| | | }); |
| | | return coder; |
| | | } |
| | | |
| | | #pragma mark - Decode |
| | | - (BOOL)canDecodeFromData:(nullable NSData *)data { |
| | | return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF); |
| | | } |
| | | |
| | | - (UIImage *)decodedImageWithData:(NSData *)data { |
| | | if (!data) { |
| | | return nil; |
| | | } |
| | | |
| | | #if SD_MAC |
| | | SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; |
| | | NSImage *animatedImage = [[NSImage alloc] initWithSize:imageRep.size]; |
| | | [animatedImage addRepresentation:imageRep]; |
| | | return animatedImage; |
| | | #else |
| | | |
| | | CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); |
| | | if (!source) { |
| | | return nil; |
| | | } |
| | | size_t count = CGImageSourceGetCount(source); |
| | | |
| | | UIImage *animatedImage; |
| | | |
| | | if (count <= 1) { |
| | | animatedImage = [[UIImage alloc] initWithData:data]; |
| | | } else { |
| | | NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array]; |
| | | |
| | | for (size_t i = 0; i < count; i++) { |
| | | CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL); |
| | | if (!imageRef) { |
| | | continue; |
| | | } |
| | | |
| | | float duration = [self sd_frameDurationAtIndex:i source:source]; |
| | | UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; |
| | | CGImageRelease(imageRef); |
| | | |
| | | SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration]; |
| | | [frames addObject:frame]; |
| | | } |
| | | |
| | | NSUInteger loopCount = 1; |
| | | 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 != nil) { |
| | | loopCount = gifLoopCount.unsignedIntegerValue; |
| | | } |
| | | } |
| | | |
| | | animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames]; |
| | | animatedImage.sd_imageLoopCount = loopCount; |
| | | } |
| | | |
| | | CFRelease(source); |
| | | |
| | | return animatedImage; |
| | | #endif |
| | | } |
| | | |
| | | - (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { |
| | | float frameDuration = 0.1f; |
| | | CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); |
| | | if (!cfFrameProperties) { |
| | | return frameDuration; |
| | | } |
| | | NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; |
| | | NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; |
| | | |
| | | NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; |
| | | if (delayTimeUnclampedProp != nil) { |
| | | frameDuration = [delayTimeUnclampedProp floatValue]; |
| | | } else { |
| | | NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; |
| | | if (delayTimeProp != nil) { |
| | | 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 { |
| | | // GIF do not decompress |
| | | return image; |
| | | } |
| | | |
| | | #pragma mark - Encode |
| | | - (BOOL)canEncodeToFormat:(SDImageFormat)format { |
| | | return (format == SDImageFormatGIF); |
| | | } |
| | | |
| | | - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format { |
| | | if (!image) { |
| | | return nil; |
| | | } |
| | | |
| | | if (format != SDImageFormatGIF) { |
| | | return nil; |
| | | } |
| | | |
| | | NSMutableData *imageData = [NSMutableData data]; |
| | | CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF]; |
| | | NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image]; |
| | | |
| | | // Create an image destination. GIF does not support EXIF image orientation |
| | | CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL); |
| | | if (!imageDestination) { |
| | | // Handle failure. |
| | | return nil; |
| | | } |
| | | if (frames.count == 0) { |
| | | // for static single GIF images |
| | | CGImageDestinationAddImage(imageDestination, image.CGImage, nil); |
| | | } else { |
| | | // for animated GIF images |
| | | NSUInteger loopCount = image.sd_imageLoopCount; |
| | | NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}}; |
| | | CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties); |
| | | |
| | | for (size_t i = 0; i < frames.count; i++) { |
| | | SDWebImageFrame *frame = frames[i]; |
| | | float frameDuration = frame.duration; |
| | | CGImageRef frameImageRef = frame.image.CGImage; |
| | | NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}}; |
| | | CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties); |
| | | } |
| | | } |
| | | // Finalize the destination. |
| | | if (CGImageDestinationFinalize(imageDestination) == NO) { |
| | | // Handle failure. |
| | | imageData = nil; |
| | | } |
| | | |
| | | CFRelease(imageDestination); |
| | | |
| | | return [imageData copy]; |
| | | } |
| | | |
| | | @end |