Created
September 13, 2020 06:26
-
-
Save aryamansharda/019e6ad47fa5b1481692917e62cd9e4a to your computer and use it in GitHub Desktop.
GaussianBlur
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
// | |
// 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