Skip to content

Instantly share code, notes, and snippets.

@devpolant
Last active February 11, 2018 15:40
Show Gist options
  • Save devpolant/98585c1787f1c0294b7e891aef03ed8a to your computer and use it in GitHub Desktop.
Save devpolant/98585c1787f1c0294b7e891aef03ed8a to your computer and use it in GitHub Desktop.
//
// 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