SDAnimatedImage.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDAnimatedImage.h"
  9. #import "NSImage+Compatibility.h"
  10. #import "SDImageCoder.h"
  11. #import "SDImageCodersManager.h"
  12. #import "SDImageFrame.h"
  13. #import "UIImage+MemoryCacheCost.h"
  14. #import "SDImageAssetManager.h"
  15. #import "objc/runtime.h"
  16. static CGFloat SDImageScaleFromPath(NSString *string) {
  17. if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
  18. NSString *name = string.stringByDeletingPathExtension;
  19. __block CGFloat scale = 1;
  20. NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
  21. [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  22. if (result.range.location >= 3) {
  23. scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
  24. }
  25. }];
  26. return scale;
  27. }
  28. @interface SDAnimatedImage ()
  29. @property (nonatomic, strong) id<SDAnimatedImageCoder> coder;
  30. @property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
  31. @property (atomic, copy) NSArray<SDImageFrame *> *loadedAnimatedImageFrames; // Mark as atomic to keep thread-safe
  32. @property (nonatomic, assign, getter=isAllFramesLoaded) BOOL allFramesLoaded;
  33. @end
  34. @implementation SDAnimatedImage
  35. @dynamic scale; // call super
  36. #pragma mark - UIImage override method
  37. + (instancetype)imageNamed:(NSString *)name {
  38. #if __has_include(<UIKit/UITraitCollection.h>)
  39. return [self imageNamed:name inBundle:nil compatibleWithTraitCollection:nil];
  40. #else
  41. return [self imageNamed:name inBundle:nil];
  42. #endif
  43. }
  44. #if __has_include(<UIKit/UITraitCollection.h>)
  45. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection {
  46. if (!traitCollection) {
  47. traitCollection = UIScreen.mainScreen.traitCollection;
  48. }
  49. CGFloat scale = traitCollection.displayScale;
  50. return [self imageNamed:name inBundle:bundle scale:scale];
  51. }
  52. #else
  53. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle {
  54. return [self imageNamed:name inBundle:bundle scale:0];
  55. }
  56. #endif
  57. // 0 scale means automatically check
  58. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale {
  59. if (!name) {
  60. return nil;
  61. }
  62. if (!bundle) {
  63. bundle = [NSBundle mainBundle];
  64. }
  65. SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager];
  66. SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name];
  67. if ([image isKindOfClass:[SDAnimatedImage class]]) {
  68. return image;
  69. }
  70. NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale];
  71. if (!path) {
  72. return image;
  73. }
  74. NSData *data = [NSData dataWithContentsOfFile:path];
  75. if (!data) {
  76. return image;
  77. }
  78. image = [[self alloc] initWithData:data scale:scale];
  79. if (image) {
  80. [assetManager storeImage:image forName:name];
  81. }
  82. return image;
  83. }
  84. + (instancetype)imageWithContentsOfFile:(NSString *)path {
  85. return [[self alloc] initWithContentsOfFile:path];
  86. }
  87. + (instancetype)imageWithData:(NSData *)data {
  88. return [[self alloc] initWithData:data];
  89. }
  90. + (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
  91. return [[self alloc] initWithData:data scale:scale];
  92. }
  93. - (instancetype)initWithContentsOfFile:(NSString *)path {
  94. NSData *data = [NSData dataWithContentsOfFile:path];
  95. return [self initWithData:data scale:SDImageScaleFromPath(path)];
  96. }
  97. - (instancetype)initWithData:(NSData *)data {
  98. return [self initWithData:data scale:1];
  99. }
  100. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
  101. return [self initWithData:data scale:scale options:nil];
  102. }
  103. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
  104. if (!data || data.length == 0) {
  105. return nil;
  106. }
  107. data = [data copy]; // avoid mutable data
  108. id<SDAnimatedImageCoder> animatedCoder = nil;
  109. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
  110. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  111. if ([coder canDecodeFromData:data]) {
  112. if (!options) {
  113. options = @{SDImageCoderDecodeScaleFactor : @(scale)};
  114. }
  115. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options];
  116. break;
  117. }
  118. }
  119. }
  120. if (!animatedCoder) {
  121. return nil;
  122. }
  123. return [self initWithAnimatedCoder:animatedCoder scale:scale];
  124. }
  125. - (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
  126. if (!animatedCoder) {
  127. return nil;
  128. }
  129. UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
  130. if (!image) {
  131. return nil;
  132. }
  133. #if SD_MAC
  134. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
  135. #else
  136. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
  137. #endif
  138. if (self) {
  139. _coder = animatedCoder;
  140. NSData *data = [animatedCoder animatedImageData];
  141. SDImageFormat format = [NSData sd_imageFormatForImageData:data];
  142. _animatedImageFormat = format;
  143. }
  144. return self;
  145. }
  146. #pragma mark - Preload
  147. - (void)preloadAllFrames {
  148. if (!self.isAllFramesLoaded) {
  149. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
  150. for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
  151. UIImage *image = [self animatedImageFrameAtIndex:i];
  152. NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
  153. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:`
  154. [frames addObject:frame];
  155. }
  156. self.loadedAnimatedImageFrames = frames;
  157. self.allFramesLoaded = YES;
  158. }
  159. }
  160. - (void)unloadAllFrames {
  161. if (self.isAllFramesLoaded) {
  162. self.loadedAnimatedImageFrames = nil;
  163. self.allFramesLoaded = NO;
  164. }
  165. }
  166. #pragma mark - NSSecureCoding
  167. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  168. self = [super initWithCoder:aDecoder];
  169. if (self) {
  170. NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
  171. CGFloat scale = self.scale;
  172. if (!animatedImageData) {
  173. return self;
  174. }
  175. id<SDAnimatedImageCoder> animatedCoder = nil;
  176. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
  177. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  178. if ([coder canDecodeFromData:animatedImageData]) {
  179. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
  180. break;
  181. }
  182. }
  183. }
  184. if (!animatedCoder) {
  185. return self;
  186. }
  187. _coder = animatedCoder;
  188. SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData];
  189. _animatedImageFormat = format;
  190. }
  191. return self;
  192. }
  193. - (void)encodeWithCoder:(NSCoder *)aCoder {
  194. [super encodeWithCoder:aCoder];
  195. NSData *animatedImageData = self.animatedImageData;
  196. if (animatedImageData) {
  197. [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
  198. }
  199. }
  200. + (BOOL)supportsSecureCoding {
  201. return YES;
  202. }
  203. #pragma mark - SDAnimatedImage
  204. - (NSData *)animatedImageData {
  205. return [self.coder animatedImageData];
  206. }
  207. - (NSUInteger)animatedImageLoopCount {
  208. return [self.coder animatedImageLoopCount];
  209. }
  210. - (NSUInteger)animatedImageFrameCount {
  211. return [self.coder animatedImageFrameCount];
  212. }
  213. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  214. if (index >= self.animatedImageFrameCount) {
  215. return nil;
  216. }
  217. if (self.isAllFramesLoaded) {
  218. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  219. return frame.image;
  220. }
  221. return [self.coder animatedImageFrameAtIndex:index];
  222. }
  223. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  224. if (index >= self.animatedImageFrameCount) {
  225. return 0;
  226. }
  227. if (self.isAllFramesLoaded) {
  228. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  229. return frame.duration;
  230. }
  231. return [self.coder animatedImageDurationAtIndex:index];
  232. }
  233. @end
  234. @implementation SDAnimatedImage (MemoryCacheCost)
  235. - (NSUInteger)sd_memoryCost {
  236. NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
  237. if (value != nil) {
  238. return value.unsignedIntegerValue;
  239. }
  240. CGImageRef imageRef = self.CGImage;
  241. if (!imageRef) {
  242. return 0;
  243. }
  244. NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
  245. NSUInteger frameCount = 1;
  246. if (self.isAllFramesLoaded) {
  247. frameCount = self.animatedImageFrameCount;
  248. }
  249. frameCount = frameCount > 0 ? frameCount : 1;
  250. NSUInteger cost = bytesPerFrame * frameCount;
  251. return cost;
  252. }
  253. @end