SDImageIOAnimatedCoder.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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 "SDImageIOAnimatedCoder.h"
  9. #import "NSImage+Compatibility.h"
  10. #import "UIImage+Metadata.h"
  11. #import "NSData+ImageContentType.h"
  12. #import "SDImageCoderHelper.h"
  13. #import "SDAnimatedImageRep.h"
  14. #import "UIImage+ForceDecode.h"
  15. #import "SDInternalMacros.h"
  16. #import <ImageIO/ImageIO.h>
  17. #import <CoreServices/CoreServices.h>
  18. #if SD_CHECK_CGIMAGE_RETAIN_SOURCE
  19. #import <dlfcn.h>
  20. // SPI to check thread safe during Example and Test
  21. static CGImageSourceRef (*SDCGImageGetImageSource)(CGImageRef);
  22. #endif
  23. // Specify File Size for lossy format encoding, like JPEG
  24. static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
  25. // This strip the un-wanted CGImageProperty, like the internal CGImageSourceRef in iOS 15+
  26. // However, CGImageCreateCopy still keep those CGImageProperty, not suit for our use case
  27. static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
  28. if (!image) return nil;
  29. size_t width = CGImageGetWidth(image);
  30. size_t height = CGImageGetHeight(image);
  31. size_t bitsPerComponent = CGImageGetBitsPerComponent(image);
  32. size_t bitsPerPixel = CGImageGetBitsPerPixel(image);
  33. size_t bytesPerRow = CGImageGetBytesPerRow(image);
  34. CGColorSpaceRef space = CGImageGetColorSpace(image);
  35. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(image);
  36. CGDataProviderRef provider = CGImageGetDataProvider(image);
  37. const CGFloat *decode = CGImageGetDecode(image);
  38. bool shouldInterpolate = CGImageGetShouldInterpolate(image);
  39. CGColorRenderingIntent intent = CGImageGetRenderingIntent(image);
  40. CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, provider, decode, shouldInterpolate, intent);
  41. return newImage;
  42. }
  43. @interface SDImageIOCoderFrame : NSObject
  44. @property (nonatomic, assign) NSUInteger index; // Frame index (zero based)
  45. @property (nonatomic, assign) NSTimeInterval duration; // Frame duration in seconds
  46. @end
  47. @implementation SDImageIOCoderFrame
  48. @end
  49. @implementation SDImageIOAnimatedCoder {
  50. size_t _width, _height;
  51. CGImageSourceRef _imageSource;
  52. BOOL _incremental;
  53. SD_LOCK_DECLARE(_lock); // Lock only apply for incremental animation decoding
  54. NSData *_imageData;
  55. CGFloat _scale;
  56. NSUInteger _loopCount;
  57. NSUInteger _frameCount;
  58. NSArray<SDImageIOCoderFrame *> *_frames;
  59. BOOL _finished;
  60. BOOL _preserveAspectRatio;
  61. CGSize _thumbnailSize;
  62. BOOL _lazyDecode;
  63. }
  64. - (void)dealloc
  65. {
  66. if (_imageSource) {
  67. CFRelease(_imageSource);
  68. _imageSource = NULL;
  69. }
  70. #if SD_UIKIT
  71. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  72. #endif
  73. }
  74. - (void)didReceiveMemoryWarning:(NSNotification *)notification
  75. {
  76. if (_imageSource) {
  77. for (size_t i = 0; i < _frameCount; i++) {
  78. CGImageSourceRemoveCacheAtIndex(_imageSource, i);
  79. }
  80. }
  81. }
  82. #pragma mark - Subclass Override
  83. + (SDImageFormat)imageFormat {
  84. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  85. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  86. userInfo:nil];
  87. }
  88. + (NSString *)imageUTType {
  89. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  90. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  91. userInfo:nil];
  92. }
  93. + (NSString *)dictionaryProperty {
  94. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  95. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  96. userInfo:nil];
  97. }
  98. + (NSString *)unclampedDelayTimeProperty {
  99. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  100. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  101. userInfo:nil];
  102. }
  103. + (NSString *)delayTimeProperty {
  104. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  105. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  106. userInfo:nil];
  107. }
  108. + (NSString *)loopCountProperty {
  109. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  110. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  111. userInfo:nil];
  112. }
  113. + (NSUInteger)defaultLoopCount {
  114. @throw [NSException exceptionWithName:NSInternalInconsistencyException
  115. reason:[NSString stringWithFormat:@"For `SDImageIOAnimatedCoder` subclass, you must override %@ method", NSStringFromSelector(_cmd)]
  116. userInfo:nil];
  117. }
  118. #pragma mark - Utils
  119. + (BOOL)canDecodeFromFormat:(SDImageFormat)format {
  120. static dispatch_once_t onceToken;
  121. static NSSet *imageUTTypeSet;
  122. dispatch_once(&onceToken, ^{
  123. NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageSourceCopyTypeIdentifiers();
  124. imageUTTypeSet = [NSSet setWithArray:imageUTTypes];
  125. });
  126. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
  127. if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) {
  128. // Can decode from target format
  129. return YES;
  130. }
  131. return NO;
  132. }
  133. + (BOOL)canEncodeToFormat:(SDImageFormat)format {
  134. static dispatch_once_t onceToken;
  135. static NSSet *imageUTTypeSet;
  136. dispatch_once(&onceToken, ^{
  137. NSArray *imageUTTypes = (__bridge_transfer NSArray *)CGImageDestinationCopyTypeIdentifiers();
  138. imageUTTypeSet = [NSSet setWithArray:imageUTTypes];
  139. });
  140. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
  141. if ([imageUTTypeSet containsObject:(__bridge NSString *)(imageUTType)]) {
  142. // Can encode to target format
  143. return YES;
  144. }
  145. return NO;
  146. }
  147. + (NSUInteger)imageLoopCountWithSource:(CGImageSourceRef)source {
  148. NSUInteger loopCount = self.defaultLoopCount;
  149. NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, NULL);
  150. NSDictionary *containerProperties = imageProperties[self.dictionaryProperty];
  151. if (containerProperties) {
  152. NSNumber *containerLoopCount = containerProperties[self.loopCountProperty];
  153. if (containerLoopCount != nil) {
  154. loopCount = containerLoopCount.unsignedIntegerValue;
  155. }
  156. }
  157. return loopCount;
  158. }
  159. + (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
  160. NSTimeInterval frameDuration = 0.1;
  161. CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
  162. if (!cfFrameProperties) {
  163. return frameDuration;
  164. }
  165. NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
  166. NSDictionary *containerProperties = frameProperties[self.dictionaryProperty];
  167. NSNumber *delayTimeUnclampedProp = containerProperties[self.unclampedDelayTimeProperty];
  168. if (delayTimeUnclampedProp != nil) {
  169. frameDuration = [delayTimeUnclampedProp doubleValue];
  170. } else {
  171. NSNumber *delayTimeProp = containerProperties[self.delayTimeProperty];
  172. if (delayTimeProp != nil) {
  173. frameDuration = [delayTimeProp doubleValue];
  174. }
  175. }
  176. // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
  177. // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
  178. // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
  179. // for more information.
  180. if (frameDuration < 0.011) {
  181. frameDuration = 0.1;
  182. }
  183. CFRelease(cfFrameProperties);
  184. return frameDuration;
  185. }
  186. + (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize lazyDecode:(BOOL)lazyDecode animatedImage:(BOOL)animatedImage {
  187. // `animatedImage` means called from `SDAnimatedImageProvider.animatedImageFrameAtIndex`
  188. NSDictionary *options;
  189. if (animatedImage) {
  190. if (!lazyDecode) {
  191. options = @{
  192. // image decoding and caching should happen at image creation time.
  193. (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
  194. };
  195. } else {
  196. options = @{
  197. // image decoding will happen at rendering time
  198. (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(NO),
  199. };
  200. }
  201. }
  202. // Parse the image properties
  203. NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
  204. CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
  205. CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
  206. CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
  207. if (!exifOrientation) {
  208. exifOrientation = kCGImagePropertyOrientationUp;
  209. }
  210. NSMutableDictionary *decodingOptions;
  211. if (options) {
  212. decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options];
  213. } else {
  214. decodingOptions = [NSMutableDictionary dictionary];
  215. }
  216. CGImageRef imageRef;
  217. BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
  218. if (createFullImage) {
  219. imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
  220. } else {
  221. decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
  222. CGFloat maxPixelSize;
  223. if (preserveAspectRatio) {
  224. CGFloat pixelRatio = pixelWidth / pixelHeight;
  225. CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
  226. if (pixelRatio > thumbnailRatio) {
  227. maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.width / pixelRatio);
  228. } else {
  229. maxPixelSize = MAX(thumbnailSize.height, thumbnailSize.height * pixelRatio);
  230. }
  231. } else {
  232. maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
  233. }
  234. decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
  235. decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways] = @(YES);
  236. imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
  237. }
  238. if (!imageRef) {
  239. return nil;
  240. }
  241. BOOL isDecoded = NO;
  242. // Thumbnail image post-process
  243. if (!createFullImage) {
  244. if (preserveAspectRatio) {
  245. // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
  246. exifOrientation = kCGImagePropertyOrientationUp;
  247. } else {
  248. // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size
  249. CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
  250. if (scaledImageRef) {
  251. CGImageRelease(imageRef);
  252. imageRef = scaledImageRef;
  253. isDecoded = YES;
  254. }
  255. }
  256. }
  257. // Check whether output CGImage is decoded
  258. if (!lazyDecode) {
  259. if (!isDecoded) {
  260. // Use CoreGraphics to trigger immediately decode
  261. CGImageRef decodedImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
  262. if (decodedImageRef) {
  263. CGImageRelease(imageRef);
  264. imageRef = decodedImageRef;
  265. isDecoded = YES;
  266. }
  267. }
  268. } else if (animatedImage) {
  269. // iOS 15+, CGImageRef now retains CGImageSourceRef internally. To workaround its thread-safe issue, we have to strip CGImageSourceRef, using Force-Decode (or have to use SPI `CGImageSetImageSource`), See: https://github.com/SDWebImage/SDWebImage/issues/3273
  270. if (@available(iOS 15, tvOS 15, *)) {
  271. // User pass `lazyDecode == YES`, but we still have to strip the CGImageSourceRef
  272. // CGImageRef newImageRef = CGImageCreateCopy(imageRef); // This one does not strip the CGImageProperty
  273. CGImageRef newImageRef = SDCGImageCreateCopy(imageRef);
  274. if (newImageRef) {
  275. CGImageRelease(imageRef);
  276. imageRef = newImageRef;
  277. }
  278. #if SD_CHECK_CGIMAGE_RETAIN_SOURCE
  279. // Assert here to check CGImageRef should not retain the CGImageSourceRef and has possible thread-safe issue (this is behavior on iOS 15+)
  280. // If assert hit, fire issue to https://github.com/SDWebImage/SDWebImage/issues and we update the condition for this behavior check
  281. static dispatch_once_t onceToken;
  282. dispatch_once(&onceToken, ^{
  283. SDCGImageGetImageSource = dlsym(RTLD_DEFAULT, "CGImageGetImageSource");
  284. });
  285. if (SDCGImageGetImageSource) {
  286. NSCAssert(!SDCGImageGetImageSource(imageRef), @"Animated Coder created CGImageRef should not retain CGImageSourceRef, which may cause thread-safe issue without lock");
  287. }
  288. #endif
  289. }
  290. }
  291. #if SD_UIKIT || SD_WATCH
  292. UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
  293. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
  294. #else
  295. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
  296. #endif
  297. CGImageRelease(imageRef);
  298. image.sd_isDecoded = isDecoded;
  299. return image;
  300. }
  301. #pragma mark - Decode
  302. - (BOOL)canDecodeFromData:(nullable NSData *)data {
  303. return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
  304. }
  305. - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
  306. if (!data) {
  307. return nil;
  308. }
  309. CGFloat scale = 1;
  310. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  311. if (scaleFactor != nil) {
  312. scale = MAX([scaleFactor doubleValue], 1);
  313. }
  314. CGSize thumbnailSize = CGSizeZero;
  315. NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
  316. if (thumbnailSizeValue != nil) {
  317. #if SD_MAC
  318. thumbnailSize = thumbnailSizeValue.sizeValue;
  319. #else
  320. thumbnailSize = thumbnailSizeValue.CGSizeValue;
  321. #endif
  322. }
  323. BOOL preserveAspectRatio = YES;
  324. NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
  325. if (preserveAspectRatioValue != nil) {
  326. preserveAspectRatio = preserveAspectRatioValue.boolValue;
  327. }
  328. BOOL lazyDecode = YES; // Defaults YES for static image coder
  329. NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
  330. if (lazyDecodeValue != nil) {
  331. lazyDecode = lazyDecodeValue.boolValue;
  332. }
  333. #if SD_MAC
  334. // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
  335. // Which decode frames in time and reduce memory usage
  336. if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
  337. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
  338. if (imageRep) {
  339. NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
  340. imageRep.size = size;
  341. NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
  342. [animatedImage addRepresentation:imageRep];
  343. animatedImage.sd_imageFormat = self.class.imageFormat;
  344. return animatedImage;
  345. }
  346. }
  347. #endif
  348. NSString *typeIdentifierHint = options[SDImageCoderDecodeTypeIdentifierHint];
  349. if (!typeIdentifierHint) {
  350. // Check file extension and convert to UTI, from: https://stackoverflow.com/questions/1506251/getting-an-uniform-type-identifier-for-a-given-extension
  351. NSString *fileExtensionHint = options[SDImageCoderDecodeFileExtensionHint];
  352. if (fileExtensionHint) {
  353. typeIdentifierHint = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtensionHint, kUTTypeImage);
  354. // Ignore dynamic UTI
  355. if (UTTypeIsDynamic((__bridge CFStringRef)typeIdentifierHint)) {
  356. typeIdentifierHint = nil;
  357. }
  358. }
  359. } else if ([typeIdentifierHint isEqual:NSNull.null]) {
  360. // Hack if user don't want to imply file extension
  361. typeIdentifierHint = nil;
  362. }
  363. NSDictionary *creatingOptions = nil;
  364. if (typeIdentifierHint) {
  365. creatingOptions = @{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : typeIdentifierHint};
  366. }
  367. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)creatingOptions);
  368. if (!source) {
  369. // Try again without UTType hint, the call site from user may provide the wrong UTType
  370. source = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
  371. }
  372. if (!source) {
  373. return nil;
  374. }
  375. size_t count = CGImageSourceGetCount(source);
  376. UIImage *animatedImage;
  377. BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
  378. if (decodeFirstFrame || count <= 1) {
  379. animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
  380. } else {
  381. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:count];
  382. for (size_t i = 0; i < count; i++) {
  383. UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize lazyDecode:lazyDecode animatedImage:NO];
  384. if (!image) {
  385. continue;
  386. }
  387. NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
  388. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
  389. [frames addObject:frame];
  390. }
  391. NSUInteger loopCount = [self.class imageLoopCountWithSource:source];
  392. animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
  393. animatedImage.sd_imageLoopCount = loopCount;
  394. }
  395. animatedImage.sd_imageFormat = self.class.imageFormat;
  396. CFRelease(source);
  397. return animatedImage;
  398. }
  399. #pragma mark - Progressive Decode
  400. - (BOOL)canIncrementalDecodeFromData:(NSData *)data {
  401. return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
  402. }
  403. - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
  404. self = [super init];
  405. if (self) {
  406. NSString *imageUTType = self.class.imageUTType;
  407. _imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType});
  408. _incremental = YES;
  409. CGFloat scale = 1;
  410. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  411. if (scaleFactor != nil) {
  412. scale = MAX([scaleFactor doubleValue], 1);
  413. }
  414. _scale = scale;
  415. CGSize thumbnailSize = CGSizeZero;
  416. NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
  417. if (thumbnailSizeValue != nil) {
  418. #if SD_MAC
  419. thumbnailSize = thumbnailSizeValue.sizeValue;
  420. #else
  421. thumbnailSize = thumbnailSizeValue.CGSizeValue;
  422. #endif
  423. }
  424. _thumbnailSize = thumbnailSize;
  425. BOOL preserveAspectRatio = YES;
  426. NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
  427. if (preserveAspectRatioValue != nil) {
  428. preserveAspectRatio = preserveAspectRatioValue.boolValue;
  429. }
  430. _preserveAspectRatio = preserveAspectRatio;
  431. BOOL lazyDecode = NO; // Defaults NO for animated image coder
  432. NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
  433. if (lazyDecodeValue != nil) {
  434. lazyDecode = lazyDecodeValue.boolValue;
  435. }
  436. _lazyDecode = lazyDecode;
  437. SD_LOCK_INIT(_lock);
  438. #if SD_UIKIT
  439. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  440. #endif
  441. }
  442. return self;
  443. }
  444. - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
  445. NSCParameterAssert(_incremental);
  446. if (_finished) {
  447. return;
  448. }
  449. _imageData = data;
  450. _finished = finished;
  451. // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
  452. // Thanks to the author @Nyx0uf
  453. // Update the data source, we must pass ALL the data, not just the new bytes
  454. CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
  455. if (_width + _height == 0) {
  456. CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
  457. if (properties) {
  458. CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
  459. if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
  460. val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
  461. if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
  462. CFRelease(properties);
  463. }
  464. }
  465. SD_LOCK(_lock);
  466. // For animated image progressive decoding because the frame count and duration may be changed.
  467. [self scanAndCheckFramesValidWithImageSource:_imageSource];
  468. SD_UNLOCK(_lock);
  469. }
  470. - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
  471. NSCParameterAssert(_incremental);
  472. UIImage *image;
  473. if (_width + _height > 0) {
  474. // Create the image
  475. CGFloat scale = _scale;
  476. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  477. if (scaleFactor != nil) {
  478. scale = MAX([scaleFactor doubleValue], 1);
  479. }
  480. image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:NO];
  481. if (image) {
  482. image.sd_imageFormat = self.class.imageFormat;
  483. }
  484. }
  485. return image;
  486. }
  487. #pragma mark - Encode
  488. - (BOOL)canEncodeToFormat:(SDImageFormat)format {
  489. return (format == self.class.imageFormat);
  490. }
  491. - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
  492. if (!image) {
  493. return nil;
  494. }
  495. if (format != self.class.imageFormat) {
  496. return nil;
  497. }
  498. NSArray<SDImageFrame *> *frames = [SDImageCoderHelper framesFromAnimatedImage:image];
  499. if (!frames || frames.count == 0) {
  500. SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:0];
  501. frames = @[frame];
  502. }
  503. return [self encodedDataWithFrames:frames loopCount:image.sd_imageLoopCount format:format options:options];
  504. }
  505. - (NSData *)encodedDataWithFrames:(NSArray<SDImageFrame *> *)frames loopCount:(NSUInteger)loopCount format:(SDImageFormat)format options:(SDImageCoderOptions *)options {
  506. UIImage *image = frames.firstObject.image; // Primary image
  507. if (!image) {
  508. return nil;
  509. }
  510. CGImageRef imageRef = image.CGImage;
  511. if (!imageRef) {
  512. // Earily return, supports CGImage only
  513. return nil;
  514. }
  515. NSMutableData *imageData = [NSMutableData data];
  516. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
  517. // Create an image destination. Animated Image does not support EXIF image orientation TODO
  518. // The `CGImageDestinationCreateWithData` will log a warning when count is 0, use 1 instead.
  519. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frames.count ?: 1, NULL);
  520. if (!imageDestination) {
  521. // Handle failure.
  522. return nil;
  523. }
  524. NSMutableDictionary *properties = [NSMutableDictionary dictionary];
  525. #if SD_UIKIT || SD_WATCH
  526. CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
  527. #else
  528. CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
  529. #endif
  530. properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
  531. // Encoding Options
  532. double compressionQuality = 1;
  533. if (options[SDImageCoderEncodeCompressionQuality]) {
  534. compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
  535. }
  536. properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
  537. CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
  538. if (backgroundColor) {
  539. properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
  540. }
  541. CGSize maxPixelSize = CGSizeZero;
  542. NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
  543. if (maxPixelSizeValue != nil) {
  544. #if SD_MAC
  545. maxPixelSize = maxPixelSizeValue.sizeValue;
  546. #else
  547. maxPixelSize = maxPixelSizeValue.CGSizeValue;
  548. #endif
  549. }
  550. CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
  551. CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
  552. CGFloat finalPixelSize = 0;
  553. BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
  554. if (!encodeFullImage) {
  555. // Thumbnail Encoding
  556. CGFloat pixelRatio = pixelWidth / pixelHeight;
  557. CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
  558. if (pixelRatio > maxPixelSizeRatio) {
  559. finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
  560. } else {
  561. finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
  562. }
  563. properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
  564. }
  565. NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
  566. if (maxFileSize > 0) {
  567. properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
  568. // Remove the quality if we have file size limit
  569. properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
  570. }
  571. BOOL embedThumbnail = NO;
  572. if (options[SDImageCoderEncodeEmbedThumbnail]) {
  573. embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
  574. }
  575. properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
  576. BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
  577. if (encodeFirstFrame || frames.count <= 1) {
  578. // for static single images
  579. CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
  580. } else {
  581. // for animated images
  582. NSDictionary *containerProperties = @{
  583. self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)}
  584. };
  585. // container level properties (applies for `CGImageDestinationSetProperties`, not individual frames)
  586. CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties);
  587. for (size_t i = 0; i < frames.count; i++) {
  588. SDImageFrame *frame = frames[i];
  589. NSTimeInterval frameDuration = frame.duration;
  590. CGImageRef frameImageRef = frame.image.CGImage;
  591. properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)};
  592. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties);
  593. }
  594. }
  595. // Finalize the destination.
  596. if (CGImageDestinationFinalize(imageDestination) == NO) {
  597. // Handle failure.
  598. imageData = nil;
  599. }
  600. CFRelease(imageDestination);
  601. return [imageData copy];
  602. }
  603. #pragma mark - SDAnimatedImageCoder
  604. - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data options:(nullable SDImageCoderOptions *)options {
  605. if (!data) {
  606. return nil;
  607. }
  608. self = [super init];
  609. if (self) {
  610. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
  611. if (!imageSource) {
  612. return nil;
  613. }
  614. BOOL framesValid = [self scanAndCheckFramesValidWithImageSource:imageSource];
  615. if (!framesValid) {
  616. CFRelease(imageSource);
  617. return nil;
  618. }
  619. CGFloat scale = 1;
  620. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  621. if (scaleFactor != nil) {
  622. scale = MAX([scaleFactor doubleValue], 1);
  623. }
  624. _scale = scale;
  625. CGSize thumbnailSize = CGSizeZero;
  626. NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
  627. if (thumbnailSizeValue != nil) {
  628. #if SD_MAC
  629. thumbnailSize = thumbnailSizeValue.sizeValue;
  630. #else
  631. thumbnailSize = thumbnailSizeValue.CGSizeValue;
  632. #endif
  633. }
  634. _thumbnailSize = thumbnailSize;
  635. BOOL preserveAspectRatio = YES;
  636. NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
  637. if (preserveAspectRatioValue != nil) {
  638. preserveAspectRatio = preserveAspectRatioValue.boolValue;
  639. }
  640. _preserveAspectRatio = preserveAspectRatio;
  641. BOOL lazyDecode = NO; // Defaults NO for animated image coder
  642. NSNumber *lazyDecodeValue = options[SDImageCoderDecodeUseLazyDecoding];
  643. if (lazyDecodeValue != nil) {
  644. lazyDecode = lazyDecodeValue.boolValue;
  645. }
  646. _lazyDecode = lazyDecode;
  647. _imageSource = imageSource;
  648. _imageData = data;
  649. #if SD_UIKIT
  650. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  651. #endif
  652. }
  653. return self;
  654. }
  655. - (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
  656. if (!imageSource) {
  657. return NO;
  658. }
  659. NSUInteger frameCount = CGImageSourceGetCount(imageSource);
  660. NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
  661. _loopCount = loopCount;
  662. NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
  663. for (size_t i = 0; i < frameCount; i++) {
  664. SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
  665. frame.index = i;
  666. frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
  667. [frames addObject:frame];
  668. }
  669. if (frames.count != frameCount) {
  670. // frames not match, do not override current value
  671. return NO;
  672. }
  673. _frameCount = frameCount;
  674. _frames = [frames copy];
  675. return YES;
  676. }
  677. - (NSData *)animatedImageData {
  678. return _imageData;
  679. }
  680. - (NSUInteger)animatedImageLoopCount {
  681. return _loopCount;
  682. }
  683. - (NSUInteger)animatedImageFrameCount {
  684. return _frameCount;
  685. }
  686. - (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
  687. NSTimeInterval duration;
  688. // Incremental Animation decoding may update frames when new bytes available
  689. // Which should use lock to ensure frame count and frames match, ensure atomic logic
  690. if (_incremental) {
  691. SD_LOCK(_lock);
  692. if (index >= _frames.count) {
  693. SD_UNLOCK(_lock);
  694. return 0;
  695. }
  696. duration = _frames[index].duration;
  697. SD_UNLOCK(_lock);
  698. } else {
  699. if (index >= _frames.count) {
  700. return 0;
  701. }
  702. duration = _frames[index].duration;
  703. }
  704. return duration;
  705. }
  706. - (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
  707. UIImage *image;
  708. // Incremental Animation decoding may update frames when new bytes available
  709. // Which should use lock to ensure frame count and frames match, ensure atomic logic
  710. if (_incremental) {
  711. SD_LOCK(_lock);
  712. if (index >= _frames.count) {
  713. SD_UNLOCK(_lock);
  714. return nil;
  715. }
  716. image = [self safeAnimatedImageFrameAtIndex:index];
  717. SD_UNLOCK(_lock);
  718. } else {
  719. if (index >= _frames.count) {
  720. return nil;
  721. }
  722. image = [self safeAnimatedImageFrameAtIndex:index];
  723. }
  724. return image;
  725. }
  726. - (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
  727. UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize lazyDecode:_lazyDecode animatedImage:YES];
  728. if (!image) {
  729. return nil;
  730. }
  731. image.sd_imageFormat = self.class.imageFormat;
  732. return image;
  733. }
  734. @end