SDImageCoderHelper.m 24 KB

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