Last active
February 11, 2018 15:40
-
-
Save devpolant/98585c1787f1c0294b7e891aef03ed8a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIImage+Blur.swift | |
// NativeBlur | |
// | |
// Created by Anton Poltoratskyi on 11.02.2018. | |
// Copyright © 2018 Anton Poltoratskyi. All rights reserved. | |
// | |
import UIKit | |
import Accelerate | |
extension UIImage { | |
public func applyLightEffect() -> UIImage? { | |
let tintColor = UIColor(white: 1, alpha: 0.3) | |
return applyBlur(withRadius: 30, tintColor: tintColor, saturationDeltaFactor: 1.8, maskImage: nil) | |
} | |
public func applyExtraLightEffect() -> UIImage? { | |
let tintColor = UIColor(white: 0.97, alpha: 0.82) | |
return applyBlur(withRadius: 20, tintColor: tintColor, saturationDeltaFactor: 1.8, maskImage: nil) | |
} | |
public func applyDarkEffect() -> UIImage? { | |
let tintColor = UIColor(white: 0.11, alpha: 0.73) | |
return applyBlur(withRadius: 20, tintColor: tintColor, saturationDeltaFactor: 1.8, maskImage: nil) | |
} | |
public func applyColdBlur() -> UIImage? { | |
return applyBlur(withRadius: 30, tintColor: nil, saturationDeltaFactor: 1.8, maskImage: nil) | |
} | |
public func applyTintEffect(with tintColor: UIColor) -> UIImage? { | |
let effectColorAlpha: CGFloat = 0.6 | |
var effectColor = tintColor | |
if tintColor.cgColor.components?.count == 2 { | |
var white: CGFloat = 0 | |
if tintColor.getWhite(&white, alpha: nil) { | |
effectColor = UIColor(white: white, alpha: effectColorAlpha) | |
} | |
} else { | |
var r: CGFloat = 0 | |
var g: CGFloat = 0 | |
var b: CGFloat = 0 | |
if tintColor.getRed(&r, green: &g, blue: &b, alpha: nil) { | |
effectColor = UIColor(red: r, green: g, blue: b, alpha: effectColorAlpha) | |
} | |
} | |
return applyBlur(withRadius: 10, tintColor: effectColor, saturationDeltaFactor: -1, maskImage: nil) | |
} | |
public func applyBlur(withRadius blurRadius: CGFloat, tintColor: UIColor?, saturationDeltaFactor: CGFloat, maskImage: UIImage?) -> UIImage? { | |
guard size.width > 0, size.height > 0 else { | |
debugPrint("*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: \(self)", self.size.width, self.size.height) | |
return nil | |
} | |
guard let cgImage = self.cgImage else { | |
debugPrint("*** error: image must be backed by a CGImage: \(self)") | |
return nil | |
} | |
guard (maskImage == nil) || maskImage!.cgImage != nil else { | |
debugPrint("*** error: maskImage must be backed by a CGImage: \(maskImage!)") | |
return nil | |
} | |
let imageRect = CGRect(origin: .zero, size: self.size) | |
var effectImage = self | |
let hasBlur = blurRadius > 0 // __FLT_EPSILON__ | |
let hasSaturationChange = fabs(saturationDeltaFactor - 1.0) > 0 | |
let scale = UIScreen.main.scale | |
if hasBlur || hasSaturationChange { | |
UIGraphicsBeginImageContextWithOptions(self.size, false, scale) | |
let effectInContext = UIGraphicsGetCurrentContext()! | |
effectInContext.scaleBy(x: 1, y: -1) | |
effectInContext.translateBy(x: 0, y: -self.size.height) | |
effectInContext.draw(cgImage, in: imageRect) | |
var effectInBuffer = effectInContext.vImageBuffer | |
UIGraphicsBeginImageContextWithOptions(self.size, false, scale) | |
let effectOutContext = UIGraphicsGetCurrentContext()! | |
var effectOutBuffer = effectOutContext.vImageBuffer | |
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. | |
// | |
let inputRadius = blurRadius * scale | |
var radius: UInt32 = UInt32(floor(inputRadius * 3 * sqrt(CGFloat.pi * 2) / 4 + 0.5)) | |
if radius % 2 != 1 { | |
radius += 1 // force radius to be odd so that the three box-blur methodology works. | |
} | |
var backgroudColor: UInt8 = 0 | |
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, &backgroudColor, vImage_Flags(kvImageEdgeExtend)) | |
vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, &backgroudColor, vImage_Flags(kvImageEdgeExtend)) | |
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, &backgroudColor, vImage_Flags(kvImageEdgeExtend)) | |
} | |
var effectImageBuffersAreSwapped = false | |
if hasSaturationChange { | |
let s = saturationDeltaFactor | |
let floatingPointSaturationMatrix: [CGFloat] = [ | |
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 | |
] | |
let divisor: Int32 = 256 | |
let saturationMatrix: [Int16] = floatingPointSaturationMatrix.map { Int16(round($0 * CGFloat(divisor))) } | |
if hasBlur { | |
vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, nil, nil, vImage_Flags(kvImageNoFlags)) | |
effectImageBuffersAreSwapped = true | |
} else { | |
vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, nil, nil, vImage_Flags(kvImageNoFlags)) | |
} | |
} | |
if !effectImageBuffersAreSwapped, let image = UIGraphicsGetImageFromCurrentImageContext() { | |
effectImage = image | |
} | |
UIGraphicsEndImageContext() | |
if effectImageBuffersAreSwapped, let image = UIGraphicsGetImageFromCurrentImageContext() { | |
effectImage = image | |
} | |
UIGraphicsEndImageContext() | |
} | |
// Set up output context. | |
UIGraphicsBeginImageContextWithOptions(self.size, false, scale) | |
guard let context = UIGraphicsGetCurrentContext() else { return nil } | |
context.scaleBy(x: 1, y: -1) | |
context.translateBy(x: 0, y: -self.size.height) | |
// Draw base image. | |
context.draw(cgImage, in: imageRect) | |
// Draw effect image. | |
if hasBlur { | |
context.saveGState() | |
maskImage?.cgImage.map { context.clip(to: imageRect, mask: $0) } | |
effectImage.cgImage.map { context.draw($0, in: imageRect) } | |
context.restoreGState() | |
} | |
// Add in color tint. | |
if let tintColor = tintColor?.cgColor { | |
context.saveGState() | |
context.setFillColor(tintColor) | |
context.fill(imageRect) | |
context.restoreGState() | |
} | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image | |
} | |
} | |
fileprivate extension CGContext { | |
var vImageBuffer: vImage_Buffer { | |
return vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: bytesPerRow) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment