Skip to content

Instantly share code, notes, and snippets.

@aryamansharda
Created September 13, 2020 06:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aryamansharda/019e6ad47fa5b1481692917e62cd9e4a to your computer and use it in GitHub Desktop.
Save aryamansharda/019e6ad47fa5b1481692917e62cd9e4a to your computer and use it in GitHub Desktop.
GaussianBlur
//
// GaussianBlur.swift
//
// Created by Aryaman Sharda on 9/12/20.
// Copyright © 2020 com.DigitalBunker. All rights reserved.
//
import Foundation
import UIKit
// Dependency: SwiftImage
// Usage: let outputImage = GaussianBlur.createBlurredImage(radius: 5, image: UIImage(named: "IMAGE_NAME")!)
final class GaussianBlur {
/// Returns a gaussian blurred image
/// - Parameters:
/// - radius: This relates to the strength of the blur. The kernel will be 1 + (2 * radius) in width/height to ensure center pixel exists.
/// - image: The source image
/// - Returns: Returns a SwiftImage (UIImage can be extracted from this object)
static func createBlurredImage(radius: Int, image: UIImage) -> Image<RGBA<UInt8>> {
let inputImage = Image<RGBA<UInt8>>(uiImage: image)
var outputImage = Image<RGBA<UInt8>>(uiImage: image)
// We scale the sigma value in proportion to the radius
// Setting the minimum standard deviation as a baseline
let sigma = max(Double(radius / 2), 1)
// Enforces odd width kernel which ensures a center pixel is always available
let kernelWidth = (2 * radius) + 1
// Initializing the 2D array for the kernel
var kernel = Array(repeating: Array(repeating: 0.0, count: kernelWidth), count: kernelWidth)
var sum = 0.0
// Populate every position in the kernel with the respective Gaussian distribution value
// Remember that x and y represent how far we are away from the CENTER pixel
for x in -radius...radius {
for y in -radius...radius {
let exponentNumerator = Double(-(x * x + y * y))
let exponentDenominator = (2 * sigma * sigma)
let eExpression = pow(M_E, exponentNumerator / exponentDenominator)
let kernelValue = (eExpression / (2 * Double.pi * sigma * sigma))
// We add radius to the indices to prevent out of bound issues because x and y can be negative
kernel[x + radius][y + radius] = kernelValue
sum += kernelValue
}
}
// Normalize the kernel
// This ensures that all of the values in the kernel together add up to 1
for x in 0..<kernelWidth {
for y in 0..<kernelWidth {
kernel[x][y] /= sum
}
}
// Ignoring the edges for ease of implementation
// This will cause a thin border around the image that won't be processed
for x in radius..<(inputImage.width - radius) {
for y in radius..<(inputImage.height - radius) {
var redValue = 0.0
var greenValue = 0.0
var blueValue = 0.0
// This is the convolution step
// We run the kernel over this grouping of pixels centered around the pixel at (x,y)
for kernelX in -radius...radius {
for kernelY in -radius...radius {
// Load the weight for this pixel from the convolution matrix
let kernelValue = kernel[kernelX + radius][kernelY + radius]
// Multiply each channel by the weight of the pixel as specified by the kernel
redValue += Double(inputImage[x - kernelX, y - kernelY].red) * kernelValue
greenValue += Double(inputImage[x - kernelX, y - kernelY].green) * kernelValue
blueValue += Double(inputImage[x - kernelX, y - kernelY].blue) * kernelValue
}
}
// New RGB value for output image at position (x,y)
outputImage[x,y].red = UInt8(redValue)
outputImage[x,y].green = UInt8((greenValue))
outputImage[x,y].blue = UInt8(blueValue)
}
}
return outputImage
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment