From 83b9d5c682b21d88133f24da0f94dd56bd79e687 Mon Sep 17 00:00:00 2001 From: 单军华 Date: Thu, 19 Jul 2018 13:38:55 +0800 Subject: [PATCH] change --- screendisplay/Pods/YYCategories/YYCategories/UIKit/UIImage+YYAdd.m | 752 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 752 insertions(+), 0 deletions(-) diff --git a/screendisplay/Pods/YYCategories/YYCategories/UIKit/UIImage+YYAdd.m b/screendisplay/Pods/YYCategories/YYCategories/UIKit/UIImage+YYAdd.m new file mode 100755 index 0000000..bf75b5e --- /dev/null +++ b/screendisplay/Pods/YYCategories/YYCategories/UIKit/UIImage+YYAdd.m @@ -0,0 +1,752 @@ +// +// UIImage+YYAdd.m +// YYCategories <https://github.com/ibireme/YYCategories> +// +// 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 "YYCategoriesMacro.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 whiteColor].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 -- Gitblit v1.8.0