123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745 |
- //
- // UIImage+YYWebImage.m
- // YYWebImage <https://github.com/ibireme/YYWebImage>
- //
- // 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+YYWebImage.h"
- #import <ImageIO/ImageIO.h>
- #import <Accelerate/Accelerate.h>
- #import <objc/runtime.h>
- // Dummy class for category
- @interface UIImage_YYWebImage : NSObject @end
- @implementation UIImage_YYWebImage @end
- /// Convert degrees to radians.
- static inline CGFloat _DegreesToRadians(CGFloat degrees) {
- return degrees * M_PI / 180;
- }
- /**
- Resize rect to fit the size using a given contentMode.
-
- @param rect The draw rect
- @param size The content size
- @param mode The content mode
- @return A resized rect for the given content mode.
- @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
- */
- static CGRect _YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
- rect = CGRectStandardize(rect);
- size.width = size.width < 0 ? -size.width : size.width;
- size.height = size.height < 0 ? -size.height : size.height;
- CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
- switch (mode) {
- case UIViewContentModeScaleAspectFit:
- case UIViewContentModeScaleAspectFill: {
- if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
- size.width < 0.01 || size.height < 0.01) {
- rect.origin = center;
- rect.size = CGSizeZero;
- } else {
- CGFloat scale;
- if (mode == UIViewContentModeScaleAspectFit) {
- if (size.width / size.height < rect.size.width / rect.size.height) {
- scale = rect.size.height / size.height;
- } else {
- scale = rect.size.width / size.width;
- }
- } else {
- if (size.width / size.height < rect.size.width / rect.size.height) {
- scale = rect.size.width / size.width;
- } else {
- scale = rect.size.height / size.height;
- }
- }
- size.width *= scale;
- size.height *= scale;
- rect.size = size;
- rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
- }
- } break;
- case UIViewContentModeCenter: {
- rect.size = size;
- rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
- } break;
- case UIViewContentModeTop: {
- rect.origin.x = center.x - size.width * 0.5;
- rect.size = size;
- } break;
- case UIViewContentModeBottom: {
- rect.origin.x = center.x - size.width * 0.5;
- rect.origin.y += rect.size.height - size.height;
- rect.size = size;
- } break;
- case UIViewContentModeLeft: {
- rect.origin.y = center.y - size.height * 0.5;
- rect.size = size;
- } break;
- case UIViewContentModeRight: {
- rect.origin.y = center.y - size.height * 0.5;
- rect.origin.x += rect.size.width - size.width;
- rect.size = size;
- } break;
- case UIViewContentModeTopLeft: {
- rect.size = size;
- } break;
- case UIViewContentModeTopRight: {
- rect.origin.x += rect.size.width - size.width;
- rect.size = size;
- } break;
- case UIViewContentModeBottomLeft: {
- rect.origin.y += rect.size.height - size.height;
- rect.size = size;
- } break;
- case UIViewContentModeBottomRight: {
- rect.origin.x += rect.size.width - size.width;
- rect.origin.y += rect.size.height - size.height;
- rect.size = size;
- } break;
- case UIViewContentModeScaleToFill:
- case UIViewContentModeRedraw:
- default: {
- rect = rect;
- }
- }
- return rect;
- }
- 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 (YYWebImage)
- + (UIImage *)yy_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;
- }
- + (UIImage *)yy_imageWithColor:(UIColor *)color {
- return [self yy_imageWithColor:color size:CGSizeMake(1, 1)];
- }
- + (UIImage *)yy_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 *)yy_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)yy_hasAlphaChannel {
- if (self.CGImage == NULL) return NO;
- CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
- return (alpha == kCGImageAlphaFirst ||
- alpha == kCGImageAlphaLast ||
- alpha == kCGImageAlphaPremultipliedFirst ||
- alpha == kCGImageAlphaPremultipliedLast);
- }
- - (void)yy_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 *)yy_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 *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
- if (size.width <= 0 || size.height <= 0) return nil;
- UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
- [self yy_drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return image;
- }
- - (UIImage *)yy_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 *)yy_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 *)yy_imageByRoundCornerRadius:(CGFloat)radius {
- return [self yy_imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
- }
- - (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
- borderWidth:(CGFloat)borderWidth
- borderColor:(UIColor *)borderColor {
- return [self yy_imageByRoundCornerRadius:radius corners:UIRectCornerAllCorners borderWidth:borderWidth borderColor:borderColor borderLineJoin:kCGLineJoinMiter];
- }
- - (UIImage *)yy_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 *)yy_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 *)yy_imageByRotateLeft90 {
- return [self yy_imageByRotate:_DegreesToRadians(90) fitSize:YES];
- }
- - (UIImage *)yy_imageByRotateRight90 {
- return [self yy_imageByRotate:_DegreesToRadians(-90) fitSize:YES];
- }
- - (UIImage *)yy_imageByRotate180 {
- return [self _yy_flipHorizontal:YES vertical:YES];
- }
- - (UIImage *)yy_imageByFlipVertical {
- return [self _yy_flipHorizontal:NO vertical:YES];
- }
- - (UIImage *)yy_imageByFlipHorizontal {
- return [self _yy_flipHorizontal:YES vertical:NO];
- }
- - (UIImage *)yy_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 *)yy_imageByGrayscale {
- return [self yy_imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
- }
- - (UIImage *)yy_imageByBlurSoft {
- return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)yy_imageByBlurLight {
- return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)yy_imageByBlurExtraLight {
- return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)yy_imageByBlurDark {
- return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
- }
- - (UIImage *)yy_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 yy_imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
- }
- - (UIImage *)yy_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);
- // swap
- vImage_Buffer *swap_tmp = input;
- input = output;
- output = swap_tmp;
- }
- 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);
- // swap
- vImage_Buffer *swap_tmp = input;
- input = output;
- output = swap_tmp;
- }
-
- 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
|