/*
|
* 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
|