YYImage.m 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //
  2. // YYImage.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 14/10/20.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYImage.h"
  12. #import "NSString+YYAdd.h"
  13. #import "NSBundle+YYAdd.h"
  14. @implementation YYImage {
  15. YYImageDecoder *_decoder;
  16. NSArray *_preloadedFrames;
  17. dispatch_semaphore_t _preloadedLock;
  18. NSUInteger _bytesPerFrame;
  19. }
  20. + (YYImage *)imageNamed:(NSString *)name {
  21. if (name.length == 0) return nil;
  22. if ([name hasSuffix:@"/"]) return nil;
  23. NSString *res = name.stringByDeletingPathExtension;
  24. NSString *ext = name.pathExtension;
  25. NSString *path = nil;
  26. CGFloat scale = 1;
  27. // If no extension, guess by system supported (same as UIImage).
  28. NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
  29. NSArray *scales = [NSBundle preferredScales];
  30. for (int s = 0; s < scales.count; s++) {
  31. scale = ((NSNumber *)scales[s]).floatValue;
  32. NSString *scaledName = [res stringByAppendingNameScale:scale];
  33. for (NSString *e in exts) {
  34. path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
  35. if (path) break;
  36. }
  37. if (path) break;
  38. }
  39. if (path.length == 0) return nil;
  40. NSData *data = [NSData dataWithContentsOfFile:path];
  41. if (data.length == 0) return nil;
  42. return [[self alloc] initWithData:data scale:scale];
  43. }
  44. + (YYImage *)imageWithContentsOfFile:(NSString *)path {
  45. return [[self alloc] initWithContentsOfFile:path];
  46. }
  47. + (YYImage *)imageWithData:(NSData *)data {
  48. return [[self alloc] initWithData:data];
  49. }
  50. + (YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale {
  51. return [[self alloc] initWithData:data scale:scale];
  52. }
  53. - (instancetype)initWithContentsOfFile:(NSString *)path {
  54. NSData *data = [NSData dataWithContentsOfFile:path];
  55. return [self initWithData:data scale:path.pathScale];
  56. }
  57. - (instancetype)initWithData:(NSData *)data {
  58. return [self initWithData:data scale:1];
  59. }
  60. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
  61. if (data.length == 0) return nil;
  62. if (scale <= 0) scale = [UIScreen mainScreen].scale;
  63. _preloadedLock = dispatch_semaphore_create(1);
  64. @autoreleasepool {
  65. YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
  66. YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
  67. UIImage *image = frame.image;
  68. if (!image) return nil;
  69. self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
  70. if (!self) return nil;
  71. _animatedImageType = decoder.type;
  72. if (decoder.frameCount > 1) {
  73. _decoder = decoder;
  74. _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
  75. _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
  76. }
  77. self.isDecodedForDisplay = YES;
  78. }
  79. return self;
  80. }
  81. - (NSData *)animatedImageData {
  82. return _decoder.data;
  83. }
  84. - (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {
  85. if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {
  86. if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {
  87. NSMutableArray *frames = [NSMutableArray new];
  88. for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {
  89. UIImage *img = [self animatedImageFrameAtIndex:i];
  90. if (img) {
  91. [frames addObject:img];
  92. } else {
  93. [frames addObject:[NSNull null]];
  94. }
  95. }
  96. dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
  97. _preloadedFrames = frames;
  98. dispatch_semaphore_signal(_preloadedLock);
  99. } else {
  100. dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
  101. _preloadedFrames = nil;
  102. dispatch_semaphore_signal(_preloadedLock);
  103. }
  104. }
  105. }
  106. #pragma mark - protocol NSCoding
  107. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  108. NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"];
  109. NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"];
  110. if (data.length) {
  111. self = [self initWithData:data scale:scale.doubleValue];
  112. } else {
  113. self = [super initWithCoder:aDecoder];
  114. }
  115. return self;
  116. }
  117. - (void)encodeWithCoder:(NSCoder *)aCoder {
  118. if (_decoder.data.length) {
  119. [aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"];
  120. [aCoder encodeObject:_decoder.data forKey:@"YYImageData"];
  121. } else {
  122. [super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage.
  123. }
  124. }
  125. + (BOOL)supportsSecureCoding {
  126. return YES;
  127. }
  128. #pragma mark - protocol YYAnimatedImage
  129. - (NSUInteger)animatedImageFrameCount {
  130. return _decoder.frameCount;
  131. }
  132. - (NSUInteger)animatedImageLoopCount {
  133. return _decoder.loopCount;
  134. }
  135. - (NSUInteger)animatedImageBytesPerFrame {
  136. return _bytesPerFrame;
  137. }
  138. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  139. if (index >= _decoder.frameCount) return nil;
  140. dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
  141. UIImage *image = _preloadedFrames[index];
  142. dispatch_semaphore_signal(_preloadedLock);
  143. if (image) return image == (id)[NSNull null] ? nil : image;
  144. return [_decoder frameAtIndex:index decodeForDisplay:YES].image;
  145. }
  146. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  147. NSTimeInterval duration = [_decoder frameDurationAtIndex:index];
  148. /*
  149. http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp
  150. Many annoying ads specify a 0 duration to make an image flash as quickly as
  151. possible. We follow Safari and Firefox's behavior and use a duration of 100 ms
  152. for any frames that specify a duration of <= 10 ms.
  153. See <rdar://problem/7689300> and <http://webkit.org/b/36082> for more information.
  154. See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.
  155. */
  156. if (duration < 0.011f) return 0.100f;
  157. return duration;
  158. }
  159. @end