UIImage+animatedGIF.m 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #import "UIImage+animatedGIF.h"
  2. #import <ImageIO/ImageIO.h>
  3. #if __has_feature(objc_arc)
  4. #define toCF (__bridge CFTypeRef)
  5. #define fromCF (__bridge id)
  6. #else
  7. #define toCF (CFTypeRef)
  8. #define fromCF (id)
  9. #endif
  10. @implementation UIImage (animatedGIF)
  11. static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) {
  12. int delayCentiseconds = 1;
  13. CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
  14. if (properties) {
  15. CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
  16. if (gifProperties) {
  17. NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime);
  18. if (number == NULL || [number doubleValue] == 0) {
  19. number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime);
  20. }
  21. if ([number doubleValue] > 0) {
  22. // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us.
  23. delayCentiseconds = (int)lrint([number doubleValue] * 100);
  24. }
  25. }
  26. CFRelease(properties);
  27. }
  28. return delayCentiseconds;
  29. }
  30. static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) {
  31. for (size_t i = 0; i < count; ++i) {
  32. imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL);
  33. delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i);
  34. }
  35. }
  36. static int sum(size_t const count, int const *const values) {
  37. int theSum = 0;
  38. for (size_t i = 0; i < count; ++i) {
  39. theSum += values[i];
  40. }
  41. return theSum;
  42. }
  43. static int pairGCD(int a, int b) {
  44. if (a < b)
  45. return pairGCD(b, a);
  46. while (true) {
  47. int const r = a % b;
  48. if (r == 0)
  49. return b;
  50. a = b;
  51. b = r;
  52. }
  53. }
  54. static int vectorGCD(size_t const count, int const *const values) {
  55. int gcd = values[0];
  56. for (size_t i = 1; i < count; ++i) {
  57. // Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element. By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments.
  58. gcd = pairGCD(values[i], gcd);
  59. }
  60. return gcd;
  61. }
  62. static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) {
  63. int const gcd = vectorGCD(count, delayCentiseconds);
  64. size_t const frameCount = totalDurationCentiseconds / gcd;
  65. UIImage *frames[frameCount];
  66. for (size_t i = 0, f = 0; i < count; ++i) {
  67. UIImage *const frame = [UIImage imageWithCGImage:images[i]];
  68. for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) {
  69. frames[f++] = frame;
  70. }
  71. }
  72. return [NSArray arrayWithObjects:frames count:frameCount];
  73. }
  74. static void releaseImages(size_t const count, CGImageRef const images[count]) {
  75. for (size_t i = 0; i < count; ++i) {
  76. CGImageRelease(images[i]);
  77. }
  78. }
  79. static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) {
  80. size_t const count = CGImageSourceGetCount(source);
  81. CGImageRef images[count];
  82. int delayCentiseconds[count]; // in centiseconds
  83. createImagesAndDelays(source, count, images, delayCentiseconds);
  84. int const totalDurationCentiseconds = sum(count, delayCentiseconds);
  85. NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds);
  86. UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0];
  87. releaseImages(count, images);
  88. return animation;
  89. }
  90. static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) {
  91. if (source) {
  92. UIImage *const image = animatedImageWithAnimatedGIFImageSource(source);
  93. CFRelease(source);
  94. return image;
  95. } else {
  96. return nil;
  97. }
  98. }
  99. + (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data {
  100. return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL));
  101. }
  102. + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url {
  103. return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL));
  104. }
  105. @end