SDAnimatedImageRep.m 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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 "SDAnimatedImageRep.h"
  9. #if SD_MAC
  10. #import "SDImageIOAnimatedCoderInternal.h"
  11. #import "SDImageGIFCoder.h"
  12. #import "SDImageAPNGCoder.h"
  13. #import "SDImageHEICCoder.h"
  14. #import "SDImageAWebPCoder.h"
  15. @interface SDAnimatedImageRep ()
  16. /// This wrap the animated image frames for legacy animated image coder API (`encodedDataWithImage:`).
  17. @property (nonatomic, readwrite, weak) NSArray<SDImageFrame *> *frames;
  18. @end
  19. @implementation SDAnimatedImageRep {
  20. CGImageSourceRef _imageSource;
  21. }
  22. - (void)dealloc {
  23. if (_imageSource) {
  24. CFRelease(_imageSource);
  25. _imageSource = NULL;
  26. }
  27. }
  28. - (instancetype)copyWithZone:(NSZone *)zone {
  29. SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
  30. CFRetain(imageRep->_imageSource);
  31. return imageRep;
  32. }
  33. // `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer
  34. + (instancetype)imageRepWithData:(NSData *)data {
  35. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
  36. return imageRep;
  37. }
  38. // We should override init method for `NSBitmapImageRep` to do initialize about animated image format
  39. #pragma clang diagnostic push
  40. #pragma clang diagnostic ignored "-Wunguarded-availability"
  41. - (instancetype)initWithData:(NSData *)data {
  42. self = [super initWithData:data];
  43. if (self) {
  44. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) data, NULL);
  45. if (!imageSource) {
  46. return self;
  47. }
  48. _imageSource = imageSource;
  49. NSUInteger frameCount = CGImageSourceGetCount(imageSource);
  50. if (frameCount <= 1) {
  51. return self;
  52. }
  53. CFStringRef type = CGImageSourceGetType(imageSource);
  54. if (!type) {
  55. return self;
  56. }
  57. if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
  58. // GIF
  59. // Fix the `NSBitmapImageRep` GIF loop count calculation issue
  60. // Which will use 0 when there are no loop count information metadata in GIF data
  61. NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource];
  62. [self setProperty:NSImageLoopCount withValue:@(loopCount)];
  63. } else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
  64. // APNG
  65. // Do initialize about frame count, current frame/duration and loop count
  66. [self setProperty:NSImageFrameCount withValue:@(frameCount)];
  67. [self setProperty:NSImageCurrentFrame withValue:@(0)];
  68. NSUInteger loopCount = [SDImageAPNGCoder imageLoopCountWithSource:imageSource];
  69. [self setProperty:NSImageLoopCount withValue:@(loopCount)];
  70. } else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
  71. // HEIC
  72. // Do initialize about frame count, current frame/duration and loop count
  73. [self setProperty:NSImageFrameCount withValue:@(frameCount)];
  74. [self setProperty:NSImageCurrentFrame withValue:@(0)];
  75. NSUInteger loopCount = [SDImageHEICCoder imageLoopCountWithSource:imageSource];
  76. [self setProperty:NSImageLoopCount withValue:@(loopCount)];
  77. } else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
  78. // WebP
  79. // Do initialize about frame count, current frame/duration and loop count
  80. [self setProperty:NSImageFrameCount withValue:@(frameCount)];
  81. [self setProperty:NSImageCurrentFrame withValue:@(0)];
  82. NSUInteger loopCount = [SDImageAWebPCoder imageLoopCountWithSource:imageSource];
  83. [self setProperty:NSImageLoopCount withValue:@(loopCount)];
  84. }
  85. }
  86. return self;
  87. }
  88. // `NSBitmapImageRep` will use `kCGImagePropertyGIFDelayTime` whenever you call `setProperty:withValue:` with `NSImageCurrentFrame` to change the current frame. We override it and use the actual `kCGImagePropertyGIFUnclampedDelayTime` if need.
  89. - (void)setProperty:(NSBitmapImageRepPropertyKey)property withValue:(id)value {
  90. [super setProperty:property withValue:value];
  91. if ([property isEqualToString:NSImageCurrentFrame]) {
  92. // Access the image source
  93. CGImageSourceRef imageSource = _imageSource;
  94. if (!imageSource) {
  95. return;
  96. }
  97. // Check format type
  98. CFStringRef type = CGImageSourceGetType(imageSource);
  99. if (!type) {
  100. return;
  101. }
  102. NSUInteger index = [value unsignedIntegerValue];
  103. NSTimeInterval frameDuration = 0;
  104. if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
  105. // GIF
  106. frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource];
  107. } else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
  108. // APNG
  109. frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource];
  110. } else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
  111. // HEIC
  112. frameDuration = [SDImageHEICCoder frameDurationAtIndex:index source:imageSource];
  113. } else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
  114. // WebP
  115. frameDuration = [SDImageAWebPCoder frameDurationAtIndex:index source:imageSource];
  116. }
  117. if (!frameDuration) {
  118. return;
  119. }
  120. // Reset super frame duration with the actual frame duration
  121. [super setProperty:NSImageCurrentFrameDuration withValue:@(frameDuration)];
  122. }
  123. }
  124. #pragma clang diagnostic pop
  125. @end
  126. #endif