SDWebImageCoderHelper.m 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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 "SDWebImageCoderHelper.h"
  9. #import "SDWebImageFrame.h"
  10. #import "UIImage+MultiFormat.h"
  11. #import "NSImage+WebCache.h"
  12. #import <ImageIO/ImageIO.h>
  13. #import "SDAnimatedImageRep.h"
  14. @implementation SDWebImageCoderHelper
  15. + (UIImage *)animatedImageWithFrames:(NSArray<SDWebImageFrame *> *)frames {
  16. NSUInteger frameCount = frames.count;
  17. if (frameCount == 0) {
  18. return nil;
  19. }
  20. UIImage *animatedImage;
  21. #if SD_UIKIT || SD_WATCH
  22. NSUInteger durations[frameCount];
  23. for (size_t i = 0; i < frameCount; i++) {
  24. durations[i] = frames[i].duration * 1000;
  25. }
  26. NSUInteger const gcd = gcdArray(frameCount, durations);
  27. __block NSUInteger totalDuration = 0;
  28. NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
  29. [frames enumerateObjectsUsingBlock:^(SDWebImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
  30. UIImage *image = frame.image;
  31. NSUInteger duration = frame.duration * 1000;
  32. totalDuration += duration;
  33. NSUInteger repeatCount;
  34. if (gcd) {
  35. repeatCount = duration / gcd;
  36. } else {
  37. repeatCount = 1;
  38. }
  39. for (size_t i = 0; i < repeatCount; ++i) {
  40. [animatedImages addObject:image];
  41. }
  42. }];
  43. animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f];
  44. #else
  45. NSMutableData *imageData = [NSMutableData data];
  46. CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
  47. // Create an image destination. GIF does not support EXIF image orientation
  48. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
  49. if (!imageDestination) {
  50. // Handle failure.
  51. return nil;
  52. }
  53. for (size_t i = 0; i < frameCount; i++) {
  54. @autoreleasepool {
  55. SDWebImageFrame *frame = frames[i];
  56. float frameDuration = frame.duration;
  57. CGImageRef frameImageRef = frame.image.CGImage;
  58. NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  59. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  60. }
  61. }
  62. // Finalize the destination.
  63. if (CGImageDestinationFinalize(imageDestination) == NO) {
  64. // Handle failure.
  65. CFRelease(imageDestination);
  66. return nil;
  67. }
  68. CFRelease(imageDestination);
  69. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
  70. animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
  71. [animatedImage addRepresentation:imageRep];
  72. #endif
  73. return animatedImage;
  74. }
  75. + (NSArray<SDWebImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
  76. if (!animatedImage) {
  77. return nil;
  78. }
  79. NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
  80. NSUInteger frameCount = 0;
  81. #if SD_UIKIT || SD_WATCH
  82. NSArray<UIImage *> *animatedImages = animatedImage.images;
  83. frameCount = animatedImages.count;
  84. if (frameCount == 0) {
  85. return nil;
  86. }
  87. NSTimeInterval avgDuration = animatedImage.duration / frameCount;
  88. if (avgDuration == 0) {
  89. avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
  90. }
  91. __block NSUInteger index = 0;
  92. __block NSUInteger repeatCount = 1;
  93. __block UIImage *previousImage = animatedImages.firstObject;
  94. [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
  95. // ignore first
  96. if (idx == 0) {
  97. return;
  98. }
  99. if ([image isEqual:previousImage]) {
  100. repeatCount++;
  101. } else {
  102. SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  103. [frames addObject:frame];
  104. repeatCount = 1;
  105. index++;
  106. }
  107. previousImage = image;
  108. // last one
  109. if (idx == frameCount - 1) {
  110. SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  111. [frames addObject:frame];
  112. }
  113. }];
  114. #else
  115. NSBitmapImageRep *bitmapRep;
  116. for (NSImageRep *imageRep in animatedImage.representations) {
  117. if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
  118. bitmapRep = (NSBitmapImageRep *)imageRep;
  119. break;
  120. }
  121. }
  122. if (bitmapRep) {
  123. frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
  124. }
  125. if (frameCount == 0) {
  126. return nil;
  127. }
  128. for (size_t i = 0; i < frameCount; i++) {
  129. @autoreleasepool {
  130. // NSBitmapImageRep need to manually change frame. "Good taste" API
  131. [bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
  132. float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
  133. NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapRep.CGImage size:CGSizeZero];
  134. SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:frameImage duration:frameDuration];
  135. [frames addObject:frame];
  136. }
  137. }
  138. #endif
  139. return frames;
  140. }
  141. #if SD_UIKIT || SD_WATCH
  142. // Convert an EXIF image orientation to an iOS one.
  143. + (UIImageOrientation)imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
  144. // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
  145. UIImageOrientation imageOrientation = UIImageOrientationUp;
  146. switch (exifOrientation) {
  147. case 1:
  148. imageOrientation = UIImageOrientationUp;
  149. break;
  150. case 3:
  151. imageOrientation = UIImageOrientationDown;
  152. break;
  153. case 8:
  154. imageOrientation = UIImageOrientationLeft;
  155. break;
  156. case 6:
  157. imageOrientation = UIImageOrientationRight;
  158. break;
  159. case 2:
  160. imageOrientation = UIImageOrientationUpMirrored;
  161. break;
  162. case 4:
  163. imageOrientation = UIImageOrientationDownMirrored;
  164. break;
  165. case 5:
  166. imageOrientation = UIImageOrientationLeftMirrored;
  167. break;
  168. case 7:
  169. imageOrientation = UIImageOrientationRightMirrored;
  170. break;
  171. default:
  172. break;
  173. }
  174. return imageOrientation;
  175. }
  176. // Convert an iOS orientation to an EXIF image orientation.
  177. + (NSInteger)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
  178. // CGImagePropertyOrientation is available on iOS 8 above. Currently kept for compatibility
  179. NSInteger exifOrientation = 1;
  180. switch (imageOrientation) {
  181. case UIImageOrientationUp:
  182. exifOrientation = 1;
  183. break;
  184. case UIImageOrientationDown:
  185. exifOrientation = 3;
  186. break;
  187. case UIImageOrientationLeft:
  188. exifOrientation = 8;
  189. break;
  190. case UIImageOrientationRight:
  191. exifOrientation = 6;
  192. break;
  193. case UIImageOrientationUpMirrored:
  194. exifOrientation = 2;
  195. break;
  196. case UIImageOrientationDownMirrored:
  197. exifOrientation = 4;
  198. break;
  199. case UIImageOrientationLeftMirrored:
  200. exifOrientation = 5;
  201. break;
  202. case UIImageOrientationRightMirrored:
  203. exifOrientation = 7;
  204. break;
  205. default:
  206. break;
  207. }
  208. return exifOrientation;
  209. }
  210. #endif
  211. #pragma mark - Helper Fuction
  212. #if SD_UIKIT || SD_WATCH
  213. static NSUInteger gcd(NSUInteger a, NSUInteger b) {
  214. NSUInteger c;
  215. while (a != 0) {
  216. c = a;
  217. a = b % a;
  218. b = c;
  219. }
  220. return b;
  221. }
  222. static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
  223. if (count == 0) {
  224. return 0;
  225. }
  226. NSUInteger result = values[0];
  227. for (size_t i = 1; i < count; ++i) {
  228. result = gcd(values[i], result);
  229. }
  230. return result;
  231. }
  232. #endif
  233. @end