UIImage+animatedGIF.m 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. //
  2. // UIImage+animatedGIF
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 28/05/17.
  6. // Copyright (c) 2017 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. // Found in Internet
  10. //
  11. // This program is free software: you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published by
  13. // the Free Software Foundation, either version 3 of the License, or
  14. // (at your option) any later version.
  15. //
  16. // This program is distributed in the hope that it will be useful,
  17. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. // GNU General Public License for more details.
  20. //
  21. // You should have received a copy of the GNU General Public License
  22. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. //
  24. #import "UIImage+animatedGIF.h"
  25. #if __has_feature(objc_arc)
  26. #define toCF (__bridge CFTypeRef)
  27. #define fromCF (__bridge id)
  28. #else
  29. #define toCF (CFTypeRef)
  30. #define fromCF (id)
  31. #endif
  32. @implementation UIImage (animatedGIF)
  33. static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) {
  34. int delayCentiseconds = 1;
  35. CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL);
  36. if (properties) {
  37. CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
  38. if (gifProperties) {
  39. NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime);
  40. if (number == NULL || [number doubleValue] == 0) {
  41. number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime);
  42. }
  43. if ([number doubleValue] > 0) {
  44. // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us.
  45. delayCentiseconds = (int)lrint([number doubleValue] * 100);
  46. }
  47. }
  48. CFRelease(properties);
  49. }
  50. return delayCentiseconds;
  51. }
  52. static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) {
  53. for (size_t i = 0; i < count; ++i) {
  54. imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL);
  55. delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i);
  56. }
  57. }
  58. static int sum(size_t const count, int const *const values) {
  59. int theSum = 0;
  60. for (size_t i = 0; i < count; ++i) {
  61. theSum += values[i];
  62. }
  63. return theSum;
  64. }
  65. static int pairGCD(int a, int b) {
  66. if (a < b)
  67. return pairGCD(b, a);
  68. while (true) {
  69. int const r = a % b;
  70. if (r == 0)
  71. return b;
  72. a = b;
  73. b = r;
  74. }
  75. }
  76. static int vectorGCD(size_t const count, int const *const values) {
  77. int gcd = values[0];
  78. for (size_t i = 1; i < count; ++i) {
  79. // 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.
  80. gcd = pairGCD(values[i], gcd);
  81. }
  82. return gcd;
  83. }
  84. static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) {
  85. int const gcd = vectorGCD(count, delayCentiseconds);
  86. size_t const frameCount = totalDurationCentiseconds / gcd;
  87. UIImage *frames[frameCount];
  88. for (size_t i = 0, f = 0; i < count; ++i) {
  89. UIImage *const frame = [UIImage imageWithCGImage:images[i]];
  90. for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) {
  91. frames[f++] = frame;
  92. }
  93. }
  94. return [NSArray arrayWithObjects:frames count:frameCount];
  95. }
  96. static void releaseImages(size_t const count, CGImageRef const images[count]) {
  97. for (size_t i = 0; i < count; ++i) {
  98. CGImageRelease(images[i]);
  99. }
  100. }
  101. static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) {
  102. size_t const count = CGImageSourceGetCount(source);
  103. CGImageRef images[count];
  104. int delayCentiseconds[count]; // in centiseconds
  105. createImagesAndDelays(source, count, images, delayCentiseconds);
  106. int const totalDurationCentiseconds = sum(count, delayCentiseconds);
  107. NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds);
  108. UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0];
  109. releaseImages(count, images);
  110. return animation;
  111. }
  112. static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) {
  113. if (source) {
  114. UIImage *const image = animatedImageWithAnimatedGIFImageSource(source);
  115. CFRelease(source);
  116. return image;
  117. } else {
  118. return nil;
  119. }
  120. }
  121. + (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data {
  122. return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL));
  123. }
  124. + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url {
  125. return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL));
  126. }
  127. @end