SDImageCoderHelper.m 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  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 bitsPerComponent = 8;
  286. if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) {
  287. bitsPerComponent = 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 = (bitsPerComponent / 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. if (size.width == 0 || size.height == 0) {
  395. return NULL;
  396. }
  397. size_t width = CGImageGetWidth(cgImage);
  398. size_t height = CGImageGetHeight(cgImage);
  399. if (width == size.width && height == size.height) {
  400. // Already same size
  401. CGImageRetain(cgImage);
  402. return cgImage;
  403. }
  404. size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
  405. if (bitsPerComponent != 8 && bitsPerComponent != 16 && bitsPerComponent != 32) {
  406. // Unsupported
  407. return NULL;
  408. }
  409. size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
  410. CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage);
  411. CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(cgImage);
  412. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
  413. CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
  414. CGImageByteOrderInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
  415. CGBitmapInfo alphaBitmapInfo = (uint32_t)byteOrderInfo;
  416. // Input need to convert with alpha
  417. if (alphaInfo == kCGImageAlphaNone) {
  418. // Convert RGB8/16/F -> ARGB8/16/F
  419. alphaBitmapInfo |= kCGImageAlphaFirst;
  420. } else {
  421. alphaBitmapInfo |= alphaInfo;
  422. }
  423. uint32_t components;
  424. if (alphaInfo == kCGImageAlphaOnly) {
  425. // Alpha only, simple to 1 channel
  426. components = 1;
  427. } else {
  428. components = 4;
  429. }
  430. if (SD_OPTIONS_CONTAINS(bitmapInfo, kCGBitmapFloatComponents)) {
  431. // Keep float components
  432. alphaBitmapInfo |= kCGBitmapFloatComponents;
  433. }
  434. __block vImage_Buffer input_buffer = {}, output_buffer = {};
  435. @onExit {
  436. if (input_buffer.data) free(input_buffer.data);
  437. if (output_buffer.data) free(output_buffer.data);
  438. };
  439. // Always provide alpha channel
  440. vImage_CGImageFormat format = (vImage_CGImageFormat) {
  441. .bitsPerComponent = (uint32_t)bitsPerComponent,
  442. .bitsPerPixel = (uint32_t)bitsPerComponent * components,
  443. .colorSpace = colorSpace,
  444. .bitmapInfo = alphaBitmapInfo,
  445. .version = 0,
  446. .decode = NULL,
  447. .renderingIntent = renderingIntent
  448. };
  449. // input
  450. vImage_Error ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
  451. if (ret != kvImageNoError) return NULL;
  452. // output
  453. vImageBuffer_Init(&output_buffer, size.height, size.width, (uint32_t)bitsPerComponent * components, kvImageNoFlags);
  454. if (!output_buffer.data) return NULL;
  455. if (components == 4) {
  456. if (bitsPerComponent == 32) {
  457. ret = vImageScale_ARGBFFFF(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  458. } else if (bitsPerComponent == 16) {
  459. ret = vImageScale_ARGB16U(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  460. } else if (bitsPerComponent == 8) {
  461. ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  462. }
  463. } else {
  464. if (bitsPerComponent == 32) {
  465. ret = vImageScale_PlanarF(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  466. } else if (bitsPerComponent == 16) {
  467. ret = vImageScale_Planar16U(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  468. } else if (bitsPerComponent == 8) {
  469. ret = vImageScale_Planar8(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  470. }
  471. }
  472. if (ret != kvImageNoError) return NULL;
  473. // Convert back to non-alpha for RGB input to preserve pixel format
  474. if (alphaInfo == kCGImageAlphaNone) {
  475. // in-place, no extra allocation
  476. if (bitsPerComponent == 32) {
  477. ret = vImageConvert_ARGBFFFFtoRGBFFF(&output_buffer, &output_buffer, kvImageNoFlags);
  478. } else if (bitsPerComponent == 16) {
  479. ret = vImageConvert_ARGB16UtoRGB16U(&output_buffer, &output_buffer, kvImageNoFlags);
  480. } else if (bitsPerComponent == 8) {
  481. ret = vImageConvert_ARGB8888toRGB888(&output_buffer, &output_buffer, kvImageNoFlags);
  482. }
  483. if (ret != kvImageNoError) return NULL;
  484. }
  485. vImage_CGImageFormat output_format = (vImage_CGImageFormat) {
  486. .bitsPerComponent = (uint32_t)bitsPerComponent,
  487. .bitsPerPixel = (uint32_t)bitsPerPixel,
  488. .colorSpace = colorSpace,
  489. .bitmapInfo = bitmapInfo,
  490. .version = 0,
  491. .decode = NULL,
  492. .renderingIntent = renderingIntent
  493. };
  494. CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &output_format, NULL, NULL, kvImageNoFlags, &ret);
  495. if (ret != kvImageNoError) {
  496. CGImageRelease(outputImage);
  497. return NULL;
  498. }
  499. return outputImage;
  500. }
  501. + (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize preserveAspectRatio:(BOOL)preserveAspectRatio shouldScaleUp:(BOOL)shouldScaleUp {
  502. CGFloat width = imageSize.width;
  503. CGFloat height = imageSize.height;
  504. CGFloat resultWidth;
  505. CGFloat resultHeight;
  506. if (width <= 0 || height <= 0 || scaleSize.width <= 0 || scaleSize.height <= 0) {
  507. // Protect
  508. resultWidth = width;
  509. resultHeight = height;
  510. } else {
  511. // Scale to fit
  512. if (preserveAspectRatio) {
  513. CGFloat pixelRatio = width / height;
  514. CGFloat scaleRatio = scaleSize.width / scaleSize.height;
  515. if (pixelRatio > scaleRatio) {
  516. resultWidth = scaleSize.width;
  517. resultHeight = ceil(scaleSize.width / pixelRatio);
  518. } else {
  519. resultHeight = scaleSize.height;
  520. resultWidth = ceil(scaleSize.height * pixelRatio);
  521. }
  522. } else {
  523. // Stretch
  524. resultWidth = scaleSize.width;
  525. resultHeight = scaleSize.height;
  526. }
  527. if (!shouldScaleUp) {
  528. // Scale down only
  529. resultWidth = MIN(width, resultWidth);
  530. resultHeight = MIN(height, resultHeight);
  531. }
  532. }
  533. return CGSizeMake(resultWidth, resultHeight);
  534. }
  535. + (CGSize)scaledSizeWithImageSize:(CGSize)imageSize limitBytes:(NSUInteger)limitBytes bytesPerPixel:(NSUInteger)bytesPerPixel frameCount:(NSUInteger)frameCount {
  536. if (CGSizeEqualToSize(imageSize, CGSizeZero)) return CGSizeMake(1, 1);
  537. NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1);
  538. CGFloat ratio = imageSize.height / imageSize.width;
  539. CGFloat width = sqrt(totalFramePixelSize / ratio);
  540. CGFloat height = width * ratio;
  541. width = MAX(1, floor(width));
  542. height = MAX(1, floor(height));
  543. CGSize size = CGSizeMake(width, height);
  544. return size;
  545. }
  546. + (UIImage *)decodedImageWithImage:(UIImage *)image {
  547. return [self decodedImageWithImage:image policy:SDImageForceDecodePolicyAutomatic];
  548. }
  549. + (UIImage *)decodedImageWithImage:(UIImage *)image policy:(SDImageForceDecodePolicy)policy {
  550. if (![self shouldDecodeImage:image policy:policy]) {
  551. return image;
  552. }
  553. UIImage *decodedImage;
  554. SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution;
  555. #if SD_UIKIT
  556. if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) {
  557. // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :(
  558. SDImageFormat format = image.sd_imageFormat;
  559. if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) {
  560. decodedImage = SDImageDecodeUIKit(image);
  561. } else if (format == SDImageFormatJPEG) {
  562. decodedImage = SDImageDecodeUIKit(image);
  563. }
  564. } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) {
  565. // Arbitrarily call CMPhoto
  566. decodedImage = SDImageDecodeUIKit(image);
  567. }
  568. if (decodedImage) {
  569. return decodedImage;
  570. }
  571. #endif
  572. CGImageRef imageRef = image.CGImage;
  573. if (!imageRef) {
  574. // Only decode for CGImage-based
  575. return image;
  576. }
  577. if (decodeSolution == SDImageCoderDecodeSolutionCoreGraphics) {
  578. CGImageRef decodedImageRef = [self CGImageCreateDecoded:imageRef];
  579. #if SD_MAC
  580. decodedImage = [[UIImage alloc] initWithCGImage:decodedImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  581. #else
  582. decodedImage = [[UIImage alloc] initWithCGImage:decodedImageRef scale:image.scale orientation:image.imageOrientation];
  583. #endif
  584. CGImageRelease(decodedImageRef);
  585. } else {
  586. BOOL hasAlpha = [self CGImageContainsAlpha:imageRef];
  587. // Prefer to use new Image Renderer to re-draw image, instead of low-level CGBitmapContext and CGContextDrawImage
  588. // This can keep both OS compatible and don't fight with Apple's performance optimization
  589. SDGraphicsImageRendererFormat *format = SDGraphicsImageRendererFormat.preferredFormat;
  590. format.opaque = !hasAlpha;
  591. format.scale = image.scale;
  592. CGSize imageSize = image.size;
  593. SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:imageSize format:format];
  594. decodedImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
  595. [image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
  596. }];
  597. }
  598. SDImageCopyAssociatedObject(image, decodedImage);
  599. decodedImage.sd_isDecoded = YES;
  600. return decodedImage;
  601. }
  602. + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
  603. return [self decodedAndScaledDownImageWithImage:image limitBytes:bytes policy:SDImageForceDecodePolicyAutomatic];
  604. }
  605. + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes policy:(SDImageForceDecodePolicy)policy {
  606. if (![self shouldDecodeImage:image policy:policy]) {
  607. return image;
  608. }
  609. CGFloat destTotalPixels;
  610. CGFloat tileTotalPixels;
  611. if (bytes == 0) {
  612. bytes = [self defaultScaleDownLimitBytes];
  613. }
  614. bytes = MAX(bytes, kBytesPerPixel);
  615. destTotalPixels = bytes / kBytesPerPixel;
  616. tileTotalPixels = destTotalPixels / 3;
  617. CGImageRef sourceImageRef = image.CGImage;
  618. if (!sourceImageRef) {
  619. // Only decode for CGImage-based
  620. return image;
  621. }
  622. CGSize sourceResolution = CGSizeZero;
  623. sourceResolution.width = CGImageGetWidth(sourceImageRef);
  624. sourceResolution.height = CGImageGetHeight(sourceImageRef);
  625. if (![self shouldScaleDownImagePixelSize:sourceResolution limitBytes:bytes]) {
  626. return [self decodedImageWithImage:image];
  627. }
  628. CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  629. // Determine the scale ratio to apply to the input image
  630. // that results in an output image of the defined size.
  631. // see kDestImageSizeMB, and how it relates to destTotalPixels.
  632. CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
  633. CGSize destResolution = CGSizeZero;
  634. destResolution.width = MAX(1, (int)(sourceResolution.width * imageScale));
  635. destResolution.height = MAX(1, (int)(sourceResolution.height * imageScale));
  636. UIImage *decodedImage;
  637. #if SD_UIKIT
  638. SDImageCoderDecodeSolution decodeSolution = self.defaultDecodeSolution;
  639. if (decodeSolution == SDImageCoderDecodeSolutionAutomatic) {
  640. // See #3365, CMPhoto iOS 15 only supports JPEG/HEIF format, or it will print an error log :(
  641. SDImageFormat format = image.sd_imageFormat;
  642. if ((format == SDImageFormatHEIC || format == SDImageFormatHEIF) && SDImageSupportsHardwareHEVCDecoder()) {
  643. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  644. } else if (format == SDImageFormatJPEG) {
  645. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  646. }
  647. } else if (decodeSolution == SDImageCoderDecodeSolutionUIKit) {
  648. // Arbitrarily call CMPhoto
  649. decodedImage = SDImageDecodeAndScaleDownUIKit(image, destResolution);
  650. }
  651. if (decodedImage) {
  652. return decodedImage;
  653. }
  654. #endif
  655. // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
  656. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
  657. @autoreleasepool {
  658. // device color space
  659. CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
  660. BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
  661. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  662. // Check #3330 for more detail about why this bitmap is choosen.
  663. // From v5.17.0, use runtime detection of bitmap info instead of hardcode.
  664. CGBitmapInfo bitmapInfo = [SDImageCoderHelper preferredPixelFormat:hasAlpha].bitmapInfo;
  665. CGContextRef destContext = CGBitmapContextCreate(NULL,
  666. destResolution.width,
  667. destResolution.height,
  668. kBitsPerComponent,
  669. 0,
  670. colorspaceRef,
  671. bitmapInfo);
  672. if (destContext == NULL) {
  673. return image;
  674. }
  675. CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
  676. // Now define the size of the rectangle to be used for the
  677. // incremental bits from the input image to the output image.
  678. // we use a source tile width equal to the width of the source
  679. // image due to the way that iOS retrieves image data from disk.
  680. // iOS must decode an image from disk in full width 'bands', even
  681. // if current graphics context is clipped to a subrect within that
  682. // band. Therefore we fully utilize all of the pixel data that results
  683. // from a decoding operation by anchoring our tile size to the full
  684. // width of the input image.
  685. CGRect sourceTile = CGRectZero;
  686. sourceTile.size.width = sourceResolution.width;
  687. // The source tile height is dynamic. Since we specified the size
  688. // of the source tile in MB, see how many rows of pixels high it
  689. // can be given the input image width.
  690. sourceTile.size.height = MAX(1, (int)(tileTotalPixels / sourceTile.size.width));
  691. sourceTile.origin.x = 0.0f;
  692. // The output tile is the same proportions as the input tile, but
  693. // scaled to image scale.
  694. CGRect destTile;
  695. destTile.size.width = destResolution.width;
  696. destTile.size.height = sourceTile.size.height * imageScale;
  697. destTile.origin.x = 0.0f;
  698. // The source seem overlap is proportionate to the destination seem overlap.
  699. // this is the amount of pixels to overlap each tile as we assemble the output image.
  700. float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
  701. CGImageRef sourceTileImageRef;
  702. // calculate the number of read/write operations required to assemble the
  703. // output image.
  704. int iterations = (int)( sourceResolution.height / sourceTile.size.height );
  705. // If tile height doesn't divide the image height evenly, add another iteration
  706. // to account for the remaining pixels.
  707. int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
  708. if(remainder) {
  709. iterations++;
  710. }
  711. // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
  712. float sourceTileHeightMinusOverlap = sourceTile.size.height;
  713. sourceTile.size.height += sourceSeemOverlap;
  714. destTile.size.height += kDestSeemOverlap;
  715. for( int y = 0; y < iterations; ++y ) {
  716. sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
  717. destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
  718. sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
  719. if( y == iterations - 1 && remainder ) {
  720. float dify = destTile.size.height;
  721. destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale + kDestSeemOverlap;
  722. dify -= destTile.size.height;
  723. destTile.origin.y = MIN(0, destTile.origin.y + dify);
  724. }
  725. CGContextDrawImage( destContext, destTile, sourceTileImageRef );
  726. CGImageRelease( sourceTileImageRef );
  727. }
  728. CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
  729. CGContextRelease(destContext);
  730. if (destImageRef == NULL) {
  731. return image;
  732. }
  733. #if SD_MAC
  734. decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  735. #else
  736. decodedImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
  737. #endif
  738. CGImageRelease(destImageRef);
  739. SDImageCopyAssociatedObject(image, decodedImage);
  740. decodedImage.sd_isDecoded = YES;
  741. return decodedImage;
  742. }
  743. }
  744. + (SDImageCoderDecodeSolution)defaultDecodeSolution {
  745. return kDefaultDecodeSolution;
  746. }
  747. + (void)setDefaultDecodeSolution:(SDImageCoderDecodeSolution)defaultDecodeSolution {
  748. kDefaultDecodeSolution = defaultDecodeSolution;
  749. }
  750. + (NSUInteger)defaultScaleDownLimitBytes {
  751. return kDestImageLimitBytes;
  752. }
  753. + (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
  754. if (defaultScaleDownLimitBytes < kBytesPerPixel) {
  755. return;
  756. }
  757. kDestImageLimitBytes = defaultScaleDownLimitBytes;
  758. }
  759. #if SD_UIKIT || SD_WATCH
  760. // Convert an EXIF image orientation to an iOS one.
  761. + (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation {
  762. UIImageOrientation imageOrientation = UIImageOrientationUp;
  763. switch (exifOrientation) {
  764. case kCGImagePropertyOrientationUp:
  765. imageOrientation = UIImageOrientationUp;
  766. break;
  767. case kCGImagePropertyOrientationDown:
  768. imageOrientation = UIImageOrientationDown;
  769. break;
  770. case kCGImagePropertyOrientationLeft:
  771. imageOrientation = UIImageOrientationLeft;
  772. break;
  773. case kCGImagePropertyOrientationRight:
  774. imageOrientation = UIImageOrientationRight;
  775. break;
  776. case kCGImagePropertyOrientationUpMirrored:
  777. imageOrientation = UIImageOrientationUpMirrored;
  778. break;
  779. case kCGImagePropertyOrientationDownMirrored:
  780. imageOrientation = UIImageOrientationDownMirrored;
  781. break;
  782. case kCGImagePropertyOrientationLeftMirrored:
  783. imageOrientation = UIImageOrientationLeftMirrored;
  784. break;
  785. case kCGImagePropertyOrientationRightMirrored:
  786. imageOrientation = UIImageOrientationRightMirrored;
  787. break;
  788. default:
  789. break;
  790. }
  791. return imageOrientation;
  792. }
  793. // Convert an iOS orientation to an EXIF image orientation.
  794. + (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
  795. CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
  796. switch (imageOrientation) {
  797. case UIImageOrientationUp:
  798. exifOrientation = kCGImagePropertyOrientationUp;
  799. break;
  800. case UIImageOrientationDown:
  801. exifOrientation = kCGImagePropertyOrientationDown;
  802. break;
  803. case UIImageOrientationLeft:
  804. exifOrientation = kCGImagePropertyOrientationLeft;
  805. break;
  806. case UIImageOrientationRight:
  807. exifOrientation = kCGImagePropertyOrientationRight;
  808. break;
  809. case UIImageOrientationUpMirrored:
  810. exifOrientation = kCGImagePropertyOrientationUpMirrored;
  811. break;
  812. case UIImageOrientationDownMirrored:
  813. exifOrientation = kCGImagePropertyOrientationDownMirrored;
  814. break;
  815. case UIImageOrientationLeftMirrored:
  816. exifOrientation = kCGImagePropertyOrientationLeftMirrored;
  817. break;
  818. case UIImageOrientationRightMirrored:
  819. exifOrientation = kCGImagePropertyOrientationRightMirrored;
  820. break;
  821. default:
  822. break;
  823. }
  824. return exifOrientation;
  825. }
  826. #endif
  827. #pragma mark - Helper Function
  828. + (BOOL)shouldDecodeImage:(nullable UIImage *)image policy:(SDImageForceDecodePolicy)policy {
  829. // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
  830. if (image == nil) {
  831. return NO;
  832. }
  833. // Check policy (never)
  834. if (policy == SDImageForceDecodePolicyNever) {
  835. return NO;
  836. }
  837. // Avoid extra decode
  838. if (image.sd_isDecoded) {
  839. return NO;
  840. }
  841. // do not decode animated images
  842. if (image.sd_isAnimated) {
  843. return NO;
  844. }
  845. // do not decode vector images
  846. if (image.sd_isVector) {
  847. return NO;
  848. }
  849. // Check policy (always)
  850. if (policy == SDImageForceDecodePolicyAlways) {
  851. return YES;
  852. } else {
  853. // Check policy (automatic)
  854. CGImageRef cgImage = image.CGImage;
  855. if (cgImage) {
  856. CFStringRef uttype = CGImageGetUTType(cgImage);
  857. if (uttype) {
  858. // Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
  859. return YES;
  860. } else {
  861. // Now, let's check if the CGImage is hardware supported (not byte-aligned will cause extra copy)
  862. BOOL isSupported = [SDImageCoderHelper CGImageIsHardwareSupported:cgImage];
  863. return !isSupported;
  864. }
  865. }
  866. }
  867. return YES;
  868. }
  869. + (BOOL)shouldScaleDownImagePixelSize:(CGSize)sourceResolution limitBytes:(NSUInteger)bytes {
  870. BOOL shouldScaleDown = YES;
  871. CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  872. if (sourceTotalPixels <= 0) {
  873. return NO;
  874. }
  875. CGFloat destTotalPixels;
  876. if (bytes == 0) {
  877. bytes = [self defaultScaleDownLimitBytes];
  878. }
  879. bytes = MAX(bytes, kBytesPerPixel);
  880. destTotalPixels = bytes / kBytesPerPixel;
  881. CGFloat imageScale = destTotalPixels / sourceTotalPixels;
  882. if (imageScale < 1) {
  883. shouldScaleDown = YES;
  884. } else {
  885. shouldScaleDown = NO;
  886. }
  887. return shouldScaleDown;
  888. }
  889. static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
  890. // Inspiration from @libfeihu
  891. // We need to calculate the proper transformation to make the image upright.
  892. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
  893. CGAffineTransform transform = CGAffineTransformIdentity;
  894. switch (orientation) {
  895. case kCGImagePropertyOrientationDown:
  896. case kCGImagePropertyOrientationDownMirrored:
  897. transform = CGAffineTransformTranslate(transform, size.width, size.height);
  898. transform = CGAffineTransformRotate(transform, M_PI);
  899. break;
  900. case kCGImagePropertyOrientationLeft:
  901. case kCGImagePropertyOrientationLeftMirrored:
  902. transform = CGAffineTransformTranslate(transform, size.width, 0);
  903. transform = CGAffineTransformRotate(transform, M_PI_2);
  904. break;
  905. case kCGImagePropertyOrientationRight:
  906. case kCGImagePropertyOrientationRightMirrored:
  907. transform = CGAffineTransformTranslate(transform, 0, size.height);
  908. transform = CGAffineTransformRotate(transform, -M_PI_2);
  909. break;
  910. case kCGImagePropertyOrientationUp:
  911. case kCGImagePropertyOrientationUpMirrored:
  912. break;
  913. }
  914. switch (orientation) {
  915. case kCGImagePropertyOrientationUpMirrored:
  916. case kCGImagePropertyOrientationDownMirrored:
  917. transform = CGAffineTransformTranslate(transform, size.width, 0);
  918. transform = CGAffineTransformScale(transform, -1, 1);
  919. break;
  920. case kCGImagePropertyOrientationLeftMirrored:
  921. case kCGImagePropertyOrientationRightMirrored:
  922. transform = CGAffineTransformTranslate(transform, size.height, 0);
  923. transform = CGAffineTransformScale(transform, -1, 1);
  924. break;
  925. case kCGImagePropertyOrientationUp:
  926. case kCGImagePropertyOrientationDown:
  927. case kCGImagePropertyOrientationLeft:
  928. case kCGImagePropertyOrientationRight:
  929. break;
  930. }
  931. return transform;
  932. }
  933. #if SD_UIKIT || SD_WATCH
  934. static NSUInteger gcd(NSUInteger a, NSUInteger b) {
  935. NSUInteger c;
  936. while (a != 0) {
  937. c = a;
  938. a = b % a;
  939. b = c;
  940. }
  941. return b;
  942. }
  943. static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
  944. if (count == 0) {
  945. return 0;
  946. }
  947. NSUInteger result = values[0];
  948. for (size_t i = 1; i < count; ++i) {
  949. result = gcd(values[i], result);
  950. }
  951. return result;
  952. }
  953. #endif
  954. @end