diff --git a/src/ImageProcessor/Filters/Convolution/Convolution2DFilter - Copy.cs b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter - Copy.cs
new file mode 100644
index 000000000..d67b86462
--- /dev/null
+++ b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter - Copy.cs
@@ -0,0 +1,99 @@
+//
+// Copyright (c) James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.Filters
+{
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// Defines a filter that uses a matrix to perform convolution across two dimensions against an image.
+ ///
+ public abstract class Convolution2DFilter : ParallelImageProcessor
+ {
+ ///
+ /// 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;
+ int kernelYLength = kernelY.GetLength(0);
+ int kernelXLength = kernelX.GetLength(0);
+ int radiusY = kernelYLength >> 1;
+ int radiusX = kernelXLength >> 1;
+
+ int sourceY = sourceRectangle.Y;
+ int sourceBottom = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int maxY = sourceBottom - 1;
+ int maxX = endX - 1;
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= sourceY && y < sourceBottom)
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ float rX = 0;
+ float gX = 0;
+ float bX = 0;
+ float rY = 0;
+ float gY = 0;
+ float bY = 0;
+
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelYLength; fy++)
+ {
+ int fyr = fy - radiusY;
+ int offsetY = y + fyr;
+
+ offsetY = offsetY.Clamp(0, maxY);
+
+ for (int fx = 0; fx < kernelXLength; fx++)
+ {
+ int fxr = fx - radiusX;
+ int offsetX = x + fxr;
+
+ offsetX = offsetX.Clamp(0, maxX);
+
+ Color currentColor = source[offsetX, offsetY];
+ float r = currentColor.R;
+ float g = currentColor.G;
+ float b = currentColor.B;
+
+ rX += kernelX[fx, fy] * r;
+ gX += kernelX[fx, fy] * g;
+ bX += kernelX[fx, fy] * b;
+
+ rY += kernelY[fy, fx] * r;
+ gY += kernelY[fy, fx] * g;
+ bY += kernelY[fy, fx] * b;
+ }
+ }
+
+ float red = (float)Math.Sqrt((rX * rX) + (rY * rY));
+ float green = (float)Math.Sqrt((gX * gX) + (gY * gY));
+ float blue = (float)Math.Sqrt((bX * bX) + (bY * bY));
+
+ target[x, y] = new Color(red, green, blue);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
index 37fa90173..d760221e3 100644
--- a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
+++ b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
@@ -1,14 +1,16 @@
namespace ImageProcessor.Filters.Convolution
{
using System;
- using System.Runtime.CompilerServices;
- public class GuassianBlur : ConvolutionFilter
+ public class GuassianBlur : Convolution2DFilter
{
private int kernelSize;
private float standardDeviation;
+ private float[,] kernelY;
+ private float[,] kernelX;
+
///
/// Initializes a new instance of the class.
///
@@ -18,81 +20,132 @@
///
/// The standard deviation 'sigma' value for calculating Gaussian curves.
///
- public GuassianBlur(int size, float standardDeviation)
+ public GuassianBlur(int size, float standardDeviation = 1.4f)
{
this.kernelSize = size;
this.standardDeviation = standardDeviation;
}
- public override float[,] KernelX { get; }
+ ///
+ public override float[,] KernelX => this.kernelX;
+
+ ///
+ public override float[,] KernelY => this.kernelY;
+
+ ///
+ protected override void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ if (this.kernelY == null)
+ {
+ this.kernelY = this.CreateGaussianKernel(false);
+ }
+
+ if (this.kernelX == null)
+ {
+ this.kernelX = this.CreateGaussianKernel(true);
+ }
+ }
///
- /// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
- /// blurring images.
+ /// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
///
- /// Kernel Size
- /// A Gaussian Kernel with the given size.
- private float[,] CreateGuassianBlurFilter()
+ private void CreateGaussianKernel2D()
{
- // Create kernel
int size = this.kernelSize;
- float[,] kernel = this.CreateGaussianKernel2D(size);
- float min = kernel[0, 0];
-
- // Convert to integer blurring kernel. First of all the integer kernel is calculated from Kernel2D
- // by dividing all elements by the element with the smallest value.
- float[,] intKernel = new float[size, size];
- int divider = 0;
+ float[,] kernel = new float[size, size];
+ int midpoint = size / 2;
+ float sum = 0;
for (int i = 0; i < size; i++)
{
+ int x = i - midpoint;
+
for (int j = 0; j < size; j++)
{
- float v = kernel[i, j] / min;
-
- if (v > ushort.MaxValue)
- {
- v = ushort.MaxValue;
- }
-
- intKernel[i, j] = (int)v;
-
- // Collect the divider
- divider += (int)intKernel[i, j];
+ int y = j - midpoint;
+ float gxy = this.Gaussian2D(x, y);
+ sum += gxy;
+ kernel[i, j] = gxy;
}
}
- // Update filter
- //this.Divider = divider;
- return intKernel;
+ // Normalise kernel so that the sum of all weights equals 1
+ //for (int i = 0; i < size; i++)
+ //{
+ // for (int j = 0; j < size; j++)
+ // {
+ // kernel[i, 0] = kernel[i, j] / sum;
+ // }
+ //}
+
+ this.kernelY = kernel;
}
///
- /// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
+ /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
///
- /// Kernel Size
- /// A Gaussian Kernel with the given size and deviation.
- public float[,] CreateGaussianKernel2D(int kernelSize)
+ /// The
+ private float[,] CreateGaussianKernel(bool horizontal)
{
- float[,] kernel = new float[kernelSize, kernelSize];
-
- int midpoint = kernelSize / 2;
+ int size = this.kernelSize;
+ float[,] kernel = horizontal ? new float[1, size] : new float[size, 1];
+ float sum = 0.0f;
- for (int i = 0; i < kernelSize; i++)
+ int midpoint = size / 2;
+ for (int i = 0; i < size; i++)
{
int x = i - midpoint;
+ float gx = this.Gaussian(x);
+ sum += gx;
+ if (horizontal)
+ {
+ kernel[0, i] = gx;
+ }
+ else
+ {
+ kernel[i, 0] = gx;
+ }
+ }
- for (int j = 0; j < kernelSize; j++)
+ // Normalise kernel so that the sum of all weights equals 1
+ if (horizontal)
+ {
+ for (int i = 0; i < size; i++)
{
- int y = j - midpoint;
- float gxy = this.Gaussian2D(x, y);
- kernel[i, j] = gxy;
+ kernel[0, i] = kernel[0, i] / sum;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < size; i++)
+ {
+ kernel[i, 0] = kernel[i, 0] / sum;
}
}
return kernel;
}
+ ///
+ /// Implementation of 1D Gaussian G(x) function
+ ///
+ /// The x provided to G(x)
+ /// The Gaussian G(x)
+ private float Gaussian(float x)
+ {
+ const float Numerator = 1.0f;
+ float deviation = this.standardDeviation;
+ float denominator = (float)(Math.Sqrt(2 * Math.PI) * deviation);
+
+ float exponentNumerator = -x * x;
+ float exponentDenominator = (float)(2 * Math.Pow(deviation, 2));
+
+ float left = Numerator / denominator;
+ float right = (float)Math.Exp(exponentNumerator / exponentDenominator);
+
+ return left * right;
+ }
+
///
/// Implementation of 2D Gaussian G(x) function
///
@@ -102,10 +155,12 @@
private float Gaussian2D(float x, float y)
{
const float Numerator = 1.0f;
- float denominator = (float)((2 * Math.PI) * Math.Pow(this.standardDeviation, 2));
+ float deviation = this.standardDeviation;
+ double pow = Math.Pow(deviation, 2);
+ float denominator = (float)((2 * Math.PI) * pow);
float exponentNumerator = (-x * x) + (-y * y);
- float exponentDenominator = (float)(2 * Math.Pow(this.standardDeviation, 2));
+ float exponentDenominator = (float)(2 * pow);
float left = Numerator / denominator;
float right = (float)Math.Exp(exponentNumerator / exponentDenominator);
diff --git a/src/ImageProcessor/project.lock.json.REMOVED.git-id b/src/ImageProcessor/project.lock.json.REMOVED.git-id
index dba2656f5..24339fed2 100644
--- a/src/ImageProcessor/project.lock.json.REMOVED.git-id
+++ b/src/ImageProcessor/project.lock.json.REMOVED.git-id
@@ -1 +1 @@
-eb00c54ee74016c2b70f81963e7e8f83cb2dd54b
\ No newline at end of file
+3f05708641eb3ed085d4689aae4a960eb067fd16
\ No newline at end of file
diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
index c5fb051d7..204210249 100644
--- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
@@ -3,8 +3,10 @@ namespace ImageProcessor.Tests
{
using System.Diagnostics;
using System.IO;
+ using System.Numerics;
using ImageProcessor.Filters;
+ using ImageProcessor.Filters.Convolution;
using Xunit;
@@ -12,31 +14,32 @@ namespace ImageProcessor.Tests
{
public static readonly TheoryData Filters = new TheoryData
{
- { "Brightness-50", new Brightness(50) },
- { "Brightness--50", new Brightness(-50) },
- { "Contrast-50", new Contrast(50) },
- { "Contrast--50", new Contrast(-50) },
- { "Blend", new Blend(new Image(File.OpenRead("../../TestImages/Formats/Bmp/Car.bmp")),15)},
- { "Saturation-50", new Saturation(50) },
- { "Saturation--50", new Saturation(-50) },
- { "Alpha--50", new Alpha(50) },
- { "Invert", new Invert() },
- { "Sepia", new Sepia() },
- { "BlackWhite", new BlackWhite() },
- { "Lomograph", new Lomograph() },
- { "Polaroid", new Polaroid() },
- { "Kodachrome", new Kodachrome() },
- { "GreyscaleBt709", new GreyscaleBt709() },
- { "GreyscaleBt601", new GreyscaleBt601() },
- { "Kayyali", new Kayyali() },
- { "Kirsch", new Kirsch() },
- { "Laplacian3X3", new Laplacian3X3() },
- { "Laplacian5X5", new Laplacian5X5() },
- { "LaplacianOfGaussian", new LaplacianOfGaussian() },
- { "Prewitt", new Prewitt() },
- { "RobertsCross", new RobertsCross() },
- { "Scharr", new Scharr() },
- { "Sobel", new Sobel() }
+ //{ "Brightness-50", new Brightness(50) },
+ //{ "Brightness--50", new Brightness(-50) },
+ //{ "Contrast-50", new Contrast(50) },
+ //{ "Contrast--50", new Contrast(-50) },
+ //{ "Blend", new Blend(new Image(File.OpenRead("../../TestImages/Formats/Bmp/Car.bmp")),15)},
+ //{ "Saturation-50", new Saturation(50) },
+ //{ "Saturation--50", new Saturation(-50) },
+ //{ "Alpha--50", new Alpha(50) },
+ //{ "Invert", new Invert() },
+ //{ "Sepia", new Sepia() },
+ //{ "BlackWhite", new BlackWhite() },
+ //{ "Lomograph", new Lomograph() },
+ //{ "Polaroid", new Polaroid() },
+ //{ "Kodachrome", new Kodachrome() },
+ //{ "GreyscaleBt709", new GreyscaleBt709() },
+ //{ "GreyscaleBt601", new GreyscaleBt601() },
+ //{ "Kayyali", new Kayyali() },
+ //{ "Kirsch", new Kirsch() },
+ //{ "Laplacian3X3", new Laplacian3X3() },
+ //{ "Laplacian5X5", new Laplacian5X5() },
+ //{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
+ //{ "Prewitt", new Prewitt() },
+ //{ "RobertsCross", new RobertsCross() },
+ //{ "Scharr", new Scharr() },
+ //{ "Sobel", new Sobel() },
+ { "GuassianBlur", new GuassianBlur(20) }
};
[Theory]
diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
index 0e33d5438..7ba500e5e 100644
--- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
+++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
@@ -19,20 +19,21 @@ namespace ImageProcessor.Tests
///
public static readonly List Files = new List
{
- "../../TestImages/Formats/Jpg/Backdrop.jpg",
- "../../TestImages/Formats/Jpg/Calliphora.jpg",
- "../../TestImages/Formats/Jpg/lomo.jpg",
- "../../TestImages/Formats/Jpg/shaftesbury.jpg",
- "../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
- "../../TestImages/Formats/Jpg/greyscale.jpg",
- "../../TestImages/Formats/Bmp/Car.bmp",
- "../../TestImages/Formats/Png/cmyk.png",
- "../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
- "../../TestImages/Formats/Png/splash.png",
- "../../TestImages/Formats/Gif/leaf.gif",
- "../../TestImages/Formats/Gif/rings.gif",
- "../../TestImages/Formats/Gif/ani2.gif",
- "../../TestImages/Formats/Gif/giphy.gif"
+ //"../../TestImages/Formats/Jpg/Backdrop.jpg",
+ //"../../TestImages/Formats/Jpg/Calliphora.jpg",
+ "../../TestImages/Formats/Jpg/ant.jpg",
+ //"../../TestImages/Formats/Jpg/lomo.jpg",
+ //"../../TestImages/Formats/Jpg/shaftesbury.jpg",
+ //"../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
+ //"../../TestImages/Formats/Jpg/greyscale.jpg",
+ //"../../TestImages/Formats/Bmp/Car.bmp",
+ //"../../TestImages/Formats/Png/cmyk.png",
+ //"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
+ //"../../TestImages/Formats/Png/splash.png",
+ //"../../TestImages/Formats/Gif/leaf.gif",
+ //"../../TestImages/Formats/Gif/rings.gif",
+ //"../../TestImages/Formats/Gif/ani2.gif",
+ //"../../TestImages/Formats/Gif/giphy.gif"
};
}
}
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Jpg/ant.jpg.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Jpg/ant.jpg.REMOVED.git-id
new file mode 100644
index 000000000..1485b2316
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Jpg/ant.jpg.REMOVED.git-id
@@ -0,0 +1 @@
+1da0ed59ed220f32d72b01e6cbb9c73a7f3b9b35
\ No newline at end of file