PageRenderTime 40ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/TreasureBox/Pods/YYKit/YYKit/Base/UIKit/UIImage+YYAdd.m

https://gitlab.com/chenguibang/TreasureBox
Objective C | 752 lines | 638 code | 83 blank | 31 comment | 142 complexity | a4a4a84c28b8e2dfb9f89af074b4a618 MD5 | raw file
  1. //
  2. // UIImage+YYAdd.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  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+YYAdd.h"
  12. #import "UIDevice+YYAdd.h"
  13. #import "NSString+YYAdd.h"
  14. #import "YYKitMacro.h"
  15. #import "YYCGUtilities.h"
  16. #import <ImageIO/ImageIO.h>
  17. #import <Accelerate/Accelerate.h>
  18. #import <CoreText/CoreText.h>
  19. #import <objc/runtime.h>
  20. #import "YYCGUtilities.h"
  21. YYSYNTH_DUMMY_CLASS(UIImage_YYAdd)
  22. static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
  23. NSTimeInterval delay = 0;
  24. CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
  25. if (dic) {
  26. CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
  27. if (dicGIF) {
  28. NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
  29. if (num.doubleValue <= __FLT_EPSILON__) {
  30. num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
  31. }
  32. delay = num.doubleValue;
  33. }
  34. CFRelease(dic);
  35. }
  36. // http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
  37. if (delay < 0.02) delay = 0.1;
  38. return delay;
  39. }
  40. @implementation UIImage (YYAdd)
  41. + (UIImage *)imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
  42. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
  43. if (!source) return nil;
  44. size_t count = CGImageSourceGetCount(source);
  45. if (count <= 1) {
  46. CFRelease(source);
  47. return [self.class imageWithData:data scale:scale];
  48. }
  49. NSUInteger frames[count];
  50. double oneFrameTime = 1 / 50.0; // 50 fps
  51. NSTimeInterval totalTime = 0;
  52. NSUInteger totalFrame = 0;
  53. NSUInteger gcdFrame = 0;
  54. for (size_t i = 0; i < count; i++) {
  55. NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
  56. totalTime += delay;
  57. NSInteger frame = lrint(delay / oneFrameTime);
  58. if (frame < 1) frame = 1;
  59. frames[i] = frame;
  60. totalFrame += frames[i];
  61. if (i == 0) gcdFrame = frames[i];
  62. else {
  63. NSUInteger frame = frames[i], tmp;
  64. if (frame < gcdFrame) {
  65. tmp = frame; frame = gcdFrame; gcdFrame = tmp;
  66. }
  67. while (true) {
  68. tmp = frame % gcdFrame;
  69. if (tmp == 0) break;
  70. frame = gcdFrame;
  71. gcdFrame = tmp;
  72. }
  73. }
  74. }
  75. NSMutableArray *array = [NSMutableArray new];
  76. for (size_t i = 0; i < count; i++) {
  77. CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
  78. if (!imageRef) {
  79. CFRelease(source);
  80. return nil;
  81. }
  82. size_t width = CGImageGetWidth(imageRef);
  83. size_t height = CGImageGetHeight(imageRef);
  84. if (width == 0 || height == 0) {
  85. CFRelease(source);
  86. CFRelease(imageRef);
  87. return nil;
  88. }
  89. CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
  90. BOOL hasAlpha = NO;
  91. if (alphaInfo == kCGImageAlphaPremultipliedLast ||
  92. alphaInfo == kCGImageAlphaPremultipliedFirst ||
  93. alphaInfo == kCGImageAlphaLast ||
  94. alphaInfo == kCGImageAlphaFirst) {
  95. hasAlpha = YES;
  96. }
  97. // BGRA8888 (premultiplied) or BGRX8888
  98. // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
  99. CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
  100. bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
  101. CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
  102. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
  103. CGColorSpaceRelease(space);
  104. if (!context) {
  105. CFRelease(source);
  106. CFRelease(imageRef);
  107. return nil;
  108. }
  109. CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
  110. CGImageRef decoded = CGBitmapContextCreateImage(context);
  111. CFRelease(context);
  112. if (!decoded) {
  113. CFRelease(source);
  114. CFRelease(imageRef);
  115. return nil;
  116. }
  117. UIImage *image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
  118. CGImageRelease(imageRef);
  119. CGImageRelease(decoded);
  120. if (!image) {
  121. CFRelease(source);
  122. return nil;
  123. }
  124. for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
  125. [array addObject:image];
  126. }
  127. }
  128. CFRelease(source);
  129. UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
  130. return image;
  131. }
  132. + (BOOL)isAnimatedGIFData:(NSData *)data {
  133. if (data.length < 16) return NO;
  134. UInt32 magic = *(UInt32 *)data.bytes;
  135. // http://www.w3.org/Graphics/GIF/spec-gif89a.txt
  136. if ((magic & 0xFFFFFF) != '\0FIG') return NO;
  137. CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL);
  138. if (!source) return NO;
  139. size_t count = CGImageSourceGetCount(source);
  140. CFRelease(source);
  141. return count > 1;
  142. }
  143. + (BOOL)isAnimatedGIFFile:(NSString *)path {
  144. if (path.length == 0) return NO;
  145. const char *cpath = path.UTF8String;
  146. FILE *fd = fopen(cpath, "rb");
  147. if (!fd) return NO;
  148. BOOL isGIF = NO;
  149. UInt32 magic = 0;
  150. if (fread(&magic, sizeof(UInt32), 1, fd) == 1) {
  151. if ((magic & 0xFFFFFF) == '\0FIG') isGIF = YES;
  152. }
  153. fclose(fd);
  154. return isGIF;
  155. }
  156. + (UIImage *)imageWithPDF:(id)dataOrPath {
  157. return [self _yy_imageWithPDF:dataOrPath resize:NO size:CGSizeZero];
  158. }
  159. + (UIImage *)imageWithPDF:(id)dataOrPath size:(CGSize)size {
  160. return [self _yy_imageWithPDF:dataOrPath resize:YES size:size];
  161. }
  162. + (UIImage *)imageWithEmoji:(NSString *)emoji size:(CGFloat)size {
  163. if (emoji.length == 0) return nil;
  164. if (size < 1) return nil;
  165. CGFloat scale = [UIScreen mainScreen].scale;
  166. CTFontRef font = CTFontCreateWithName(CFSTR("AppleColorEmoji"), size * scale, NULL);
  167. if (!font) return nil;
  168. NSAttributedString *str = [[NSAttributedString alloc] initWithString:emoji attributes:@{ (__bridge id)kCTFontAttributeName:(__bridge id)font, (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor clearColor].CGColor }];
  169. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  170. CGContextRef ctx = CGBitmapContextCreate(NULL, size * scale, size * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  171. CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
  172. CTLineRef line = CTLineCreateWithAttributedString((__bridge CFTypeRef)str);
  173. CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
  174. CGContextSetTextPosition(ctx, 0, -bounds.origin.y);
  175. CTLineDraw(line, ctx);
  176. CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
  177. UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
  178. CFRelease(font);
  179. CGColorSpaceRelease(colorSpace);
  180. CGContextRelease(ctx);
  181. if (line)CFRelease(line);
  182. if (imageRef) CFRelease(imageRef);
  183. return image;
  184. }
  185. + (UIImage *)_yy_imageWithPDF:(id)dataOrPath resize:(BOOL)resize size:(CGSize)size {
  186. CGPDFDocumentRef pdf = NULL;
  187. if ([dataOrPath isKindOfClass:[NSData class]]) {
  188. CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)dataOrPath);
  189. pdf = CGPDFDocumentCreateWithProvider(provider);
  190. CGDataProviderRelease(provider);
  191. } else if ([dataOrPath isKindOfClass:[NSString class]]) {
  192. pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:dataOrPath]);
  193. }
  194. if (!pdf) return nil;
  195. CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
  196. if (!page) {
  197. CGPDFDocumentRelease(pdf);
  198. return nil;
  199. }
  200. CGRect pdfRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
  201. CGSize pdfSize = resize ? size : pdfRect.size;
  202. CGFloat scale = [UIScreen mainScreen].scale;
  203. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  204. CGContextRef ctx = CGBitmapContextCreate(NULL, pdfSize.width * scale, pdfSize.height * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  205. if (!ctx) {
  206. CGColorSpaceRelease(colorSpace);
  207. CGPDFDocumentRelease(pdf);
  208. return nil;
  209. }
  210. CGContextScaleCTM(ctx, scale, scale);
  211. CGContextTranslateCTM(ctx, -pdfRect.origin.x, -pdfRect.origin.y);
  212. CGContextDrawPDFPage(ctx, page);
  213. CGPDFDocumentRelease(pdf);
  214. CGImageRef image = CGBitmapContextCreateImage(ctx);
  215. UIImage *pdfImage = [[UIImage alloc] initWithCGImage:image scale:scale orientation:UIImageOrientationUp];
  216. CGImageRelease(image);
  217. CGContextRelease(ctx);
  218. CGColorSpaceRelease(colorSpace);
  219. return pdfImage;
  220. }
  221. + (UIImage *)imageWithColor:(UIColor *)color {
  222. return [self imageWithColor:color size:CGSizeMake(1, 1)];
  223. }
  224. + (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
  225. if (!color || size.width <= 0 || size.height <= 0) return nil;
  226. CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
  227. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
  228. CGContextRef context = UIGraphicsGetCurrentContext();
  229. CGContextSetFillColorWithColor(context, color.CGColor);
  230. CGContextFillRect(context, rect);
  231. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  232. UIGraphicsEndImageContext();
  233. return image;
  234. }
  235. + (UIImage *)imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
  236. if (!drawBlock) return nil;
  237. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  238. CGContextRef context = UIGraphicsGetCurrentContext();
  239. if (!context) return nil;
  240. drawBlock(context);
  241. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  242. UIGraphicsEndImageContext();
  243. return image;
  244. }
  245. - (BOOL)hasAlphaChannel {
  246. if (self.CGImage == NULL) return NO;
  247. CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
  248. return (alpha == kCGImageAlphaFirst ||
  249. alpha == kCGImageAlphaLast ||
  250. alpha == kCGImageAlphaPremultipliedFirst ||
  251. alpha == kCGImageAlphaPremultipliedLast);
  252. }
  253. - (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
  254. CGRect drawRect = YYCGRectFitWithContentMode(rect, self.size, contentMode);
  255. if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
  256. if (clips) {
  257. CGContextRef context = UIGraphicsGetCurrentContext();
  258. if (context) {
  259. CGContextSaveGState(context);
  260. CGContextAddRect(context, rect);
  261. CGContextClip(context);
  262. [self drawInRect:drawRect];
  263. CGContextRestoreGState(context);
  264. }
  265. } else {
  266. [self drawInRect:drawRect];
  267. }
  268. }
  269. - (UIImage *)imageByResizeToSize:(CGSize)size {
  270. if (size.width <= 0 || size.height <= 0) return nil;
  271. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  272. [self drawInRect:CGRectMake(0, 0, size.width, size.height)];
  273. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  274. UIGraphicsEndImageContext();
  275. return image;
  276. }
  277. - (UIImage *)imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
  278. if (size.width <= 0 || size.height <= 0) return nil;
  279. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  280. [self drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
  281. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  282. UIGraphicsEndImageContext();
  283. return image;
  284. }
  285. - (UIImage *)imageByCropToRect:(CGRect)rect {
  286. rect.origin.x *= self.scale;
  287. rect.origin.y *= self.scale;
  288. rect.size.width *= self.scale;
  289. rect.size.height *= self.scale;
  290. if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
  291. CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
  292. UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
  293. CGImageRelease(imageRef);
  294. return image;
  295. }
  296. - (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
  297. CGSize size = self.size;
  298. size.width -= insets.left + insets.right;
  299. size.height -= insets.top + insets.bottom;
  300. if (size.width <= 0 || size.height <= 0) return nil;
  301. CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
  302. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  303. CGContextRef context = UIGraphicsGetCurrentContext();
  304. if (color) {
  305. CGContextSetFillColorWithColor(context, color.CGColor);
  306. CGMutablePathRef path = CGPathCreateMutable();
  307. CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
  308. CGPathAddRect(path, NULL, rect);
  309. CGContextAddPath(context, path);
  310. CGContextEOFillPath(context);
  311. CGPathRelease(path);
  312. }
  313. [self drawInRect:rect];
  314. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  315. UIGraphicsEndImageContext();
  316. return image;
  317. }
  318. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
  319. return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
  320. }
  321. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
  322. borderWidth:(CGFloat)borderWidth
  323. borderColor:(UIColor *)borderColor {
  324. return [self imageByRoundCornerRadius:radius
  325. corners:UIRectCornerAllCorners
  326. borderWidth:borderWidth
  327. borderColor:borderColor
  328. borderLineJoin:kCGLineJoinMiter];
  329. }
  330. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
  331. corners:(UIRectCorner)corners
  332. borderWidth:(CGFloat)borderWidth
  333. borderColor:(UIColor *)borderColor
  334. borderLineJoin:(CGLineJoin)borderLineJoin {
  335. if (corners != UIRectCornerAllCorners) {
  336. UIRectCorner tmp = 0;
  337. if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
  338. if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
  339. if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
  340. if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
  341. corners = tmp;
  342. }
  343. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  344. CGContextRef context = UIGraphicsGetCurrentContext();
  345. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  346. CGContextScaleCTM(context, 1, -1);
  347. CGContextTranslateCTM(context, 0, -rect.size.height);
  348. CGFloat minSize = MIN(self.size.width, self.size.height);
  349. if (borderWidth < minSize / 2) {
  350. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
  351. [path closePath];
  352. CGContextSaveGState(context);
  353. [path addClip];
  354. CGContextDrawImage(context, rect, self.CGImage);
  355. CGContextRestoreGState(context);
  356. }
  357. if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
  358. CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
  359. CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
  360. CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
  361. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
  362. [path closePath];
  363. path.lineWidth = borderWidth;
  364. path.lineJoinStyle = borderLineJoin;
  365. [borderColor setStroke];
  366. [path stroke];
  367. }
  368. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  369. UIGraphicsEndImageContext();
  370. return image;
  371. }
  372. - (UIImage *)imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
  373. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  374. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  375. CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
  376. fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
  377. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  378. CGContextRef context = CGBitmapContextCreate(NULL,
  379. (size_t)newRect.size.width,
  380. (size_t)newRect.size.height,
  381. 8,
  382. (size_t)newRect.size.width * 4,
  383. colorSpace,
  384. kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  385. CGColorSpaceRelease(colorSpace);
  386. if (!context) return nil;
  387. CGContextSetShouldAntialias(context, true);
  388. CGContextSetAllowsAntialiasing(context, true);
  389. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  390. CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
  391. CGContextRotateCTM(context, radians);
  392. CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
  393. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  394. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  395. CGImageRelease(imgRef);
  396. CGContextRelease(context);
  397. return img;
  398. }
  399. - (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
  400. if (!self.CGImage) return nil;
  401. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  402. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  403. size_t bytesPerRow = width * 4;
  404. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  405. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  406. CGColorSpaceRelease(colorSpace);
  407. if (!context) return nil;
  408. CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
  409. UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
  410. if (!data) {
  411. CGContextRelease(context);
  412. return nil;
  413. }
  414. vImage_Buffer src = { data, height, width, bytesPerRow };
  415. vImage_Buffer dest = { data, height, width, bytesPerRow };
  416. if (vertical) {
  417. vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  418. }
  419. if (horizontal) {
  420. vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  421. }
  422. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  423. CGContextRelease(context);
  424. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  425. CGImageRelease(imgRef);
  426. return img;
  427. }
  428. - (UIImage *)imageByRotateLeft90 {
  429. return [self imageByRotate:DegreesToRadians(90) fitSize:YES];
  430. }
  431. - (UIImage *)imageByRotateRight90 {
  432. return [self imageByRotate:DegreesToRadians(-90) fitSize:YES];
  433. }
  434. - (UIImage *)imageByRotate180 {
  435. return [self _yy_flipHorizontal:YES vertical:YES];
  436. }
  437. - (UIImage *)imageByFlipVertical {
  438. return [self _yy_flipHorizontal:NO vertical:YES];
  439. }
  440. - (UIImage *)imageByFlipHorizontal {
  441. return [self _yy_flipHorizontal:YES vertical:NO];
  442. }
  443. - (UIImage *)imageByTintColor:(UIColor *)color {
  444. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  445. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  446. [color set];
  447. UIRectFill(rect);
  448. [self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
  449. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  450. UIGraphicsEndImageContext();
  451. return newImage;
  452. }
  453. - (UIImage *)imageByGrayscale {
  454. return [self imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
  455. }
  456. - (UIImage *)imageByBlurSoft {
  457. return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  458. }
  459. - (UIImage *)imageByBlurLight {
  460. return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  461. }
  462. - (UIImage *)imageByBlurExtraLight {
  463. return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  464. }
  465. - (UIImage *)imageByBlurDark {
  466. return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  467. }
  468. - (UIImage *)imageByBlurWithTint:(UIColor *)tintColor {
  469. const CGFloat EffectColorAlpha = 0.6;
  470. UIColor *effectColor = tintColor;
  471. size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
  472. if (componentCount == 2) {
  473. CGFloat b;
  474. if ([tintColor getWhite:&b alpha:NULL]) {
  475. effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
  476. }
  477. } else {
  478. CGFloat r, g, b;
  479. if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
  480. effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
  481. }
  482. }
  483. return [self imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
  484. }
  485. - (UIImage *)imageByBlurRadius:(CGFloat)blurRadius
  486. tintColor:(UIColor *)tintColor
  487. tintMode:(CGBlendMode)tintBlendMode
  488. saturation:(CGFloat)saturation
  489. maskImage:(UIImage *)maskImage {
  490. if (self.size.width < 1 || self.size.height < 1) {
  491. NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
  492. return nil;
  493. }
  494. if (!self.CGImage) {
  495. NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
  496. return nil;
  497. }
  498. if (maskImage && !maskImage.CGImage) {
  499. NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
  500. return nil;
  501. }
  502. // iOS7 and above can use new func.
  503. BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
  504. BOOL hasBlur = blurRadius > __FLT_EPSILON__;
  505. BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
  506. CGSize size = self.size;
  507. CGRect rect = { CGPointZero, size };
  508. CGFloat scale = self.scale;
  509. CGImageRef imageRef = self.CGImage;
  510. BOOL opaque = NO;
  511. if (!hasBlur && !hasSaturation) {
  512. return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  513. }
  514. vImage_Buffer effect = { 0 }, scratch = { 0 };
  515. vImage_Buffer *input = NULL, *output = NULL;
  516. vImage_CGImageFormat format = {
  517. .bitsPerComponent = 8,
  518. .bitsPerPixel = 32,
  519. .colorSpace = NULL,
  520. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
  521. .version = 0,
  522. .decode = NULL,
  523. .renderingIntent = kCGRenderingIntentDefault
  524. };
  525. if (hasNewFunc) {
  526. vImage_Error err;
  527. err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
  528. if (err != kvImageNoError) {
  529. NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
  530. return nil;
  531. }
  532. err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
  533. if (err != kvImageNoError) {
  534. NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
  535. return nil;
  536. }
  537. } else {
  538. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  539. CGContextRef effectCtx = UIGraphicsGetCurrentContext();
  540. CGContextScaleCTM(effectCtx, 1.0, -1.0);
  541. CGContextTranslateCTM(effectCtx, 0, -size.height);
  542. CGContextDrawImage(effectCtx, rect, imageRef);
  543. effect.data = CGBitmapContextGetData(effectCtx);
  544. effect.width = CGBitmapContextGetWidth(effectCtx);
  545. effect.height = CGBitmapContextGetHeight(effectCtx);
  546. effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
  547. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  548. CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
  549. scratch.data = CGBitmapContextGetData(scratchCtx);
  550. scratch.width = CGBitmapContextGetWidth(scratchCtx);
  551. scratch.height = CGBitmapContextGetHeight(scratchCtx);
  552. scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
  553. }
  554. input = &effect;
  555. output = &scratch;
  556. if (hasBlur) {
  557. // A description of how to compute the box kernel width from the Gaussian
  558. // radius (aka standard deviation) appears in the SVG spec:
  559. // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
  560. //
  561. // For larger values of 's' (s >= 2.0), an approximation can be used: Three
  562. // successive box-blurs build a piece-wise quadratic convolution kernel, which
  563. // approximates the Gaussian kernel to within roughly 3%.
  564. //
  565. // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
  566. //
  567. // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
  568. //
  569. CGFloat inputRadius = blurRadius * scale;
  570. if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
  571. uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
  572. radius |= 1; // force radius to be odd so that the three box-blur methodology works.
  573. int iterations;
  574. if (blurRadius * scale < 0.5) iterations = 1;
  575. else if (blurRadius * scale < 1.5) iterations = 2;
  576. else iterations = 3;
  577. NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
  578. void *temp = malloc(tempSize);
  579. for (int i = 0; i < iterations; i++) {
  580. vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
  581. YY_SWAP(input, output);
  582. }
  583. free(temp);
  584. }
  585. if (hasSaturation) {
  586. // These values appear in the W3C Filter Effects spec:
  587. // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
  588. CGFloat s = saturation;
  589. CGFloat matrixFloat[] = {
  590. 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
  591. 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
  592. 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
  593. 0, 0, 0, 1,
  594. };
  595. const int32_t divisor = 256;
  596. NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
  597. int16_t matrix[matrixSize];
  598. for (NSUInteger i = 0; i < matrixSize; ++i) {
  599. matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
  600. }
  601. vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
  602. YY_SWAP(input, output);
  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