diff --git a/src/ImageProcessor/Common/Helpers/ImageMaths.cs b/src/ImageProcessor/Common/Helpers/ImageMaths.cs
index b2bf0dede..933421f53 100644
--- a/src/ImageProcessor/Common/Helpers/ImageMaths.cs
+++ b/src/ImageProcessor/Common/Helpers/ImageMaths.cs
@@ -18,6 +18,26 @@ namespace ImageProcessor
// ReSharper disable once InconsistentNaming
public const float PI = 3.1415926535897931f;
+ ///
+ /// Implementation of 1D Gaussian G(x) function
+ ///
+ /// The x provided to G(x).
+ /// The spread of the blur.
+ /// The Gaussian G(x)
+ public static float Gaussian(float x, float sigma)
+ {
+ const float Numerator = 1.0f;
+ float denominator = (float)(Math.Sqrt(2 * PI) * sigma);
+
+ float exponentNumerator = -x * x;
+ float exponentDenominator = (float)(2 * Math.Pow(sigma, 2));
+
+ float left = Numerator / denominator;
+ float right = (float)Math.Exp(exponentNumerator / exponentDenominator);
+
+ return left * right;
+ }
+
///
/// Returns the result of a B-C filter against the given value.
///
diff --git a/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
index c50bc4e45..b9852b804 100644
--- a/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
+++ b/src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
@@ -31,7 +31,7 @@ namespace ImageProcessor.Filters
int kernelYHeight = kernelY.GetLength(0);
int kernelYWidth = kernelY.GetLength(1);
int kernelXHeight = kernelX.GetLength(0);
- int kernelXWidth = kernelX.GetLength(0);
+ int kernelXWidth = kernelX.GetLength(1);
int radiusY = kernelYHeight >> 1;
int radiusX = kernelXWidth >> 1;
@@ -58,8 +58,8 @@ namespace ImageProcessor.Filters
float gY = 0;
float bY = 0;
- // Apply each matrix multiplier to the color components for each pixel.
- for (int fy = 0; fy < kernelYHeight; fy++)
+ // Apply each matrix multiplier to the color components for each pixel.
+ for (int fy = 0; fy < kernelYHeight; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
@@ -98,7 +98,8 @@ namespace ImageProcessor.Filters
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);
+ Color targetColor = target[x, y];
+ target[x, y] = new Color(red, green, blue, targetColor.A);
}
}
});
diff --git a/src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs b/src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs
new file mode 100644
index 000000000..2f374b1d8
--- /dev/null
+++ b/src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.Filters
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Defines a filter that uses a matrix to perform convolution across two dimensions against an image.
+ ///
+ public abstract class Convolution2PassFilter : 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;
+
+ 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 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;
+
+ // 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);
+
+ Color currentColor = source[offsetX, offsetY];
+ float r = currentColor.R;
+ float g = currentColor.G;
+ float b = currentColor.B;
+
+ rX += kernel[fy, fx] * r;
+ gX += kernel[fy, fx] * g;
+ bX += kernel[fy, fx] * b;
+ }
+ }
+
+ float red = rX;
+ float green = gX;
+ float blue = bX;
+
+ Color targetColor = target[x, y];
+ target[x, y] = new Color(red, green, blue, targetColor.A);
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
index 44df243ee..c142b62cd 100644
--- a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
+++ b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
@@ -9,9 +9,8 @@ namespace ImageProcessor.Filters
///
/// Applies a Gaussian blur to the image.
- /// TODO: Something is not right here. The output blur is more like a motion blur.
///
- public class GuassianBlur : Convolution2DFilter
+ public class GuassianBlur : Convolution2PassFilter
{
///
/// The maximum size of the kernal in either direction.
@@ -19,9 +18,9 @@ namespace ImageProcessor.Filters
private readonly int kernelSize;
///
- /// The standard deviation (weight)
+ /// The spread of the blur.
///
- private readonly float standardDeviation;
+ private readonly float sigma;
///
/// The vertical kernel
@@ -36,13 +35,13 @@ namespace ImageProcessor.Filters
///
/// Initializes a new instance of the class.
///
- ///
- /// The standard deviation 'sigma' value for calculating Gaussian curves.
+ ///
+ /// The 'sigma' value representing the weight of the blur.
///
- public GuassianBlur(float standardDeviation = 3f)
+ public GuassianBlur(float sigma = 3f)
{
- this.kernelSize = ((int)Math.Ceiling(standardDeviation) * 2) + 1;
- this.standardDeviation = standardDeviation;
+ this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
+ this.sigma = sigma;
}
///
@@ -51,6 +50,9 @@ namespace ImageProcessor.Filters
///
public override float[,] KernelY => this.kernelY;
+ ///
+ public override int Parallelism => 1;
+
///
protected override void OnApply(Rectangle targetRectangle, Rectangle sourceRectangle)
{
@@ -73,6 +75,7 @@ namespace ImageProcessor.Filters
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;
@@ -80,7 +83,7 @@ namespace ImageProcessor.Filters
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
- float gx = this.Gaussian(x);
+ float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
if (horizontal)
{
@@ -110,25 +113,5 @@ namespace ImageProcessor.Filters
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;
- }
}
}
diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj
index c56f3b26e..19f6584e8 100644
--- a/src/ImageProcessor/ImageProcessor.csproj
+++ b/src/ImageProcessor/ImageProcessor.csproj
@@ -50,6 +50,7 @@
+
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 517738f10..4db94c522 100644
--- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
@@ -38,7 +38,7 @@ namespace ImageProcessor.Tests
//{ "RobertsCross", new RobertsCross() },
//{ "Scharr", new Scharr() },
//{ "Sobel", new Sobel() },
- { "GuassianBlur", new GuassianBlur(5) }
+ { "GuassianBlur", new GuassianBlur(10) }
};
[Theory]
diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
index 45339591e..c6393156d 100644
--- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
+++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
@@ -21,18 +21,20 @@ namespace ImageProcessor.Tests
{
//"../../TestImages/Formats/Jpg/Backdrop.jpg",
//"../../TestImages/Formats/Jpg/Calliphora.jpg",
- "../../TestImages/Formats/Jpg/ant.jpg",
- "../../TestImages/Formats/Jpg/parachute.jpg",
+ "../../TestImages/Formats/Jpg/china.jpg",
+ //"../../TestImages/Formats/Jpg/ant.jpg",
+ //"../../TestImages/Formats/Jpg/parachute.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/cballs.png",
//"../../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/ben2.gif",
+ //"../../TestImages/Formats/Gif/ben2.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/china.jpg.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Jpg/china.jpg.REMOVED.git-id
new file mode 100644
index 000000000..b8a8b4908
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Jpg/china.jpg.REMOVED.git-id
@@ -0,0 +1 @@
+8cee90e94f9f09e04697bee0477bb5059b85c63f
\ No newline at end of file
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Png/cballs.png.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Png/cballs.png.REMOVED.git-id
new file mode 100644
index 000000000..55cb73356
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Png/cballs.png.REMOVED.git-id
@@ -0,0 +1 @@
+58c3318271250961bfac9fe6e9c8b0053e1b8e28
\ No newline at end of file