123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- /*
- * 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 "SDAnimatedImage.h"
- #import "NSImage+Compatibility.h"
- #import "SDImageCoder.h"
- #import "SDImageCodersManager.h"
- #import "SDImageFrame.h"
- #import "UIImage+MemoryCacheCost.h"
- #import "SDImageAssetManager.h"
- #import "objc/runtime.h"
- static CGFloat SDImageScaleFromPath(NSString *string) {
- if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
- NSString *name = string.stringByDeletingPathExtension;
- __block CGFloat scale = 1;
-
- NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
- [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
- if (result.range.location >= 3) {
- scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
- }
- }];
-
- return scale;
- }
- @interface SDAnimatedImage ()
- @property (nonatomic, strong) id<SDAnimatedImageCoder> coder;
- @property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
- @property (atomic, copy) NSArray<SDImageFrame *> *loadedAnimatedImageFrames; // Mark as atomic to keep thread-safe
- @property (nonatomic, assign, getter=isAllFramesLoaded) BOOL allFramesLoaded;
- @end
- @implementation SDAnimatedImage
- @dynamic scale; // call super
- #pragma mark - UIImage override method
- + (instancetype)imageNamed:(NSString *)name {
- #if __has_include(<UIKit/UITraitCollection.h>)
- return [self imageNamed:name inBundle:nil compatibleWithTraitCollection:nil];
- #else
- return [self imageNamed:name inBundle:nil];
- #endif
- }
- #if __has_include(<UIKit/UITraitCollection.h>)
- + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection {
- if (!traitCollection) {
- traitCollection = UIScreen.mainScreen.traitCollection;
- }
- CGFloat scale = traitCollection.displayScale;
- return [self imageNamed:name inBundle:bundle scale:scale];
- }
- #else
- + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle {
- return [self imageNamed:name inBundle:bundle scale:0];
- }
- #endif
- // 0 scale means automatically check
- + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale {
- if (!name) {
- return nil;
- }
- if (!bundle) {
- bundle = [NSBundle mainBundle];
- }
- SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager];
- SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name];
- if ([image isKindOfClass:[SDAnimatedImage class]]) {
- return image;
- }
- NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale];
- if (!path) {
- return image;
- }
- NSData *data = [NSData dataWithContentsOfFile:path];
- if (!data) {
- return image;
- }
- image = [[self alloc] initWithData:data scale:scale];
- if (image) {
- [assetManager storeImage:image forName:name];
- }
-
- return image;
- }
- + (instancetype)imageWithContentsOfFile:(NSString *)path {
- return [[self alloc] initWithContentsOfFile:path];
- }
- + (instancetype)imageWithData:(NSData *)data {
- return [[self alloc] initWithData:data];
- }
- + (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
- return [[self alloc] initWithData:data scale:scale];
- }
- - (instancetype)initWithContentsOfFile:(NSString *)path {
- NSData *data = [NSData dataWithContentsOfFile:path];
- return [self initWithData:data scale:SDImageScaleFromPath(path)];
- }
- - (instancetype)initWithData:(NSData *)data {
- return [self initWithData:data scale:1];
- }
- - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
- return [self initWithData:data scale:scale options:nil];
- }
- - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
- if (!data || data.length == 0) {
- return nil;
- }
- data = [data copy]; // avoid mutable data
- id<SDAnimatedImageCoder> animatedCoder = nil;
- for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
- if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
- if ([coder canDecodeFromData:data]) {
- if (!options) {
- options = @{SDImageCoderDecodeScaleFactor : @(scale)};
- }
- animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options];
- break;
- }
- }
- }
- if (!animatedCoder) {
- return nil;
- }
- return [self initWithAnimatedCoder:animatedCoder scale:scale];
- }
- - (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
- if (!animatedCoder) {
- return nil;
- }
- UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
- if (!image) {
- return nil;
- }
- #if SD_MAC
- self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
- #else
- self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
- #endif
- if (self) {
- _coder = animatedCoder;
- NSData *data = [animatedCoder animatedImageData];
- SDImageFormat format = [NSData sd_imageFormatForImageData:data];
- _animatedImageFormat = format;
- }
- return self;
- }
- #pragma mark - Preload
- - (void)preloadAllFrames {
- if (!self.isAllFramesLoaded) {
- NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
- for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
- UIImage *image = [self animatedImageFrameAtIndex:i];
- NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
- SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:`
- [frames addObject:frame];
- }
- self.loadedAnimatedImageFrames = frames;
- self.allFramesLoaded = YES;
- }
- }
- - (void)unloadAllFrames {
- if (self.isAllFramesLoaded) {
- self.loadedAnimatedImageFrames = nil;
- self.allFramesLoaded = NO;
- }
- }
- #pragma mark - NSSecureCoding
- - (instancetype)initWithCoder:(NSCoder *)aDecoder {
- self = [super initWithCoder:aDecoder];
- if (self) {
- NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
- CGFloat scale = self.scale;
- if (!animatedImageData) {
- return self;
- }
- id<SDAnimatedImageCoder> animatedCoder = nil;
- for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
- if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
- if ([coder canDecodeFromData:animatedImageData]) {
- animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
- break;
- }
- }
- }
- if (!animatedCoder) {
- return self;
- }
- _coder = animatedCoder;
- SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData];
- _animatedImageFormat = format;
- }
- return self;
- }
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- [super encodeWithCoder:aCoder];
- NSData *animatedImageData = self.animatedImageData;
- if (animatedImageData) {
- [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
- }
- }
- + (BOOL)supportsSecureCoding {
- return YES;
- }
- #pragma mark - SDAnimatedImage
- - (NSData *)animatedImageData {
- return [self.coder animatedImageData];
- }
- - (NSUInteger)animatedImageLoopCount {
- return [self.coder animatedImageLoopCount];
- }
- - (NSUInteger)animatedImageFrameCount {
- return [self.coder animatedImageFrameCount];
- }
- - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
- if (index >= self.animatedImageFrameCount) {
- return nil;
- }
- if (self.isAllFramesLoaded) {
- SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
- return frame.image;
- }
- return [self.coder animatedImageFrameAtIndex:index];
- }
- - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
- if (index >= self.animatedImageFrameCount) {
- return 0;
- }
- if (self.isAllFramesLoaded) {
- SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
- return frame.duration;
- }
- return [self.coder animatedImageDurationAtIndex:index];
- }
- @end
- @implementation SDAnimatedImage (MemoryCacheCost)
- - (NSUInteger)sd_memoryCost {
- NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
- if (value != nil) {
- return value.unsignedIntegerValue;
- }
-
- CGImageRef imageRef = self.CGImage;
- if (!imageRef) {
- return 0;
- }
- NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
- NSUInteger frameCount = 1;
- if (self.isAllFramesLoaded) {
- frameCount = self.animatedImageFrameCount;
- }
- frameCount = frameCount > 0 ? frameCount : 1;
- NSUInteger cost = bytesPerFrame * frameCount;
- return cost;
- }
- @end
|