UIImage+Transform.m 22 KB

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