SDAnimatedImage.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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 "UIImage+Metadata.h"
  15. #import "UIImage+MultiFormat.h"
  16. #import "SDImageCoderHelper.h"
  17. #import "SDImageAssetManager.h"
  18. #import "objc/runtime.h"
  19. static CGFloat SDImageScaleFromPath(NSString *string) {
  20. if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
  21. NSString *name = string.stringByDeletingPathExtension;
  22. __block CGFloat scale = 1;
  23. NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
  24. [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  25. scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
  26. }];
  27. return scale;
  28. }
  29. @interface SDAnimatedImage ()
  30. @property (nonatomic, strong) id<SDAnimatedImageCoder> animatedCoder;
  31. @property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
  32. @property (atomic, copy) NSArray<SDImageFrame *> *loadedAnimatedImageFrames; // Mark as atomic to keep thread-safe
  33. @property (nonatomic, assign, getter=isAllFramesLoaded) BOOL allFramesLoaded;
  34. @end
  35. @implementation SDAnimatedImage
  36. @dynamic scale; // call super
  37. #pragma mark - UIImage override method
  38. + (instancetype)imageNamed:(NSString *)name {
  39. #if __has_include(<UIKit/UITraitCollection.h>)
  40. return [self imageNamed:name inBundle:nil compatibleWithTraitCollection:nil];
  41. #else
  42. return [self imageNamed:name inBundle:nil];
  43. #endif
  44. }
  45. #if __has_include(<UIKit/UITraitCollection.h>)
  46. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection {
  47. if (!traitCollection) {
  48. traitCollection = UIScreen.mainScreen.traitCollection;
  49. }
  50. CGFloat scale = traitCollection.displayScale;
  51. return [self imageNamed:name inBundle:bundle scale:scale];
  52. }
  53. #else
  54. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle {
  55. return [self imageNamed:name inBundle:bundle scale:0];
  56. }
  57. #endif
  58. // 0 scale means automatically check
  59. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale {
  60. if (!name) {
  61. return nil;
  62. }
  63. if (!bundle) {
  64. bundle = [NSBundle mainBundle];
  65. }
  66. SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager];
  67. SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name];
  68. if ([image isKindOfClass:[SDAnimatedImage class]]) {
  69. return image;
  70. }
  71. NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale];
  72. if (!path) {
  73. return image;
  74. }
  75. NSData *data = [NSData dataWithContentsOfFile:path];
  76. if (!data) {
  77. return image;
  78. }
  79. image = [[self alloc] initWithData:data scale:scale];
  80. if (image) {
  81. [assetManager storeImage:image forName:name];
  82. }
  83. return image;
  84. }
  85. + (instancetype)imageWithContentsOfFile:(NSString *)path {
  86. return [[self alloc] initWithContentsOfFile:path];
  87. }
  88. + (instancetype)imageWithData:(NSData *)data {
  89. return [[self alloc] initWithData:data];
  90. }
  91. + (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
  92. return [[self alloc] initWithData:data scale:scale];
  93. }
  94. - (instancetype)initWithContentsOfFile:(NSString *)path {
  95. NSData *data = [NSData dataWithContentsOfFile:path];
  96. return [self initWithData:data scale:SDImageScaleFromPath(path)];
  97. }
  98. - (instancetype)initWithData:(NSData *)data {
  99. return [self initWithData:data scale:1];
  100. }
  101. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
  102. return [self initWithData:data scale:scale options:nil];
  103. }
  104. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
  105. if (!data || data.length == 0) {
  106. return nil;
  107. }
  108. data = [data copy]; // avoid mutable data
  109. id<SDAnimatedImageCoder> animatedCoder = nil;
  110. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
  111. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  112. if ([coder canDecodeFromData:data]) {
  113. if (!options) {
  114. options = @{SDImageCoderDecodeScaleFactor : @(scale)};
  115. }
  116. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options];
  117. break;
  118. }
  119. }
  120. }
  121. if (!animatedCoder) {
  122. return nil;
  123. }
  124. return [self initWithAnimatedCoder:animatedCoder scale:scale];
  125. }
  126. - (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
  127. if (!animatedCoder) {
  128. return nil;
  129. }
  130. UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
  131. if (!image) {
  132. return nil;
  133. }
  134. #if SD_MAC
  135. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
  136. #else
  137. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
  138. #endif
  139. if (self) {
  140. // Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP)
  141. if (animatedCoder.animatedImageFrameCount > 1) {
  142. _animatedCoder = animatedCoder;
  143. }
  144. NSData *data = [animatedCoder animatedImageData];
  145. SDImageFormat format = [NSData sd_imageFormatForImageData:data];
  146. _animatedImageFormat = format;
  147. }
  148. return self;
  149. }
  150. #pragma mark - Preload
  151. - (void)preloadAllFrames {
  152. if (!_animatedCoder) {
  153. return;
  154. }
  155. if (!self.isAllFramesLoaded) {
  156. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
  157. for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
  158. UIImage *image = [self animatedImageFrameAtIndex:i];
  159. NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
  160. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:`
  161. [frames addObject:frame];
  162. }
  163. self.loadedAnimatedImageFrames = frames;
  164. self.allFramesLoaded = YES;
  165. }
  166. }
  167. - (void)unloadAllFrames {
  168. if (!_animatedCoder) {
  169. return;
  170. }
  171. if (self.isAllFramesLoaded) {
  172. self.loadedAnimatedImageFrames = nil;
  173. self.allFramesLoaded = NO;
  174. }
  175. }
  176. #pragma mark - NSSecureCoding
  177. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  178. self = [super initWithCoder:aDecoder];
  179. if (self) {
  180. _animatedImageFormat = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(animatedImageFormat))];
  181. NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
  182. if (!animatedImageData) {
  183. return self;
  184. }
  185. CGFloat scale = self.scale;
  186. id<SDAnimatedImageCoder> animatedCoder = nil;
  187. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
  188. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  189. if ([coder canDecodeFromData:animatedImageData]) {
  190. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
  191. break;
  192. }
  193. }
  194. }
  195. if (!animatedCoder) {
  196. return self;
  197. }
  198. if (animatedCoder.animatedImageFrameCount > 1) {
  199. _animatedCoder = animatedCoder;
  200. }
  201. }
  202. return self;
  203. }
  204. - (void)encodeWithCoder:(NSCoder *)aCoder {
  205. [super encodeWithCoder:aCoder];
  206. [aCoder encodeInteger:self.animatedImageFormat forKey:NSStringFromSelector(@selector(animatedImageFormat))];
  207. NSData *animatedImageData = self.animatedImageData;
  208. if (animatedImageData) {
  209. [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
  210. }
  211. }
  212. + (BOOL)supportsSecureCoding {
  213. return YES;
  214. }
  215. #pragma mark - SDAnimatedImageProvider
  216. - (NSData *)animatedImageData {
  217. return [self.animatedCoder animatedImageData];
  218. }
  219. - (NSUInteger)animatedImageLoopCount {
  220. return [self.animatedCoder animatedImageLoopCount];
  221. }
  222. - (NSUInteger)animatedImageFrameCount {
  223. return [self.animatedCoder animatedImageFrameCount];
  224. }
  225. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  226. if (index >= self.animatedImageFrameCount) {
  227. return nil;
  228. }
  229. if (self.isAllFramesLoaded) {
  230. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  231. return frame.image;
  232. }
  233. return [self.animatedCoder animatedImageFrameAtIndex:index];
  234. }
  235. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  236. if (index >= self.animatedImageFrameCount) {
  237. return 0;
  238. }
  239. if (self.isAllFramesLoaded) {
  240. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  241. return frame.duration;
  242. }
  243. return [self.animatedCoder animatedImageDurationAtIndex:index];
  244. }
  245. @end
  246. @implementation SDAnimatedImage (MemoryCacheCost)
  247. - (NSUInteger)sd_memoryCost {
  248. NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
  249. if (value != nil) {
  250. return value.unsignedIntegerValue;
  251. }
  252. CGImageRef imageRef = self.CGImage;
  253. if (!imageRef) {
  254. return 0;
  255. }
  256. NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
  257. NSUInteger frameCount = 1;
  258. if (self.isAllFramesLoaded) {
  259. frameCount = self.animatedImageFrameCount;
  260. }
  261. frameCount = frameCount > 0 ? frameCount : 1;
  262. NSUInteger cost = bytesPerFrame * frameCount;
  263. return cost;
  264. }
  265. @end
  266. @implementation SDAnimatedImage (Metadata)
  267. - (BOOL)sd_isAnimated {
  268. return YES;
  269. }
  270. - (NSUInteger)sd_imageLoopCount {
  271. return self.animatedImageLoopCount;
  272. }
  273. - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
  274. return;
  275. }
  276. - (SDImageFormat)sd_imageFormat {
  277. return self.animatedImageFormat;
  278. }
  279. - (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat {
  280. return;
  281. }
  282. - (BOOL)sd_isVector {
  283. return NO;
  284. }
  285. @end
  286. @implementation SDAnimatedImage (MultiFormat)
  287. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
  288. return [self sd_imageWithData:data scale:1];
  289. }
  290. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale {
  291. return [self sd_imageWithData:data scale:scale firstFrameOnly:NO];
  292. }
  293. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly {
  294. if (!data) {
  295. return nil;
  296. }
  297. return [[self alloc] initWithData:data scale:scale options:@{SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}];
  298. }
  299. - (nullable NSData *)sd_imageData {
  300. NSData *imageData = self.animatedImageData;
  301. if (imageData) {
  302. return imageData;
  303. } else {
  304. return [self sd_imageDataAsFormat:self.animatedImageFormat];
  305. }
  306. }
  307. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
  308. return [self sd_imageDataAsFormat:imageFormat compressionQuality:1];
  309. }
  310. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality {
  311. return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO];
  312. }
  313. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly {
  314. if (firstFrameOnly) {
  315. // First frame, use super implementation
  316. return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
  317. }
  318. NSUInteger frameCount = self.animatedImageFrameCount;
  319. if (frameCount <= 1) {
  320. // Static image, use super implementation
  321. return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
  322. }
  323. // Keep animated image encoding, loop each frame.
  324. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
  325. for (size_t i = 0; i < frameCount; i++) {
  326. UIImage *image = [self animatedImageFrameAtIndex:i];
  327. NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
  328. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
  329. [frames addObject:frame];
  330. }
  331. UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
  332. NSData *imageData = [animatedImage sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
  333. return imageData;
  334. }
  335. @end