diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
index 7cfa98ec1..170292e29 100644
--- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs
+++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
@@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Primitives
/// The at the specified position.
public ref T this[int row, int column]
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
get
{
this.CheckCoordinates(row, column);
@@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Primitives
///
/// The representation on the source data.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data);
///
@@ -135,9 +135,9 @@ namespace SixLabors.ImageSharp.Primitives
///
/// The representation on the source data.
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
#pragma warning disable SA1008 // Opening parenthesis should be spaced correctly
- public static implicit operator T[,] (DenseMatrix data)
+ public static implicit operator T[,] (in DenseMatrix data)
#pragma warning restore SA1008 // Opening parenthesis should be spaced correctly
{
var result = new T[data.Rows, data.Columns];
@@ -154,17 +154,38 @@ namespace SixLabors.ImageSharp.Primitives
return result;
}
+ ///
+ /// Transposes the rows and columns of the dense matrix.
+ ///
+ /// The .
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public DenseMatrix Transpose()
+ {
+ var result = new DenseMatrix(this.Rows, this.Columns);
+
+ for (int y = 0; y < this.Rows; y++)
+ {
+ for (int x = 0; x < this.Columns; x++)
+ {
+ ref T value = ref result[x, y];
+ value = this[y, x];
+ }
+ }
+
+ return result;
+ }
+
///
/// Fills the matrix with the given value
///
/// The value to fill each item with
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public void Fill(T value) => this.Span.Fill(value);
///
/// Clears the matrix setting each value to the default value for the element type
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public void Clear() => this.Span.Clear();
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
index 38dc638b9..644d6c9e1 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
@@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
this.Radius = radius;
this.kernelSize = (radius * 2) + 1;
- this.KernelX = this.CreateBoxKernel(true);
- this.KernelY = this.CreateBoxKernel(false);
+ this.KernelX = this.CreateBoxKernel();
+ this.KernelY = this.KernelX.Transpose();
}
///
@@ -49,24 +49,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public DenseMatrix KernelY { get; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
- }
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Box kernel.
///
- /// Whether to calculate a horizontal kernel.
/// The
- private DenseMatrix CreateBoxKernel(bool horizontal)
+ private DenseMatrix CreateBoxKernel()
{
int size = this.kernelSize;
- DenseMatrix kernel = horizontal
- ? new DenseMatrix(size, 1)
- : new DenseMatrix(1, size);
+ var kernel = new DenseMatrix(size, 1);
- kernel.Fill(1.0F / size);
+ kernel.Fill(1F / size);
return kernel;
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
index 3045b9993..b3bc15d39 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
@@ -4,7 +4,6 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
-using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@@ -26,11 +25,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
/// The 'sigma' value representing the weight of the blur.
public GaussianBlurProcessor(float sigma = 3F)
+ : this(sigma, (int)MathF.Ceiling(sigma * 3))
{
- this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
- this.Sigma = sigma;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
+ // Kernel radius is calculated using the minimum viable value.
+ // http://chemaguerra.com/gaussian-filter-radius/
}
///
@@ -40,11 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The 'radius' value representing the size of the area to sample.
///
public GaussianBlurProcessor(int radius)
+ : this(radius / 3F, radius)
{
- this.kernelSize = (radius * 2) + 1;
- this.Sigma = radius;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
}
///
@@ -61,8 +56,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
this.kernelSize = (radius * 2) + 1;
this.Sigma = sigma;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
+ this.KernelX = this.CreateGaussianKernel();
+ this.KernelY = this.KernelX.Transpose();
}
///
@@ -82,22 +77,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
- }
+ => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
///
- /// Whether to calculate a horizontal kernel.
/// The
- private DenseMatrix CreateGaussianKernel(bool horizontal)
+ private DenseMatrix CreateGaussianKernel()
{
int size = this.kernelSize;
float weight = this.Sigma;
- DenseMatrix kernel = horizontal
- ? new DenseMatrix(size, 1)
- : new DenseMatrix(1, size);
+ var kernel = new DenseMatrix(size, 1);
float sum = 0F;
float midpoint = (size - 1) / 2F;
@@ -107,30 +97,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
- if (horizontal)
- {
- kernel[0, i] = gx;
- }
- else
- {
- kernel[i, 0] = gx;
- }
+ kernel[0, i] = gx;
}
// Normalize 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++)
{
- for (int i = 0; i < size; i++)
- {
- kernel[i, 0] = kernel[i, 0] / sum;
- }
+ kernel[0, i] /= sum;
}
return kernel;
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
index 18963c73c..786bf7757 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
@@ -27,11 +27,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The 'sigma' value representing the weight of the sharpening.
///
public GaussianSharpenProcessor(float sigma = 3F)
+ : this(sigma, (int)MathF.Ceiling(sigma * 3))
{
- this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1;
- this.Sigma = sigma;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
+ // Kernel radius is calculated using the minimum viable value.
+ // http://chemaguerra.com/gaussian-filter-radius/
}
///
@@ -41,11 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The 'radius' value representing the size of the area to sample.
///
public GaussianSharpenProcessor(int radius)
+ : this(radius / 3F, radius)
{
- this.kernelSize = (radius * 2) + 1;
- this.Sigma = radius;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
}
///
@@ -62,8 +58,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
this.kernelSize = (radius * 2) + 1;
this.Sigma = sigma;
- this.KernelX = this.CreateGaussianKernel(true);
- this.KernelY = this.CreateGaussianKernel(false);
+ this.KernelX = this.CreateGaussianKernel();
+ this.KernelY = this.KernelX.Transpose();
}
///
@@ -83,91 +79,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
- }
+ => new Convolution2PassProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
///
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
///
- /// Whether to calculate a horizontal kernel.
/// The
- private DenseMatrix CreateGaussianKernel(bool horizontal)
+ private DenseMatrix CreateGaussianKernel()
{
int size = this.kernelSize;
float weight = this.Sigma;
- DenseMatrix kernel = horizontal
- ? new DenseMatrix(size, 1)
- : new DenseMatrix(1, size);
+ var kernel = new DenseMatrix(size, 1);
float sum = 0;
- float midpoint = (size - 1) / 2f;
+ 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;
- }
+ kernel[0, i] = gx;
}
// Invert the kernel for sharpening.
int midpointRounded = (int)midpoint;
-
- if (horizontal)
+ for (int i = 0; i < size; i++)
{
- for (int i = 0; i < size; i++)
+ if (i == midpointRounded)
{
- if (i == midpointRounded)
- {
- // Calculate central value
- kernel[0, i] = (2F * sum) - kernel[0, i];
- }
- else
- {
- // invert value
- kernel[0, i] = -kernel[0, i];
- }
+ // Calculate central value
+ kernel[0, i] = (2F * sum) - kernel[0, i];
}
- }
- else
- {
- for (int i = 0; i < size; i++)
+ else
{
- if (i == midpointRounded)
- {
- // Calculate central value
- kernel[i, 0] = (2 * sum) - kernel[i, 0];
- }
- else
- {
- // invert value
- kernel[i, 0] = -kernel[i, 0];
- }
+ // invert value
+ kernel[0, i] = -kernel[0, i];
}
}
// Normalize kernel so that the sum of all weights equals 1
- if (horizontal)
- {
- for (int i = 0; i < size; i++)
- {
- kernel[0, i] /= sum;
- }
- }
- else
+ for (int i = 0; i < size; i++)
{
- for (int i = 0; i < size; i++)
- {
- kernel[i, 0] /= sum;
- }
+ kernel[0, i] /= sum;
}
return kernel;
diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs
index fa4862293..0af8ae45f 100644
--- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs
+++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs
@@ -106,5 +106,29 @@ namespace SixLabors.ImageSharp.Tests.Primitives
Assert.Equal(0, dense.Data[i]);
}
}
+
+ [Fact]
+ public void DenseMatrixCorrectlyCasts()
+ {
+ float[,] actual = new DenseMatrix(FloydSteinbergMatrix);
+ Assert.Equal(FloydSteinbergMatrix, actual);
+ }
+
+ [Fact]
+ public void DenseMatrixCanTranspose()
+ {
+ var dense = new DenseMatrix(3, 1);
+ dense[0, 0] = 1;
+ dense[0, 1] = 2;
+ dense[0, 2] = 3;
+
+ DenseMatrix transposed = dense.Transpose();
+
+ Assert.Equal(dense.Columns, transposed.Rows);
+ Assert.Equal(dense.Rows, transposed.Columns);
+ Assert.Equal(1, transposed[0, 0]);
+ Assert.Equal(2, transposed[1, 0]);
+ Assert.Equal(3, transposed[2, 0]);
+ }
}
}
\ No newline at end of file