SDWebImageGIFCoder.m 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 "SDWebImageGIFCoder.h"
  9. #import "NSImage+WebCache.h"
  10. #import <ImageIO/ImageIO.h>
  11. #import "NSData+ImageContentType.h"
  12. #import "UIImage+MultiFormat.h"
  13. #import "SDWebImageCoderHelper.h"
  14. #import "SDAnimatedImageRep.h"
  15. @implementation SDWebImageGIFCoder
  16. + (instancetype)sharedCoder {
  17. static SDWebImageGIFCoder *coder;
  18. static dispatch_once_t onceToken;
  19. dispatch_once(&onceToken, ^{
  20. coder = [[SDWebImageGIFCoder alloc] init];
  21. });
  22. return coder;
  23. }
  24. #pragma mark - Decode
  25. - (BOOL)canDecodeFromData:(nullable NSData *)data {
  26. return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
  27. }
  28. - (UIImage *)decodedImageWithData:(NSData *)data {
  29. if (!data) {
  30. return nil;
  31. }
  32. #if SD_MAC
  33. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
  34. NSImage *animatedImage = [[NSImage alloc] initWithSize:imageRep.size];
  35. [animatedImage addRepresentation:imageRep];
  36. return animatedImage;
  37. #else
  38. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
  39. if (!source) {
  40. return nil;
  41. }
  42. size_t count = CGImageSourceGetCount(source);
  43. UIImage *animatedImage;
  44. if (count <= 1) {
  45. animatedImage = [[UIImage alloc] initWithData:data];
  46. } else {
  47. NSMutableArray<SDWebImageFrame *> *frames = [NSMutableArray array];
  48. for (size_t i = 0; i < count; i++) {
  49. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
  50. if (!imageRef) {
  51. continue;
  52. }
  53. float duration = [self sd_frameDurationAtIndex:i source:source];
  54. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
  55. CGImageRelease(imageRef);
  56. SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:image duration:duration];
  57. [frames addObject:frame];
  58. }
  59. NSUInteger loopCount = 1;
  60. NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
  61. NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
  62. if (gifProperties) {
  63. NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount];
  64. if (gifLoopCount != nil) {
  65. loopCount = gifLoopCount.unsignedIntegerValue;
  66. }
  67. }
  68. animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
  69. animatedImage.sd_imageLoopCount = loopCount;
  70. }
  71. CFRelease(source);
  72. return animatedImage;
  73. #endif
  74. }
  75. - (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
  76. float frameDuration = 0.1f;
  77. CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
  78. if (!cfFrameProperties) {
  79. return frameDuration;
  80. }
  81. NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
  82. NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
  83. NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
  84. if (delayTimeUnclampedProp != nil) {
  85. frameDuration = [delayTimeUnclampedProp floatValue];
  86. } else {
  87. NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
  88. if (delayTimeProp != nil) {
  89. frameDuration = [delayTimeProp floatValue];
  90. }
  91. }
  92. // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
  93. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
  94. // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
  95. // for more information.
  96. if (frameDuration < 0.011f) {
  97. frameDuration = 0.100f;
  98. }
  99. CFRelease(cfFrameProperties);
  100. return frameDuration;
  101. }
  102. - (UIImage *)decompressedImageWithImage:(UIImage *)image
  103. data:(NSData *__autoreleasing _Nullable *)data
  104. options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
  105. // GIF do not decompress
  106. return image;
  107. }
  108. #pragma mark - Encode
  109. - (BOOL)canEncodeToFormat:(SDImageFormat)format {
  110. return (format == SDImageFormatGIF);
  111. }
  112. - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
  113. if (!image) {
  114. return nil;
  115. }
  116. if (format != SDImageFormatGIF) {
  117. return nil;
  118. }
  119. NSMutableData *imageData = [NSMutableData data];
  120. CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
  121. NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
  122. // Create an image destination. GIF does not support EXIF image orientation
  123. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
  124. if (!imageDestination) {
  125. // Handle failure.
  126. return nil;
  127. }
  128. if (frames.count == 0) {
  129. // for static single GIF images
  130. CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
  131. } else {
  132. // for animated GIF images
  133. NSUInteger loopCount = image.sd_imageLoopCount;
  134. NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary: @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
  135. CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
  136. for (size_t i = 0; i < frames.count; i++) {
  137. SDWebImageFrame *frame = frames[i];
  138. float frameDuration = frame.duration;
  139. CGImageRef frameImageRef = frame.image.CGImage;
  140. NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  141. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  142. }
  143. }
  144. // Finalize the destination.
  145. if (CGImageDestinationFinalize(imageDestination) == NO) {
  146. // Handle failure.
  147. imageData = nil;
  148. }
  149. CFRelease(imageDestination);
  150. return [imageData copy];
  151. }
  152. @end