SDAnimatedImage.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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 (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>) && !SD_WATCH
  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>) && !SD_WATCH
  45. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection {
  46. #if SD_VISION
  47. if (!traitCollection) {
  48. traitCollection = UITraitCollection.currentTraitCollection;
  49. }
  50. #else
  51. if (!traitCollection) {
  52. traitCollection = UIScreen.mainScreen.traitCollection;
  53. }
  54. #endif
  55. CGFloat scale = traitCollection.displayScale;
  56. return [self imageNamed:name inBundle:bundle scale:scale];
  57. }
  58. #else
  59. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle {
  60. return [self imageNamed:name inBundle:bundle scale:0];
  61. }
  62. #endif
  63. // 0 scale means automatically check
  64. + (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale {
  65. if (!name) {
  66. return nil;
  67. }
  68. if (!bundle) {
  69. bundle = [NSBundle mainBundle];
  70. }
  71. SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager];
  72. SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name];
  73. if ([image isKindOfClass:[SDAnimatedImage class]]) {
  74. return image;
  75. }
  76. NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale];
  77. if (!path) {
  78. return image;
  79. }
  80. NSData *data = [NSData dataWithContentsOfFile:path];
  81. if (!data) {
  82. return image;
  83. }
  84. image = [[self alloc] initWithData:data scale:scale];
  85. if (image) {
  86. [assetManager storeImage:image forName:name];
  87. }
  88. return image;
  89. }
  90. + (instancetype)imageWithContentsOfFile:(NSString *)path {
  91. return [[self alloc] initWithContentsOfFile:path];
  92. }
  93. + (instancetype)imageWithData:(NSData *)data {
  94. return [[self alloc] initWithData:data];
  95. }
  96. + (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
  97. return [[self alloc] initWithData:data scale:scale];
  98. }
  99. - (instancetype)initWithContentsOfFile:(NSString *)path {
  100. NSData *data = [NSData dataWithContentsOfFile:path];
  101. if (!data) {
  102. return nil;
  103. }
  104. CGFloat scale = SDImageScaleFromPath(path);
  105. // path extension may be useful for coder like raw-image
  106. NSString *fileExtensionHint = path.pathExtension; // without dot
  107. if (fileExtensionHint.length == 0) {
  108. // Ignore file extension which is empty
  109. fileExtensionHint = nil;
  110. }
  111. SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:1];
  112. mutableCoderOptions[SDImageCoderDecodeFileExtensionHint] = fileExtensionHint;
  113. return [self initWithData:data scale:scale options:[mutableCoderOptions copy]];
  114. }
  115. - (instancetype)initWithData:(NSData *)data {
  116. return [self initWithData:data scale:1];
  117. }
  118. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
  119. return [self initWithData:data scale:scale options:nil];
  120. }
  121. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
  122. if (!data || data.length == 0) {
  123. return nil;
  124. }
  125. // Vector image does not supported, guard firstly
  126. SDImageFormat format = [NSData sd_imageFormatForImageData:data];
  127. if (format == SDImageFormatSVG || format == SDImageFormatPDF) {
  128. return nil;
  129. }
  130. id<SDAnimatedImageCoder> animatedCoder = nil;
  131. SDImageCoderMutableOptions *mutableCoderOptions;
  132. if (options != nil) {
  133. mutableCoderOptions = [NSMutableDictionary dictionaryWithDictionary:options];
  134. } else {
  135. mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:1];
  136. }
  137. mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
  138. options = [mutableCoderOptions copy];
  139. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
  140. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  141. if ([coder canDecodeFromData:data]) {
  142. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options];
  143. break;
  144. }
  145. }
  146. }
  147. if (animatedCoder) {
  148. // Animated Image
  149. return [self initWithAnimatedCoder:animatedCoder scale:scale];
  150. } else {
  151. // Static Image (Before 5.19 this code path return nil)
  152. UIImage *image = [[SDImageCodersManager sharedManager] decodedImageWithData:data options:options];
  153. if (!image) {
  154. return nil;
  155. }
  156. // Vector image does not supported, guard secondly
  157. if (image.sd_isVector) {
  158. return nil;
  159. }
  160. #if SD_MAC
  161. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
  162. #else
  163. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
  164. #endif
  165. // Defines the associated object that holds the format for static images
  166. super.sd_imageFormat = format;
  167. return self;
  168. }
  169. }
  170. - (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
  171. if (!animatedCoder) {
  172. return nil;
  173. }
  174. UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
  175. if (!image) {
  176. return nil;
  177. }
  178. #if SD_MAC
  179. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
  180. #else
  181. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
  182. #endif
  183. if (self) {
  184. // Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP)
  185. if (animatedCoder.animatedImageFrameCount > 1) {
  186. _animatedCoder = animatedCoder;
  187. }
  188. }
  189. return self;
  190. }
  191. - (SDImageFormat)animatedImageFormat {
  192. return [NSData sd_imageFormatForImageData:self.animatedImageData];
  193. }
  194. #pragma mark - Preload
  195. - (void)preloadAllFrames {
  196. if (!_animatedCoder) {
  197. return;
  198. }
  199. if (!self.isAllFramesLoaded) {
  200. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
  201. for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
  202. UIImage *image = [self animatedImageFrameAtIndex:i];
  203. NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
  204. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:`
  205. [frames addObject:frame];
  206. }
  207. self.loadedAnimatedImageFrames = frames;
  208. self.allFramesLoaded = YES;
  209. }
  210. }
  211. - (void)unloadAllFrames {
  212. if (!_animatedCoder) {
  213. return;
  214. }
  215. if (self.isAllFramesLoaded) {
  216. self.loadedAnimatedImageFrames = nil;
  217. self.allFramesLoaded = NO;
  218. }
  219. }
  220. #pragma mark - NSSecureCoding
  221. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  222. self = [super initWithCoder:aDecoder];
  223. if (self) {
  224. NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
  225. if (!animatedImageData) {
  226. return self;
  227. }
  228. CGFloat scale = self.scale;
  229. id<SDAnimatedImageCoder> animatedCoder = nil;
  230. for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
  231. if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
  232. if ([coder canDecodeFromData:animatedImageData]) {
  233. animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
  234. break;
  235. }
  236. }
  237. }
  238. if (!animatedCoder) {
  239. return self;
  240. }
  241. if (animatedCoder.animatedImageFrameCount > 1) {
  242. _animatedCoder = animatedCoder;
  243. }
  244. }
  245. return self;
  246. }
  247. - (void)encodeWithCoder:(NSCoder *)aCoder {
  248. [super encodeWithCoder:aCoder];
  249. NSData *animatedImageData = self.animatedImageData;
  250. if (animatedImageData) {
  251. [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
  252. }
  253. }
  254. + (BOOL)supportsSecureCoding {
  255. return YES;
  256. }
  257. #pragma mark - SDAnimatedImageProvider
  258. - (NSData *)animatedImageData {
  259. return [self.animatedCoder animatedImageData];
  260. }
  261. - (NSUInteger)animatedImageLoopCount {
  262. return [self.animatedCoder animatedImageLoopCount];
  263. }
  264. - (NSUInteger)animatedImageFrameCount {
  265. return [self.animatedCoder animatedImageFrameCount];
  266. }
  267. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  268. if (index >= self.animatedImageFrameCount) {
  269. return nil;
  270. }
  271. if (self.isAllFramesLoaded) {
  272. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  273. return frame.image;
  274. }
  275. return [self.animatedCoder animatedImageFrameAtIndex:index];
  276. }
  277. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  278. if (index >= self.animatedImageFrameCount) {
  279. return 0;
  280. }
  281. if (self.isAllFramesLoaded) {
  282. SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
  283. return frame.duration;
  284. }
  285. return [self.animatedCoder animatedImageDurationAtIndex:index];
  286. }
  287. @end
  288. @implementation SDAnimatedImage (MemoryCacheCost)
  289. - (NSUInteger)sd_memoryCost {
  290. NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
  291. if (value != nil) {
  292. return value.unsignedIntegerValue;
  293. }
  294. CGImageRef imageRef = self.CGImage;
  295. if (!imageRef) {
  296. return 0;
  297. }
  298. NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
  299. NSUInteger frameCount = 1;
  300. if (self.isAllFramesLoaded) {
  301. frameCount = self.animatedImageFrameCount;
  302. }
  303. frameCount = frameCount > 0 ? frameCount : 1;
  304. NSUInteger cost = bytesPerFrame * frameCount;
  305. return cost;
  306. }
  307. @end
  308. @implementation SDAnimatedImage (Metadata)
  309. - (BOOL)sd_isAnimated {
  310. return self.animatedImageFrameCount > 1;
  311. }
  312. - (NSUInteger)sd_imageLoopCount {
  313. return self.animatedImageLoopCount;
  314. }
  315. - (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
  316. return;
  317. }
  318. - (NSUInteger)sd_imageFrameCount {
  319. NSUInteger frameCount = self.animatedImageFrameCount;
  320. if (frameCount > 1) {
  321. return frameCount;
  322. } else {
  323. return 1;
  324. }
  325. }
  326. - (SDImageFormat)sd_imageFormat {
  327. NSData *animatedImageData = self.animatedImageData;
  328. if (animatedImageData) {
  329. return [NSData sd_imageFormatForImageData:animatedImageData];
  330. } else {
  331. return [super sd_imageFormat];
  332. }
  333. }
  334. - (BOOL)sd_isVector {
  335. return NO;
  336. }
  337. @end
  338. @implementation SDAnimatedImage (MultiFormat)
  339. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
  340. return [self sd_imageWithData:data scale:1];
  341. }
  342. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale {
  343. return [self sd_imageWithData:data scale:scale firstFrameOnly:NO];
  344. }
  345. + (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly {
  346. if (!data) {
  347. return nil;
  348. }
  349. return [[self alloc] initWithData:data scale:scale options:@{SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}];
  350. }
  351. - (nullable NSData *)sd_imageData {
  352. NSData *imageData = self.animatedImageData;
  353. if (imageData) {
  354. return imageData;
  355. } else {
  356. return [self sd_imageDataAsFormat:self.animatedImageFormat];
  357. }
  358. }
  359. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
  360. return [self sd_imageDataAsFormat:imageFormat compressionQuality:1];
  361. }
  362. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality {
  363. return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO];
  364. }
  365. - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly {
  366. // Protect when user input the imageFormat == self.animatedImageFormat && compressionQuality == 1
  367. // This should be treated as grabbing `self.animatedImageData` as well :)
  368. NSData *imageData;
  369. if (imageFormat == self.animatedImageFormat && compressionQuality == 1) {
  370. imageData = self.animatedImageData;
  371. }
  372. if (imageData) return imageData;
  373. SDImageCoderOptions *options = @{SDImageCoderEncodeCompressionQuality : @(compressionQuality), SDImageCoderEncodeFirstFrameOnly : @(firstFrameOnly)};
  374. NSUInteger frameCount = self.animatedImageFrameCount;
  375. if (frameCount <= 1) {
  376. // Static image
  377. imageData = [SDImageCodersManager.sharedManager encodedDataWithImage:self format:imageFormat options:options];
  378. }
  379. if (imageData) return imageData;
  380. NSUInteger loopCount = self.animatedImageLoopCount;
  381. // Keep animated image encoding, loop each frame.
  382. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
  383. for (size_t i = 0; i < frameCount; i++) {
  384. UIImage *image = [self animatedImageFrameAtIndex:i];
  385. NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
  386. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
  387. [frames addObject:frame];
  388. }
  389. imageData = [SDImageCodersManager.sharedManager encodedDataWithFrames:frames loopCount:loopCount format:imageFormat options:options];
  390. return imageData;
  391. }
  392. @end