Browse Source

Fix Gaussian blur

Former-commit-id: 496e8a9a7d2e3bd994329d0514004e243ce33812
Former-commit-id: 5ab1b37f8bcd2a55d9e6a2f00fee695ced150e2f
Former-commit-id: a68f819af644bae5e87da1af62759f471e7a09ce
af/merge-core
James Jackson-South 10 years ago
parent
commit
479b5fa7e0
  1. 20
      src/ImageProcessor/Common/Helpers/ImageMaths.cs
  2. 9
      src/ImageProcessor/Filters/Convolution/Convolution2DFilter.cs
  3. 124
      src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs
  4. 43
      src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
  5. 1
      src/ImageProcessor/ImageProcessor.csproj
  6. 2
      src/ImageProcessor/project.lock.json.REMOVED.git-id
  7. 2
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  8. 8
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  9. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Jpg/china.jpg.REMOVED.git-id
  10. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Png/cballs.png.REMOVED.git-id

20
src/ImageProcessor/Common/Helpers/ImageMaths.cs

@ -18,6 +18,26 @@ namespace ImageProcessor
// ReSharper disable once InconsistentNaming
public const float PI = 3.1415926535897931f;
/// <summary>
/// Implementation of 1D Gaussian G(x) function
/// </summary>
/// <param name="x">The x provided to G(x).</param>
/// <param name="sigma">The spread of the blur.</param>
/// <returns>The Gaussian G(x)</returns>
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;
}
/// <summary>
/// Returns the result of a B-C filter against the given value.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>

9
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);
}
}
});

124
src/ImageProcessor/Filters/Convolution/Convolution2PassFilter.cs

@ -0,0 +1,124 @@
// <copyright file="Convolution2PassFilter.cs" company="James South">
// Copyright (c) James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Filters
{
using System.Threading.Tasks;
/// <summary>
/// Defines a filter that uses a matrix to perform convolution across two dimensions against an image.
/// </summary>
public abstract class Convolution2PassFilter : ParallelImageProcessor
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public abstract float[,] KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public abstract float[,] KernelY { get; }
/// <inheritdoc/>
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);
}
/// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageBase"/> at the specified location
/// and with the specified size.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <param name="startY">The index of the row within the source image to start processing.</param>
/// <param name="endY">The index of the row within the source image to end processing.</param>
/// <param name="kernel">The kernel operator.</param>
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);
}
}
});
}
}
}

43
src/ImageProcessor/Filters/Convolution/GuassianBlur.cs

@ -9,9 +9,8 @@ namespace ImageProcessor.Filters
/// <summary>
/// Applies a Gaussian blur to the image.
/// TODO: Something is not right here. The output blur is more like a motion blur.
/// </summary>
public class GuassianBlur : Convolution2DFilter
public class GuassianBlur : Convolution2PassFilter
{
/// <summary>
/// The maximum size of the kernal in either direction.
@ -19,9 +18,9 @@ namespace ImageProcessor.Filters
private readonly int kernelSize;
/// <summary>
/// The standard deviation (weight)
/// The spread of the blur.
/// </summary>
private readonly float standardDeviation;
private readonly float sigma;
/// <summary>
/// The vertical kernel
@ -36,13 +35,13 @@ namespace ImageProcessor.Filters
/// <summary>
/// Initializes a new instance of the <see cref="GuassianBlur"/> class.
/// </summary>
/// <param name="standardDeviation">
/// The standard deviation 'sigma' value for calculating Gaussian curves.
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
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;
}
/// <inheritdoc/>
@ -51,6 +50,9 @@ namespace ImageProcessor.Filters
/// <inheritdoc/>
public override float[,] KernelY => this.kernelY;
/// <inheritdoc/>
public override int Parallelism => 1;
/// <inheritdoc/>
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;
}
/// <summary>
/// Implementation of 1D Gaussian G(x) function
/// </summary>
/// <param name="x">The x provided to G(x)</param>
/// <returns>The Gaussian G(x)</returns>
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;
}
}
}

1
src/ImageProcessor/ImageProcessor.csproj

@ -50,6 +50,7 @@
<Compile Include="Common\Helpers\PixelOperations.cs" />
<Compile Include="Filters\Blend.cs" />
<Compile Include="Filters\ColorMatrix\IColorMatrixFilter.cs" />
<Compile Include="Filters\Convolution\Convolution2PassFilter.cs" />
<Compile Include="Filters\Convolution\Convolution2DFilter.cs" />
<Compile Include="Filters\ColorMatrix\ColorMatrixFilter.cs" />
<Compile Include="Filters\ColorMatrix\Kodachrome.cs" />

2
src/ImageProcessor/project.lock.json.REMOVED.git-id

@ -1 +1 @@
eb00c54ee74016c2b70f81963e7e8f83cb2dd54b
3f05708641eb3ed085d4689aae4a960eb067fd16

2
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]

8
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"

1
tests/ImageProcessor.Tests/TestImages/Formats/Jpg/china.jpg.REMOVED.git-id

@ -0,0 +1 @@
8cee90e94f9f09e04697bee0477bb5059b85c63f

1
tests/ImageProcessor.Tests/TestImages/Formats/Png/cballs.png.REMOVED.git-id

@ -0,0 +1 @@
58c3318271250961bfac9fe6e9c8b0053e1b8e28
Loading…
Cancel
Save