UIImage+YYWebImage.m 30 KB

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