// // UIImage+Blur.m // IOS-Categories (https://github.com/shaojiankui/iOS-Categories) // // Created by Jakey on 15/6/5. // Copyright (c) 2015年 www.skyfox.org. All rights reserved. // #import "UIImage+Blur.h" @import Accelerate; @implementation UIImage (Blur) #pragma mark - #pragma mark - Effects //| ---------------------------------------------------------------------------- - (UIImage *)lightImage { UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; return [self blurredImageWithSize:CGSizeMake(60, 60) tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; } //| ---------------------------------------------------------------------------- - (UIImage *)extraLightImage { UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; return [self blurredImageWithSize:CGSizeMake(40, 40) tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; } //| ---------------------------------------------------------------------------- - (UIImage *)darkImage { UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; return [self blurredImageWithSize:CGSizeMake(40, 40) tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; } //| ---------------------------------------------------------------------------- - (UIImage *)tintedImageWithColor:(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 blurredImageWithSize:CGSizeMake(20, 20) tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil]; } //| ---------------------------------------------------------------------------- - (UIImage *)blurredImageWithRadius:(CGFloat)blurRadius { return [self blurredImageWithSize:CGSizeMake(blurRadius, blurRadius)]; } //| ---------------------------------------------------------------------------- - (UIImage *)blurredImageWithSize:(CGSize)blurSize { return [self blurredImageWithSize:blurSize tintColor:nil saturationDeltaFactor:1.0 maskImage:nil]; } #pragma mark - #pragma mark - Implementation //| ---------------------------------------------------------------------------- - (UIImage *)blurredImageWithSize:(CGSize)blurSize tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage { #define ENABLE_BLUR 1 #define ENABLE_SATURATION_ADJUSTMENT 1 #define ENABLE_TINT 1 // Check pre-conditions. if (self.size.width < 1 || self.size.height < 1) { NSLog(@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self); return nil; } if (!self.CGImage) { NSLog(@"*** error: inputImage must be backed by a CGImage: %@", self); return nil; } if (maskImage && !maskImage.CGImage) { NSLog(@"*** error: effectMaskImage must be backed by a CGImage: %@", maskImage); return nil; } BOOL hasBlur = blurSize.width > __FLT_EPSILON__ || blurSize.height > __FLT_EPSILON__; BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; CGImageRef inputCGImage = self.CGImage; CGFloat inputImageScale = self.scale; CGBitmapInfo inputImageBitmapInfo = CGImageGetBitmapInfo(inputCGImage); CGImageAlphaInfo inputImageAlphaInfo = (inputImageBitmapInfo & kCGBitmapAlphaInfoMask); CGSize outputImageSizeInPoints = self.size; CGRect outputImageRectInPoints = { CGPointZero, outputImageSizeInPoints }; // Set up output context. BOOL useOpaqueContext; if (inputImageAlphaInfo == kCGImageAlphaNone || inputImageAlphaInfo == kCGImageAlphaNoneSkipLast || inputImageAlphaInfo == kCGImageAlphaNoneSkipFirst) useOpaqueContext = YES; else useOpaqueContext = NO; UIGraphicsBeginImageContextWithOptions(outputImageRectInPoints.size, useOpaqueContext, inputImageScale); CGContextRef outputContext = UIGraphicsGetCurrentContext(); CGContextScaleCTM(outputContext, 1.0, -1.0); CGContextTranslateCTM(outputContext, 0, -outputImageRectInPoints.size.height); if (hasBlur || hasSaturationChange) { vImage_Buffer effectInBuffer; vImage_Buffer scratchBuffer1; vImage_Buffer *inputBuffer; vImage_Buffer *outputBuffer; vImage_CGImageFormat format = { .bitsPerComponent = 8, .bitsPerPixel = 32, .colorSpace = NULL, // (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) // requests a BGRA buffer. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, .version = 0, .decode = NULL, .renderingIntent = kCGRenderingIntentDefault }; vImage_Error e = vImageBuffer_InitWithCGImage(&effectInBuffer, &format, NULL, self.CGImage, kvImagePrintDiagnosticsToConsole); if (e != kvImageNoError) { NSLog(@"*** error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", e, self); UIGraphicsEndImageContext(); return nil; } vImageBuffer_Init(&scratchBuffer1, effectInBuffer.height, effectInBuffer.width, format.bitsPerPixel, kvImageNoFlags); inputBuffer = &effectInBuffer; outputBuffer = &scratchBuffer1; #if ENABLE_BLUR if (hasBlur) { CGFloat radiusX = [self gaussianBlurRadiusWithBlurRadius:blurSize.width * inputImageScale]; CGFloat radiusY = [self gaussianBlurRadiusWithBlurRadius:blurSize.height * inputImageScale]; NSInteger tempBufferSize = vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, NULL, 0, 0, radiusY, radiusX, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); void *tempBuffer = malloc(tempBufferSize); vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radiusY, radiusX, NULL, kvImageEdgeExtend); vImageBoxConvolve_ARGB8888(outputBuffer, inputBuffer, tempBuffer, 0, 0, radiusY, radiusX, NULL, kvImageEdgeExtend); vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radiusY, radiusX, NULL, kvImageEdgeExtend); free(tempBuffer); vImage_Buffer *temp = inputBuffer; inputBuffer = outputBuffer; outputBuffer = temp; } #endif #if ENABLE_SATURATION_ADJUSTMENT if (hasSaturationChange) { CGFloat s = saturationDeltaFactor; // These values appear in the W3C Filter Effects spec: // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/index.html#grayscaleEquivalent // CGFloat floatingPointSaturationMatrix[] = { 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(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]); int16_t saturationMatrix[matrixSize]; for (NSUInteger i = 0; i < matrixSize; ++i) { saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); } vImageMatrixMultiply_ARGB8888(inputBuffer, outputBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); vImage_Buffer *temp = inputBuffer; inputBuffer = outputBuffer; outputBuffer = temp; } #endif CGImageRef effectCGImage; if ( (effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, &cleanupBuffer, NULL, kvImageNoAllocate, NULL)) == NULL ) { effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, NULL, NULL, kvImageNoFlags, NULL); free(inputBuffer->data); } if (maskImage) { // Only need to draw the base image if the effect image will be masked. CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); } // draw effect image CGContextSaveGState(outputContext); if (maskImage) CGContextClipToMask(outputContext, outputImageRectInPoints, maskImage.CGImage); CGContextDrawImage(outputContext, outputImageRectInPoints, effectCGImage); CGContextRestoreGState(outputContext); // Cleanup CGImageRelease(effectCGImage); free(outputBuffer->data); } else { // draw base image CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); } #if ENABLE_TINT // Add in color tint. if (tintColor) { CGContextSaveGState(outputContext); CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); CGContextFillRect(outputContext, outputImageRectInPoints); CGContextRestoreGState(outputContext); } #endif // Output image is ready. UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return outputImage; #undef ENABLE_BLUR #undef ENABLE_SATURATION_ADJUSTMENT #undef ENABLE_TINT } // 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)gaussianBlurRadiusWithBlurRadius:(CGFloat)blurRadius { if (blurRadius - 2. < __FLT_EPSILON__) { blurRadius = 2.; } uint32_t radius = floor((blurRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5) / 2); radius |= 1; // force radius to be odd so that the three box-blur methodology works. return radius; } //| ---------------------------------------------------------------------------- // Helper function to handle deferred cleanup of a buffer. // void cleanupBuffer(void *userData, void *buf_data) { free(buf_data); } @end