SDImageCoderHelper.m 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  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 "SDImageCoderHelper.h"
  9. #import "SDImageFrame.h"
  10. #import "NSImage+Compatibility.h"
  11. #import "NSData+ImageContentType.h"
  12. #import "SDAnimatedImageRep.h"
  13. #import "UIImage+ForceDecode.h"
  14. #import "SDAssociatedObject.h"
  15. #import "UIImage+Metadata.h"
  16. #import "SDInternalMacros.h"
  17. #import "SDGraphicsImageRenderer.h"
  18. #import "SDInternalMacros.h"
  19. #import "SDDeviceHelper.h"
  20. #import <Accelerate/Accelerate.h>
  21. #define kCGColorSpaceDeviceRGB CFSTR("kCGColorSpaceDeviceRGB")
  22. #if SD_UIKIT
  23. static inline UIImage *SDImageDecodeUIKit(UIImage *image) {
  24. // See: https://developer.apple.com/documentation/uikit/uiimage/3750834-imagebypreparingfordisplay
  25. // Need CGImage-based
  26. if (@available(iOS 15, tvOS 15, *)) {
  27. UIImage *decodedImage = [image imageByPreparingForDisplay];
  28. if (decodedImage) {
  29. SDImageCopyAssociatedObject(image, decodedImage);
  30. decodedImage.sd_isDecoded = YES;
  31. return decodedImage;
  32. }
  33. }
  34. return nil;
  35. }
  36. static inline UIImage *SDImageDecodeAndScaleDownUIKit(UIImage *image, CGSize destResolution) {
  37. // See: https://developer.apple.com/documentation/uikit/uiimage/3750835-imagebypreparingthumbnailofsize
  38. // Need CGImage-based
  39. if (@available(iOS 15, tvOS 15, *)) {
  40. // Calculate thumbnail point size
  41. CGFloat scale = image.scale ?: 1;
  42. CGSize thumbnailSize = CGSizeMake(destResolution.width / scale, destResolution.height / scale);
  43. UIImage *decodedImage = [image imageByPreparingThumbnailOfSize:thumbnailSize];
  44. if (decodedImage) {
  45. SDImageCopyAssociatedObject(image, decodedImage);
  46. decodedImage.sd_isDecoded = YES;
  47. return decodedImage;
  48. }
  49. }
  50. return nil;
  51. }
  52. static inline BOOL SDImageSupportsHardwareHEVCDecoder(void) {
  53. static dispatch_once_t onceToken;
  54. static BOOL supportsHardware = NO;
  55. dispatch_once(&onceToken, ^{
  56. SEL DeviceInfoSelector = SD_SEL_SPI(deviceInfoForKey:);
  57. NSString *HEVCDecoder8bitSupported = @"N8lZxRgC7lfdRS3dRLn+Ag";
  58. #pragma clang diagnostic push
  59. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  60. if ([UIDevice.currentDevice respondsToSelector:DeviceInfoSelector] && [UIDevice.currentDevice performSelector:DeviceInfoSelector withObject:HEVCDecoder8bitSupported]) {
  61. supportsHardware = YES;
  62. }
  63. #pragma clang diagnostic pop
  64. });
  65. return supportsHardware;
  66. }
  67. #endif
  68. static UIImage * _Nonnull SDImageGetAlphaDummyImage(void) {
  69. static dispatch_once_t onceToken;
  70. static UIImage *dummyImage;
  71. dispatch_once(&onceToken, ^{
  72. SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
  73. format.scale = 1;
  74. format.opaque = NO;
  75. CGSize size = CGSizeMake(1, 1);
  76. SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
  77. dummyImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
  78. CGContextSetFillColorWithColor(context, UIColor.redColor.CGColor);
  79. CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
  80. }];
  81. NSCAssert(dummyImage, @"The sample alpha image (1x1 pixels) returns nil, OS bug ?");
  82. });
  83. return dummyImage;
  84. }
  85. static UIImage * _Nonnull SDImageGetNonAlphaDummyImage(void) {
  86. static dispatch_once_t onceToken;
  87. static UIImage *dummyImage;
  88. dispatch_once(&onceToken, ^{
  89. SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
  90. format.scale = 1;
  91. format.opaque = YES;
  92. CGSize size = CGSizeMake(1, 1);
  93. SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
  94. dummyImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
  95. CGContextSetFillColorWithColor(context, UIColor.redColor.CGColor);
  96. CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
  97. }];
  98. NSCAssert(dummyImage, @"The sample non-alpha image (1x1 pixels) returns nil, OS bug ?");
  99. });
  100. return dummyImage;
  101. }
  102. static SDImageCoderDecodeSolution kDefaultDecodeSolution = SDImageCoderDecodeSolutionAutomatic;
  103. static const size_t kBytesPerPixel = 4;
  104. static const size_t kBitsPerComponent = 8;
  105. static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
  106. /*
  107. * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
  108. * Suggested value for iPad1 and iPhone 3GS: 60.
  109. * Suggested value for iPad2 and iPhone 4: 120.
  110. * Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
  111. */
  112. #if SD_MAC
  113. static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB;
  114. #elif SD_UIKIT
  115. static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
  116. #elif SD_WATCH
  117. static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB;
  118. #endif
  119. static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
  120. #if SD_MAC
  121. @interface SDAnimatedImageRep (Private)
  122. /// This wrap the animated image frames for legacy animated image coder API (`encodedDataWithImage:`).
  123. @property (nonatomic, readwrite, weak) NSArray<SDImageFrame *> *frames;
  124. @end
  125. #endif
  126. @implementation SDImageCoderHelper
  127. + (UIImage *)animatedImageWithFrames:(NSArray<SDImageFrame *> *)frames {
  128. NSUInteger frameCount = frames.count;
  129. if (frameCount == 0) {
  130. return nil;
  131. }
  132. UIImage *animatedImage;
  133. #if SD_UIKIT || SD_WATCH
  134. NSUInteger durations[frameCount];
  135. for (size_t i = 0; i < frameCount; i++) {
  136. durations[i] = frames[i].duration * 1000;
  137. }
  138. NSUInteger const gcd = gcdArray(frameCount, durations);
  139. __block NSTimeInterval totalDuration = 0;
  140. NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
  141. [frames enumerateObjectsUsingBlock:^(SDImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
  142. UIImage *image = frame.image;
  143. NSUInteger duration = frame.duration * 1000;
  144. totalDuration += frame.duration;
  145. NSUInteger repeatCount;
  146. if (gcd) {
  147. repeatCount = duration / gcd;
  148. } else {
  149. repeatCount = 1;
  150. }
  151. for (size_t i = 0; i < repeatCount; ++i) {
  152. [animatedImages addObject:image];
  153. }
  154. }];
  155. animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration];
  156. #else
  157. NSMutableData *imageData = [NSMutableData data];
  158. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF];
  159. // Create an image destination. GIF does not support EXIF image orientation
  160. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
  161. if (!imageDestination) {
  162. // Handle failure.
  163. return nil;
  164. }
  165. for (size_t i = 0; i < frameCount; i++) {
  166. SDImageFrame *frame = frames[i];
  167. NSTimeInterval frameDuration = frame.duration;
  168. CGImageRef frameImageRef = frame.image.CGImage;
  169. NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  170. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  171. }
  172. // Finalize the destination.
  173. if (CGImageDestinationFinalize(imageDestination) == NO) {
  174. // Handle failure.
  175. CFRelease(imageDestination);
  176. return nil;
  177. }
  178. CFRelease(imageDestination);
  179. CGFloat scale = MAX(frames.firstObject.image.scale, 1);
  180. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
  181. NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
  182. imageRep.size = size;
  183. imageRep.frames = frames; // Weak assign to avoid effect lazy semantic of NSBitmapImageRep
  184. animatedImage = [[NSImage alloc] initWithSize:size];
  185. [animatedImage addRepresentation:imageRep];
  186. #endif
  187. return animatedImage;
  188. }
  189. + (NSArray<SDImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
  190. if (!animatedImage) {
  191. return nil;
  192. }
  193. NSMutableArray<SDImageFrame *> *frames;
  194. NSUInteger frameCount = 0;
  195. #if SD_UIKIT || SD_WATCH
  196. NSArray<UIImage *> *animatedImages = animatedImage.images;
  197. frameCount = animatedImages.count;
  198. if (frameCount == 0) {
  199. return nil;
  200. }
  201. frames = [NSMutableArray arrayWithCapacity:frameCount];
  202. NSTimeInterval avgDuration = animatedImage.duration / frameCount;
  203. if (avgDuration == 0) {
  204. avgDuration = 0.1; // if it's a animated image but no duration, set it to default 100ms (this do not have that 10ms limit like GIF or WebP to allow custom coder provide the limit)
  205. }
  206. __block NSUInteger repeatCount = 1;
  207. __block UIImage *previousImage = animatedImages.firstObject;
  208. [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
  209. // ignore first
  210. if (idx == 0) {
  211. return;
  212. }
  213. if ([image isEqual:previousImage]) {
  214. repeatCount++;
  215. } else {
  216. SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  217. [frames addObject:frame];
  218. repeatCount = 1;
  219. }
  220. previousImage = image;
  221. }];
  222. // last one
  223. SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  224. [frames addObject:frame];
  225. #else
  226. NSRect imageRect = NSMakeRect(0, 0, animatedImage.size.width, animatedImage.size.height);
  227. NSImageRep *imageRep = [animatedImage bestRepresentationForRect:imageRect context:nil hints:nil];
  228. // Check weak assigned frames firstly
  229. if ([imageRep isKindOfClass:[SDAnimatedImageRep class]]) {
  230. SDAnimatedImageRep *animatedImageRep = (SDAnimatedImageRep *)imageRep;
  231. if (animatedImageRep.frames) {
  232. return animatedImageRep.frames;
  233. }
  234. }
  235. NSBitmapImageRep *bitmapImageRep;
  236. if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
  237. bitmapImageRep = (NSBitmapImageRep *)imageRep;
  238. }
  239. if (!bitmapImageRep) {
  240. return nil;
  241. }
  242. frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
  243. if (frameCount == 0) {
  244. return nil;
  245. }
  246. frames = [NSMutableArray arrayWithCapacity:frameCount];
  247. CGFloat scale = animatedImage.scale;
  248. for (size_t i = 0; i < frameCount; i++) {
  249. // NSBitmapImageRep need to manually change frame. "Good taste" API
  250. [bitmapImageRep setProperty:NSImageCurrentFrame withValue:@(i)];
  251. NSTimeInterval frameDuration = [[bitmapImageRep valueForProperty:NSImageCurrentFrameDuration] doubleValue];
  252. NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapImageRep.CGImage scale:scale orientation:kCGImagePropertyOrientationUp];
  253. SDImageFrame *frame = [SDImageFrame frameWithImage:frameImage duration:frameDuration];
  254. [frames addObject:frame];
  255. }
  256. #endif
  257. return [frames copy];
  258. }
  259. + (CGColorSpaceRef)colorSpaceGetDeviceRGB {
  260. static CGColorSpaceRef colorSpace;
  261. static dispatch_once_t onceToken;
  262. dispatch_once(&onceToken, ^{
  263. #if SD_MAC
  264. NSScreen *mainScreen = nil;
  265. if (@available(macOS 10.12, *)) {
  266. mainScreen = [NSScreen mainScreen];
  267. } else {
  268. mainScreen = [NSScreen screens].firstObject;
  269. }
  270. colorSpace = mainScreen.colorSpace.CGColorSpace;
  271. #else
  272. colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
  273. #endif
  274. });
  275. return colorSpace;
  276. }
  277. + (SDImagePixelFormat)preferredPixelFormat:(BOOL)containsAlpha {
  278. CGImageRef cgImage;
  279. if (containsAlpha) {
  280. cgImage = SDImageGetAlphaDummyImage().CGImage;
  281. } else {
  282. cgImage = SDImageGetNonAlphaDummyImage().CGImage;
  283. }
  284. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
  285. size_t bitsPerPixel = 8;
  286. if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) {
  287. bitsPerPixel = 16;
  288. }
  289. size_t components = 4; // Hardcode now
  290. // https://github.com/path/FastImageCache#byte-alignment
  291. // A properly aligned bytes-per-row value must be a multiple of 8 pixels × bytes per pixel.
  292. size_t alignment = (bitsPerPixel / 8) * components * 8;
  293. SDImagePixelFormat pixelFormat = {
  294. .bitmapInfo = bitmapInfo,
  295. .alignment = alignment
  296. };
  297. return pixelFormat;
  298. }
  299. + (BOOL)CGImageIsHardwareSupported:(CGImageRef)cgImage {
  300. BOOL supported = YES;
  301. // 1. Check byte alignment
  302. size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
  303. BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
  304. SDImagePixelFormat pixelFormat = [self preferredPixelFormat:hasAlpha];
  305. if (SDByteAlign(bytesPerRow, pixelFormat.alignment) == bytesPerRow) {
  306. // byte aligned, OK
  307. supported &= YES;
  308. } else {
  309. // not aligned
  310. supported &= NO;
  311. }
  312. if (!supported) return supported;
  313. // 2. Check color space
  314. CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage);
  315. CGColorSpaceRef perferredColorSpace = [self colorSpaceGetDeviceRGB];
  316. if (colorSpace == perferredColorSpace) {
  317. return supported;
  318. } else {
  319. if (@available(iOS 10.0, tvOS 10.0, macOS 10.6, watchOS 3.0, *)) {
  320. NSString *colorspaceName = (__bridge_transfer NSString *)CGColorSpaceCopyName(colorSpace);
  321. // Seems sRGB/deviceRGB always supported, P3 not always
  322. if ([colorspaceName isEqualToString:(__bridge NSString *)kCGColorSpaceDeviceRGB]
  323. || [colorspaceName isEqualToString:(__bridge NSString *)kCGColorSpaceSRGB]) {
  324. supported &= YES;
  325. } else {
  326. supported &= NO;
  327. }
  328. return supported;
  329. } else {
  330. // Fallback on earlier versions
  331. return supported;
  332. }
  333. }
  334. }
  335. + (BOOL)CGImageContainsAlpha:(CGImageRef)cgImage {
  336. if (!cgImage) {
  337. return NO;
  338. }
  339. CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
  340. BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
  341. alphaInfo == kCGImageAlphaNoneSkipFirst ||
  342. alphaInfo == kCGImageAlphaNoneSkipLast);
  343. return hasAlpha;
  344. }
  345. + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
  346. return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
  347. }
  348. + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation {
  349. if (!cgImage) {
  350. return NULL;
  351. }
  352. size_t width = CGImageGetWidth(cgImage);
  353. size_t height = CGImageGetHeight(cgImage);
  354. if (width == 0 || height == 0) return NULL;
  355. size_t newWidth;
  356. size_t newHeight;
  357. switch (orientation) {
  358. case kCGImagePropertyOrientationLeft:
  359. case kCGImagePropertyOrientationLeftMirrored:
  360. case kCGImagePropertyOrientationRight:
  361. case kCGImagePropertyOrientationRightMirrored: {
  362. // These orientation should swap width & height
  363. newWidth = height;
  364. newHeight = width;
  365. }
  366. break;
  367. default: {
  368. newWidth = width;
  369. newHeight = height;
  370. }
  371. break;
  372. }
  373. BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
  374. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  375. // Check #3330 for more detail about why this bitmap is choosen.
  376. // From v5.17.0, use runtime detection of bitmap info instead of hardcode.
  377. CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo;
  378. CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
  379. if (!context) {
  380. return NULL;
  381. }
  382. // Apply transform
  383. CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight));
  384. CGContextConcatCTM(context, transform);
  385. CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height
  386. CGImageRef newImageRef = CGBitmapContextCreateImage(context);
  387. CGContextRelease(context);
  388. return newImageRef;
  389. }
  390. + (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size {
  391. if (!cgImage) {
  392. return NULL;
  393. }
  394. size_t width = CGImageGetWidth(cgImage);
  395. size_t height = CGImageGetHeight(cgImage);
  396. if (width == size.width && height == size.height) {
  397. CGImageRetain(cgImage);
  398. return cgImage;
  399. }
  400. __block vImage_Buffer input_buffer = {}, output_buffer = {};
  401. @onExit {
  402. if (input_buffer.data) free(input_buffer.data);
  403. if (output_buffer.data) free(output_buffer.data);
  404. };
  405. BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
  406. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  407. // Check #3330 for more detail about why this bitmap is choosen.
  408. // From v5.17.0, use runtime detection of bitmap info instead of hardcode.
  409. CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo;
  410. vImage_CGImageFormat format = (vImage_CGImageFormat) {
  411. .bitsPerComponent = 8,
  412. .bitsPerPixel = 32,
  413. .colorSpace = NULL,
  414. .bitmapInfo = bitmapInfo,
  415. .version = 0,
  416. .decode = NULL,
  417. .renderingIntent = CGImageGetRenderingIntent(cgImage)
  418. };
  419. vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
  420. if (a_ret != kvImageNoError) return NULL;
  421. output_buffer.width = MAX(size.width, 0);
  422. output_buffer.height = MAX(size.height, 0);
  423. output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64);
  424. output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height);
  425. if (!output_buffer.data) return NULL;
  426. vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  427. if (ret != kvImageNoError) return NULL;
  428. CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret);
  429. if (ret != kvImageNoError) {
  430. CGImageRelease(outputImage);
  431. return NULL;
  432. }
  433. return outputImage;
  434. }
  435. + (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize preserveAspectRatio:(BOOL)preserveAspectRatio shouldScaleUp:(BOOL)shouldScaleUp {
  436. CGFloat width = imageSize.width;
  437. CGFloat height = imageSize.height;
  438. CGFloat resultWidth;
  439. CGFloat resultHeight;
  440. if (width <= 0 || height <= 0 || scaleSize.width <= 0 || scaleSize.height <= 0) {
  441. // Protect
  442. resultWidth = width;
  443. resultHeight = height;
  444. } else {
  445. // Scale to fit
  446. if (preserveAspectRatio) {
  447. CGFloat pixelRatio = width / height;
  448. CGFloat scaleRatio = scaleSize.width / scaleSize.height;
  449. if (pixelRatio > scaleRatio) {
  450. resultWidth = scaleSize.width;
  451. resultHeight = ceil(scaleSize.width / pixelRatio);
  452. } else {
  453. resultHeight = scaleSize.height;
  454. resultWidth = ceil(scaleSize.height * pixelRatio);
  455. }
  456. } else {
  457. // Stretch
  458. resultWidth = scaleSize.width;
  459. resultHeight = scaleSize.height;
  460. }
  461. if (!shouldScaleUp) {
  462. // Scale down only
  463. resultWidth = MIN(width, resultWidth);
  464. resultHeight = MIN(height, resultHeight);
  465. }
  466. }
  467. return CGSizeMake(resultWidth, resultHeight);
  468. }
  469. + (CGSize)scaledSizeWithImageSize:(CGSize)imageSize limitBytes:(NSUInteger)limitBytes bytesPerPixel:(NSUInteger)bytesPerPixel frameCount:(NSUInteger)frameCount {
  470. if (CGSizeEqualToSize(imageSize, CGSizeZero)) return CGSizeMake(1, 1);
  471. NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1);
  472. CGFloat ratio = imageSize.height / imageSize.width;
  473. CGFloat width = sqrt(totalFramePixelSize / ratio);
  474. CGFloat height = width * ratio;
  475. width = MAX(1, floor(width));
  476. height = MAX(1, floor(height));
  477. CGSize size = CGSizeMake(width, height);
  478. return size;
  479. }
  480. + (UIImage *)decodedImageWithImage:(UIImage *)image {
  481. return [self decodedImageWithImage:image policy:SDImageForceDecodePolicyAutomatic];
  482. }
  483. + (UIImage *)decodedImageWithImage:(UIImage *)image policy:(SDImageForceDecodePolicy)policy {
  484. if (![self shouldDecodeImage:image policy:policy]) {
  485. return image;
  486. }
  487. UIImage *decodedImage;
  488. SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution;
  489. #if SD_UIKIT
  490. if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) {
  491. // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :(
  492. SDImageFormat format = image.sd_imageFormat;
  493. if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) {
  494. decodedImage = SDImageDecodeUIKit(image);
  495. } else if (format == SDImageFormatJPEG) {
  496. decodedImage = SDImageDecodeUIKit(image);
  497. }
  498. } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) {
  499. // Arbitrarily call CMPhoto
  500. decodedImage = SDImageDecodeUIKit(image);
  501. }
  502. if (decodedImage) {
  503. return decodedImage;
  504. }
  505. #endif
  506. CGImageRef imageRef = image.CGImage;
  507. if (!imageRef) {
  508. // Only decode for CGImage-based
  509. return image;
  510. }
  511. if (decodeSolution == SDImageCoderDecodeSolutionCoreGraphics) {
  512. CGImageRef decodedImageRef = [self CGImageCreateDecoded:imageRef];
  513. #if SD_MAC
  514. decodedImage = [[UIImage alloc] initWithCGImage:decodedImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  515. #else
  516. decodedImage = [[UIImage alloc] initWithCGImage:decodedImageRef scale:image.scale orientation:image.imageOrientation];
  517. #endif
  518. CGImageRelease(decodedImageRef);
  519. } else {
  520. BOOL hasAlpha = [self CGImageContainsAlpha:imageRef];
  521. // Prefer to use new Image Renderer to re-draw image, instead of low-level CGBitmapContext and CGContextDrawImage
  522. // This can keep both OS compatible and don't fight with Apple's performance optimization
  523. SDGraphicsImageRendererFormat *format = SDGraphicsImageRendererFormat.preferredFormat;
  524. format.opaque = !hasAlpha;
  525. format.scale = image.scale;
  526. CGSize imageSize = image.size;
  527. SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:imageSize format:format];
  528. decodedImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
  529. [image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
  530. }];
  531. }
  532. SDImageCopyAssociatedObject(image, decodedImage);
  533. decodedImage.sd_isDecoded = YES;
  534. return decodedImage;
  535. }
  536. + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
  537. return [self decodedAndScaledDownImageWithImage:image limitBytes:bytes policy:SDImageForceDecodePolicyAutomatic];
  538. }
  539. + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes policy:(SDImageForceDecodePolicy)policy {
  540. if (![self shouldDecodeImage:image policy:policy]) {
  541. return image;
  542. }
  543. CGFloat destTotalPixels;
  544. CGFloat tileTotalPixels;
  545. if (bytes == 0) {
  546. bytes = [self defaultScaleDownLimitBytes];
  547. }
  548. bytes = MAX(bytes, kBytesPerPixel);
  549. destTotalPixels = bytes / kBytesPerPixel;
  550. tileTotalPixels = destTotalPixels / 3;
  551. CGImageRef sourceImageRef = image.CGImage;
  552. if (!sourceImageRef) {
  553. // Only decode for CGImage-based
  554. return image;
  555. }
  556. CGSize sourceResolution = CGSizeZero;
  557. sourceResolution.width = CGImageGetWidth(sourceImageRef);
  558. sourceResolution.height = CGImageGetHeight(sourceImageRef);
  559. if (![self shouldScaleDownImagePixelSize:sourceResolution limitBytes:bytes]) {
  560. return [self decodedImageWithImage:image];
  561. }
  562. CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  563. // Determine the scale ratio to apply to the input image
  564. // that results in an output image of the defined size.
  565. // see kDestImageSizeMB, and how it relates to destTotalPixels.
  566. CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
  567. CGSize destResolution = CGSizeZero;
  568. destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale));
  569. destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale));
  570. UIImage *decodedImage;
  571. #if SD_UIKIT
  572. SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution;
  573. if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) {
  574. // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :(
  575. SDImageFormat format = image.sd_imageFormat;
  576. if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) {
  577. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  578. } else if (format == SDImageFormatJPEG) {
  579. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  580. }
  581. } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) {
  582. // Arbitrarily call CMPhoto
  583. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  584. }
  585. if (decodedImage) {
  586. return decodedImage;
  587. }
  588. #endif
  589. // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
  590. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
  591. @autoreleasepool {
  592. // device color space
  593. CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
  594. BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
  595. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  596. // Check #3330 for more detail about why this bitmap is choosen.
  597. // From v5.17.0, use runtime detection of bitmap info instead of hardcode.
  598. CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo;
  599. CGContextRef destContext = CGBitmapContextCreate(NULL,
  600. destResolution.width,
  601. destResolution.height,
  602. kBitsPerComponent,
  603. 0,
  604. colorspaceRef,
  605. bitmapInfo);
  606. if (destContext == NULL) {
  607. return image;
  608. }
  609. CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
  610. // Now define the size of the rectangle to be used for the
  611. // incremental bits from the input image to the output image.
  612. // we use a source tile width equal to the width of the source
  613. // image due to the way that iOS retrieves image data from disk.
  614. // iOS must decode an image from disk in full width 'bands', even
  615. // if current graphics context is clipped to a subrect within that
  616. // band. Therefore we fully utilize all of the pixel data that results
  617. // from a decoding operation by anchoring our tile size to the full
  618. // width of the input image.
  619. CGRect sourceTile = CGRectZero;
  620. sourceTile.size.width = sourceResolution.width;
  621. // The source tile height is dynamic. Since we specified the size
  622. // of the source tile in MB, see how many rows of pixels high it
  623. // can be given the input image width.
  624. sourceTile.size.height = MAX(1, (int)(tileTotalPixels / sourceTile.size.width));
  625. sourceTile.origin.x = 0.0f;
  626. // The output tile is the same proportions as the input tile, but
  627. // scaled to image scale.
  628. CGRect destTile;
  629. destTile.size.width = destResolution.width;
  630. destTile.size.height = sourceTile.size.height * imageScale;
  631. destTile.origin.x = 0.0f;
  632. // The source seem overlap is proportionate to the destination seem overlap.
  633. // this is the amount of pixels to overlap each tile as we assemble the output image.
  634. float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
  635. CGImageRef sourceTileImageRef;
  636. // calculate the number of read/write operations required to assemble the
  637. // output image.
  638. int iterations = (int)( sourceResolution.height / sourceTile.size.height );
  639. // If tile height doesn't divide the image height evenly, add another iteration
  640. // to account for the remaining pixels.
  641. int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
  642. if(remainder) {
  643. iterations++;
  644. }
  645. // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
  646. float sourceTileHeightMinusOverlap = sourceTile.size.height;
  647. sourceTile.size.height += sourceSeemOverlap;
  648. destTile.size.height += kDestSeemOverlap;
  649. for( int y = 0; y < iterations; ++y ) {
  650. sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
  651. destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
  652. sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
  653. if( y == iterations - 1 && remainder ) {
  654. float dify = destTile.size.height;
  655. destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
  656. dify -= destTile.size.height;
  657. destTile.origin.y = MIN(0, destTile.origin.y + dify);
  658. }
  659. CGContextDrawImage( destContext, destTile, sourceTileImageRef );
  660. CGImageRelease( sourceTileImageRef );
  661. }
  662. CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
  663. CGContextRelease(destContext);
  664. if (destImageRef == NULL) {
  665. return image;
  666. }
  667. #if SD_MAC
  668. decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  669. #else
  670. decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
  671. #endif
  672. CGImageRelease(destImageRef);
  673. SDImageCopyAssociatedObject(image, decodedImage);
  674. decodedImage.sd_isDecoded = YES;
  675. return decodedImage;
  676. }
  677. }
  678. + (SDImageCoderDecodeSolution)defaultDecodeSolution {
  679. return kDefaultDecodeSolution;
  680. }
  681. + (void)setDefaultDecodeSolution:(SDImageCoderDecodeSolution)defaultDecodeSolution {
  682. kDefaultDecodeSolution = defaultDecodeSolution;
  683. }
  684. + (NSUInteger)defaultScaleDownLimitBytes {
  685. return kDestImageLimitBytes;
  686. }
  687. + (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
  688. if (defaultScaleDownLimitBytes < kBytesPerPixel) {
  689. return;
  690. }
  691. kDestImageLimitBytes = defaultScaleDownLimitBytes;
  692. }
  693. #if SD_UIKIT || SD_WATCH
  694. // Convert an EXIF image orientation to an iOS one.
  695. + (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation {
  696. UIImageOrientation imageOrientation = UIImageOrientationUp;
  697. switch (exifOrientation) {
  698. case kCGImagePropertyOrientationUp:
  699. imageOrientation = UIImageOrientationUp;
  700. break;
  701. case kCGImagePropertyOrientationDown:
  702. imageOrientation = UIImageOrientationDown;
  703. break;
  704. case kCGImagePropertyOrientationLeft:
  705. imageOrientation = UIImageOrientationLeft;
  706. break;
  707. case kCGImagePropertyOrientationRight:
  708. imageOrientation = UIImageOrientationRight;
  709. break;
  710. case kCGImagePropertyOrientationUpMirrored:
  711. imageOrientation = UIImageOrientationUpMirrored;
  712. break;
  713. case kCGImagePropertyOrientationDownMirrored:
  714. imageOrientation = UIImageOrientationDownMirrored;
  715. break;
  716. case kCGImagePropertyOrientationLeftMirrored:
  717. imageOrientation = UIImageOrientationLeftMirrored;
  718. break;
  719. case kCGImagePropertyOrientationRightMirrored:
  720. imageOrientation = UIImageOrientationRightMirrored;
  721. break;
  722. default:
  723. break;
  724. }
  725. return imageOrientation;
  726. }
  727. // Convert an iOS orientation to an EXIF image orientation.
  728. + (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
  729. CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
  730. switch (imageOrientation) {
  731. case UIImageOrientationUp:
  732. exifOrientation = kCGImagePropertyOrientationUp;
  733. break;
  734. case UIImageOrientationDown:
  735. exifOrientation = kCGImagePropertyOrientationDown;
  736. break;
  737. case UIImageOrientationLeft:
  738. exifOrientation = kCGImagePropertyOrientationLeft;
  739. break;
  740. case UIImageOrientationRight:
  741. exifOrientation = kCGImagePropertyOrientationRight;
  742. break;
  743. case UIImageOrientationUpMirrored:
  744. exifOrientation = kCGImagePropertyOrientationUpMirrored;
  745. break;
  746. case UIImageOrientationDownMirrored:
  747. exifOrientation = kCGImagePropertyOrientationDownMirrored;
  748. break;
  749. case UIImageOrientationLeftMirrored:
  750. exifOrientation = kCGImagePropertyOrientationLeftMirrored;
  751. break;
  752. case UIImageOrientationRightMirrored:
  753. exifOrientation = kCGImagePropertyOrientationRightMirrored;
  754. break;
  755. default:
  756. break;
  757. }
  758. return exifOrientation;
  759. }
  760. #endif
  761. #pragma mark - Helper Function
  762. + (BOOL)shouldDecodeImage:(nullable UIImage *)image policy:(SDImageForceDecodePolicy)policy {
  763. // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
  764. if (image == nil) {
  765. return NO;
  766. }
  767. // Check policy (never)
  768. if (policy == SDImageForceDecodePolicyNever) {
  769. return NO;
  770. }
  771. // Avoid extra decode
  772. if (image.sd_isDecoded) {
  773. return NO;
  774. }
  775. // do not decode animated images
  776. if (image.sd_isAnimated) {
  777. return NO;
  778. }
  779. // do not decode vector images
  780. if (image.sd_isVector) {
  781. return NO;
  782. }
  783. // Check policy (always)
  784. if (policy == SDImageForceDecodePolicyAlways) {
  785. return YES;
  786. } else {
  787. // Check policy (automatic)
  788. CGImageRef cgImage = image.CGImage;
  789. if (cgImage) {
  790. CFStringRef uttype = CGImageGetUTType(cgImage);
  791. if (uttype) {
  792. // Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
  793. return YES;
  794. } else {
  795. // Now, let's check if the CGImage is hardware supported (not byte-aligned will cause extra copy)
  796. BOOL isSupported = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
  797. return !isSupported;
  798. }
  799. }
  800. }
  801. return YES;
  802. }
  803. + (BOOL)shouldScaleDownImagePixelSize:(CGSize)sourceResolution limitBytes:(NSUInteger)bytes {
  804. BOOL shouldScaleDown = YES;
  805. CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  806. if (sourceTotalPixels <= 0) {
  807. return NO;
  808. }
  809. CGFloat destTotalPixels;
  810. if (bytes == 0) {
  811. bytes = [self defaultScaleDownLimitBytes];
  812. }
  813. bytes = MAX(bytes, kBytesPerPixel);
  814. destTotalPixels = bytes / kBytesPerPixel;
  815. CGFloat imageScale = destTotalPixels / sourceTotalPixels;
  816. if (imageScale < 1) {
  817. shouldScaleDown = YES;
  818. } else {
  819. shouldScaleDown = NO;
  820. }
  821. return shouldScaleDown;
  822. }
  823. static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
  824. // Inspiration from @libfeihu
  825. // We need to calculate the proper transformation to make the image upright.
  826. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
  827. CGAffineTransform transform = CGAffineTransformIdentity;
  828. switch (orientation) {
  829. case kCGImagePropertyOrientationDown:
  830. case kCGImagePropertyOrientationDownMirrored:
  831. transform = CGAffineTransformTranslate(transform, size.width, size.height);
  832. transform = CGAffineTransformRotate(transform, M_PI);
  833. break;
  834. case kCGImagePropertyOrientationLeft:
  835. case kCGImagePropertyOrientationLeftMirrored:
  836. transform = CGAffineTransformTranslate(transform, size.width, 0);
  837. transform = CGAffineTransformRotate(transform, M_PI_2);
  838. break;
  839. case kCGImagePropertyOrientationRight:
  840. case kCGImagePropertyOrientationRightMirrored:
  841. transform = CGAffineTransformTranslate(transform, 0, size.height);
  842. transform = CGAffineTransformRotate(transform, -M_PI_2);
  843. break;
  844. case kCGImagePropertyOrientationUp:
  845. case kCGImagePropertyOrientationUpMirrored:
  846. break;
  847. }
  848. switch (orientation) {
  849. case kCGImagePropertyOrientationUpMirrored:
  850. case kCGImagePropertyOrientationDownMirrored:
  851. transform = CGAffineTransformTranslate(transform, size.width, 0);
  852. transform = CGAffineTransformScale(transform, -1, 1);
  853. break;
  854. case kCGImagePropertyOrientationLeftMirrored:
  855. case kCGImagePropertyOrientationRightMirrored:
  856. transform = CGAffineTransformTranslate(transform, size.height, 0);
  857. transform = CGAffineTransformScale(transform, -1, 1);
  858. break;
  859. case kCGImagePropertyOrientationUp:
  860. case kCGImagePropertyOrientationDown:
  861. case kCGImagePropertyOrientationLeft:
  862. case kCGImagePropertyOrientationRight:
  863. break;
  864. }
  865. return transform;
  866. }
  867. #if SD_UIKIT || SD_WATCH
  868. static NSUInteger gcd(NSUInteger a, NSUInteger b) {
  869. NSUInteger c;
  870. while (a != 0) {
  871. c = a;
  872. a = b % a;
  873. b = c;
  874. }
  875. return b;
  876. }
  877. static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
  878. if (count == 0) {
  879. return 0;
  880. }
  881. NSUInteger result = values[0];
  882. for (size_t i = 1; i < count; ++i) {
  883. result = gcd(values[i], result);
  884. }
  885. return result;
  886. }
  887. #endif
  888. @end