酷店

SDImageCoderHelper.m 24KB

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