UIImage+Transform.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  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 "UIImage+Transform.h"
  9. #import "NSImage+Compatibility.h"
  10. #import <Accelerate/Accelerate.h>
  11. #if SD_UIKIT || SD_MAC
  12. #import <CoreImage/CoreImage.h>
  13. #import "objc/runtime.h"
  14. #endif
  15. #if SD_MAC
  16. static void *kNSGraphicsContextScaleFactorKey;
  17. static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
  18. size_t width = ceil(size.width * scale);
  19. size_t height = ceil(size.height * scale);
  20. if (width < 1 || height < 1) return NULL;
  21. //pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc
  22. CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
  23. CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
  24. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
  25. CGColorSpaceRelease(space);
  26. if (!context) {
  27. return NULL;
  28. }
  29. if (scale == 0) {
  30. // Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the device’s main screen if scale is 0.
  31. scale = [NSScreen mainScreen].backingScaleFactor;
  32. }
  33. CGContextScaleCTM(context, scale, scale);
  34. return context;
  35. }
  36. #endif
  37. static void SDGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) {
  38. #if SD_UIKIT || SD_WATCH
  39. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  40. #else
  41. CGContextRef context = SDCGContextCreateBitmapContext(size, opaque, scale);
  42. if (!context) {
  43. return;
  44. }
  45. NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
  46. objc_setAssociatedObject(graphicsContext, &kNSGraphicsContextScaleFactorKey, @(scale), OBJC_ASSOCIATION_RETAIN);
  47. CGContextRelease(context);
  48. [NSGraphicsContext saveGraphicsState];
  49. NSGraphicsContext.currentContext = graphicsContext;
  50. #endif
  51. }
  52. static CGContextRef SDGraphicsGetCurrentContext(void) {
  53. #if SD_UIKIT || SD_WATCH
  54. return UIGraphicsGetCurrentContext();
  55. #else
  56. return NSGraphicsContext.currentContext.CGContext;
  57. #endif
  58. }
  59. static void SDGraphicsEndImageContext(void) {
  60. #if SD_UIKIT || SD_WATCH
  61. UIGraphicsEndImageContext();
  62. #else
  63. [NSGraphicsContext restoreGraphicsState];
  64. #endif
  65. }
  66. static UIImage * SDGraphicsGetImageFromCurrentImageContext(void) {
  67. #if SD_UIKIT || SD_WATCH
  68. return UIGraphicsGetImageFromCurrentImageContext();
  69. #else
  70. NSGraphicsContext *context = NSGraphicsContext.currentContext;
  71. CGContextRef contextRef = context.CGContext;
  72. if (!contextRef) {
  73. return nil;
  74. }
  75. CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
  76. if (!imageRef) {
  77. return nil;
  78. }
  79. CGFloat scale = 0;
  80. NSNumber *scaleFactor = objc_getAssociatedObject(context, &kNSGraphicsContextScaleFactorKey);
  81. if ([scaleFactor isKindOfClass:[NSNumber class]]) {
  82. scale = scaleFactor.doubleValue;
  83. }
  84. if (!scale) {
  85. // reset to the scale factor of the device’s main screen if scale is 0.
  86. scale = [NSScreen mainScreen].backingScaleFactor;
  87. }
  88. NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp];
  89. CGImageRelease(imageRef);
  90. return image;
  91. #endif
  92. }
  93. static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageScaleMode scaleMode) {
  94. rect = CGRectStandardize(rect);
  95. size.width = size.width < 0 ? -size.width : size.width;
  96. size.height = size.height < 0 ? -size.height : size.height;
  97. CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
  98. switch (scaleMode) {
  99. case SDImageScaleModeAspectFit:
  100. case SDImageScaleModeAspectFill: {
  101. if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
  102. size.width < 0.01 || size.height < 0.01) {
  103. rect.origin = center;
  104. rect.size = CGSizeZero;
  105. } else {
  106. CGFloat scale;
  107. if (scaleMode == SDImageScaleModeAspectFit) {
  108. if (size.width / size.height < rect.size.width / rect.size.height) {
  109. scale = rect.size.height / size.height;
  110. } else {
  111. scale = rect.size.width / size.width;
  112. }
  113. } else {
  114. if (size.width / size.height < rect.size.width / rect.size.height) {
  115. scale = rect.size.width / size.width;
  116. } else {
  117. scale = rect.size.height / size.height;
  118. }
  119. }
  120. size.width *= scale;
  121. size.height *= scale;
  122. rect.size = size;
  123. rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
  124. }
  125. } break;
  126. case SDImageScaleModeFill:
  127. default: {
  128. rect = rect;
  129. }
  130. }
  131. return rect;
  132. }
  133. static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) {
  134. // Get alpha info, byteOrder info
  135. CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
  136. CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
  137. CGFloat r = 0, g = 0, b = 0, a = 255.0;
  138. BOOL byteOrderNormal = NO;
  139. switch (byteOrderInfo) {
  140. case kCGBitmapByteOrderDefault: {
  141. byteOrderNormal = YES;
  142. } break;
  143. case kCGBitmapByteOrder32Little: {
  144. } break;
  145. case kCGBitmapByteOrder32Big: {
  146. byteOrderNormal = YES;
  147. } break;
  148. default: break;
  149. }
  150. switch (alphaInfo) {
  151. case kCGImageAlphaPremultipliedFirst:
  152. case kCGImageAlphaFirst: {
  153. if (byteOrderNormal) {
  154. // ARGB8888
  155. a = pixel[0] / 255.0;
  156. r = pixel[1] / 255.0;
  157. g = pixel[2] / 255.0;
  158. b = pixel[3] / 255.0;
  159. } else {
  160. // BGRA8888
  161. b = pixel[0] / 255.0;
  162. g = pixel[1] / 255.0;
  163. r = pixel[2] / 255.0;
  164. a = pixel[3] / 255.0;
  165. }
  166. }
  167. break;
  168. case kCGImageAlphaPremultipliedLast:
  169. case kCGImageAlphaLast: {
  170. if (byteOrderNormal) {
  171. // RGBA8888
  172. r = pixel[0] / 255.0;
  173. g = pixel[1] / 255.0;
  174. b = pixel[2] / 255.0;
  175. a = pixel[3] / 255.0;
  176. } else {
  177. // ABGR8888
  178. a = pixel[0] / 255.0;
  179. b = pixel[1] / 255.0;
  180. g = pixel[2] / 255.0;
  181. r = pixel[3] / 255.0;
  182. }
  183. }
  184. break;
  185. case kCGImageAlphaNone: {
  186. if (byteOrderNormal) {
  187. // RGB
  188. r = pixel[0] / 255.0;
  189. g = pixel[1] / 255.0;
  190. b = pixel[2] / 255.0;
  191. } else {
  192. // BGR
  193. b = pixel[0] / 255.0;
  194. g = pixel[1] / 255.0;
  195. r = pixel[2] / 255.0;
  196. }
  197. }
  198. break;
  199. case kCGImageAlphaNoneSkipLast: {
  200. if (byteOrderNormal) {
  201. // RGBX
  202. r = pixel[0] / 255.0;
  203. g = pixel[1] / 255.0;
  204. b = pixel[2] / 255.0;
  205. } else {
  206. // XBGR
  207. b = pixel[1] / 255.0;
  208. g = pixel[2] / 255.0;
  209. r = pixel[3] / 255.0;
  210. }
  211. }
  212. break;
  213. case kCGImageAlphaNoneSkipFirst: {
  214. if (byteOrderNormal) {
  215. // XRGB
  216. r = pixel[1] / 255.0;
  217. g = pixel[2] / 255.0;
  218. b = pixel[3] / 255.0;
  219. } else {
  220. // BGRX
  221. b = pixel[0] / 255.0;
  222. g = pixel[1] / 255.0;
  223. r = pixel[2] / 255.0;
  224. }
  225. }
  226. break;
  227. case kCGImageAlphaOnly: {
  228. // A
  229. a = pixel[0];
  230. }
  231. break;
  232. default:
  233. break;
  234. }
  235. return [UIColor colorWithRed:r green:g blue:b alpha:a];
  236. }
  237. #if SD_MAC
  238. @interface NSBezierPath (RoundedCorners)
  239. /**
  240. Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`.
  241. */
  242. + (nonnull instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius;
  243. @end
  244. @implementation NSBezierPath (RoundedCorners)
  245. + (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius {
  246. NSBezierPath *path = [NSBezierPath bezierPath];
  247. CGFloat maxCorner = MIN(NSWidth(rect), NSHeight(rect)) / 2;
  248. CGFloat topLeftRadius = MIN(maxCorner, (corners & SDRectCornerTopLeft) ? cornerRadius : 0);
  249. CGFloat topRightRadius = MIN(maxCorner, (corners & SDRectCornerTopRight) ? cornerRadius : 0);
  250. CGFloat bottomLeftRadius = MIN(maxCorner, (corners & SDRectCornerBottomLeft) ? cornerRadius : 0);
  251. CGFloat bottomRightRadius = MIN(maxCorner, (corners & SDRectCornerBottomRight) ? cornerRadius : 0);
  252. NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect));
  253. NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
  254. NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect));
  255. NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect));
  256. [path moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))];
  257. [path appendBezierPathWithArcFromPoint:topLeft toPoint:bottomLeft radius:topLeftRadius];
  258. [path appendBezierPathWithArcFromPoint:bottomLeft toPoint:bottomRight radius:bottomLeftRadius];
  259. [path appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:bottomRightRadius];
  260. [path appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:topRightRadius];
  261. [path closePath];
  262. return path;
  263. }
  264. @end
  265. #endif
  266. @implementation UIImage (Transform)
  267. - (void)sd_drawInRect:(CGRect)rect withScaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
  268. CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode);
  269. if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
  270. if (clips) {
  271. CGContextRef context = SDGraphicsGetCurrentContext();
  272. if (context) {
  273. CGContextSaveGState(context);
  274. CGContextAddRect(context, rect);
  275. CGContextClip(context);
  276. [self drawInRect:drawRect];
  277. CGContextRestoreGState(context);
  278. }
  279. } else {
  280. [self drawInRect:drawRect];
  281. }
  282. }
  283. - (UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode {
  284. if (size.width <= 0 || size.height <= 0) return nil;
  285. SDGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  286. [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) withScaleMode:scaleMode clipsToBounds:NO];
  287. UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
  288. SDGraphicsEndImageContext();
  289. return image;
  290. }
  291. - (UIImage *)sd_croppedImageWithRect:(CGRect)rect {
  292. if (!self.CGImage) return nil;
  293. rect.origin.x *= self.scale;
  294. rect.origin.y *= self.scale;
  295. rect.size.width *= self.scale;
  296. rect.size.height *= self.scale;
  297. if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
  298. CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
  299. if (!imageRef) {
  300. return nil;
  301. }
  302. #if SD_UIKIT || SD_WATCH
  303. UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
  304. #else
  305. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
  306. #endif
  307. CGImageRelease(imageRef);
  308. return image;
  309. }
  310. - (UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor {
  311. if (!self.CGImage) return nil;
  312. SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  313. CGContextRef context = SDGraphicsGetCurrentContext();
  314. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  315. CGFloat minSize = MIN(self.size.width, self.size.height);
  316. if (borderWidth < minSize / 2) {
  317. #if SD_UIKIT || SD_WATCH
  318. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
  319. #else
  320. NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
  321. #endif
  322. [path closePath];
  323. CGContextSaveGState(context);
  324. [path addClip];
  325. CGContextDrawImage(context, rect, self.CGImage);
  326. CGContextRestoreGState(context);
  327. }
  328. if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
  329. CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
  330. CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
  331. CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
  332. #if SD_UIKIT || SD_WATCH
  333. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
  334. #else
  335. NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
  336. #endif
  337. [path closePath];
  338. path.lineWidth = borderWidth;
  339. [borderColor setStroke];
  340. [path stroke];
  341. }
  342. UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
  343. SDGraphicsEndImageContext();
  344. return image;
  345. }
  346. - (UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize {
  347. if (!self.CGImage) return nil;
  348. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  349. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  350. CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
  351. fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity);
  352. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  353. CGContextRef context = CGBitmapContextCreate(NULL,
  354. (size_t)newRect.size.width,
  355. (size_t)newRect.size.height,
  356. 8,
  357. (size_t)newRect.size.width * 4,
  358. colorSpace,
  359. kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  360. CGColorSpaceRelease(colorSpace);
  361. if (!context) return nil;
  362. CGContextSetShouldAntialias(context, true);
  363. CGContextSetAllowsAntialiasing(context, true);
  364. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  365. CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
  366. CGContextRotateCTM(context, angle);
  367. CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
  368. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  369. #if SD_UIKIT || SD_WATCH
  370. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  371. #else
  372. UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
  373. #endif
  374. CGImageRelease(imgRef);
  375. CGContextRelease(context);
  376. return img;
  377. }
  378. - (UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
  379. if (!self.CGImage) return nil;
  380. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  381. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  382. size_t bytesPerRow = width * 4;
  383. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  384. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  385. CGColorSpaceRelease(colorSpace);
  386. if (!context) return nil;
  387. CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
  388. UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
  389. if (!data) {
  390. CGContextRelease(context);
  391. return nil;
  392. }
  393. vImage_Buffer src = { data, height, width, bytesPerRow };
  394. vImage_Buffer dest = { data, height, width, bytesPerRow };
  395. if (vertical) {
  396. vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  397. }
  398. if (horizontal) {
  399. vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  400. }
  401. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  402. CGContextRelease(context);
  403. #if SD_UIKIT || SD_WATCH
  404. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  405. #else
  406. UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
  407. #endif
  408. CGImageRelease(imgRef);
  409. return img;
  410. }
  411. #pragma mark - Image Blending
  412. - (UIImage *)sd_tintedImageWithColor:(UIColor *)tintColor {
  413. if (!self.CGImage) return nil;
  414. if (!tintColor.CGColor) return nil;
  415. BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
  416. if (!hasTint) {
  417. #if SD_UIKIT || SD_WATCH
  418. return [UIImage imageWithCGImage:self.CGImage scale:self.scale orientation:self.imageOrientation];
  419. #else
  420. return [[UIImage alloc] initWithCGImage:self.CGImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
  421. #endif
  422. }
  423. CGSize size = self.size;
  424. CGRect rect = { CGPointZero, size };
  425. CGFloat scale = self.scale;
  426. // blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
  427. CGBlendMode blendMode = kCGBlendModeSourceAtop;
  428. SDGraphicsBeginImageContextWithOptions(size, NO, scale);
  429. CGContextRef context = SDGraphicsGetCurrentContext();
  430. CGContextDrawImage(context, rect, self.CGImage);
  431. CGContextSetBlendMode(context, blendMode);
  432. CGContextSetFillColorWithColor(context, tintColor.CGColor);
  433. CGContextFillRect(context, rect);
  434. UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
  435. SDGraphicsEndImageContext();
  436. return image;
  437. }
  438. - (UIColor *)sd_colorAtPoint:(CGPoint)point {
  439. if (!self) {
  440. return nil;
  441. }
  442. CGImageRef imageRef = self.CGImage;
  443. if (!imageRef) {
  444. return nil;
  445. }
  446. // Check point
  447. CGFloat width = CGImageGetWidth(imageRef);
  448. CGFloat height = CGImageGetHeight(imageRef);
  449. if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) {
  450. return nil;
  451. }
  452. // Get pixels
  453. CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
  454. if (!provider) {
  455. return nil;
  456. }
  457. CFDataRef data = CGDataProviderCopyData(provider);
  458. if (!data) {
  459. return nil;
  460. }
  461. // Get pixel at point
  462. size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
  463. size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
  464. CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4);
  465. if (CFDataGetLength(data) < range.location + range.length) {
  466. CFRelease(data);
  467. return nil;
  468. }
  469. Pixel_8888 pixel = {0};
  470. CFDataGetBytes(data, range, pixel);
  471. CFRelease(data);
  472. // Convert to color
  473. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
  474. return SDGetColorFromPixel(pixel, bitmapInfo);
  475. }
  476. - (NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect {
  477. if (!self) {
  478. return nil;
  479. }
  480. CGImageRef imageRef = self.CGImage;
  481. if (!imageRef) {
  482. return nil;
  483. }
  484. // Check rect
  485. CGFloat width = CGImageGetWidth(imageRef);
  486. CGFloat height = CGImageGetHeight(imageRef);
  487. if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) {
  488. return nil;
  489. }
  490. // Get pixels
  491. CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
  492. if (!provider) {
  493. return nil;
  494. }
  495. CFDataRef data = CGDataProviderCopyData(provider);
  496. if (!data) {
  497. return nil;
  498. }
  499. // Get pixels with rect
  500. size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
  501. size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
  502. size_t start = bytesPerRow * CGRectGetMinY(rect) + components * CGRectGetMinX(rect);
  503. size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect);
  504. if (CFDataGetLength(data) < (CFIndex)end) {
  505. CFRelease(data);
  506. return nil;
  507. }
  508. const UInt8 *pixels = CFDataGetBytePtr(data);
  509. size_t row = CGRectGetMinY(rect);
  510. size_t col = CGRectGetMaxX(rect);
  511. // Convert to color
  512. CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
  513. NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)];
  514. for (size_t index = start; index < end; index += 4) {
  515. if (index >= row * bytesPerRow + col * components) {
  516. // Index beyond the end of current row, go next row
  517. row++;
  518. index = row * bytesPerRow + CGRectGetMinX(rect) * components;
  519. index -= 4;
  520. continue;
  521. }
  522. Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]};
  523. UIColor *color = SDGetColorFromPixel(pixel, bitmapInfo);
  524. [colors addObject:color];
  525. }
  526. CFRelease(data);
  527. return [colors copy];
  528. }
  529. #pragma mark - Image Effect
  530. // We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur`
  531. - (UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius {
  532. if (self.size.width < 1 || self.size.height < 1) {
  533. return nil;
  534. }
  535. if (!self.CGImage) {
  536. return nil;
  537. }
  538. BOOL hasBlur = blurRadius > __FLT_EPSILON__;
  539. if (!hasBlur) {
  540. return self;
  541. }
  542. CGFloat scale = self.scale;
  543. CGImageRef imageRef = self.CGImage;
  544. vImage_Buffer effect = {}, scratch = {};
  545. vImage_Buffer *input = NULL, *output = NULL;
  546. vImage_CGImageFormat format = {
  547. .bitsPerComponent = 8,
  548. .bitsPerPixel = 32,
  549. .colorSpace = NULL,
  550. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
  551. .version = 0,
  552. .decode = NULL,
  553. .renderingIntent = kCGRenderingIntentDefault
  554. };
  555. vImage_Error err;
  556. err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
  557. if (err != kvImageNoError) {
  558. NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
  559. return nil;
  560. }
  561. err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
  562. if (err != kvImageNoError) {
  563. NSLog(@"UIImage+Transform error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
  564. return nil;
  565. }
  566. input = &effect;
  567. output = &scratch;
  568. if (hasBlur) {
  569. // A description of how to compute the box kernel width from the Gaussian
  570. // radius (aka standard deviation) appears in the SVG spec:
  571. // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
  572. //
  573. // For larger values of 's' (s >= 2.0), an approximation can be used: Three
  574. // successive box-blurs build a piece-wise quadratic convolution kernel, which
  575. // approximates the Gaussian kernel to within roughly 3%.
  576. //
  577. // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
  578. //
  579. // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
  580. //
  581. CGFloat inputRadius = blurRadius * scale;
  582. if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
  583. uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
  584. radius |= 1; // force radius to be odd so that the three box-blur methodology works.
  585. int iterations;
  586. if (blurRadius * scale < 0.5) iterations = 1;
  587. else if (blurRadius * scale < 1.5) iterations = 2;
  588. else iterations = 3;
  589. NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
  590. void *temp = malloc(tempSize);
  591. for (int i = 0; i < iterations; i++) {
  592. vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
  593. vImage_Buffer *tmp = input;
  594. input = output;
  595. output = tmp;
  596. }
  597. free(temp);
  598. }
  599. CGImageRef effectCGImage = NULL;
  600. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoAllocate, NULL);
  601. if (effectCGImage == NULL) {
  602. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
  603. free(input->data);
  604. }
  605. free(output->data);
  606. #if SD_UIKIT || SD_WATCH
  607. UIImage *outputImage = [UIImage imageWithCGImage:effectCGImage scale:self.scale orientation:self.imageOrientation];
  608. #else
  609. UIImage *outputImage = [[UIImage alloc] initWithCGImage:effectCGImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
  610. #endif
  611. CGImageRelease(effectCGImage);
  612. return outputImage;
  613. }
  614. #if SD_UIKIT || SD_MAC
  615. - (UIImage *)sd_filteredImageWithFilter:(CIFilter *)filter {
  616. if (!self.CGImage) return nil;
  617. CIContext *context = [CIContext context];
  618. CIImage *inputImage = [CIImage imageWithCGImage:self.CGImage];
  619. if (!inputImage) return nil;
  620. [filter setValue:inputImage forKey:kCIInputImageKey];
  621. CIImage *outputImage = filter.outputImage;
  622. if (!outputImage) return nil;
  623. CGImageRef imageRef = [context createCGImage:outputImage fromRect:outputImage.extent];
  624. if (!imageRef) return nil;
  625. #if SD_UIKIT
  626. UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
  627. #else
  628. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
  629. #endif
  630. CGImageRelease(imageRef);
  631. return image;
  632. }
  633. #endif
  634. @end