SDWebImageGIFCoder.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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+Compatibility.h"
  10. #import "UIImage+WebCache.h"
  11. #import <ImageIO/ImageIO.h>
  12. #import "NSData+ImageContentType.h"
  13. #import "SDImageCoderHelper.h"
  14. #import "SDAnimatedImageRep.h"
  15. @interface SDGIFCoderFrame : NSObject
  16. @property (nonatomic, assign) NSUInteger index; // Frame index (zero based)
  17. @property (nonatomic, assign) NSTimeInterval duration; // Frame duration in seconds
  18. @end
  19. @implementation SDGIFCoderFrame
  20. @end
  21. @implementation SDWebImageGIFCoder {
  22. size_t _width, _height;
  23. CGImageSourceRef _imageSource;
  24. NSData *_imageData;
  25. CGFloat _scale;
  26. NSUInteger _loopCount;
  27. NSUInteger _frameCount;
  28. NSArray<SDGIFCoderFrame *> *_frames;
  29. BOOL _finished;
  30. }
  31. - (void)dealloc
  32. {
  33. if (_imageSource) {
  34. CFRelease(_imageSource);
  35. _imageSource = NULL;
  36. }
  37. #if SD_UIKIT
  38. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  39. #endif
  40. }
  41. - (void)didReceiveMemoryWarning:(NSNotification *)notification
  42. {
  43. if (_imageSource) {
  44. for (size_t i = 0; i < _frameCount; i++) {
  45. CGImageSourceRemoveCacheAtIndex(_imageSource, i);
  46. }
  47. }
  48. }
  49. + (instancetype)sharedCoder {
  50. static SDWebImageGIFCoder *coder;
  51. static dispatch_once_t onceToken;
  52. dispatch_once(&onceToken, ^{
  53. coder = [[SDWebImageGIFCoder alloc] init];
  54. });
  55. return coder;
  56. }
  57. #pragma mark - Decode
  58. - (BOOL)canDecodeFromData:(nullable NSData *)data {
  59. return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
  60. }
  61. - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDWebImageCoderOptions *)options {
  62. if (!data) {
  63. return nil;
  64. }
  65. CGFloat scale = 1;
  66. if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) {
  67. scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue];
  68. if (scale < 1) {
  69. scale = 1;
  70. }
  71. }
  72. #if SD_MAC
  73. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
  74. NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
  75. imageRep.size = size;
  76. NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
  77. [animatedImage addRepresentation:imageRep];
  78. return animatedImage;
  79. #else
  80. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
  81. if (!source) {
  82. return nil;
  83. }
  84. size_t count = CGImageSourceGetCount(source);
  85. UIImage *animatedImage;
  86. BOOL decodeFirstFrame = [options[SDWebImageCoderDecodeFirstFrameOnly] boolValue];
  87. if (decodeFirstFrame || count <= 1) {
  88. animatedImage = [[UIImage alloc] initWithData:data scale:scale];
  89. } else {
  90. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
  91. for (size_t i = 0; i < count; i++) {
  92. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
  93. if (!imageRef) {
  94. continue;
  95. }
  96. float duration = [self sd_frameDurationAtIndex:i source:source];
  97. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
  98. CGImageRelease(imageRef);
  99. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
  100. [frames addObject:frame];
  101. }
  102. NSUInteger loopCount = [self sd_imageLoopCountWithSource:source];
  103. animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
  104. animatedImage.sd_imageLoopCount = loopCount;
  105. }
  106. CFRelease(source);
  107. return animatedImage;
  108. #endif
  109. }
  110. - (NSUInteger)sd_imageLoopCountWithSource:(CGImageSourceRef)source {
  111. NSUInteger loopCount = 1;
  112. NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
  113. NSDictionary *gifProperties = [imageProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
  114. if (gifProperties) {
  115. NSNumber *gifLoopCount = [gifProperties valueForKey:(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount];
  116. if (gifLoopCount != nil) {
  117. loopCount = gifLoopCount.unsignedIntegerValue;
  118. }
  119. }
  120. return loopCount;
  121. }
  122. - (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
  123. float frameDuration = 0.1f;
  124. CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
  125. if (!cfFrameProperties) {
  126. return frameDuration;
  127. }
  128. NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
  129. NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
  130. NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
  131. if (delayTimeUnclampedProp != nil) {
  132. frameDuration = [delayTimeUnclampedProp floatValue];
  133. } else {
  134. NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
  135. if (delayTimeProp != nil) {
  136. frameDuration = [delayTimeProp floatValue];
  137. }
  138. }
  139. // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
  140. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
  141. // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
  142. // for more information.
  143. if (frameDuration < 0.011f) {
  144. frameDuration = 0.100f;
  145. }
  146. CFRelease(cfFrameProperties);
  147. return frameDuration;
  148. }
  149. #pragma mark - Progressive Decode
  150. - (BOOL)canIncrementalDecodeFromData:(NSData *)data {
  151. return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
  152. }
  153. - (instancetype)initIncrementalWithOptions:(nullable SDWebImageCoderOptions *)options {
  154. self = [super init];
  155. if (self) {
  156. CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
  157. _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceTypeIdentifierHint : (__bridge_transfer NSString *)imageUTType});
  158. CGFloat scale = 1;
  159. if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) {
  160. scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue];
  161. if (scale < 1) {
  162. scale = 1;
  163. }
  164. }
  165. _scale = scale;
  166. #if SD_UIKIT
  167. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  168. #endif
  169. }
  170. return self;
  171. }
  172. - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
  173. if (_finished) {
  174. return;
  175. }
  176. _imageData = data;
  177. _finished = finished;
  178. // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
  179. // Thanks to the author @Nyx0uf
  180. // Update the data source, we must pass ALL the data, not just the new bytes
  181. CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
  182. if (_width + _height == 0) {
  183. CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
  184. if (properties) {
  185. CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
  186. if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
  187. val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
  188. if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
  189. CFRelease(properties);
  190. }
  191. }
  192. // For animated image progressive decoding because the frame count and duration may be changed.
  193. [self scanAndCheckFramesValidWithImageSource:_imageSource];
  194. }
  195. - (UIImage *)incrementalDecodedImageWithOptions:(SDWebImageCoderOptions *)options {
  196. UIImage *image;
  197. if (_width + _height > 0) {
  198. // Create the image
  199. CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
  200. if (partialImageRef) {
  201. CGFloat scale = _scale;
  202. if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) {
  203. scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue];
  204. if (scale < 1) {
  205. scale = 1;
  206. }
  207. }
  208. #if SD_UIKIT || SD_WATCH
  209. image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:UIImageOrientationUp];
  210. #else
  211. image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:kCGImagePropertyOrientationUp];
  212. #endif
  213. CGImageRelease(partialImageRef);
  214. }
  215. }
  216. return image;
  217. }
  218. #pragma mark - Encode
  219. - (BOOL)canEncodeToFormat:(SDImageFormat)format {
  220. return (format == SDImageFormatGIF);
  221. }
  222. - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDWebImageCoderOptions *)options {
  223. if (!image) {
  224. return nil;
  225. }
  226. if (format != SDImageFormatGIF) {
  227. return nil;
  228. }
  229. NSMutableData *imageData = [NSMutableData data];
  230. CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat:SDImageFormatGIF];
  231. NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
  232. // Create an image destination. GIF does not support EXIF image orientation
  233. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count, NULL);
  234. if (!imageDestination) {
  235. // Handle failure.
  236. return nil;
  237. }
  238. NSMutableDictionary *properties = [NSMutableDictionary dictionary];
  239. double compressionQuality = 1;
  240. if ([options valueForKey:SDWebImageCoderEncodeCompressionQuality]) {
  241. compressionQuality = [[options valueForKey:SDWebImageCoderEncodeCompressionQuality] doubleValue];
  242. }
  243. [properties setValue:@(compressionQuality) forKey:(__bridge_transfer NSString *)kCGImageDestinationLossyCompressionQuality];
  244. BOOL encodeFirstFrame = [options[SDWebImageCoderEncodeFirstFrameOnly] boolValue];
  245. if (encodeFirstFrame || frames.count == 0) {
  246. // for static single GIF images
  247. CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
  248. } else {
  249. // for animated GIF images
  250. NSUInteger loopCount = image.sd_imageLoopCount;
  251. NSDictionary *gifProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFLoopCount : @(loopCount)};
  252. [properties setValue:gifProperties forKey:(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary];
  253. CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties);
  254. for (size_t i = 0; i < frames.count; i++) {
  255. SDImageFrame *frame = frames[i];
  256. float frameDuration = frame.duration;
  257. CGImageRef frameImageRef = frame.image.CGImage;
  258. NSDictionary *frameProperties = @{(__bridge_transfer NSString *)kCGImagePropertyGIFDictionary : @{(__bridge_transfer NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  259. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  260. }
  261. }
  262. // Finalize the destination.
  263. if (CGImageDestinationFinalize(imageDestination) == NO) {
  264. // Handle failure.
  265. imageData = nil;
  266. }
  267. CFRelease(imageDestination);
  268. return [imageData copy];
  269. }
  270. #pragma mark - SDWebImageAnimatedCoder
  271. - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDWebImageCoderOptions *)options {
  272. if (!data) {
  273. return nil;
  274. }
  275. self = [super init];
  276. if (self) {
  277. // use Image/IO cache because it's already keep a balance between CPU & memory
  278. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)});
  279. if (!imageSource) {
  280. return nil;
  281. }
  282. BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource];
  283. if (!framesValid) {
  284. CFRelease(imageSource);
  285. return nil;
  286. }
  287. CGFloat scale = 1;
  288. if ([options valueForKey:SDWebImageCoderDecodeScaleFactor]) {
  289. scale = [[options valueForKey:SDWebImageCoderDecodeScaleFactor] doubleValue];
  290. if (scale < 1) {
  291. scale = 1;
  292. }
  293. }
  294. _scale = scale;
  295. _imageSource = imageSource;
  296. _imageData = data;
  297. #if SD_UIKIT
  298. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  299. #endif
  300. }
  301. return self;
  302. }
  303. - (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
  304. if (!imageSource) {
  305. return NO;
  306. }
  307. NSUInteger frameCount = CGImageSourceGetCount(imageSource);
  308. NSUInteger loopCount = [self sd_imageLoopCountWithSource:imageSource];
  309. NSMutableArray<SDGIFCoderFrame *> *frames = [NSMutableArray array];
  310. for (size_t i = 0; i < frameCount; i++) {
  311. SDGIFCoderFrame *frame = [[SDGIFCoderFrame alloc] init];
  312. frame.index = i;
  313. frame.duration = [self sd_frameDurationAtIndex:i source:imageSource];
  314. [frames addObject:frame];
  315. }
  316. _frameCount = frameCount;
  317. _loopCount = loopCount;
  318. _frames = [frames copy];
  319. return YES;
  320. }
  321. - (NSData *)animatedImageData {
  322. return _imageData;
  323. }
  324. - (NSUInteger)animatedImageLoopCount {
  325. return _loopCount;
  326. }
  327. - (NSUInteger)animatedImageFrameCount {
  328. return _frameCount;
  329. }
  330. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  331. if (index >= _frameCount) {
  332. return 0;
  333. }
  334. return _frames[index].duration;
  335. }
  336. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  337. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
  338. if (!imageRef) {
  339. return nil;
  340. }
  341. // Image/IO create CGImage does not decode, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
  342. CGImageRef newImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
  343. if (!newImageRef) {
  344. newImageRef = imageRef;
  345. } else {
  346. CGImageRelease(imageRef);
  347. }
  348. #if SD_MAC
  349. UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:kCGImagePropertyOrientationUp];
  350. #else
  351. UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp];
  352. #endif
  353. CGImageRelease(newImageRef);
  354. return image;
  355. }
  356. @end