/TreasureBox/Pods/YYKit/YYKit/Base/UIKit/UIImage+YYAdd.m
Objective C | 752 lines | 638 code | 83 blank | 31 comment | 142 complexity | a4a4a84c28b8e2dfb9f89af074b4a618 MD5 | raw file
- //
- // UIImage+YYAdd.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 13/4/4.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "UIImage+YYAdd.h"
- #import "UIDevice+YYAdd.h"
- #import "NSString+YYAdd.h"
- #import "YYKitMacro.h"
- #import "YYCGUtilities.h"
- #import <ImageIO/ImageIO.h>
- #import <Accelerate/Accelerate.h>
- #import <CoreText/CoreText.h>
- #import <objc/runtime.h>
- #import "YYCGUtilities.h"
- YYSYNTH_DUMMY_CLASS(UIImage_YYAdd)
- static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
- NSTimeInterval delay = 0;
- CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
- if (dic) {
- CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
- if (dicGIF) {
- NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
- if (num.doubleValue <= __FLT_EPSILON__) {
- num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
- }
- delay = num.doubleValue;
- }
- CFRelease(dic);
- }
-
- // http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
- if (delay < 0.02) delay = 0.1;
- return delay;
- }
- @implementation UIImage (YYAdd)
- + (UIImage *)imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
- CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
- if (!source) return nil;
-
- size_t count = CGImageSourceGetCount(source);
- if (count <= 1) {
- CFRelease(source);
- return [self.class imageWithData:data scale:scale];
- }
-
- NSUInteger frames[count];
- double oneFrameTime = 1 / 50.0; // 50 fps
- NSTimeInterval totalTime = 0;
- NSUInteger totalFrame = 0;
- NSUInteger gcdFrame = 0;
- for (size_t i = 0; i < count; i++) {
- NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
- totalTime += delay;
- NSInteger frame = lrint(delay / oneFrameTime);
- if (frame < 1) frame = 1;
- frames[i] = frame;
- totalFrame += frames[i];
- if (i == 0) gcdFrame = frames[i];
- else {
- NSUInteger frame = frames[i], tmp;
- if (frame < gcdFrame) {
- tmp = frame; frame = gcdFrame; gcdFrame = tmp;
- }
- while (true) {
- tmp = frame % gcdFrame;
- if (tmp == 0) break;
- frame = gcdFrame;
- gcdFrame = tmp;
- }
- }
- }
- NSMutableArray *array = [NSMutableArray new];
- for (size_t i = 0; i < count; i++) {
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
- if (!imageRef) {
- CFRelease(source);
- return nil;
- }
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
- if (width == 0 || height == 0) {
- CFRelease(source);
- CFRelease(imageRef);
- return nil;
- }
-
- CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
- BOOL hasAlpha = NO;
- if (alphaInfo == kCGImageAlphaPremultipliedLast ||
- alphaInfo == kCGImageAlphaPremultipliedFirst ||
- alphaInfo == kCGImageAlphaLast ||
- alphaInfo == kCGImageAlphaFirst) {
- hasAlpha = YES;
- }
- // BGRA8888 (premultiplied) or BGRX8888
- // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
- CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
- CGColorSpaceRelease(space);
- if (!context) {
- CFRelease(source);
- CFRelease(imageRef);
- return nil;
- }
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
- CGImageRef decoded = CGBitmapContextCreateImage(context);
- CFRelease(context);
- if (!decoded) {
- CFRelease(source);
- CFRelease(imageRef);
- return nil;
- }
- UIImage *image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
- CGImageRelease(imageRef);
- CGImageRelease(decoded);
- if (!image) {
- CFRelease(source);
- return nil;
- }
- for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
- [array addObject:image];
- }
- }
- CFRelease(source);
- UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
- return image;
- }
- + (BOOL)isAnimatedGIFData:(NSData *)data {
- if (data.length < 16) return NO;
- UInt32 magic = *(UInt32 *)data.bytes;
- // http://www.w3.org/Graphics/GIF/spec-gif89a.txt
- if ((magic & 0xFFFFFF) != '\0FIG') return NO;
- CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL);
- if (!source) return NO;
- size_t count = CGImageSourceGetCount(source);
- CFRelease(source);
- return count > 1;
- }
- + (BOOL)isAnimatedGIFFile:(NSString *)path {
- if (path.length == 0) return NO;
- const char *cpath = path.UTF8String;
- FILE *fd = fopen(cpath, "rb");
- if (!fd) return NO;
-
- BOOL isGIF = NO;
- UInt32 magic = 0;
- if (fread(&magic, sizeof(UInt32), 1, fd) == 1) {
- if ((magic & 0xFFFFFF) == '\0FIG') isGIF = YES;
- }
- fclose(fd);
- return isGIF;
- }
- + (UIImage *)imageWithPDF:(id)dataOrPath {
- return [self _yy_imageWithPDF:dataOrPath resize:NO size:CGSizeZero];
- }
- + (UIImage *)imageWithPDF:(id)dataOrPath size:(CGSize)size {
- return [self _yy_imageWithPDF:dataOrPath resize:YES size:size];
- }
- + (UIImage *)imageWithEmoji:(NSString *)emoji size:(CGFloat)size {
- if (emoji.length == 0) return nil;
- if (size < 1) return nil;
-
- CGFloat scale = [UIScreen mainScreen].scale;
- CTFontRef font = CTFontCreateWithName(CFSTR("AppleColorEmoji"), size * scale, NULL);
- if (!font) return nil;
-
- NSAttributedString *str = [[NSAttributedString alloc] initWithString:emoji attributes:@{ (__bridge id)kCTFontAttributeName:(__bridge id)font, (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor clearColor].CGColor }];
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef ctx = CGBitmapContextCreate(NULL, size * scale, size * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
- CTLineRef line = CTLineCreateWithAttributedString((__bridge CFTypeRef)str);
- CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
- CGContextSetTextPosition(ctx, 0, -bounds.origin.y);
- CTLineDraw(line, ctx);
- CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
-
- CFRelease(font);
- CGColorSpaceRelease(colorSpace);
- CGContextRelease(ctx);
- if (line)CFRelease(line);
- if (imageRef) CFRelease(imageRef);
-
- return image;
- }
- + (UIImage *)_yy_imageWithPDF:(id)dataOrPath resize:(BOOL)resize size:(CGSize)size {
- CGPDFDocumentRef pdf = NULL;
- if ([dataOrPath isKindOfClass:[NSData class]]) {
- CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)dataOrPath);
- pdf = CGPDFDocumentCreateWithProvider(provider);
- CGDataProviderRelease(provider);
- } else if ([dataOrPath isKindOfClass:[NSString class]]) {
- pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:dataOrPath]);
- }
- if (!pdf) return nil;
-
- CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
- if (!page) {
- CGPDFDocumentRelease(pdf);
- return nil;
- }
-
- CGRect pdfRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
- CGSize pdfSize = resize ? size : pdfRect.size;
- CGFloat scale = [UIScreen mainScreen].scale;
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef ctx = CGBitmapContextCreate(NULL, pdfSize.width * scale, pdfSize.height * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- if (!ctx) {
- CGColorSpaceRelease(colorSpace);
- CGPDFDocumentRelease(pdf);
- return nil;
- }
-
- CGContextScaleCTM(ctx, scale, scale);
- CGContextTranslateCTM(ctx, -pdfRect.origin.x, -pdfRect.origin.y);
- CGContextDrawPDFPage(ctx, page);
- CGPDFDocumentRelease(pdf);
-
- CGImageRef image = CGBitmapContextCreateImage(ctx);
- UIImage *pdfImage = [[UIImage alloc] initWithCGImage:image scale:scale orientation:UIImageOrientationUp];
- CGImageRelease(image);
- CGContextRelease(ctx);
- CGColorSpaceRelease(colorSpace);
-
- return pdfImage;
- }
- + (UIImage *)imageWithColor:(UIColor *)color {
- return [self imageWithColor:color size:CGSizeMake(1, 1)];
- }
- + (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
- if (!color || size.width <= 0 || size.height <= 0) return nil;
- CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
- UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGContextSetFillColorWithColor(context, color.CGColor);
- CGContextFillRect(context, rect);
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- + (UIImage *)imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
- if (!drawBlock) return nil;
- UIGraphicsBeginImageContextWithOptions(size, NO, 0);
- CGContextRef context = UIGraphicsGetCurrentContext();
- if (!context) return nil;
- drawBlock(context);
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (BOOL)hasAlphaChannel {
- if (self.CGImage == NULL) return NO;
- CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
- return (alpha == kCGImageAlphaFirst ||
- alpha == kCGImageAlphaLast ||
- alpha == kCGImageAlphaPremultipliedFirst ||
- alpha == kCGImageAlphaPremultipliedLast);
- }
- - (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
- CGRect drawRect = YYCGRectFitWithContentMode(rect, self.size, contentMode);
- if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
- if (clips) {
- CGContextRef context = UIGraphicsGetCurrentContext();
- if (context) {
- CGContextSaveGState(context);
- CGContextAddRect(context, rect);
- CGContextClip(context);
- [self drawInRect:drawRect];
- CGContextRestoreGState(context);
- }
- } else {
- [self drawInRect:drawRect];
- }
- }
- - (UIImage *)imageByResizeToSize:(CGSize)size {
- if (size.width <= 0 || size.height <= 0) return nil;
- UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
- [self drawInRect:CGRectMake(0, 0, size.width, size.height)];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (UIImage *)imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
- if (size.width <= 0 || size.height <= 0) return nil;
- UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
- [self drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (UIImage *)imageByCropToRect:(CGRect)rect {
- rect.origin.x *= self.scale;
- rect.origin.y *= self.scale;
- rect.size.width *= self.scale;
- rect.size.height *= self.scale;
- if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
- CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
- UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
- CGImageRelease(imageRef);
- return image;
- }
- - (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
- CGSize size = self.size;
- size.width -= insets.left + insets.right;
- size.height -= insets.top + insets.bottom;
- if (size.width <= 0 || size.height <= 0) return nil;
- CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
- UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
- CGContextRef context = UIGraphicsGetCurrentContext();
- if (color) {
- CGContextSetFillColorWithColor(context, color.CGColor);
- CGMutablePathRef path = CGPathCreateMutable();
- CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
- CGPathAddRect(path, NULL, rect);
- CGContextAddPath(context, path);
- CGContextEOFillPath(context);
- CGPathRelease(path);
- }
- [self drawInRect:rect];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
- return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
- }
- - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
- borderWidth:(CGFloat)borderWidth
- borderColor:(UIColor *)borderColor {
- return [self imageByRoundCornerRadius:radius
- corners:UIRectCornerAllCorners
- borderWidth:borderWidth
- borderColor:borderColor
- borderLineJoin:kCGLineJoinMiter];
- }
- - (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
- corners:(UIRectCorner)corners
- borderWidth:(CGFloat)borderWidth
- borderColor:(UIColor *)borderColor
- borderLineJoin:(CGLineJoin)borderLineJoin {
-
- if (corners != UIRectCornerAllCorners) {
- UIRectCorner tmp = 0;
- if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
- if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
- if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
- if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
- corners = tmp;
- }
-
- UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
- CGContextScaleCTM(context, 1, -1);
- CGContextTranslateCTM(context, 0, -rect.size.height);
-
- CGFloat minSize = MIN(self.size.width, self.size.height);
- if (borderWidth < minSize / 2) {
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
- [path closePath];
-
- CGContextSaveGState(context);
- [path addClip];
- CGContextDrawImage(context, rect, self.CGImage);
- CGContextRestoreGState(context);
- }
-
- if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
- CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
- CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
- CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
- [path closePath];
-
- path.lineWidth = borderWidth;
- path.lineJoinStyle = borderLineJoin;
- [borderColor setStroke];
- [path stroke];
- }
-
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (UIImage *)imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
- size_t width = (size_t)CGImageGetWidth(self.CGImage);
- size_t height = (size_t)CGImageGetHeight(self.CGImage);
- CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
- fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
-
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = CGBitmapContextCreate(NULL,
- (size_t)newRect.size.width,
- (size_t)newRect.size.height,
- 8,
- (size_t)newRect.size.width * 4,
- colorSpace,
- kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- CGColorSpaceRelease(colorSpace);
- if (!context) return nil;
-
- CGContextSetShouldAntialias(context, true);
- CGContextSetAllowsAntialiasing(context, true);
- CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
-
- CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
- CGContextRotateCTM(context, radians);
-
- CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
- CGImageRef imgRef = CGBitmapContextCreateImage(context);
- UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
- CGImageRelease(imgRef);
- CGContextRelease(context);
- return img;
- }
- - (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
- if (!self.CGImage) return nil;
- size_t width = (size_t)CGImageGetWidth(self.CGImage);
- size_t height = (size_t)CGImageGetHeight(self.CGImage);
- size_t bytesPerRow = width * 4;
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- CGColorSpaceRelease(colorSpace);
- if (!context) return nil;
-
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
- UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
- if (!data) {
- CGContextRelease(context);
- return nil;
- }
- vImage_Buffer src = { data, height, width, bytesPerRow };
- vImage_Buffer dest = { data, height, width, bytesPerRow };
- if (vertical) {
- vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
- }
- if (horizontal) {
- vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
- }
- CGImageRef imgRef = CGBitmapContextCreateImage(context);
- CGContextRelease(context);
- UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
- CGImageRelease(imgRef);
- return img;
- }
- - (UIImage *)imageByRotateLeft90 {
- return [self imageByRotate:DegreesToRadians(90) fitSize:YES];
- }
- - (UIImage *)imageByRotateRight90 {
- return [self imageByRotate:DegreesToRadians(-90) fitSize:YES];
- }
- - (UIImage *)imageByRotate180 {
- return [self _yy_flipHorizontal:YES vertical:YES];
- }
- - (UIImage *)imageByFlipVertical {
- return [self _yy_flipHorizontal:NO vertical:YES];
- }
- - (UIImage *)imageByFlipHorizontal {
- return [self _yy_flipHorizontal:YES vertical:NO];
- }
- - (UIImage *)imageByTintColor:(UIColor *)color {
- UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
- CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
- [color set];
- UIRectFill(rect);
- [self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
- UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return newImage;
- }
- - (UIImage *)imageByGrayscale {
- return [self imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
- }
- - (UIImage *)imageByBlurSoft {
- return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)imageByBlurLight {
- return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)imageByBlurExtraLight {
- return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)imageByBlurDark {
- return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)imageByBlurWithTint:(UIColor *)tintColor {
- const CGFloat EffectColorAlpha = 0.6;
- UIColor *effectColor = tintColor;
- size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
- if (componentCount == 2) {
- CGFloat b;
- if ([tintColor getWhite:&b alpha:NULL]) {
- effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
- }
- } else {
- CGFloat r, g, b;
- if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
- effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
- }
- }
- return [self imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
- }
- - (UIImage *)imageByBlurRadius:(CGFloat)blurRadius
- tintColor:(UIColor *)tintColor
- tintMode:(CGBlendMode)tintBlendMode
- saturation:(CGFloat)saturation
- maskImage:(UIImage *)maskImage {
- if (self.size.width < 1 || self.size.height < 1) {
- NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
- return nil;
- }
- if (!self.CGImage) {
- NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
- return nil;
- }
- if (maskImage && !maskImage.CGImage) {
- NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
- return nil;
- }
-
- // iOS7 and above can use new func.
- BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
- BOOL hasBlur = blurRadius > __FLT_EPSILON__;
- BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
-
- CGSize size = self.size;
- CGRect rect = { CGPointZero, size };
- CGFloat scale = self.scale;
- CGImageRef imageRef = self.CGImage;
- BOOL opaque = NO;
-
- if (!hasBlur && !hasSaturation) {
- return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
- }
-
- vImage_Buffer effect = { 0 }, scratch = { 0 };
- vImage_Buffer *input = NULL, *output = NULL;
-
- vImage_CGImageFormat format = {
- .bitsPerComponent = 8,
- .bitsPerPixel = 32,
- .colorSpace = NULL,
- .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
- .version = 0,
- .decode = NULL,
- .renderingIntent = kCGRenderingIntentDefault
- };
-
- if (hasNewFunc) {
- vImage_Error err;
- err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
- if (err != kvImageNoError) {
- NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
- return nil;
- }
- err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
- if (err != kvImageNoError) {
- NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
- return nil;
- }
- } else {
- UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
- CGContextRef effectCtx = UIGraphicsGetCurrentContext();
- CGContextScaleCTM(effectCtx, 1.0, -1.0);
- CGContextTranslateCTM(effectCtx, 0, -size.height);
- CGContextDrawImage(effectCtx, rect, imageRef);
- effect.data = CGBitmapContextGetData(effectCtx);
- effect.width = CGBitmapContextGetWidth(effectCtx);
- effect.height = CGBitmapContextGetHeight(effectCtx);
- effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
-
- UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
- CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
- scratch.data = CGBitmapContextGetData(scratchCtx);
- scratch.width = CGBitmapContextGetWidth(scratchCtx);
- scratch.height = CGBitmapContextGetHeight(scratchCtx);
- scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
- }
-
- input = &effect;
- output = &scratch;
-
- if (hasBlur) {
- // A description of how to compute the box kernel width from the Gaussian
- // radius (aka standard deviation) appears in the SVG spec:
- // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
- //
- // For larger values of 's' (s >= 2.0), an approximation can be used: Three
- // successive box-blurs build a piece-wise quadratic convolution kernel, which
- // approximates the Gaussian kernel to within roughly 3%.
- //
- // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
- //
- // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
- //
- CGFloat inputRadius = blurRadius * scale;
- if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
- uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
- radius |= 1; // force radius to be odd so that the three box-blur methodology works.
- int iterations;
- if (blurRadius * scale < 0.5) iterations = 1;
- else if (blurRadius * scale < 1.5) iterations = 2;
- else iterations = 3;
- NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
- void *temp = malloc(tempSize);
- for (int i = 0; i < iterations; i++) {
- vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
- YY_SWAP(input, output);
- }
- free(temp);
- }
-
-
- if (hasSaturation) {
- // These values appear in the W3C Filter Effects spec:
- // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
- CGFloat s = saturation;
- CGFloat matrixFloat[] = {
- 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
- 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
- 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
- 0, 0, 0, 1,
- };
- const int32_t divisor = 256;
- NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
- int16_t matrix[matrixSize];
- for (NSUInteger i = 0; i < matrixSize; ++i) {
- matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
- }
- vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
- YY_SWAP(input, output);
- }
-
- UIImage *outputImage = nil;
- if (hasNewFunc) {
- CGImageRef effectCGImage = NULL;
- effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
- if (effectCGImage == NULL) {
- effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
- free(input->data);
- }
- free(output->data);
- outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
- CGImageRelease(effectCGImage);
- } else {
- CGImageRef effectCGImage;
- UIImage *effectImage;
- if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- effectCGImage = effectImage.CGImage;
- outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
- }
- return outputImage;
- }
- // Helper function to handle deferred cleanup of a buffer.
- static void _yy_cleanupBuffer(void *userData, void *buf_data) {
- free(buf_data);
- }
- // Helper function to add tint and mask.
- - (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
- tintColor:(UIColor *)tintColor
- tintBlendMode:(CGBlendMode)tintBlendMode
- maskImage:(UIImage *)maskImage
- opaque:(BOOL)opaque {
- BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
- BOOL hasMask = maskImage != nil;
- CGSize size = self.size;
- CGRect rect = { CGPointZero, size };
- CGFloat scale = self.scale;
-
- if (!hasTint && !hasMask) {
- return [UIImage imageWithCGImage:effectCGImage];
- }
-
- UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
- CGContextRef context = UIGraphicsGetCurrentContext();
- CGContextScaleCTM(context, 1.0, -1.0);
- CGContextTranslateCTM(context, 0, -size.height);
- if (hasMask) {
- CGContextDrawImage(context, rect, self.CGImage);
- CGContextSaveGState(context);
- CGContextClipToMask(context, rect, maskImage.CGImage);
- }
- CGContextDrawImage(context, rect, effectCGImage);
- if (hasTint) {
- CGContextSaveGState(context);
- CGContextSetBlendMode(context, tintBlendMode);
- CGContextSetFillColorWithColor(context, tintColor.CGColor);
- CGContextFillRect(context, rect);
- CGContextRestoreGState(context);
- }
- if (hasMask) {
- CGContextRestoreGState(context);
- }
- UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return outputImage;
- }
- @end