Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use correct Kernel dimensions and optimize. Fix #786 #789

Merged
merged 2 commits into from
Dec 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions src/ImageSharp/Primitives/DenseMatrix{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public DenseMatrix(T[,] data)
/// <returns>The <see typeparam="T"/> at the specified position.</returns>
public ref T this[int row, int column]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
get
{
this.CheckCoordinates(row, column);
Expand All @@ -125,7 +125,7 @@ public DenseMatrix(T[,] data)
/// <returns>
/// The <see cref="DenseMatrix{T}"/> representation on the source data.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public static implicit operator DenseMatrix<T>(T[,] data) => new DenseMatrix<T>(data);

/// <summary>
Expand All @@ -135,9 +135,9 @@ public DenseMatrix(T[,] data)
/// <returns>
/// The <see cref="T:T[,]"/> representation on the source data.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
#pragma warning disable SA1008 // Opening parenthesis should be spaced correctly
public static implicit operator T[,] (DenseMatrix<T> data)
public static implicit operator T[,] (in DenseMatrix<T> data)
#pragma warning restore SA1008 // Opening parenthesis should be spaced correctly
{
var result = new T[data.Rows, data.Columns];
Expand All @@ -154,17 +154,38 @@ public DenseMatrix(T[,] data)
return result;
}

/// <summary>
/// Transposes the rows and columns of the dense matrix.
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public DenseMatrix<T> Transpose()
{
var result = new DenseMatrix<T>(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;
}

/// <summary>
/// Fills the matrix with the given value
/// </summary>
/// <param name="value">The value to fill each item with</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void Fill(T value) => this.Span.Fill(value);

/// <summary>
/// Clears the matrix setting each value to the default value for the element type
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear() => this.Span.Clear();

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public BoxBlurProcessor(int radius = 7)
{
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();
}

/// <summary>
Expand All @@ -49,24 +49,18 @@ public BoxBlurProcessor(int radius = 7)
public DenseMatrix<float> KernelY { get; }

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
}
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);

/// <summary>
/// Create a 1 dimensional Box kernel.
/// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="DenseMatrix{T}"/></returns>
private DenseMatrix<float> CreateBoxKernel(bool horizontal)
private DenseMatrix<float> CreateBoxKernel()
{
int size = this.kernelSize;
DenseMatrix<float> kernel = horizontal
? new DenseMatrix<float>(size, 1)
: new DenseMatrix<float>(1, size);
var kernel = new DenseMatrix<float>(size, 1);

kernel.Fill(1.0F / size);
kernel.Fill(1F / size);

return kernel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,11 +25,10 @@ internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
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/
}

/// <summary>
Expand All @@ -40,11 +38,8 @@ public GaussianBlurProcessor(float sigma = 3F)
/// The 'radius' value representing the size of the area to sample.
/// </param>
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);
}

/// <summary>
Expand All @@ -61,8 +56,8 @@ public GaussianBlurProcessor(float sigma, int radius)
{
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();
}

/// <summary>
Expand All @@ -82,22 +77,17 @@ public GaussianBlurProcessor(float sigma, int radius)

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
}
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);

/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="DenseMatrix{T}"/></returns>
private DenseMatrix<float> CreateGaussianKernel(bool horizontal)
private DenseMatrix<float> CreateGaussianKernel()
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
{
int size = this.kernelSize;
float weight = this.Sigma;
DenseMatrix<float> kernel = horizontal
? new DenseMatrix<float>(size, 1)
: new DenseMatrix<float>(1, size);
var kernel = new DenseMatrix<float>(size, 1);

float sum = 0F;
float midpoint = (size - 1) / 2F;
Expand All @@ -107,30 +97,13 @@ private DenseMatrix<float> CreateGaussianKernel(bool horizontal)
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
/// The 'sigma' value representing the weight of the sharpening.
/// </param>
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/
}

/// <summary>
Expand All @@ -41,11 +40,8 @@ public GaussianSharpenProcessor(float sigma = 3F)
/// The 'radius' value representing the size of the area to sample.
/// </param>
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);
}

/// <summary>
Expand All @@ -62,8 +58,8 @@ public GaussianSharpenProcessor(float sigma, int radius)
{
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();
}

/// <summary>
Expand All @@ -83,91 +79,49 @@ public GaussianSharpenProcessor(float sigma, int radius)

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
}
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);

/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <param name="horizontal">Whether to calculate a horizontal kernel.</param>
/// <returns>The <see cref="DenseMatrix{T}"/></returns>
private DenseMatrix<float> CreateGaussianKernel(bool horizontal)
private DenseMatrix<float> CreateGaussianKernel()
{
int size = this.kernelSize;
float weight = this.Sigma;
DenseMatrix<float> kernel = horizontal
? new DenseMatrix<float>(size, 1)
: new DenseMatrix<float>(1, size);
var kernel = new DenseMatrix<float>(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;
Expand Down
24 changes: 24 additions & 0 deletions tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,29 @@ public void DenseMatrixCanFillAndClear()
Assert.Equal(0, dense.Data[i]);
}
}

[Fact]
public void DenseMatrixCorrectlyCasts()
{
float[,] actual = new DenseMatrix<float>(FloydSteinbergMatrix);
Assert.Equal(FloydSteinbergMatrix, actual);
}

[Fact]
public void DenseMatrixCanTranspose()
{
var dense = new DenseMatrix<int>(3, 1);
dense[0, 0] = 1;
dense[0, 1] = 2;
dense[0, 2] = 3;

DenseMatrix<int> 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]);
}
}
}