RCTAnimatedImage.m 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <ImageIO/ImageIO.h>
  8. #import <React/RCTAnimatedImage.h>
  9. @interface RCTGIFCoderFrame : NSObject
  10. @property (nonatomic, assign) NSUInteger index;
  11. @property (nonatomic, assign) NSTimeInterval duration;
  12. @end
  13. @implementation RCTGIFCoderFrame
  14. @end
  15. @implementation RCTAnimatedImage {
  16. CGImageSourceRef _imageSource;
  17. CGFloat _scale;
  18. NSUInteger _loopCount;
  19. NSUInteger _frameCount;
  20. NSArray<RCTGIFCoderFrame *> *_frames;
  21. }
  22. - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale
  23. {
  24. if (self = [super init]) {
  25. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
  26. if (!imageSource) {
  27. return nil;
  28. }
  29. BOOL framesValid = [self scanAndCheckFramesValidWithSource:imageSource];
  30. if (!framesValid) {
  31. CFRelease(imageSource);
  32. return nil;
  33. }
  34. _imageSource = imageSource;
  35. // grab image at the first index
  36. UIImage *image = [self animatedImageFrameAtIndex:0];
  37. if (!image) {
  38. return nil;
  39. }
  40. self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
  41. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  42. }
  43. return self;
  44. }
  45. - (BOOL)scanAndCheckFramesValidWithSource:(CGImageSourceRef)imageSource
  46. {
  47. if (!imageSource) {
  48. return NO;
  49. }
  50. NSUInteger frameCount = CGImageSourceGetCount(imageSource);
  51. NSUInteger loopCount = [self imageLoopCountWithSource:imageSource];
  52. NSMutableArray<RCTGIFCoderFrame *> *frames = [NSMutableArray array];
  53. for (size_t i = 0; i < frameCount; i++) {
  54. RCTGIFCoderFrame *frame = [[RCTGIFCoderFrame alloc] init];
  55. frame.index = i;
  56. frame.duration = [self frameDurationAtIndex:i source:imageSource];
  57. [frames addObject:frame];
  58. }
  59. _frameCount = frameCount;
  60. _loopCount = loopCount;
  61. _frames = [frames copy];
  62. return YES;
  63. }
  64. - (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source
  65. {
  66. NSUInteger loopCount = 1;
  67. NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
  68. NSDictionary *gifProperties = imageProperties[(__bridge NSString *)kCGImagePropertyGIFDictionary];
  69. if (gifProperties) {
  70. NSNumber *gifLoopCount = gifProperties[(__bridge NSString *)kCGImagePropertyGIFLoopCount];
  71. if (gifLoopCount != nil) {
  72. loopCount = gifLoopCount.unsignedIntegerValue;
  73. // A loop count of 1 means it should repeat twice, 2 means, thrice, etc.
  74. if (loopCount != 0) {
  75. loopCount++;
  76. }
  77. }
  78. }
  79. return loopCount;
  80. }
  81. - (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source
  82. {
  83. float frameDuration = 0.1f;
  84. CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
  85. if (!cfFrameProperties) {
  86. return frameDuration;
  87. }
  88. NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
  89. NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
  90. NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
  91. if (delayTimeUnclampedProp != nil && [delayTimeUnclampedProp floatValue] != 0.0f) {
  92. frameDuration = [delayTimeUnclampedProp floatValue];
  93. } else {
  94. NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
  95. if (delayTimeProp != nil) {
  96. frameDuration = [delayTimeProp floatValue];
  97. }
  98. }
  99. CFRelease(cfFrameProperties);
  100. return frameDuration;
  101. }
  102. - (NSUInteger)animatedImageLoopCount
  103. {
  104. return _loopCount;
  105. }
  106. - (NSUInteger)animatedImageFrameCount
  107. {
  108. return _frameCount;
  109. }
  110. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index
  111. {
  112. if (index >= _frameCount) {
  113. return 0;
  114. }
  115. return _frames[index].duration;
  116. }
  117. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index
  118. {
  119. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
  120. if (!imageRef) {
  121. return nil;
  122. }
  123. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:UIImageOrientationUp];
  124. CGImageRelease(imageRef);
  125. return image;
  126. }
  127. - (void)didReceiveMemoryWarning:(NSNotification *)notification
  128. {
  129. if (_imageSource) {
  130. for (size_t i = 0; i < _frameCount; i++) {
  131. CGImageSourceRemoveCacheAtIndex(_imageSource, i);
  132. }
  133. }
  134. }
  135. - (void)dealloc
  136. {
  137. if (_imageSource) {
  138. CFRelease(_imageSource);
  139. _imageSource = NULL;
  140. }
  141. }
  142. @end