I wanted to implement a Gaussian Blur algorithm in Xojo. As an example I use this Swift code:
// 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
}
My Xojo port:
Function GBlur(Radius as Double, InputPicture as Picture) As Picture
Const PI = 3.141592
Const E = 2.71828
Var InSurf As RGBSurface = InputPicture.RGBSurface
Var OutputPicture As Picture
OutputPicture = New Picture(InputPicture.Width, InputPicture.Height,32)
Var OutSurf As RGBSurface = OutputPicture.RGBSurface
Var Sigma As Double = Max(Radius / 2, 1)
Var KernelWidth As Integer = ( 2 * Radius) + 1
Var Kernel(-1,-1) As Double
Kernel.ResizeTo(KernelWidth,KernelWidth)
Var Sum As Double = 0.0
For X As Integer = -Radius To Radius
For Y As Integer = -Radius To Radius
Var ExponentNumerator As Double = -(x * x + y * y)
Var ExponentDenominator As Double = (2 * Sigma * Sigma)
Var eExpression As Double = Pow(E, exponentNumerator / exponentDenominator)
Var KernelValue As Double = (eExpression / (2 * PI * sigma * sigma))
Kernel(X + Radius, Y + Radius) = KernelValue
Sum = Sum + KernelValue
Next
Next
For X As Integer = 0 To KernelWidth
For Y As Integer = 0 To KernelWidth
Kernel(X,Y) = Kernel(X,Y) / Sum
Next
Next
For KernelX As Integer = Radius To InputPicture.Width - Radius
For KernelY As Integer = Radius To InputPicture.Height - Radius
Var RedValue As Double = 0.0
Var GreenValue As Double = 0.0
Var BlueValue As Double = 0.0
Var KernelValue As Double = Kernel(KernelX + radius, KernelY + radius)
RedValue = RedValue + InSurf.Pixel(KernelX, KernelY).Red * KernelValue
GreenValue = GreenValue + InSurf.Pixel(KernelX, KernelY).Green * KernelValue
BlueValue = BlueValue + InSurf.Pixel(KernelX, KernelY).Blue * KernelValue
OutSurf.Pixel(KernelX, KernelY) = RGB(RedValue,GreenValue,BlueValue)
Next
Next
Return OutputPicture
End Function
This code looks at a specific pixel (center pixel) and calculates the color values of the output pixel by examining the pixels around the center pixel. This is the Radius parameter. A Radius of 5 means that X±5 to X+5 and Y-5 to Y+5 gets examined (this is the ‘-Radius to Radius’ statement). The multidim Kernel array keeps track of this. I know that some vars should be integer instead of double but I will fix this later.
My Xojo code compiles… but I get an ‘out of bounds’ error while iterating over the kernel array in the last for-next loop. I can’t find where it goes wrong. I know this is a bit complex but anybody who can help me out?