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