// // UIImage+YYWebImage.m // 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 #import #import // 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