123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- /*
- * 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 NSString *)kCGImagePropertyGIFDictionary];
- if (gifProperties) {
- NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge NSString *)kCGImagePropertyGIFLoopCount];
- if (gifLoopCount != nil) {
- loopCount = gifLoopCount.unsignedIntegerValue;
- }
- }
-
- animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
- animatedImage.sd_imageLoopCount = loopCount;
- animatedImage.sd_imageFormat = SDImageFormatGIF;
- }
-
- 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 NSString *)kCGImagePropertyGIFDictionary: @{(__bridge 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 NSString *)kCGImagePropertyGIFDictionary : @{(__bridge 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
|