SDImageCoderHelper.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  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 <Accelerate/Accelerate.h>
  18. static inline size_t SDByteAlign(size_t size, size_t alignment) {
  19. return ((size + (alignment - 1)) / alignment) * alignment;
  20. }
  21. static const size_t kBytesPerPixel = 4;
  22. static const size_t kBitsPerComponent = 8;
  23. static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
  24. static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
  25. /*
  26. * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
  27. * Suggested value for iPad1 and iPhone 3GS: 60.
  28. * Suggested value for iPad2 and iPhone 4: 120.
  29. * Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
  30. */
  31. #if SD_MAC
  32. static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB;
  33. #elif SD_UIKIT
  34. static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
  35. #elif SD_WATCH
  36. static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB;
  37. #endif
  38. static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
  39. @implementation SDImageCoderHelper
  40. + (UIImage *)animatedImageWithFrames:(NSArray<SDImageFrame *> *)frames {
  41. NSUInteger frameCount = frames.count;
  42. if (frameCount == 0) {
  43. return nil;
  44. }
  45. UIImage *animatedImage;
  46. #if SD_UIKIT || SD_WATCH
  47. NSUInteger durations[frameCount];
  48. for (size_t i = 0; i < frameCount; i++) {
  49. durations[i] = frames[i].duration * 1000;
  50. }
  51. NSUInteger const gcd = gcdArray(frameCount, durations);
  52. __block NSUInteger totalDuration = 0;
  53. NSMutableArray<UIImage *> *animatedImages = [NSMutableArray arrayWithCapacity:frameCount];
  54. [frames enumerateObjectsUsingBlock:^(SDImageFrame * _Nonnull frame, NSUInteger idx, BOOL * _Nonnull stop) {
  55. UIImage *image = frame.image;
  56. NSUInteger duration = frame.duration * 1000;
  57. totalDuration += duration;
  58. NSUInteger repeatCount;
  59. if (gcd) {
  60. repeatCount = duration / gcd;
  61. } else {
  62. repeatCount = 1;
  63. }
  64. for (size_t i = 0; i < repeatCount; ++i) {
  65. [animatedImages addObject:image];
  66. }
  67. }];
  68. animatedImage = [UIImage animatedImageWithImages:animatedImages duration:totalDuration / 1000.f];
  69. #else
  70. NSMutableData *imageData = [NSMutableData data];
  71. CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:SDImageFormatGIF];
  72. // Create an image destination. GIF does not support EXIF image orientation
  73. CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, frameCount, NULL);
  74. if (!imageDestination) {
  75. // Handle failure.
  76. return nil;
  77. }
  78. for (size_t i = 0; i < frameCount; i++) {
  79. @autoreleasepool {
  80. SDImageFrame *frame = frames[i];
  81. NSTimeInterval frameDuration = frame.duration;
  82. CGImageRef frameImageRef = frame.image.CGImage;
  83. NSDictionary *frameProperties = @{(__bridge NSString *)kCGImagePropertyGIFDictionary : @{(__bridge NSString *)kCGImagePropertyGIFDelayTime : @(frameDuration)}};
  84. CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
  85. }
  86. }
  87. // Finalize the destination.
  88. if (CGImageDestinationFinalize(imageDestination) == NO) {
  89. // Handle failure.
  90. CFRelease(imageDestination);
  91. return nil;
  92. }
  93. CFRelease(imageDestination);
  94. CGFloat scale = MAX(frames.firstObject.image.scale, 1);
  95. SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:imageData];
  96. NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
  97. imageRep.size = size;
  98. animatedImage = [[NSImage alloc] initWithSize:size];
  99. [animatedImage addRepresentation:imageRep];
  100. #endif
  101. return animatedImage;
  102. }
  103. + (NSArray<SDImageFrame *> *)framesFromAnimatedImage:(UIImage *)animatedImage {
  104. if (!animatedImage) {
  105. return nil;
  106. }
  107. NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
  108. NSUInteger frameCount = 0;
  109. #if SD_UIKIT || SD_WATCH
  110. NSArray<UIImage *> *animatedImages = animatedImage.images;
  111. frameCount = animatedImages.count;
  112. if (frameCount == 0) {
  113. return nil;
  114. }
  115. NSTimeInterval avgDuration = animatedImage.duration / frameCount;
  116. if (avgDuration == 0) {
  117. 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)
  118. }
  119. __block NSUInteger index = 0;
  120. __block NSUInteger repeatCount = 1;
  121. __block UIImage *previousImage = animatedImages.firstObject;
  122. [animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
  123. // ignore first
  124. if (idx == 0) {
  125. return;
  126. }
  127. if ([image isEqual:previousImage]) {
  128. repeatCount++;
  129. } else {
  130. SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  131. [frames addObject:frame];
  132. repeatCount = 1;
  133. index++;
  134. }
  135. previousImage = image;
  136. // last one
  137. if (idx == frameCount - 1) {
  138. SDImageFrame *frame = [SDImageFrame frameWithImage:previousImage duration:avgDuration * repeatCount];
  139. [frames addObject:frame];
  140. }
  141. }];
  142. #else
  143. NSRect imageRect = NSMakeRect(0, 0, animatedImage.size.width, animatedImage.size.height);
  144. NSImageRep *imageRep = [animatedImage bestRepresentationForRect:imageRect context:nil hints:nil];
  145. NSBitmapImageRep *bitmapImageRep;
  146. if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
  147. bitmapImageRep = (NSBitmapImageRep *)imageRep;
  148. }
  149. if (!bitmapImageRep) {
  150. return nil;
  151. }
  152. frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
  153. if (frameCount == 0) {
  154. return nil;
  155. }
  156. CGFloat scale = animatedImage.scale;
  157. for (size_t i = 0; i < frameCount; i++) {
  158. @autoreleasepool {
  159. // NSBitmapImageRep need to manually change frame. "Good taste" API
  160. [bitmapImageRep setProperty:NSImageCurrentFrame withValue:@(i)];
  161. NSTimeInterval frameDuration = [[bitmapImageRep valueForProperty:NSImageCurrentFrameDuration] doubleValue];
  162. NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapImageRep.CGImage scale:scale orientation:kCGImagePropertyOrientationUp];
  163. SDImageFrame *frame = [SDImageFrame frameWithImage:frameImage duration:frameDuration];
  164. [frames addObject:frame];
  165. }
  166. }
  167. #endif
  168. return frames;
  169. }
  170. + (CGColorSpaceRef)colorSpaceGetDeviceRGB {
  171. #if SD_MAC
  172. CGColorSpaceRef screenColorSpace = NSScreen.mainScreen.colorSpace.CGColorSpace;
  173. if (screenColorSpace) {
  174. return screenColorSpace;
  175. }
  176. #endif
  177. static CGColorSpaceRef colorSpace;
  178. static dispatch_once_t onceToken;
  179. dispatch_once(&onceToken, ^{
  180. #if SD_UIKIT
  181. if (@available(iOS 9.0, tvOS 9.0, *)) {
  182. colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
  183. } else {
  184. colorSpace = CGColorSpaceCreateDeviceRGB();
  185. }
  186. #else
  187. colorSpace = CGColorSpaceCreateDeviceRGB();
  188. #endif
  189. });
  190. return colorSpace;
  191. }
  192. + (BOOL)CGImageContainsAlpha:(CGImageRef)cgImage {
  193. if (!cgImage) {
  194. return NO;
  195. }
  196. CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
  197. BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
  198. alphaInfo == kCGImageAlphaNoneSkipFirst ||
  199. alphaInfo == kCGImageAlphaNoneSkipLast);
  200. return hasAlpha;
  201. }
  202. + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage {
  203. return [self CGImageCreateDecoded:cgImage orientation:kCGImagePropertyOrientationUp];
  204. }
  205. + (CGImageRef)CGImageCreateDecoded:(CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation {
  206. if (!cgImage) {
  207. return NULL;
  208. }
  209. size_t width = CGImageGetWidth(cgImage);
  210. size_t height = CGImageGetHeight(cgImage);
  211. if (width == 0 || height == 0) return NULL;
  212. size_t newWidth;
  213. size_t newHeight;
  214. switch (orientation) {
  215. case kCGImagePropertyOrientationLeft:
  216. case kCGImagePropertyOrientationLeftMirrored:
  217. case kCGImagePropertyOrientationRight:
  218. case kCGImagePropertyOrientationRightMirrored: {
  219. // These orientation should swap width & height
  220. newWidth = height;
  221. newHeight = width;
  222. }
  223. break;
  224. default: {
  225. newWidth = width;
  226. newHeight = height;
  227. }
  228. break;
  229. }
  230. BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
  231. // iOS prefer BGRA8888 (premultiplied) or BGRX8888 bitmapInfo for screen rendering, which is same as `UIGraphicsBeginImageContext()` or `- [CALayer drawInContext:]`
  232. // Though you can use any supported bitmapInfo (see: https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB ) and let Core Graphics reorder it when you call `CGContextDrawImage`
  233. // But since our build-in coders use this bitmapInfo, this can have a little performance benefit
  234. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
  235. bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  236. CGContextRef context = CGBitmapContextCreate(NULL, newWidth, newHeight, 8, 0, [self colorSpaceGetDeviceRGB], bitmapInfo);
  237. if (!context) {
  238. return NULL;
  239. }
  240. // Apply transform
  241. CGAffineTransform transform = SDCGContextTransformFromOrientation(orientation, CGSizeMake(newWidth, newHeight));
  242. CGContextConcatCTM(context, transform);
  243. CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); // The rect is bounding box of CGImage, don't swap width & height
  244. CGImageRef newImageRef = CGBitmapContextCreateImage(context);
  245. CGContextRelease(context);
  246. return newImageRef;
  247. }
  248. + (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size {
  249. if (!cgImage) {
  250. return NULL;
  251. }
  252. size_t width = CGImageGetWidth(cgImage);
  253. size_t height = CGImageGetHeight(cgImage);
  254. if (width == size.width && height == size.height) {
  255. CGImageRetain(cgImage);
  256. return cgImage;
  257. }
  258. __block vImage_Buffer input_buffer = {}, output_buffer = {};
  259. @onExit {
  260. if (input_buffer.data) free(input_buffer.data);
  261. if (output_buffer.data) free(output_buffer.data);
  262. };
  263. BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
  264. // iOS display alpha info (BGRA8888/BGRX8888)
  265. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
  266. bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  267. vImage_CGImageFormat format = (vImage_CGImageFormat) {
  268. .bitsPerComponent = 8,
  269. .bitsPerPixel = 32,
  270. .colorSpace = NULL,
  271. .bitmapInfo = bitmapInfo,
  272. .version = 0,
  273. .decode = NULL,
  274. .renderingIntent = kCGRenderingIntentDefault,
  275. };
  276. vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
  277. if (a_ret != kvImageNoError) return NULL;
  278. output_buffer.width = MAX(size.width, 0);
  279. output_buffer.height = MAX(size.height, 0);
  280. output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64);
  281. output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height);
  282. if (!output_buffer.data) return NULL;
  283. vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
  284. if (ret != kvImageNoError) return NULL;
  285. CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret);
  286. if (ret != kvImageNoError) {
  287. CGImageRelease(outputImage);
  288. return NULL;
  289. }
  290. return outputImage;
  291. }
  292. + (UIImage *)decodedImageWithImage:(UIImage *)image {
  293. if (![self shouldDecodeImage:image]) {
  294. return image;
  295. }
  296. CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
  297. if (!imageRef) {
  298. return image;
  299. }
  300. #if SD_MAC
  301. UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  302. #else
  303. UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
  304. #endif
  305. CGImageRelease(imageRef);
  306. SDImageCopyAssociatedObject(image, decodedImage);
  307. decodedImage.sd_isDecoded = YES;
  308. return decodedImage;
  309. }
  310. + (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
  311. if (![self shouldDecodeImage:image]) {
  312. return image;
  313. }
  314. if (![self shouldScaleDownImage:image limitBytes:bytes]) {
  315. return [self decodedImageWithImage:image];
  316. }
  317. CGFloat destTotalPixels;
  318. CGFloat tileTotalPixels;
  319. if (bytes == 0) {
  320. bytes = kDestImageLimitBytes;
  321. }
  322. destTotalPixels = bytes / kBytesPerPixel;
  323. tileTotalPixels = destTotalPixels / 3;
  324. CGContextRef destContext;
  325. // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
  326. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
  327. @autoreleasepool {
  328. CGImageRef sourceImageRef = image.CGImage;
  329. CGSize sourceResolution = CGSizeZero;
  330. sourceResolution.width = CGImageGetWidth(sourceImageRef);
  331. sourceResolution.height = CGImageGetHeight(sourceImageRef);
  332. CGFloat sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  333. // Determine the scale ratio to apply to the input image
  334. // that results in an output image of the defined size.
  335. // see kDestImageSizeMB, and how it relates to destTotalPixels.
  336. CGFloat imageScale = sqrt(destTotalPixels / sourceTotalPixels);
  337. CGSize destResolution = CGSizeZero;
  338. destResolution.width = (int)(sourceResolution.width * imageScale);
  339. destResolution.height = (int)(sourceResolution.height * imageScale);
  340. // device color space
  341. CGColorSpaceRef colorspaceRef = [self colorSpaceGetDeviceRGB];
  342. BOOL hasAlpha = [self CGImageContainsAlpha:sourceImageRef];
  343. // iOS display alpha info (BGRA8888/BGRX8888)
  344. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
  345. bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  346. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  347. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipFirst
  348. // to create bitmap graphics contexts without alpha info.
  349. destContext = CGBitmapContextCreate(NULL,
  350. destResolution.width,
  351. destResolution.height,
  352. kBitsPerComponent,
  353. 0,
  354. colorspaceRef,
  355. bitmapInfo);
  356. if (destContext == NULL) {
  357. return image;
  358. }
  359. CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
  360. // Now define the size of the rectangle to be used for the
  361. // incremental blits from the input image to the output image.
  362. // we use a source tile width equal to the width of the source
  363. // image due to the way that iOS retrieves image data from disk.
  364. // iOS must decode an image from disk in full width 'bands', even
  365. // if current graphics context is clipped to a subrect within that
  366. // band. Therefore we fully utilize all of the pixel data that results
  367. // from a decoding opertion by achnoring our tile size to the full
  368. // width of the input image.
  369. CGRect sourceTile = CGRectZero;
  370. sourceTile.size.width = sourceResolution.width;
  371. // The source tile height is dynamic. Since we specified the size
  372. // of the source tile in MB, see how many rows of pixels high it
  373. // can be given the input image width.
  374. sourceTile.size.height = (int)(tileTotalPixels / sourceTile.size.width );
  375. sourceTile.origin.x = 0.0f;
  376. // The output tile is the same proportions as the input tile, but
  377. // scaled to image scale.
  378. CGRect destTile;
  379. destTile.size.width = destResolution.width;
  380. destTile.size.height = sourceTile.size.height * imageScale;
  381. destTile.origin.x = 0.0f;
  382. // The source seem overlap is proportionate to the destination seem overlap.
  383. // this is the amount of pixels to overlap each tile as we assemble the ouput image.
  384. float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
  385. CGImageRef sourceTileImageRef;
  386. // calculate the number of read/write operations required to assemble the
  387. // output image.
  388. int iterations = (int)( sourceResolution.height / sourceTile.size.height );
  389. // If tile height doesn't divide the image height evenly, add another iteration
  390. // to account for the remaining pixels.
  391. int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
  392. if(remainder) {
  393. iterations++;
  394. }
  395. // Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
  396. float sourceTileHeightMinusOverlap = sourceTile.size.height;
  397. sourceTile.size.height += sourceSeemOverlap;
  398. destTile.size.height += kDestSeemOverlap;
  399. for( int y = 0; y < iterations; ++y ) {
  400. @autoreleasepool {
  401. sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
  402. destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
  403. sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
  404. if( y == iterations - 1 && remainder ) {
  405. float dify = destTile.size.height;
  406. destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
  407. dify -= destTile.size.height;
  408. destTile.origin.y += dify;
  409. }
  410. CGContextDrawImage( destContext, destTile, sourceTileImageRef );
  411. CGImageRelease( sourceTileImageRef );
  412. }
  413. }
  414. CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
  415. CGContextRelease(destContext);
  416. if (destImageRef == NULL) {
  417. return image;
  418. }
  419. #if SD_MAC
  420. UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
  421. #else
  422. UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
  423. #endif
  424. CGImageRelease(destImageRef);
  425. if (destImage == nil) {
  426. return image;
  427. }
  428. SDImageCopyAssociatedObject(image, destImage);
  429. destImage.sd_isDecoded = YES;
  430. return destImage;
  431. }
  432. }
  433. + (NSUInteger)defaultScaleDownLimitBytes {
  434. return kDestImageLimitBytes;
  435. }
  436. + (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
  437. if (defaultScaleDownLimitBytes < kBytesPerMB) {
  438. return;
  439. }
  440. kDestImageLimitBytes = defaultScaleDownLimitBytes;
  441. }
  442. #if SD_UIKIT || SD_WATCH
  443. // Convert an EXIF image orientation to an iOS one.
  444. + (UIImageOrientation)imageOrientationFromEXIFOrientation:(CGImagePropertyOrientation)exifOrientation {
  445. UIImageOrientation imageOrientation = UIImageOrientationUp;
  446. switch (exifOrientation) {
  447. case kCGImagePropertyOrientationUp:
  448. imageOrientation = UIImageOrientationUp;
  449. break;
  450. case kCGImagePropertyOrientationDown:
  451. imageOrientation = UIImageOrientationDown;
  452. break;
  453. case kCGImagePropertyOrientationLeft:
  454. imageOrientation = UIImageOrientationLeft;
  455. break;
  456. case kCGImagePropertyOrientationRight:
  457. imageOrientation = UIImageOrientationRight;
  458. break;
  459. case kCGImagePropertyOrientationUpMirrored:
  460. imageOrientation = UIImageOrientationUpMirrored;
  461. break;
  462. case kCGImagePropertyOrientationDownMirrored:
  463. imageOrientation = UIImageOrientationDownMirrored;
  464. break;
  465. case kCGImagePropertyOrientationLeftMirrored:
  466. imageOrientation = UIImageOrientationLeftMirrored;
  467. break;
  468. case kCGImagePropertyOrientationRightMirrored:
  469. imageOrientation = UIImageOrientationRightMirrored;
  470. break;
  471. default:
  472. break;
  473. }
  474. return imageOrientation;
  475. }
  476. // Convert an iOS orientation to an EXIF image orientation.
  477. + (CGImagePropertyOrientation)exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
  478. CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
  479. switch (imageOrientation) {
  480. case UIImageOrientationUp:
  481. exifOrientation = kCGImagePropertyOrientationUp;
  482. break;
  483. case UIImageOrientationDown:
  484. exifOrientation = kCGImagePropertyOrientationDown;
  485. break;
  486. case UIImageOrientationLeft:
  487. exifOrientation = kCGImagePropertyOrientationLeft;
  488. break;
  489. case UIImageOrientationRight:
  490. exifOrientation = kCGImagePropertyOrientationRight;
  491. break;
  492. case UIImageOrientationUpMirrored:
  493. exifOrientation = kCGImagePropertyOrientationUpMirrored;
  494. break;
  495. case UIImageOrientationDownMirrored:
  496. exifOrientation = kCGImagePropertyOrientationDownMirrored;
  497. break;
  498. case UIImageOrientationLeftMirrored:
  499. exifOrientation = kCGImagePropertyOrientationLeftMirrored;
  500. break;
  501. case UIImageOrientationRightMirrored:
  502. exifOrientation = kCGImagePropertyOrientationRightMirrored;
  503. break;
  504. default:
  505. break;
  506. }
  507. return exifOrientation;
  508. }
  509. #endif
  510. #pragma mark - Helper Fuction
  511. + (BOOL)shouldDecodeImage:(nullable UIImage *)image {
  512. // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
  513. if (image == nil) {
  514. return NO;
  515. }
  516. // Avoid extra decode
  517. if (image.sd_isDecoded) {
  518. return NO;
  519. }
  520. // do not decode animated images
  521. if (image.sd_isAnimated) {
  522. return NO;
  523. }
  524. // do not decode vector images
  525. if (image.sd_isVector) {
  526. return NO;
  527. }
  528. return YES;
  529. }
  530. + (BOOL)shouldScaleDownImage:(nonnull UIImage *)image limitBytes:(NSUInteger)bytes {
  531. BOOL shouldScaleDown = YES;
  532. CGImageRef sourceImageRef = image.CGImage;
  533. CGSize sourceResolution = CGSizeZero;
  534. sourceResolution.width = CGImageGetWidth(sourceImageRef);
  535. sourceResolution.height = CGImageGetHeight(sourceImageRef);
  536. float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
  537. if (sourceTotalPixels <= 0) {
  538. return NO;
  539. }
  540. CGFloat destTotalPixels;
  541. if (bytes == 0) {
  542. bytes = kDestImageLimitBytes;
  543. }
  544. destTotalPixels = bytes / kBytesPerPixel;
  545. if (destTotalPixels <= kPixelsPerMB) {
  546. // Too small to scale down
  547. return NO;
  548. }
  549. float imageScale = destTotalPixels / sourceTotalPixels;
  550. if (imageScale < 1) {
  551. shouldScaleDown = YES;
  552. } else {
  553. shouldScaleDown = NO;
  554. }
  555. return shouldScaleDown;
  556. }
  557. static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
  558. // Inspiration from @libfeihu
  559. // We need to calculate the proper transformation to make the image upright.
  560. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
  561. CGAffineTransform transform = CGAffineTransformIdentity;
  562. switch (orientation) {
  563. case kCGImagePropertyOrientationDown:
  564. case kCGImagePropertyOrientationDownMirrored:
  565. transform = CGAffineTransformTranslate(transform, size.width, size.height);
  566. transform = CGAffineTransformRotate(transform, M_PI);
  567. break;
  568. case kCGImagePropertyOrientationLeft:
  569. case kCGImagePropertyOrientationLeftMirrored:
  570. transform = CGAffineTransformTranslate(transform, size.width, 0);
  571. transform = CGAffineTransformRotate(transform, M_PI_2);
  572. break;
  573. case kCGImagePropertyOrientationRight:
  574. case kCGImagePropertyOrientationRightMirrored:
  575. transform = CGAffineTransformTranslate(transform, 0, size.height);
  576. transform = CGAffineTransformRotate(transform, -M_PI_2);
  577. break;
  578. case kCGImagePropertyOrientationUp:
  579. case kCGImagePropertyOrientationUpMirrored:
  580. break;
  581. }
  582. switch (orientation) {
  583. case kCGImagePropertyOrientationUpMirrored:
  584. case kCGImagePropertyOrientationDownMirrored:
  585. transform = CGAffineTransformTranslate(transform, size.width, 0);
  586. transform = CGAffineTransformScale(transform, -1, 1);
  587. break;
  588. case kCGImagePropertyOrientationLeftMirrored:
  589. case kCGImagePropertyOrientationRightMirrored:
  590. transform = CGAffineTransformTranslate(transform, size.height, 0);
  591. transform = CGAffineTransformScale(transform, -1, 1);
  592. break;
  593. case kCGImagePropertyOrientationUp:
  594. case kCGImagePropertyOrientationDown:
  595. case kCGImagePropertyOrientationLeft:
  596. case kCGImagePropertyOrientationRight:
  597. break;
  598. }
  599. return transform;
  600. }
  601. #if SD_UIKIT || SD_WATCH
  602. static NSUInteger gcd(NSUInteger a, NSUInteger b) {
  603. NSUInteger c;
  604. while (a != 0) {
  605. c = a;
  606. a = b % a;
  607. b = c;
  608. }
  609. return b;
  610. }
  611. static NSUInteger gcdArray(size_t const count, NSUInteger const * const values) {
  612. if (count == 0) {
  613. return 0;
  614. }
  615. NSUInteger result = values[0];
  616. for (size_t i = 1; i < count; ++i) {
  617. result = gcd(values[i], result);
  618. }
  619. return result;
  620. }
  621. #endif
  622. @end