SDWebImageGIFCoder.m 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 NSString *)kCGImagePropertyGIFDictionary];
  62. if (gifProperties) {
  63. NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge NSString *)kCGImagePropertyGIFLoopCount];
  64. if (gifLoopCount != nil) {
  65. loopCount = gifLoopCount.unsignedIntegerValue;
  66. }
  67. }
  68. animatedImage = [SDWebImageCoderHelper animatedImageWithFrames:frames];
  69. animatedImage.sd_imageLoopCount = loopCount;
  70. animatedImage.sd_imageFormat = SDImageFormatGIF;
  71. }
  72. CFRelease(source);
  73. return animatedImage;
  74. #endif
  75. }
  76. - (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
  77. float frameDuration = 0.1f;
  78. CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
  79. if (!cfFrameProperties) {
  80. return frameDuration;
  81. }
  82. NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
  83. NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
  84. NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
  85. if (delayTimeUnclampedProp != nil) {
  86. frameDuration = [delayTimeUnclampedProp floatValue];
  87. } else {
  88. NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
  89. if (delayTimeProp != nil) {
  90. frameDuration = [delayTimeProp floatValue];
  91. }
  92. }
  93. // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
  94. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
  95. // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
  96. // for more information.
  97. if (frameDuration < 0.011f) {
  98. frameDuration = 0.100f;
  99. }
  100. CFRelease(cfFrameProperties);
  101. return frameDuration;
  102. }
  103. - (UIImage *)decompressedImageWithImage:(UIImage *)image
  104. data:(NSData *__autoreleasing _Nullable *)data
  105. options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
  106. // GIF do not decompress
  107. return image;
  108. }
  109. #pragma mark - Encode
  110. - (BOOL)canEncodeToFormat:(SDImageFormat)format {
  111. return (format == SDImageFormatGIF);
  112. }
  113. - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
  114. if (!image) {
  115. return nil;
  116. }
  117. if (format != SDImageFormatGIF) {
  118. return nil;
  119. }
  120. NSMutableData *imageData = [NSMutableData data];
  121. CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
  122. NSArray<SDWebImageFrame *> *frames = [SDWebImageCoderHelper framesFromAnimatedImage:image];
  123. // Create an image destination. GIF does not support EXIF image orientation
  124. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
  125. if (!imageDestination) {
  126. // Handle failure.
  127. return nil;
  128. }
  129. if (frames.count == 0) {
  130. // for static single GIF images
  131. CGImageDestinationAddImage(imageDestination, image.CGImage, nil);
  132. } else {
  133. // for animated GIF images
  134. NSUInteger loopCount = image.sd_imageLoopCount;
  135. NSDictionary *gifProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary: @{(__bridge NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)}};
  136. CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)gifProperties);
  137. for (size_t i = 0; i < frames.count; i++) {
  138. SDWebImageFrame *frame = frames[i];
  139. float frameDuration = frame.duration;
  140. CGImageRef frameImageRef = frame.image.CGImage;
  141. NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  142. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  143. }
  144. }
  145. // Finalize the destination.
  146. if (CGImageDestinationFinalize(imageDestination) == NO) {
  147. // Handle failure.
  148. imageData = nil;
  149. }
  150. CFRelease(imageDestination);
  151. return [imageData copy];
  152. }
  153. @end