SDWebImage/SDWebImage/Core/SDAnimatedImageRep.m

152 lines
6.2 KiB
Objective-C

/*
* 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 "SDAnimatedImageRep.h"
#if SD_MAC
#import "SDImageIOAnimatedCoderInternal.h"
#import "SDImageGIFCoder.h"
#import "SDImageAPNGCoder.h"
#import "SDImageHEICCoder.h"
#import "SDImageAWebPCoder.h"
@interface SDAnimatedImageRep ()
/// This wrap the animated image frames for legacy animated image coder API (`encodedDataWithImage:`).
@property (nonatomic, readwrite, weak) NSArray<SDImageFrame *> *frames;
@property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
@end
@implementation SDAnimatedImageRep {
CGImageSourceRef _imageSource;
}
- (void)dealloc {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
- (instancetype)copyWithZone:(NSZone *)zone {
SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
// super will copy all ivars
if (imageRep->_imageSource) {
CFRetain(imageRep->_imageSource);
}
return imageRep;
}
// `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer
+ (instancetype)imageRepWithData:(NSData *)data {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
return imageRep;
}
// We should override init method for `NSBitmapImageRep` to do initialize about animated image format
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
- (instancetype)initWithData:(NSData *)data {
self = [super initWithData:data];
if (self) {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) data, NULL);
if (!imageSource) {
return self;
}
_imageSource = imageSource;
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
if (frameCount <= 1) {
return self;
}
CFStringRef type = CGImageSourceGetType(imageSource);
if (!type) {
return self;
}
_animatedImageData = data; // CGImageSource will retain the data internally, no extra copy
SDImageFormat format = SDImageFormatUndefined;
if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
// Fix the `NSBitmapImageRep` GIF loop count calculation issue
// Which will use 0 when there are no loop count information metadata in GIF data
format = SDImageFormatGIF;
NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatPNG;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageAPNGCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
// HEIC
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatHEIC;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageHEICCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
// WebP
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatWebP;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageAWebPCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else {
format = [NSData sd_imageFormatForImageData:data];
}
_animatedImageFormat = format;
}
return self;
}
// `NSBitmapImageRep` will use `kCGImagePropertyGIFDelayTime` whenever you call `setProperty:withValue:` with `NSImageCurrentFrame` to change the current frame. We override it and use the actual `kCGImagePropertyGIFUnclampedDelayTime` if need.
- (void)setProperty:(NSBitmapImageRepPropertyKey)property withValue:(id)value {
[super setProperty:property withValue:value];
if ([property isEqualToString:NSImageCurrentFrame]) {
// Access the image source
CGImageSourceRef imageSource = _imageSource;
if (!imageSource) {
return;
}
// Check format type
CFStringRef type = CGImageSourceGetType(imageSource);
if (!type) {
return;
}
NSUInteger index = [value unsignedIntegerValue];
NSTimeInterval frameDuration = 0;
if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
// HEIC
frameDuration = [SDImageHEICCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
// WebP
frameDuration = [SDImageAWebPCoder frameDurationAtIndex:index source:imageSource];
}
if (!frameDuration) {
return;
}
// Reset super frame duration with the actual frame duration
[super setProperty:NSImageCurrentFrameDuration withValue:@(frameDuration)];
}
}
#pragma clang diagnostic pop
@end
#endif