Browse Source

Cleanup General Convolution (#887)

* Remove multiple premultiplication.

* Use in DenseMatrix everywhere.

* Make private

* Dont convert vector row on first pass

* Remove incorrectly assigned alpha.

* Remove boxing.

* Use correct min row.

* Reorder parameters

* Correctly handle alpha component.

* Update tests

* Use dedicated methods over branching.
af/merge-core
James Jackson-South 7 years ago
committed by GitHub
parent
commit
e59e5315a8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
  2. 240
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  3. 3
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
  4. 48
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  5. 53
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  6. 50
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  7. 5
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs
  8. 20
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs
  9. 6
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs
  10. 2
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  11. 2
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  12. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs
  13. 19
      tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs
  14. 2
      tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs
  15. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs
  16. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

9
src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs

@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
internal PatternBrush(TPixel foreColor, TPixel backColor, DenseMatrix<bool> pattern)
internal PatternBrush(TPixel foreColor, TPixel backColor, in DenseMatrix<bool> pattern)
{
var foreColorVector = foreColor.ToVector4();
var backColorVector = backColor.ToVector4();
@ -93,10 +93,7 @@ namespace SixLabors.ImageSharp.Processing
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
{
return new PatternBrushApplicator(source, this.pattern, this.patternVector, options);
}
public BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options) => new PatternBrushApplicator(source, this.pattern, this.patternVector, options);
/// <summary>
/// The pattern brush applicator.
@ -116,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param>
/// <param name="options">The options</param>
public PatternBrushApplicator(ImageFrame<TPixel> source, DenseMatrix<TPixel> pattern, DenseMatrix<Vector4> patternVector, GraphicsOptions options)
public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, DenseMatrix<Vector4> patternVector, GraphicsOptions options)
: base(source, options)
{
this.pattern = pattern;

240
src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs

@ -3,6 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
@ -11,83 +12,120 @@ namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for <see cref="DenseMatrix{T}"/>.
/// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement.
/// </summary>
internal static class DenseMatrixUtils
{
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// Computes the sum of vectors in the span referenced by <paramref name="targetRowRef"/> weighted by the two kernel weight values.
/// Using this method the convolution filter is not applied to alpha in addition to the color channels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="matrixY">The vertical dense matrix.</param>
/// <param name="matrixX">The horizontal dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve<TPixel>(
in DenseMatrix<float> matrix,
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve2D3<TPixel>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int maxColumn,
int offsetColumn)
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + offsetColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(0, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
}
}
Convolve2DImpl(
in matrixY,
in matrixX,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref targetRow[column];
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the two kernel weight values.
/// Computes the sum of vectors in the span referenced by <paramref name="targetRowRef"/> weighted by the two kernel weight values.
/// Using this method the convolution filter is applied to alpha in addition to the color channels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrixY">The vertical dense matrix.</param>
/// <param name="matrixX">The horizontal dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve2D<TPixel>(
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve2D4<TPixel>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
Convolve2DImpl(
in matrixY,
in matrixX,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve2DImpl<TPixel>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
Buffer2D<TPixel> sourcePixels,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn,
int offsetColumn)
ref Vector4 vector)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vectorY = default;
@ -96,16 +134,16 @@ namespace SixLabors.ImageSharp
int matrixWidth = matrixY.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + offsetColumn;
int sourceOffsetColumnBase = column + minColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(0, maxRow);
int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn);
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
@ -114,11 +152,133 @@ namespace SixLabors.ImageSharp
}
}
var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
ref Vector4 target = ref targetRow[column];
vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
}
/// <summary>
/// Computes the sum of vectors in the span referenced by <paramref name="targetRowRef"/> weighted by the kernel weight values.
/// Using this method the convolution filter is not applied to alpha in addition to the color channels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve3<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
vector.W = target.W;
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
/// <summary>
/// Computes the sum of vectors in the span referenced by <paramref name="targetRowRef"/> weighted by the kernel weight values.
/// Using this method the convolution filter is applied to alpha in addition to the color channels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve4<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void ConvolveImpl<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn,
ref Vector4 vector)
where TPixel : struct, IPixel<TPixel>
{
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + minColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = (row + y - radiusY).Clamp(minRow, maxRow);
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn);
var currentColor = sourceRowSpan[offsetX].ToVector4();
Vector4Utils.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
}
}
}
}
}

3
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs

@ -49,7 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
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, false).Apply(source, sourceRectangle, configuration);
/// <summary>
/// Create a 1 dimensional Box kernel.

48
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs

@ -3,6 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@ -23,11 +24,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
public Convolution2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY)
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public Convolution2DProcessor(in DenseMatrix<float> kernelX, in DenseMatrix<float> kernelY, bool preserveAlpha)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
this.KernelY = kernelY;
this.PreserveAlpha = preserveAlpha;
}
/// <summary>
@ -40,6 +43,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
/// </summary>
public bool PreserveAlpha { get; }
/// <inheritdoc/>
protected override void OnFrameApply(
ImageFrame<TPixel> source,
@ -48,6 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
DenseMatrix<float> matrixY = this.KernelY;
DenseMatrix<float> matrixX = this.KernelX;
bool preserveAlpha = this.PreserveAlpha;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
@ -71,18 +80,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
for (int x = 0; x < width; x++)
if (preserveAlpha)
{
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve2D3(
in matrixY,
in matrixX,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
else
{
DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve2D4(
in matrixY,
in matrixX,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});

53
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -3,7 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@ -24,10 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
public Convolution2PassProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY)
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public Convolution2PassProcessor(
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
bool preserveAlpha)
{
this.KernelX = kernelX;
this.KernelY = kernelY;
this.PreserveAlpha = preserveAlpha;
}
/// <summary>
@ -40,13 +45,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
/// </summary>
public bool PreserveAlpha { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()))
{
source.CopyTo(firstPassPixels);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration);
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration);
@ -72,6 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Configuration configuration)
{
DenseMatrix<float> matrix = kernel;
bool preserveAlpha = this.PreserveAlpha;
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
@ -89,18 +99,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
for (int x = 0; x < width; x++)
if (preserveAlpha)
{
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve3(
in matrix,
sourcePixels,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
else
{
DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX);
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve4(
in matrix,
sourcePixels,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});
}

50
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs

@ -3,6 +3,7 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@ -22,17 +23,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="ConvolutionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
public ConvolutionProcessor(DenseMatrix<float> kernelXY) => this.KernelXY = kernelXY;
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public ConvolutionProcessor(in DenseMatrix<float> kernelXY, bool preserveAlpha)
{
this.KernelXY = kernelXY;
this.PreserveAlpha = preserveAlpha;
}
/// <summary>
/// Gets the 2d gradient operator.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
/// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
/// </summary>
public bool PreserveAlpha { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
DenseMatrix<float> matrix = this.KernelXY;
bool preserveAlpha = this.PreserveAlpha;
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
@ -55,18 +68,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan);
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan);
for (int x = 0; x < width; x++)
if (preserveAlpha)
{
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve3(
in matrix,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
else
{
DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve4(
in matrix,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX);
}
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});

5
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelX">The horizontal gradient operator.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetector2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> kernelY, bool grayscale)
protected EdgeDetector2DProcessor(in DenseMatrix<float> kernelX, in DenseMatrix<float> kernelY, bool grayscale)
{
Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same.");
this.KernelX = kernelX;
@ -43,7 +43,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public bool Grayscale { get; set; }
/// <inheritdoc />
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration);
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
=> new Convolution2DProcessor<TPixel>(this.KernelX, this.KernelY, true).Apply(source, sourceRectangle, configuration);
/// <inheritdoc/>
protected override void BeforeFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)

20
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs

@ -5,14 +5,12 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@ -28,10 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="EdgeDetectorCompassProcessor{TPixel}"/> class.
/// </summary>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetectorCompassProcessor(bool grayscale)
{
this.Grayscale = grayscale;
}
protected EdgeDetectorCompassProcessor(bool grayscale) => this.Grayscale = grayscale;
/// <summary>
/// Gets the North gradient operator
@ -104,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// we need a clean copy for each pass to start from
using (ImageFrame<TPixel> cleanCopy = source.Clone())
{
new ConvolutionProcessor<TPixel>(kernels[0]).Apply(source, sourceRectangle, configuration);
new ConvolutionProcessor<TPixel>(kernels[0], true).Apply(source, sourceRectangle, configuration);
if (kernels.Length == 1)
{
@ -133,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
using (ImageFrame<TPixel> pass = cleanCopy.Clone())
{
new ConvolutionProcessor<TPixel>(kernels[i]).Apply(pass, sourceRectangle, configuration);
new ConvolutionProcessor<TPixel>(kernels[i], true).Apply(pass, sourceRectangle, configuration);
Buffer2D<TPixel> passPixels = pass.PixelBuffer;
Buffer2D<TPixel> targetPixels = source.PixelBuffer;
@ -147,10 +142,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
int offsetY = y - shiftY;
ref TPixel passPixelsBase =
ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
ref TPixel targetPixelsBase =
ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));
ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY));
ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY));
for (int x = minX; x < maxX; x++)
{
@ -158,8 +151,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Grab the max components of the two pixels
ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX);
ref TPixel currentTargetPixel =
ref Unsafe.Add(ref targetPixelsBase, offsetX);
ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX);
var pixelValue = Vector4.Max(
currentPassPixel.ToVector4(),

6
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
/// <param name="grayscale">Whether to convert the image to grayscale before performing edge detection.</param>
protected EdgeDetectorProcessor(DenseMatrix<float> kernelXY, bool grayscale)
protected EdgeDetectorProcessor(in DenseMatrix<float> kernelXY, bool grayscale)
{
this.KernelXY = kernelXY;
this.Grayscale = grayscale;
@ -45,8 +45,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new ConvolutionProcessor<TPixel>(this.KernelXY).Apply(source, sourceRectangle, configuration);
}
=> new ConvolutionProcessor<TPixel>(this.KernelXY, true).Apply(source, sourceRectangle, configuration);
}
}

2
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs

@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <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, false).Apply(source, sourceRectangle, configuration);
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function

2
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <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, false).Apply(source, sourceRectangle, configuration);
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function

2
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuserBase.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary>
/// <param name="matrix">The dithering matrix.</param>
/// <param name="divisor">The divisor.</param>
internal ErrorDiffuserBase(DenseMatrix<float> matrix, byte divisor)
internal ErrorDiffuserBase(in DenseMatrix<float> matrix, byte divisor)
{
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));

19
tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs

@ -0,0 +1,19 @@
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
{
[Config(typeof(Config.ShortClr))]
public class GaussianBlur
{
[Benchmark]
public void Blur()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Rgba32.White))
{
image.Mutate(c => c.GaussianBlur());
}
}
}
}

2
tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Icc
[Theory]
[MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))]
internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, DenseMatrix<float> data)
internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix<float> data)
{
IccDataWriter writer = CreateWriter();

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (var image = source.Clone())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
{
public class DetectEdgesTest : FileTestBase
{
// I think our comparison is not accurate enough (nor can be) for RgbaVector.
// The image pixels are identical according to BeyondCompare.
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F);
public static readonly string[] CommonTestImages = { TestImages.Png.Bike };

Loading…
Cancel
Save