diff --git a/src/ImageProcessorCore/Filters/GuassianBlur.cs b/src/ImageProcessorCore/Filters/GuassianBlur.cs
new file mode 100644
index 0000000000..a57f43b2c2
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/GuassianBlur.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Applies a Guassian blur to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ public static Image GuassianBlur(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ return GuassianBlur(source, sigma, source.Bounds, progressHandler);
+ }
+
+ ///
+ /// Applies a Guassian blur to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ public static Image GuassianBlur(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ GuassianBlurProcessor processor = new GuassianBlurProcessor(sigma);
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(rectangle, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/GuassianSharpen.cs b/src/ImageProcessorCore/Filters/GuassianSharpen.cs
new file mode 100644
index 0000000000..6fb888cdfc
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/GuassianSharpen.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Applies a Guassian sharpening filter to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ public static Image GuassianSharpen(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ return GuassianSharpen(source, sigma, source.Bounds, progressHandler);
+ }
+
+ ///
+ /// Applies a Guassian sharpening filter to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ public static Image GuassianSharpen(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null)
+ where T : IPackedVector
+ where TP : struct
+ {
+ GuassianSharpenProcessor processor = new GuassianSharpenProcessor(sigma);
+ processor.OnProgress += progressHandler;
+
+ try
+ {
+ return source.Process(rectangle, processor);
+ }
+ finally
+ {
+ processor.OnProgress -= progressHandler;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
index cc8ac82e35..3638c1384f 100644
--- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs
@@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors
///
/// Defines a filter that uses two one-dimensional matrices to perform convolution against an image.
///
+ /// The pixel format.
+ /// The packed format. long, float.
public abstract class Convolution2DFilter : ImageProcessor
where T : IPackedVector
where TP : struct
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs
new file mode 100644
index 0000000000..231929bc2a
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs
@@ -0,0 +1,108 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Defines a filter that uses two one-dimensional matrices to perform two-pass convolution against an image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public abstract class Convolution2PassFilter : ImageProcessor
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// Gets the horizontal gradient operator.
+ ///
+ public abstract float[,] KernelX { get; }
+
+ ///
+ /// Gets the vertical gradient operator.
+ ///
+ public abstract float[,] KernelY { get; }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ float[,] kernelX = this.KernelX;
+ float[,] kernelY = this.KernelY;
+
+ ImageBase firstPass = new Image(source.Width, source.Height);
+ this.ApplyConvolution(firstPass, source, sourceRectangle, startY, endY, kernelX);
+ this.ApplyConvolution(target, firstPass, sourceRectangle, startY, endY, kernelY);
+ }
+
+ ///
+ /// Applies the process to the specified portion of the specified at the specified location
+ /// and with the specified size.
+ ///
+ /// Target image to apply the process to.
+ /// The source image. Cannot be null.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// The index of the row within the source image to start processing.
+ /// The index of the row within the source image to end processing.
+ /// The kernel operator.
+ private void ApplyConvolution(ImageBase target, ImageBase source, Rectangle sourceRectangle, int startY, int endY, float[,] kernel)
+ {
+ int kernelHeight = kernel.GetLength(0);
+ int kernelWidth = kernel.GetLength(1);
+ int radiusY = kernelHeight >> 1;
+ int radiusX = kernelWidth >> 1;
+
+ int sourceBottom = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int maxY = sourceBottom - 1;
+ int maxX = endX - 1;
+
+ using (IPixelAccessor sourcePixels = source.Lock())
+ using (IPixelAccessor targetPixels = target.Lock())
+ {
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ Vector4 destination = new Vector4();
+
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelHeight; fy++)
+ {
+ int fyr = fy - radiusY;
+ int offsetY = y + fyr;
+
+ offsetY = offsetY.Clamp(0, maxY);
+
+ for (int fx = 0; fx < kernelWidth; fx++)
+ {
+ int fxr = fx - radiusX;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+
+ Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
+ destination += kernel[fy, fx] * currentColor;
+ }
+ }
+
+ T packed = default(T);
+ packed.PackVector(destination);
+ targetPixels[x, y] = packed;
+ }
+
+ this.OnRowProcessed();
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs
new file mode 100644
index 0000000000..23d6d94378
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs
@@ -0,0 +1,144 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+
+ ///
+ /// Applies a Gaussian blur filter to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public class GuassianBlurProcessor : Convolution2PassFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// The maximum size of the kernal in either direction.
+ ///
+ private readonly int kernelSize;
+
+ ///
+ /// The spread of the blur.
+ ///
+ private readonly float sigma;
+
+ ///
+ /// The vertical kernel
+ ///
+ private float[,] kernelY;
+
+ ///
+ /// The horizontal kernel
+ ///
+ private float[,] kernelX;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ public GuassianBlurProcessor(float sigma = 3f)
+ {
+ this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
+ this.sigma = sigma;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ ///
+ public GuassianBlurProcessor(int radius)
+ {
+ this.kernelSize = (radius * 2) + 1;
+ this.sigma = radius;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ /// This should be at least twice the sigma value.
+ ///
+ public GuassianBlurProcessor(float sigma, int radius)
+ {
+ this.kernelSize = (radius * 2) + 1;
+ this.sigma = sigma;
+ }
+
+ ///
+ public override float[,] KernelX => this.kernelX;
+
+ ///
+ public override float[,] KernelY => this.kernelY;
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (this.kernelY == null)
+ {
+ this.kernelY = this.CreateGaussianKernel(false);
+ }
+
+ if (this.kernelX == null)
+ {
+ this.kernelX = this.CreateGaussianKernel(true);
+ }
+ }
+
+ ///
+ /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
+ ///
+ /// Whether to calculate a horizontal kernel.
+ /// The
+ private float[,] CreateGaussianKernel(bool horizontal)
+ {
+ int size = this.kernelSize;
+ float weight = this.sigma;
+ float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
+ float sum = 0.0f;
+
+ float midpoint = (size - 1) / 2f;
+ for (int i = 0; i < size; i++)
+ {
+ float x = i - midpoint;
+ float gx = ImageMaths.Gaussian(x, weight);
+ sum += gx;
+ if (horizontal)
+ {
+ kernel[0, i] = gx;
+ }
+ else
+ {
+ kernel[i, 0] = gx;
+ }
+ }
+
+ // Normalise kernel so that the sum of all weights equals 1
+ if (horizontal)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ kernel[0, i] = kernel[0, i] / sum;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ kernel[i, 0] = kernel[i, 0] / sum;
+ }
+ }
+
+ return kernel;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs
new file mode 100644
index 0000000000..edfe594513
--- /dev/null
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs
@@ -0,0 +1,182 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+
+ ///
+ /// Applies a Gaussian sharpening filter to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public class GuassianSharpenProcessor : Convolution2PassFilter
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// The maximum size of the kernal in either direction.
+ ///
+ private readonly int kernelSize;
+
+ ///
+ /// The spread of the blur.
+ ///
+ private readonly float sigma;
+
+ ///
+ /// The vertical kernel
+ ///
+ private float[,] kernelY;
+
+ ///
+ /// The horizontal kernel
+ ///
+ private float[,] kernelX;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'sigma' value representing the weight of the sharpening.
+ ///
+ public GuassianSharpenProcessor(float sigma = 3f)
+ {
+ this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
+ this.sigma = sigma;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ ///
+ public GuassianSharpenProcessor(int radius)
+ {
+ this.kernelSize = (radius * 2) + 1;
+ this.sigma = radius;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'sigma' value representing the weight of the sharpen.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ /// This should be at least twice the sigma value.
+ ///
+ public GuassianSharpenProcessor(float sigma, int radius)
+ {
+ this.kernelSize = (radius * 2) + 1;
+ this.sigma = sigma;
+ }
+
+ ///
+ public override float[,] KernelX => this.kernelX;
+
+ ///
+ public override float[,] KernelY => this.kernelY;
+
+ ///
+ protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (this.kernelY == null)
+ {
+ this.kernelY = this.CreateGaussianKernel(false);
+ }
+
+ if (this.kernelX == null)
+ {
+ this.kernelX = this.CreateGaussianKernel(true);
+ }
+ }
+
+ ///
+ /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
+ ///
+ /// Whether to calculate a horizontal kernel.
+ /// The
+ private float[,] CreateGaussianKernel(bool horizontal)
+ {
+ int size = this.kernelSize;
+ float weight = this.sigma;
+ float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
+ float sum = 0;
+
+ float midpoint = (size - 1) / 2f;
+ for (int i = 0; i < size; i++)
+ {
+ float x = i - midpoint;
+ float gx = ImageMaths.Gaussian(x, weight);
+ sum += gx;
+ if (horizontal)
+ {
+ kernel[0, i] = gx;
+ }
+ else
+ {
+ kernel[i, 0] = gx;
+ }
+ }
+
+ // Invert the kernel for sharpening.
+ int midpointRounded = (int)midpoint;
+
+ if (horizontal)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ if (i == midpointRounded)
+ {
+ // Calculate central value
+ kernel[0, i] = (2f * sum) - kernel[0, i];
+ }
+ else
+ {
+ // invert value
+ kernel[0, i] = -kernel[0, i];
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ if (i == midpointRounded)
+ {
+ // Calculate central value
+ kernel[i, 0] = (2 * sum) - kernel[i, 0];
+ }
+ else
+ {
+ // invert value
+ kernel[i, 0] = -kernel[i, 0];
+ }
+ }
+ }
+
+ // Normalise kernel so that the sum of all weights equals 1
+ if (horizontal)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ kernel[0, i] = kernel[0, i] / sum;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ kernel[i, 0] = kernel[i, 0] / sum;
+ }
+ }
+
+ return kernel;
+ }
+ }
+}
diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs
new file mode 100644
index 0000000000..667fac55c5
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using System.IO;
+
+ using Xunit;
+
+ public class GuassianBlurTest : FileTestBase
+ {
+ public static readonly TheoryData GuassianBlurValues
+ = new TheoryData
+ {
+ 3 ,
+ 5 ,
+ };
+
+ [Theory]
+ [MemberData("GuassianBlurValues")]
+ public void ImageShouldApplyGuassianBlurFilter(int value)
+ {
+ const string path = "TestOutput/GuassianBlur";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file);
+
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"{path}/{filename}"))
+ {
+ image.GuassianBlur(value)
+ .Save(output);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs
new file mode 100644
index 0000000000..47316e59e9
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using System.IO;
+
+ using Xunit;
+
+ public class GuassianSharpenTest : FileTestBase
+ {
+ public static readonly TheoryData GuassianSharpenValues
+ = new TheoryData
+ {
+ 3 ,
+ 5 ,
+ };
+
+ [Theory]
+ [MemberData("GuassianSharpenValues")]
+ public void ImageShouldApplyGuassianSharpenFilter(int value)
+ {
+ const string path = "TestOutput/GuassianSharpen";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file);
+
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"{path}/{filename}"))
+ {
+ image.GuassianSharpen(value)
+ .Save(output);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file