UIImage+YYWebImage.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. //
  2. // UIImage+YYWebImage.m
  3. // YYWebImage <https://github.com/ibireme/YYWebImage>
  4. //
  5. // Created by ibireme on 13/4/4.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "UIImage+YYWebImage.h"
  12. #import <ImageIO/ImageIO.h>
  13. #import <Accelerate/Accelerate.h>
  14. #import <objc/runtime.h>
  15. /// Convert degrees to radians.
  16. static inline CGFloat _DegreesToRadians(CGFloat degrees) {
  17. return degrees * M_PI / 180;
  18. }
  19. /**
  20. Resize rect to fit the size using a given contentMode.
  21. @param rect The draw rect
  22. @param size The content size
  23. @param mode The content mode
  24. @return A resized rect for the given content mode.
  25. @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
  26. */
  27. static CGRect _YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
  28. rect = CGRectStandardize(rect);
  29. size.width = size.width < 0 ? -size.width : size.width;
  30. size.height = size.height < 0 ? -size.height : size.height;
  31. CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
  32. switch (mode) {
  33. case UIViewContentModeScaleAspectFit:
  34. case UIViewContentModeScaleAspectFill: {
  35. if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
  36. size.width < 0.01 || size.height < 0.01) {
  37. rect.origin = center;
  38. rect.size = CGSizeZero;
  39. } else {
  40. CGFloat scale;
  41. if (size.width / size.height < rect.size.width / rect.size.height &&
  42. mode == UIViewContentModeScaleAspectFit) {
  43. scale = rect.size.height / size.height;
  44. } else {
  45. scale = rect.size.width / size.width;
  46. }
  47. size.width *= scale;
  48. size.height *= scale;
  49. rect.size = size;
  50. rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
  51. }
  52. } break;
  53. case UIViewContentModeCenter: {
  54. rect.size = size;
  55. rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
  56. } break;
  57. case UIViewContentModeTop: {
  58. rect.origin.x = center.x - size.width * 0.5;
  59. rect.size = size;
  60. } break;
  61. case UIViewContentModeBottom: {
  62. rect.origin.x = center.x - size.width * 0.5;
  63. rect.origin.y += rect.size.height - size.height;
  64. rect.size = size;
  65. } break;
  66. case UIViewContentModeLeft: {
  67. rect.origin.y = center.y - size.height * 0.5;
  68. rect.size = size;
  69. } break;
  70. case UIViewContentModeRight: {
  71. rect.origin.y = center.y - size.height * 0.5;
  72. rect.origin.x += rect.size.width - size.width;
  73. rect.size = size;
  74. } break;
  75. case UIViewContentModeTopLeft: {
  76. rect.size = size;
  77. } break;
  78. case UIViewContentModeTopRight: {
  79. rect.origin.x += rect.size.width - size.width;
  80. rect.size = size;
  81. } break;
  82. case UIViewContentModeBottomLeft: {
  83. rect.origin.y += rect.size.height - size.height;
  84. rect.size = size;
  85. } break;
  86. case UIViewContentModeBottomRight: {
  87. rect.origin.x += rect.size.width - size.width;
  88. rect.origin.y += rect.size.height - size.height;
  89. rect.size = size;
  90. } break;
  91. case UIViewContentModeScaleToFill:
  92. case UIViewContentModeRedraw:
  93. default: {
  94. rect = rect;
  95. }
  96. }
  97. return rect;
  98. }
  99. static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
  100. NSTimeInterval delay = 0;
  101. CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
  102. if (dic) {
  103. CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
  104. if (dicGIF) {
  105. NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
  106. if (num.doubleValue <= __FLT_EPSILON__) {
  107. num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
  108. }
  109. delay = num.doubleValue;
  110. }
  111. CFRelease(dic);
  112. }
  113. // http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
  114. if (delay < 0.02) delay = 0.1;
  115. return delay;
  116. }
  117. @implementation UIImage (YYWebImage)
  118. + (UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
  119. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
  120. if (!source) return nil;
  121. size_t count = CGImageSourceGetCount(source);
  122. if (count <= 1) {
  123. CFRelease(source);
  124. return [self.class imageWithData:data scale:scale];
  125. }
  126. NSUInteger frames[count];
  127. double oneFrameTime = 1 / 50.0; // 50 fps
  128. NSTimeInterval totalTime = 0;
  129. NSUInteger totalFrame = 0;
  130. NSUInteger gcdFrame = 0;
  131. for (size_t i = 0; i < count; i++) {
  132. NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
  133. totalTime += delay;
  134. NSInteger frame = lrint(delay / oneFrameTime);
  135. if (frame < 1) frame = 1;
  136. frames[i] = frame;
  137. totalFrame += frames[i];
  138. if (i == 0) gcdFrame = frames[i];
  139. else {
  140. NSUInteger frame = frames[i], tmp;
  141. if (frame < gcdFrame) {
  142. tmp = frame; frame = gcdFrame; gcdFrame = tmp;
  143. }
  144. while (true) {
  145. tmp = frame % gcdFrame;
  146. if (tmp == 0) break;
  147. frame = gcdFrame;
  148. gcdFrame = tmp;
  149. }
  150. }
  151. }
  152. NSMutableArray *array = [NSMutableArray new];
  153. for (size_t i = 0; i < count; i++) {
  154. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
  155. if (!imageRef) {
  156. CFRelease(source);
  157. return nil;
  158. }
  159. size_t width = CGImageGetWidth(imageRef);
  160. size_t height = CGImageGetHeight(imageRef);
  161. if (width == 0 || height == 0) {
  162. CFRelease(source);
  163. CFRelease(imageRef);
  164. return nil;
  165. }
  166. CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
  167. BOOL hasAlpha = NO;
  168. if (alphaInfo == kCGImageAlphaPremultipliedLast ||
  169. alphaInfo == kCGImageAlphaPremultipliedFirst ||
  170. alphaInfo == kCGImageAlphaLast ||
  171. alphaInfo == kCGImageAlphaFirst) {
  172. hasAlpha = YES;
  173. }
  174. // BGRA8888 (premultiplied) or BGRX8888
  175. // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
  176. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
  177. bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  178. CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
  179. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
  180. CGColorSpaceRelease(space);
  181. if (!context) {
  182. CFRelease(source);
  183. CFRelease(imageRef);
  184. return nil;
  185. }
  186. CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
  187. CGImageRef decoded = CGBitmapContextCreateImage(context);
  188. CFRelease(context);
  189. if (!decoded) {
  190. CFRelease(source);
  191. CFRelease(imageRef);
  192. return nil;
  193. }
  194. UIImage *image = image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
  195. CGImageRelease(imageRef);
  196. CGImageRelease(decoded);
  197. if (!image) {
  198. CFRelease(source);
  199. return nil;
  200. }
  201. for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
  202. [array addObject:image];
  203. }
  204. }
  205. CFRelease(source);
  206. UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
  207. return image;
  208. }
  209. + (UIImage *)yy_imageWithColor:(UIColor *)color {
  210. return [self yy_imageWithColor:color size:CGSizeMake(1, 1)];
  211. }
  212. + (UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size {
  213. if (!color || size.width <= 0 || size.height <= 0) return nil;
  214. CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  215. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
  216. CGContextRef context = UIGraphicsGetCurrentContext();
  217. CGContextSetFillColorWithColor(context, color.CGColor);
  218. CGContextFillRect(context, rect);
  219. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  220. UIGraphicsEndImageContext();
  221. return image;
  222. }
  223. + (UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
  224. if (!drawBlock) return nil;
  225. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  226. CGContextRef context = UIGraphicsGetCurrentContext();
  227. if (!context) return nil;
  228. drawBlock(context);
  229. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  230. UIGraphicsEndImageContext();
  231. return image;
  232. }
  233. - (BOOL)yy_hasAlphaChannel {
  234. if (self.CGImage == NULL) return NO;
  235. CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
  236. return (alpha == kCGImageAlphaFirst ||
  237. alpha == kCGImageAlphaLast ||
  238. alpha == kCGImageAlphaPremultipliedFirst ||
  239. alpha == kCGImageAlphaPremultipliedLast);
  240. }
  241. - (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
  242. CGRect drawRect = _YYCGRectFitWithContentMode(rect, self.size, contentMode);
  243. if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
  244. if (clips) {
  245. CGContextRef context = UIGraphicsGetCurrentContext();
  246. if (context) {
  247. CGContextSaveGState(context);
  248. CGContextAddRect(context, rect);
  249. CGContextClip(context);
  250. [self drawInRect:drawRect];
  251. CGContextRestoreGState(context);
  252. }
  253. } else {
  254. [self drawInRect:drawRect];
  255. }
  256. }
  257. - (UIImage *)yy_imageByResizeToSize:(CGSize)size {
  258. if (size.width <= 0 || size.height <= 0) return nil;
  259. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  260. [self drawInRect:CGRectMake(0, 0, size.width, size.height)];
  261. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  262. UIGraphicsEndImageContext();
  263. return image;
  264. }
  265. - (UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
  266. if (size.width <= 0 || size.height <= 0) return nil;
  267. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  268. [self yy_drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
  269. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  270. UIGraphicsEndImageContext();
  271. return image;
  272. }
  273. - (UIImage *)yy_imageByCropToRect:(CGRect)rect {
  274. rect.origin.x *= self.scale;
  275. rect.origin.y *= self.scale;
  276. rect.size.width *= self.scale;
  277. rect.size.height *= self.scale;
  278. if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
  279. CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
  280. UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
  281. CGImageRelease(imageRef);
  282. return image;
  283. }
  284. - (UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
  285. CGSize size = self.size;
  286. size.width -= insets.left + insets.right;
  287. size.height -= insets.top + insets.bottom;
  288. if (size.width <= 0 || size.height <= 0) return nil;
  289. CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
  290. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  291. CGContextRef context = UIGraphicsGetCurrentContext();
  292. if (color) {
  293. CGContextSetFillColorWithColor(context, color.CGColor);
  294. CGMutablePathRef path = CGPathCreateMutable();
  295. CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
  296. CGPathAddRect(path, NULL, rect);
  297. CGContextAddPath(context, path);
  298. CGContextEOFillPath(context);
  299. CGPathRelease(path);
  300. }
  301. [self drawInRect:rect];
  302. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  303. UIGraphicsEndImageContext();
  304. return image;
  305. }
  306. - (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius {
  307. return [self yy_imageByRoundCornerRadius:radius corners:UIRectCornerAllCorners borderWidth:0];
  308. }
  309. - (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius corners:(UIRectCorner)corners borderWidth:(CGFloat)borderWidth {
  310. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  311. CGContextRef context = UIGraphicsGetCurrentContext();
  312. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  313. CGContextScaleCTM(context, 1, -1);
  314. CGContextTranslateCTM(context, 0, -rect.size.height);
  315. CGFloat minSize = MIN(self.size.width, self.size.height);
  316. if (borderWidth < minSize / 2) {
  317. [[UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)] addClip];
  318. CGContextDrawImage(context, rect, self.CGImage);
  319. }
  320. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  321. UIGraphicsEndImageContext();
  322. return image;
  323. }
  324. - (UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
  325. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  326. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  327. CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
  328. fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
  329. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  330. CGContextRef context = CGBitmapContextCreate(NULL,
  331. (size_t)newRect.size.width,
  332. (size_t)newRect.size.height,
  333. 8,
  334. (size_t)newRect.size.width * 4,
  335. colorSpace,
  336. kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  337. CGColorSpaceRelease(colorSpace);
  338. if (!context) return nil;
  339. CGContextSetShouldAntialias(context, true);
  340. CGContextSetAllowsAntialiasing(context, true);
  341. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  342. CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
  343. CGContextRotateCTM(context, radians);
  344. CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
  345. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  346. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  347. CGImageRelease(imgRef);
  348. CGContextRelease(context);
  349. return img;
  350. }
  351. - (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
  352. if (!self.CGImage) return nil;
  353. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  354. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  355. size_t bytesPerRow = width * 4;
  356. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  357. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  358. CGColorSpaceRelease(colorSpace);
  359. if (!context) return nil;
  360. CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
  361. UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
  362. if (!data) {
  363. CGContextRelease(context);
  364. return nil;
  365. }
  366. vImage_Buffer src = { data, height, width, bytesPerRow };
  367. vImage_Buffer dest = { data, height, width, bytesPerRow };
  368. if (vertical) {
  369. vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  370. }
  371. if (horizontal) {
  372. vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  373. }
  374. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  375. CGContextRelease(context);
  376. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  377. CGImageRelease(imgRef);
  378. return img;
  379. }
  380. - (UIImage *)yy_imageByRotateLeft90 {
  381. return [self yy_imageByRotate:_DegreesToRadians(90) fitSize:YES];
  382. }
  383. - (UIImage *)yy_imageByRotateRight90 {
  384. return [self yy_imageByRotate:_DegreesToRadians(-90) fitSize:YES];
  385. }
  386. - (UIImage *)yy_imageByRotate180 {
  387. return [self _yy_flipHorizontal:YES vertical:YES];
  388. }
  389. - (UIImage *)yy_imageByFlipVertical {
  390. return [self _yy_flipHorizontal:NO vertical:YES];
  391. }
  392. - (UIImage *)yy_imageByFlipHorizontal {
  393. return [self _yy_flipHorizontal:YES vertical:NO];
  394. }
  395. - (UIImage *)yy_imageByTintColor:(UIColor *)color {
  396. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  397. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  398. [color set];
  399. UIRectFill(rect);
  400. [self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
  401. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  402. UIGraphicsEndImageContext();
  403. return newImage;
  404. }
  405. - (UIImage *)yy_imageByGrayscale {
  406. return [self yy_imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
  407. }
  408. - (UIImage *)yy_imageByBlurSoft {
  409. return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  410. }
  411. - (UIImage *)yy_imageByBlurLight {
  412. return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  413. }
  414. - (UIImage *)yy_imageByBlurExtraLight {
  415. return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  416. }
  417. - (UIImage *)yy_imageByBlurDark {
  418. return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  419. }
  420. - (UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor {
  421. const CGFloat EffectColorAlpha = 0.6;
  422. UIColor *effectColor = tintColor;
  423. size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
  424. if (componentCount == 2) {
  425. CGFloat b;
  426. if ([tintColor getWhite:&b alpha:NULL]) {
  427. effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
  428. }
  429. } else {
  430. CGFloat r, g, b;
  431. if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
  432. effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
  433. }
  434. }
  435. return [self yy_imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
  436. }
  437. - (UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius
  438. tintColor:(UIColor *)tintColor
  439. tintMode:(CGBlendMode)tintBlendMode
  440. saturation:(CGFloat)saturation
  441. maskImage:(UIImage *)maskImage {
  442. if (self.size.width < 1 || self.size.height < 1) {
  443. NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
  444. return nil;
  445. }
  446. if (!self.CGImage) {
  447. NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
  448. return nil;
  449. }
  450. if (maskImage && !maskImage.CGImage) {
  451. NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
  452. return nil;
  453. }
  454. // iOS7 and above can use new func.
  455. BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
  456. BOOL hasBlur = blurRadius > __FLT_EPSILON__;
  457. BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
  458. CGSize size = self.size;
  459. CGRect rect = { CGPointZero, size };
  460. CGFloat scale = self.scale;
  461. CGImageRef imageRef = self.CGImage;
  462. BOOL opaque = NO;
  463. if (!hasBlur && !hasSaturation) {
  464. return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  465. }
  466. vImage_Buffer effect = { 0 }, scratch = { 0 };
  467. vImage_Buffer *input = NULL, *output = NULL;
  468. vImage_CGImageFormat format = {
  469. .bitsPerComponent = 8,
  470. .bitsPerPixel = 32,
  471. .colorSpace = NULL,
  472. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
  473. .version = 0,
  474. .decode = NULL,
  475. .renderingIntent = kCGRenderingIntentDefault
  476. };
  477. if (hasNewFunc) {
  478. vImage_Error err;
  479. err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
  480. if (err != kvImageNoError) {
  481. NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
  482. return nil;
  483. }
  484. err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
  485. if (err != kvImageNoError) {
  486. NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
  487. return nil;
  488. }
  489. } else {
  490. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  491. CGContextRef effectCtx = UIGraphicsGetCurrentContext();
  492. CGContextScaleCTM(effectCtx, 1.0, -1.0);
  493. CGContextTranslateCTM(effectCtx, 0, -size.height);
  494. CGContextDrawImage(effectCtx, rect, imageRef);
  495. effect.data = CGBitmapContextGetData(effectCtx);
  496. effect.width = CGBitmapContextGetWidth(effectCtx);
  497. effect.height = CGBitmapContextGetHeight(effectCtx);
  498. effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
  499. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  500. CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
  501. scratch.data = CGBitmapContextGetData(scratchCtx);
  502. scratch.width = CGBitmapContextGetWidth(scratchCtx);
  503. scratch.height = CGBitmapContextGetHeight(scratchCtx);
  504. scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
  505. }
  506. input = &effect;
  507. output = &scratch;
  508. if (hasBlur) {
  509. // A description of how to compute the box kernel width from the Gaussian
  510. // radius (aka standard deviation) appears in the SVG spec:
  511. // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
  512. //
  513. // For larger values of 's' (s >= 2.0), an approximation can be used: Three
  514. // successive box-blurs build a piece-wise quadratic convolution kernel, which
  515. // approximates the Gaussian kernel to within roughly 3%.
  516. //
  517. // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
  518. //
  519. // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
  520. //
  521. CGFloat inputRadius = blurRadius * scale;
  522. if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
  523. uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
  524. radius |= 1; // force radius to be odd so that the three box-blur methodology works.
  525. int iterations;
  526. if (blurRadius * scale < 0.5) iterations = 1;
  527. else if (blurRadius * scale < 1.5) iterations = 2;
  528. else iterations = 3;
  529. NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
  530. void *temp = malloc(tempSize);
  531. for (int i = 0; i < iterations; i++) {
  532. vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
  533. // swap
  534. vImage_Buffer *swap_tmp = input;
  535. input = output;
  536. output = swap_tmp;
  537. }
  538. free(temp);
  539. }
  540. if (hasSaturation) {
  541. // These values appear in the W3C Filter Effects spec:
  542. // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
  543. CGFloat s = saturation;
  544. CGFloat matrixFloat[] = {
  545. 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
  546. 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
  547. 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
  548. 0, 0, 0, 1,
  549. };
  550. const int32_t divisor = 256;
  551. NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
  552. int16_t matrix[matrixSize];
  553. for (NSUInteger i = 0; i < matrixSize; ++i) {
  554. matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
  555. }
  556. vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
  557. // swap
  558. vImage_Buffer *swap_tmp = input;
  559. input = output;
  560. output = swap_tmp;
  561. }
  562. UIImage *outputImage = nil;
  563. if (hasNewFunc) {
  564. CGImageRef effectCGImage = NULL;
  565. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
  566. if (effectCGImage == NULL) {
  567. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
  568. free(input->data);
  569. }
  570. free(output->data);
  571. outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  572. CGImageRelease(effectCGImage);
  573. } else {
  574. CGImageRef effectCGImage;
  575. UIImage *effectImage;
  576. if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  577. UIGraphicsEndImageContext();
  578. if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  579. UIGraphicsEndImageContext();
  580. effectCGImage = effectImage.CGImage;
  581. outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  582. }
  583. return outputImage;
  584. }
  585. // Helper function to handle deferred cleanup of a buffer.
  586. static void _yy_cleanupBuffer(void *userData, void *buf_data) {
  587. free(buf_data);
  588. }
  589. // Helper function to add tint and mask.
  590. - (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
  591. tintColor:(UIColor *)tintColor
  592. tintBlendMode:(CGBlendMode)tintBlendMode
  593. maskImage:(UIImage *)maskImage
  594. opaque:(BOOL)opaque {
  595. BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
  596. BOOL hasMask = maskImage != nil;
  597. CGSize size = self.size;
  598. CGRect rect = { CGPointZero, size };
  599. CGFloat scale = self.scale;
  600. if (!hasTint && !hasMask) {
  601. return [UIImage imageWithCGImage:effectCGImage];
  602. }
  603. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  604. CGContextRef context = UIGraphicsGetCurrentContext();
  605. CGContextScaleCTM(context, 1.0, -1.0);
  606. CGContextTranslateCTM(context, 0, -size.height);
  607. if (hasMask) {
  608. CGContextDrawImage(context, rect, self.CGImage);
  609. CGContextSaveGState(context);
  610. CGContextClipToMask(context, rect, maskImage.CGImage);
  611. }
  612. CGContextDrawImage(context, rect, effectCGImage);
  613. if (hasTint) {
  614. CGContextSaveGState(context);
  615. CGContextSetBlendMode(context, tintBlendMode);
  616. CGContextSetFillColorWithColor(context, tintColor.CGColor);
  617. CGContextFillRect(context, rect);
  618. CGContextRestoreGState(context);
  619. }
  620. if (hasMask) {
  621. CGContextRestoreGState(context);
  622. }
  623. UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
  624. UIGraphicsEndImageContext();
  625. return outputImage;
  626. }
  627. @end