SDImageIOCoder.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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 "SDImageIOCoder.h"
  9. #import "SDImageCoderHelper.h"
  10. #import "NSImage+Compatibility.h"
  11. #import <ImageIO/ImageIO.h>
  12. #import "UIImage+Metadata.h"
  13. #import "SDImageHEICCoderInternal.h"
  14. #import "SDImageIOAnimatedCoderInternal.h"
  15. // Specify File Size for lossy format encoding, like JPEG
  16. static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
  17. @implementation SDImageIOCoder {
  18. size_t _width, _height;
  19. CGImagePropertyOrientation _orientation;
  20. CGImageSourceRef _imageSource;
  21. CGFloat _scale;
  22. BOOL _finished;
  23. BOOL _preserveAspectRatio;
  24. CGSize _thumbnailSize;
  25. }
  26. - (void)dealloc {
  27. if (_imageSource) {
  28. CFRelease(_imageSource);
  29. _imageSource = NULL;
  30. }
  31. #if SD_UIKIT
  32. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  33. #endif
  34. }
  35. - (void)didReceiveMemoryWarning:(NSNotification *)notification
  36. {
  37. if (_imageSource) {
  38. CGImageSourceRemoveCacheAtIndex(_imageSource, 0);
  39. }
  40. }
  41. + (instancetype)sharedCoder {
  42. static SDImageIOCoder *coder;
  43. static dispatch_once_t onceToken;
  44. dispatch_once(&onceToken, ^{
  45. coder = [[SDImageIOCoder alloc] init];
  46. });
  47. return coder;
  48. }
  49. #pragma mark - Decode
  50. - (BOOL)canDecodeFromData:(nullable NSData *)data {
  51. switch ([NSData sd_imageFormatForImageData:data]) {
  52. case SDImageFormatWebP:
  53. // Do not support WebP decoding
  54. return NO;
  55. case SDImageFormatHEIC:
  56. // Check HEIC decoding compatibility
  57. return [SDImageHEICCoder canDecodeFromHEICFormat];
  58. case SDImageFormatHEIF:
  59. // Check HEIF decoding compatibility
  60. return [SDImageHEICCoder canDecodeFromHEIFFormat];
  61. default:
  62. return YES;
  63. }
  64. }
  65. - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
  66. if (!data) {
  67. return nil;
  68. }
  69. CGFloat scale = 1;
  70. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  71. if (scaleFactor != nil) {
  72. scale = MAX([scaleFactor doubleValue], 1) ;
  73. }
  74. CGSize thumbnailSize = CGSizeZero;
  75. NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
  76. if (thumbnailSizeValue != nil) {
  77. #if SD_MAC
  78. thumbnailSize = thumbnailSizeValue.sizeValue;
  79. #else
  80. thumbnailSize = thumbnailSizeValue.CGSizeValue;
  81. #endif
  82. }
  83. BOOL preserveAspectRatio = YES;
  84. NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
  85. if (preserveAspectRatioValue != nil) {
  86. preserveAspectRatio = preserveAspectRatioValue.boolValue;
  87. }
  88. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
  89. if (!source) {
  90. return nil;
  91. }
  92. UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
  93. CFRelease(source);
  94. if (!image) {
  95. return nil;
  96. }
  97. image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
  98. return image;
  99. }
  100. #pragma mark - Progressive Decode
  101. - (BOOL)canIncrementalDecodeFromData:(NSData *)data {
  102. return [self canDecodeFromData:data];
  103. }
  104. - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
  105. self = [super init];
  106. if (self) {
  107. _imageSource = CGImageSourceCreateIncremental(NULL);
  108. CGFloat scale = 1;
  109. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  110. if (scaleFactor != nil) {
  111. scale = MAX([scaleFactor doubleValue], 1);
  112. }
  113. _scale = scale;
  114. CGSize thumbnailSize = CGSizeZero;
  115. NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
  116. if (thumbnailSizeValue != nil) {
  117. #if SD_MAC
  118. thumbnailSize = thumbnailSizeValue.sizeValue;
  119. #else
  120. thumbnailSize = thumbnailSizeValue.CGSizeValue;
  121. #endif
  122. }
  123. _thumbnailSize = thumbnailSize;
  124. BOOL preserveAspectRatio = YES;
  125. NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
  126. if (preserveAspectRatioValue != nil) {
  127. preserveAspectRatio = preserveAspectRatioValue.boolValue;
  128. }
  129. _preserveAspectRatio = preserveAspectRatio;
  130. #if SD_UIKIT
  131. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  132. #endif
  133. }
  134. return self;
  135. }
  136. - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
  137. if (_finished) {
  138. return;
  139. }
  140. _finished = finished;
  141. // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
  142. // Thanks to the author @Nyx0uf
  143. // Update the data source, we must pass ALL the data, not just the new bytes
  144. CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
  145. if (_width + _height == 0) {
  146. CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
  147. if (properties) {
  148. NSInteger orientationValue = 1;
  149. CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
  150. if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
  151. val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
  152. if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
  153. val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
  154. if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
  155. CFRelease(properties);
  156. // When we draw to Core Graphics, we lose orientation information,
  157. // which means the image below born of initWithCGIImage will be
  158. // oriented incorrectly sometimes. (Unlike the image born of initWithData
  159. // in didCompleteWithError.) So save it here and pass it on later.
  160. _orientation = (CGImagePropertyOrientation)orientationValue;
  161. }
  162. }
  163. }
  164. - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
  165. UIImage *image;
  166. if (_width + _height > 0) {
  167. // Create the image
  168. CGFloat scale = _scale;
  169. NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
  170. if (scaleFactor != nil) {
  171. scale = MAX([scaleFactor doubleValue], 1);
  172. }
  173. image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
  174. if (image) {
  175. CFStringRef uttype = CGImageSourceGetType(_imageSource);
  176. image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
  177. }
  178. }
  179. return image;
  180. }
  181. #pragma mark - Encode
  182. - (BOOL)canEncodeToFormat:(SDImageFormat)format {
  183. switch (format) {
  184. case SDImageFormatWebP:
  185. // Do not support WebP encoding
  186. return NO;
  187. case SDImageFormatHEIC:
  188. // Check HEIC encoding compatibility
  189. return [SDImageHEICCoder canEncodeToHEICFormat];
  190. case SDImageFormatHEIF:
  191. // Check HEIF encoding compatibility
  192. return [SDImageHEICCoder canEncodeToHEIFFormat];
  193. default:
  194. return YES;
  195. }
  196. }
  197. - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
  198. if (!image) {
  199. return nil;
  200. }
  201. CGImageRef imageRef = image.CGImage;
  202. if (!imageRef) {
  203. // Earily return, supports CGImage only
  204. return nil;
  205. }
  206. if (format == SDImageFormatUndefined) {
  207. BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:imageRef];
  208. if (hasAlpha) {
  209. format = SDImageFormatPNG;
  210. } else {
  211. format = SDImageFormatJPEG;
  212. }
  213. }
  214. NSMutableData *imageData = [NSMutableData data];
  215. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
  216. // Create an image destination.
  217. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
  218. if (!imageDestination) {
  219. // Handle failure.
  220. return nil;
  221. }
  222. NSMutableDictionary *properties = [NSMutableDictionary dictionary];
  223. #if SD_UIKIT || SD_WATCH
  224. CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
  225. #else
  226. CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
  227. #endif
  228. properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
  229. // Encoding Options
  230. double compressionQuality = 1;
  231. if (options[SDImageCoderEncodeCompressionQuality]) {
  232. compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
  233. }
  234. properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
  235. CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
  236. if (backgroundColor) {
  237. properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
  238. }
  239. CGSize maxPixelSize = CGSizeZero;
  240. NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
  241. if (maxPixelSizeValue != nil) {
  242. #if SD_MAC
  243. maxPixelSize = maxPixelSizeValue.sizeValue;
  244. #else
  245. maxPixelSize = maxPixelSizeValue.CGSizeValue;
  246. #endif
  247. }
  248. NSUInteger pixelWidth = CGImageGetWidth(imageRef);
  249. NSUInteger pixelHeight = CGImageGetHeight(imageRef);
  250. if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
  251. CGFloat pixelRatio = pixelWidth / pixelHeight;
  252. CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
  253. CGFloat finalPixelSize;
  254. if (pixelRatio > maxPixelSizeRatio) {
  255. finalPixelSize = maxPixelSize.width;
  256. } else {
  257. finalPixelSize = maxPixelSize.height;
  258. }
  259. properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
  260. }
  261. NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
  262. if (maxFileSize > 0) {
  263. properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
  264. // Remove the quality if we have file size limit
  265. properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
  266. }
  267. BOOL embedThumbnail = NO;
  268. if (options[SDImageCoderEncodeEmbedThumbnail]) {
  269. embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
  270. }
  271. properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
  272. // Add your image to the destination.
  273. CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
  274. // Finalize the destination.
  275. if (CGImageDestinationFinalize(imageDestination) == NO) {
  276. // Handle failure.
  277. imageData = nil;
  278. }
  279. CFRelease(imageDestination);
  280. return [imageData copy];
  281. }
  282. @end