UIImage+YYAdd.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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 whiteColor].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. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  228. if (CGSizeEqualToSize(rect.size, CGSizeZero)) {
  229. return nil;
  230. }
  231. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
  232. CGContextRef context = UIGraphicsGetCurrentContext();
  233. CGContextSetFillColorWithColor(context, color.CGColor);
  234. CGContextFillRect(context, rect);
  235. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  236. UIGraphicsEndImageContext();
  237. return image;
  238. }
  239. + (UIImage *)imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
  240. if (!drawBlock) return nil;
  241. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  242. if (CGSizeEqualToSize(size, CGSizeZero)) {
  243. return nil;
  244. }
  245. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  246. CGContextRef context = UIGraphicsGetCurrentContext();
  247. if (!context) return nil;
  248. drawBlock(context);
  249. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  250. UIGraphicsEndImageContext();
  251. return image;
  252. }
  253. - (BOOL)hasAlphaChannel {
  254. if (self.CGImage == NULL) return NO;
  255. CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
  256. return (alpha == kCGImageAlphaFirst ||
  257. alpha == kCGImageAlphaLast ||
  258. alpha == kCGImageAlphaPremultipliedFirst ||
  259. alpha == kCGImageAlphaPremultipliedLast);
  260. }
  261. - (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
  262. CGRect drawRect = YYCGRectFitWithContentMode(rect, self.size, contentMode);
  263. if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
  264. if (clips) {
  265. CGContextRef context = UIGraphicsGetCurrentContext();
  266. if (context) {
  267. CGContextSaveGState(context);
  268. CGContextAddRect(context, rect);
  269. CGContextClip(context);
  270. [self drawInRect:drawRect];
  271. CGContextRestoreGState(context);
  272. }
  273. } else {
  274. [self drawInRect:drawRect];
  275. }
  276. }
  277. - (UIImage *)imageByResizeToSize:(CGSize)size {
  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)];
  281. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  282. UIGraphicsEndImageContext();
  283. return image;
  284. }
  285. - (UIImage *)imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
  286. if (size.width <= 0 || size.height <= 0) return nil;
  287. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  288. [self drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
  289. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  290. UIGraphicsEndImageContext();
  291. return image;
  292. }
  293. - (UIImage *)imageByCropToRect:(CGRect)rect {
  294. rect.origin.x *= self.scale;
  295. rect.origin.y *= self.scale;
  296. rect.size.width *= self.scale;
  297. rect.size.height *= self.scale;
  298. if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
  299. CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
  300. UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
  301. CGImageRelease(imageRef);
  302. return image;
  303. }
  304. - (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
  305. CGSize size = self.size;
  306. size.width -= insets.left + insets.right;
  307. size.height -= insets.top + insets.bottom;
  308. if (size.width <= 0 || size.height <= 0) return nil;
  309. CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
  310. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  311. if (CGSizeEqualToSize(size, CGSizeZero)) {
  312. return nil;
  313. }
  314. UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
  315. CGContextRef context = UIGraphicsGetCurrentContext();
  316. if (color) {
  317. CGContextSetFillColorWithColor(context, color.CGColor);
  318. CGMutablePathRef path = CGPathCreateMutable();
  319. CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
  320. CGPathAddRect(path, NULL, rect);
  321. CGContextAddPath(context, path);
  322. CGContextEOFillPath(context);
  323. CGPathRelease(path);
  324. }
  325. [self drawInRect:rect];
  326. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  327. UIGraphicsEndImageContext();
  328. return image;
  329. }
  330. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
  331. return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
  332. }
  333. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
  334. borderWidth:(CGFloat)borderWidth
  335. borderColor:(UIColor *)borderColor {
  336. return [self imageByRoundCornerRadius:radius
  337. corners:UIRectCornerAllCorners
  338. borderWidth:borderWidth
  339. borderColor:borderColor
  340. borderLineJoin:kCGLineJoinMiter];
  341. }
  342. - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
  343. corners:(UIRectCorner)corners
  344. borderWidth:(CGFloat)borderWidth
  345. borderColor:(UIColor *)borderColor
  346. borderLineJoin:(CGLineJoin)borderLineJoin {
  347. if (corners != UIRectCornerAllCorners) {
  348. UIRectCorner tmp = 0;
  349. if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
  350. if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
  351. if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
  352. if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
  353. corners = tmp;
  354. }
  355. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  356. if (CGSizeEqualToSize(self.size, CGSizeZero)) {
  357. return nil;
  358. }
  359. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  360. CGContextRef context = UIGraphicsGetCurrentContext();
  361. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  362. CGContextScaleCTM(context, 1, -1);
  363. CGContextTranslateCTM(context, 0, -rect.size.height);
  364. CGFloat minSize = MIN(self.size.width, self.size.height);
  365. if (borderWidth < minSize / 2) {
  366. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
  367. [path closePath];
  368. CGContextSaveGState(context);
  369. [path addClip];
  370. CGContextDrawImage(context, rect, self.CGImage);
  371. CGContextRestoreGState(context);
  372. }
  373. if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
  374. CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
  375. CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
  376. CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
  377. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
  378. [path closePath];
  379. path.lineWidth = borderWidth;
  380. path.lineJoinStyle = borderLineJoin;
  381. [borderColor setStroke];
  382. [path stroke];
  383. }
  384. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  385. UIGraphicsEndImageContext();
  386. return image;
  387. }
  388. - (UIImage *)imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
  389. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  390. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  391. CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
  392. fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
  393. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  394. CGContextRef context = CGBitmapContextCreate(NULL,
  395. (size_t)newRect.size.width,
  396. (size_t)newRect.size.height,
  397. 8,
  398. (size_t)newRect.size.width * 4,
  399. colorSpace,
  400. kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  401. CGColorSpaceRelease(colorSpace);
  402. if (!context) return nil;
  403. CGContextSetShouldAntialias(context, true);
  404. CGContextSetAllowsAntialiasing(context, true);
  405. CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
  406. CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
  407. CGContextRotateCTM(context, radians);
  408. CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
  409. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  410. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  411. CGImageRelease(imgRef);
  412. CGContextRelease(context);
  413. return img;
  414. }
  415. - (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
  416. if (!self.CGImage) return nil;
  417. size_t width = (size_t)CGImageGetWidth(self.CGImage);
  418. size_t height = (size_t)CGImageGetHeight(self.CGImage);
  419. size_t bytesPerRow = width * 4;
  420. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  421. CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  422. CGColorSpaceRelease(colorSpace);
  423. if (!context) return nil;
  424. CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
  425. UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
  426. if (!data) {
  427. CGContextRelease(context);
  428. return nil;
  429. }
  430. vImage_Buffer src = { data, height, width, bytesPerRow };
  431. vImage_Buffer dest = { data, height, width, bytesPerRow };
  432. if (vertical) {
  433. vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  434. }
  435. if (horizontal) {
  436. vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
  437. }
  438. CGImageRef imgRef = CGBitmapContextCreateImage(context);
  439. CGContextRelease(context);
  440. UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
  441. CGImageRelease(imgRef);
  442. return img;
  443. }
  444. - (UIImage *)imageByRotateLeft90 {
  445. return [self imageByRotate:DegreesToRadians(90) fitSize:YES];
  446. }
  447. - (UIImage *)imageByRotateRight90 {
  448. return [self imageByRotate:DegreesToRadians(-90) fitSize:YES];
  449. }
  450. - (UIImage *)imageByRotate180 {
  451. return [self _yy_flipHorizontal:YES vertical:YES];
  452. }
  453. - (UIImage *)imageByFlipVertical {
  454. return [self _yy_flipHorizontal:NO vertical:YES];
  455. }
  456. - (UIImage *)imageByFlipHorizontal {
  457. return [self _yy_flipHorizontal:YES vertical:NO];
  458. }
  459. - (UIImage *)imageByTintColor:(UIColor *)color {
  460. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  461. if (CGSizeEqualToSize(self.size, CGSizeZero)) {
  462. return nil;
  463. }
  464. UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
  465. CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
  466. [color set];
  467. UIRectFill(rect);
  468. [self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
  469. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  470. UIGraphicsEndImageContext();
  471. return newImage;
  472. }
  473. - (UIImage *)imageByGrayscale {
  474. return [self imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
  475. }
  476. - (UIImage *)imageByBlurSoft {
  477. return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  478. }
  479. - (UIImage *)imageByBlurLight {
  480. return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  481. }
  482. - (UIImage *)imageByBlurExtraLight {
  483. return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  484. }
  485. - (UIImage *)imageByBlurDark {
  486. return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
  487. }
  488. - (UIImage *)imageByBlurWithTint:(UIColor *)tintColor {
  489. const CGFloat EffectColorAlpha = 0.6;
  490. UIColor *effectColor = tintColor;
  491. size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
  492. if (componentCount == 2) {
  493. CGFloat b;
  494. if ([tintColor getWhite:&b alpha:NULL]) {
  495. effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
  496. }
  497. } else {
  498. CGFloat r, g, b;
  499. if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
  500. effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
  501. }
  502. }
  503. return [self imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
  504. }
  505. - (UIImage *)imageByBlurRadius:(CGFloat)blurRadius
  506. tintColor:(UIColor *)tintColor
  507. tintMode:(CGBlendMode)tintBlendMode
  508. saturation:(CGFloat)saturation
  509. maskImage:(UIImage *)maskImage {
  510. if (self.size.width < 1 || self.size.height < 1) {
  511. NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
  512. return nil;
  513. }
  514. if (!self.CGImage) {
  515. NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
  516. return nil;
  517. }
  518. if (maskImage && !maskImage.CGImage) {
  519. NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
  520. return nil;
  521. }
  522. // iOS7 and above can use new func.
  523. BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
  524. BOOL hasBlur = blurRadius > __FLT_EPSILON__;
  525. BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
  526. CGSize size = self.size;
  527. CGRect rect = { CGPointZero, size };
  528. CGFloat scale = self.scale;
  529. CGImageRef imageRef = self.CGImage;
  530. BOOL opaque = NO;
  531. if (!hasBlur && !hasSaturation) {
  532. return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  533. }
  534. vImage_Buffer effect = { 0 }, scratch = { 0 };
  535. vImage_Buffer *input = NULL, *output = NULL;
  536. vImage_CGImageFormat format = {
  537. .bitsPerComponent = 8,
  538. .bitsPerPixel = 32,
  539. .colorSpace = NULL,
  540. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
  541. .version = 0,
  542. .decode = NULL,
  543. .renderingIntent = kCGRenderingIntentDefault
  544. };
  545. if (hasNewFunc) {
  546. vImage_Error err;
  547. err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
  548. if (err != kvImageNoError) {
  549. NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
  550. return nil;
  551. }
  552. err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
  553. if (err != kvImageNoError) {
  554. NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
  555. return nil;
  556. }
  557. } else {
  558. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  559. if (CGSizeEqualToSize(size, CGSizeZero)) {
  560. return nil;
  561. }
  562. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  563. CGContextRef effectCtx = UIGraphicsGetCurrentContext();
  564. CGContextScaleCTM(effectCtx, 1.0, -1.0);
  565. CGContextTranslateCTM(effectCtx, 0, -size.height);
  566. CGContextDrawImage(effectCtx, rect, imageRef);
  567. effect.data = CGBitmapContextGetData(effectCtx);
  568. effect.width = CGBitmapContextGetWidth(effectCtx);
  569. effect.height = CGBitmapContextGetHeight(effectCtx);
  570. effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
  571. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  572. CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
  573. scratch.data = CGBitmapContextGetData(scratchCtx);
  574. scratch.width = CGBitmapContextGetWidth(scratchCtx);
  575. scratch.height = CGBitmapContextGetHeight(scratchCtx);
  576. scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
  577. }
  578. input = &effect;
  579. output = &scratch;
  580. if (hasBlur) {
  581. // A description of how to compute the box kernel width from the Gaussian
  582. // radius (aka standard deviation) appears in the SVG spec:
  583. // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
  584. //
  585. // For larger values of 's' (s >= 2.0), an approximation can be used: Three
  586. // successive box-blurs build a piece-wise quadratic convolution kernel, which
  587. // approximates the Gaussian kernel to within roughly 3%.
  588. //
  589. // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
  590. //
  591. // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
  592. //
  593. CGFloat inputRadius = blurRadius * scale;
  594. if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
  595. uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
  596. radius |= 1; // force radius to be odd so that the three box-blur methodology works.
  597. int iterations;
  598. if (blurRadius * scale < 0.5) iterations = 1;
  599. else if (blurRadius * scale < 1.5) iterations = 2;
  600. else iterations = 3;
  601. NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
  602. void *temp = malloc(tempSize);
  603. for (int i = 0; i < iterations; i++) {
  604. vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
  605. YY_SWAP(input, output);
  606. }
  607. free(temp);
  608. }
  609. if (hasSaturation) {
  610. // These values appear in the W3C Filter Effects spec:
  611. // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
  612. CGFloat s = saturation;
  613. CGFloat matrixFloat[] = {
  614. 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
  615. 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
  616. 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
  617. 0, 0, 0, 1,
  618. };
  619. const int32_t divisor = 256;
  620. NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
  621. int16_t matrix[matrixSize];
  622. for (NSUInteger i = 0; i < matrixSize; ++i) {
  623. matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
  624. }
  625. vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
  626. YY_SWAP(input, output);
  627. }
  628. UIImage *outputImage = nil;
  629. if (hasNewFunc) {
  630. CGImageRef effectCGImage = NULL;
  631. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
  632. if (effectCGImage == NULL) {
  633. effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
  634. free(input->data);
  635. }
  636. free(output->data);
  637. outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  638. CGImageRelease(effectCGImage);
  639. } else {
  640. CGImageRef effectCGImage;
  641. UIImage *effectImage;
  642. if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  643. UIGraphicsEndImageContext();
  644. if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
  645. UIGraphicsEndImageContext();
  646. effectCGImage = effectImage.CGImage;
  647. outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
  648. }
  649. return outputImage;
  650. }
  651. // Helper function to handle deferred cleanup of a buffer.
  652. static void _yy_cleanupBuffer(void *userData, void *buf_data) {
  653. free(buf_data);
  654. }
  655. // Helper function to add tint and mask.
  656. - (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
  657. tintColor:(UIColor *)tintColor
  658. tintBlendMode:(CGBlendMode)tintBlendMode
  659. maskImage:(UIImage *)maskImage
  660. opaque:(BOOL)opaque {
  661. BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
  662. BOOL hasMask = maskImage != nil;
  663. CGSize size = self.size;
  664. CGRect rect = { CGPointZero, size };
  665. CGFloat scale = self.scale;
  666. if (!hasTint && !hasMask) {
  667. return [UIImage imageWithCGImage:effectCGImage];
  668. }
  669. // 传入的View.frame.size是0的话,直接返回nil,防止 UIGraphicsBeginImageContext() 传入0,导致崩溃
  670. if (CGSizeEqualToSize(size, CGSizeZero)) {
  671. return nil;
  672. }
  673. UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
  674. CGContextRef context = UIGraphicsGetCurrentContext();
  675. CGContextScaleCTM(context, 1.0, -1.0);
  676. CGContextTranslateCTM(context, 0, -size.height);
  677. if (hasMask) {
  678. CGContextDrawImage(context, rect, self.CGImage);
  679. CGContextSaveGState(context);
  680. CGContextClipToMask(context, rect, maskImage.CGImage);
  681. }
  682. CGContextDrawImage(context, rect, effectCGImage);
  683. if (hasTint) {
  684. CGContextSaveGState(context);
  685. CGContextSetBlendMode(context, tintBlendMode);
  686. CGContextSetFillColorWithColor(context, tintColor.CGColor);
  687. CGContextFillRect(context, rect);
  688. CGContextRestoreGState(context);
  689. }
  690. if (hasMask) {
  691. CGContextRestoreGState(context);
  692. }
  693. UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
  694. UIGraphicsEndImageContext();
  695. return outputImage;
  696. }
  697. @end