YLGIFImage.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. //
  2. // YLGIFImage.m
  3. // YLGIFImage
  4. //
  5. // Created by Yong Li on 14-3-2.
  6. // Copyright (c) 2014年 Yong Li. All rights reserved.
  7. //
  8. #import "YLGIFImage.h"
  9. #import <MobileCoreServices/MobileCoreServices.h>
  10. #import <ImageIO/ImageIO.h>
  11. //Define FLT_EPSILON because, reasons.
  12. //Actually, I don't know why but it seems under certain circumstances it is not defined
  13. #ifndef FLT_EPSILON
  14. #define FLT_EPSILON __FLT_EPSILON__
  15. #endif
  16. inline static NSTimeInterval CGImageSourceGetGifFrameDelay(CGImageSourceRef imageSource, NSUInteger index)
  17. {
  18. NSTimeInterval frameDuration = 0;
  19. CFDictionaryRef theImageProperties;
  20. if ((theImageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL))) {
  21. CFDictionaryRef gifProperties;
  22. if (CFDictionaryGetValueIfPresent(theImageProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties)) {
  23. const void *frameDurationValue;
  24. if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFUnclampedDelayTime, &frameDurationValue)) {
  25. frameDuration = [(__bridge NSNumber *)frameDurationValue doubleValue];
  26. if (frameDuration <= 0) {
  27. if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFDelayTime, &frameDurationValue)) {
  28. frameDuration = [(__bridge NSNumber *)frameDurationValue doubleValue];
  29. }
  30. }
  31. }
  32. }
  33. CFRelease(theImageProperties);
  34. }
  35. #ifndef OLExactGIFRepresentation
  36. //Implement as Browsers do.
  37. //See: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
  38. //Also: http://blogs.msdn.com/b/ieinternals/archive/2010/06/08/animated-gifs-slow-down-to-under-20-frames-per-second.aspx
  39. if (frameDuration < 0.02 - FLT_EPSILON) {
  40. frameDuration = 0.1;
  41. }
  42. #endif
  43. return frameDuration;
  44. }
  45. inline static BOOL CGImageSourceContainsAnimatedGif(CGImageSourceRef imageSource)
  46. {
  47. return imageSource && UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF) && CGImageSourceGetCount(imageSource) > 1;
  48. }
  49. inline static BOOL isRetinaFilePath(NSString *path)
  50. {
  51. NSRange retinaSuffixRange = [[path lastPathComponent] rangeOfString:@"@2x" options:NSCaseInsensitiveSearch];
  52. return retinaSuffixRange.length && retinaSuffixRange.location != NSNotFound;
  53. }
  54. @interface YLGIFImage ()
  55. @property (nonatomic, readwrite) NSMutableArray *images;
  56. @property (nonatomic, readwrite) NSTimeInterval *frameDurations;
  57. @property (nonatomic, readwrite) NSTimeInterval totalDuration;
  58. @property (nonatomic, readwrite) NSUInteger loopCount;
  59. @property (nonatomic, readwrite) CGImageSourceRef incrementalSource;
  60. @end
  61. static NSUInteger _prefetchedNum = 10;
  62. @implementation YLGIFImage
  63. {
  64. dispatch_queue_t readFrameQueue;
  65. CGImageSourceRef _imageSourceRef;
  66. CGFloat _scale;
  67. }
  68. @synthesize images;
  69. #pragma mark - Class Methods
  70. + (id)imageNamed:(NSString *)name
  71. {
  72. NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
  73. return ([[NSFileManager defaultManager] fileExistsAtPath:path]) ? [self imageWithContentsOfFile:path] : nil;
  74. }
  75. + (id)imageWithContentsOfFile:(NSString *)path
  76. {
  77. return [self imageWithData:[NSData dataWithContentsOfFile:path]
  78. scale:isRetinaFilePath(path) ? 2.0f : 1.0f];
  79. }
  80. + (id)imageWithData:(NSData *)data
  81. {
  82. return [self imageWithData:data scale:1.0f];
  83. }
  84. + (id)imageWithData:(NSData *)data scale:(CGFloat)scale
  85. {
  86. if (!data) {
  87. return nil;
  88. }
  89. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(data), NULL);
  90. UIImage *image;
  91. if (CGImageSourceContainsAnimatedGif(imageSource)) {
  92. image = [[self alloc] initWithCGImageSource:imageSource scale:scale];
  93. } else {
  94. image = [super imageWithData:data scale:scale];
  95. }
  96. if (imageSource) {
  97. CFRelease(imageSource);
  98. }
  99. return image;
  100. }
  101. #pragma mark - Initialization methods
  102. - (id)initWithContentsOfFile:(NSString *)path
  103. {
  104. return [self initWithData:[NSData dataWithContentsOfFile:path]
  105. scale:isRetinaFilePath(path) ? 2.0f : 1.0f];
  106. }
  107. - (id)initWithData:(NSData *)data
  108. {
  109. return [self initWithData:data scale:1.0f];
  110. }
  111. - (id)initWithData:(NSData *)data scale:(CGFloat)scale
  112. {
  113. if (!data) {
  114. return nil;
  115. }
  116. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(data), NULL);
  117. if (CGImageSourceContainsAnimatedGif(imageSource)) {
  118. self = [self initWithCGImageSource:imageSource scale:scale];
  119. } else {
  120. if (scale == 1.0f) {
  121. self = [super initWithData:data];
  122. } else {
  123. self = [super initWithData:data scale:scale];
  124. }
  125. }
  126. if (imageSource) {
  127. CFRelease(imageSource);
  128. }
  129. return self;
  130. }
  131. - (id)initWithCGImageSource:(CGImageSourceRef)imageSource scale:(CGFloat)scale
  132. {
  133. self = [super init];
  134. if (!imageSource || !self) {
  135. return nil;
  136. }
  137. CFRetain(imageSource);
  138. NSUInteger numberOfFrames = CGImageSourceGetCount(imageSource);
  139. NSDictionary *imageProperties = CFBridgingRelease(CGImageSourceCopyProperties(imageSource, NULL));
  140. NSDictionary *gifProperties = [imageProperties objectForKey:(NSString *)kCGImagePropertyGIFDictionary];
  141. self.frameDurations = (NSTimeInterval *)malloc(numberOfFrames * sizeof(NSTimeInterval));
  142. self.loopCount = [gifProperties[(NSString *)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
  143. self.images = [NSMutableArray arrayWithCapacity:numberOfFrames];
  144. NSNull *aNull = [NSNull null];
  145. for (NSUInteger i = 0; i < numberOfFrames; ++i) {
  146. [self.images addObject:aNull];
  147. NSTimeInterval frameDuration = CGImageSourceGetGifFrameDelay(imageSource, i);
  148. self.frameDurations[i] = frameDuration;
  149. self.totalDuration += frameDuration;
  150. }
  151. //CFTimeInterval start = CFAbsoluteTimeGetCurrent();
  152. // Load first frame
  153. NSUInteger num = MIN(_prefetchedNum, numberOfFrames);
  154. for (NSUInteger i=0; i<num; i++) {
  155. CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
  156. if (image != NULL) {
  157. [self.images replaceObjectAtIndex:i withObject:[UIImage imageWithCGImage:image scale:_scale orientation:UIImageOrientationUp]];
  158. CFRelease(image);
  159. } else {
  160. [self.images replaceObjectAtIndex:i withObject:[NSNull null]];
  161. }
  162. }
  163. _imageSourceRef = imageSource;
  164. CFRetain(_imageSourceRef);
  165. CFRelease(imageSource);
  166. //});
  167. _scale = scale;
  168. readFrameQueue = dispatch_queue_create("com.ronnie.gifreadframe", DISPATCH_QUEUE_SERIAL);
  169. return self;
  170. }
  171. - (UIImage*)getFrameWithIndex:(NSUInteger)idx
  172. {
  173. // if([self.images[idx] isKindOfClass:[NSNull class]])
  174. // return nil;
  175. UIImage* frame = nil;
  176. @synchronized(self.images) {
  177. frame = self.images[idx];
  178. }
  179. if(!frame) {
  180. CGImageRef image = CGImageSourceCreateImageAtIndex(_imageSourceRef, idx, NULL);
  181. if (image != NULL) {
  182. frame = [UIImage imageWithCGImage:image scale:_scale orientation:UIImageOrientationUp];
  183. CFRelease(image);
  184. }
  185. }
  186. if(self.images.count > _prefetchedNum) {
  187. if(idx != 0) {
  188. [self.images replaceObjectAtIndex:idx withObject:[NSNull null]];
  189. }
  190. NSUInteger nextReadIdx = (idx + _prefetchedNum);
  191. __weak typeof(self)ws = self;
  192. for(NSUInteger i=idx+1; i<=nextReadIdx; i++) {
  193. NSUInteger _idx = i%self.images.count;
  194. if([self.images[_idx] isKindOfClass:[NSNull class]]) {
  195. dispatch_async(readFrameQueue, ^{
  196. CGImageRef image = CGImageSourceCreateImageAtIndex(_imageSourceRef, _idx, NULL);
  197. @synchronized(self.images) {
  198. if (image != NULL) {
  199. [ws.images replaceObjectAtIndex:_idx withObject:[UIImage imageWithCGImage:image scale:_scale orientation:UIImageOrientationUp]];
  200. CFRelease(image);
  201. } else {
  202. [ws.images replaceObjectAtIndex:_idx withObject:[NSNull null]];
  203. }
  204. }
  205. });
  206. }
  207. }
  208. }
  209. return frame;
  210. }
  211. #pragma mark - Compatibility methods
  212. - (CGSize)size
  213. {
  214. if (self.images.count) {
  215. return [[self.images objectAtIndex:0] size];
  216. }
  217. return [super size];
  218. }
  219. - (CGImageRef)CGImage
  220. {
  221. if (self.images.count) {
  222. return [[self.images objectAtIndex:0] CGImage];
  223. } else {
  224. return [super CGImage];
  225. }
  226. }
  227. - (UIImageOrientation)imageOrientation
  228. {
  229. if (self.images.count) {
  230. return [[self.images objectAtIndex:0] imageOrientation];
  231. } else {
  232. return [super imageOrientation];
  233. }
  234. }
  235. - (CGFloat)scale
  236. {
  237. if (self.images.count) {
  238. return [(UIImage *)[self.images objectAtIndex:0] scale];
  239. } else {
  240. return [super scale];
  241. }
  242. }
  243. - (NSTimeInterval)duration
  244. {
  245. return self.images ? self.totalDuration : [super duration];
  246. }
  247. - (void)dealloc {
  248. if(_imageSourceRef) {
  249. CFRelease(_imageSourceRef);
  250. }
  251. free(_frameDurations);
  252. if (_incrementalSource) {
  253. CFRelease(_incrementalSource);
  254. }
  255. }
  256. @end