Skip to content

Commit

Permalink
Cleanup General Convolution (#887)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
JimBobSquarePants committed Apr 25, 2019
1 parent af0d3dd commit 0a78af5
Show file tree
Hide file tree
Showing 16 changed files with 378 additions and 87 deletions.
9 changes: 3 additions & 6 deletions src/ImageSharp.Drawing/Processing/PatternBrush{TPixel}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public PatternBrush(TPixel foreColor, TPixel backColor, bool[,] pattern)
/// <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();
Expand Down Expand Up @@ -93,10 +93,7 @@ internal PatternBrush(PatternBrush<TPixel> brush)
}

/// <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.
Expand All @@ -116,7 +113,7 @@ private class PatternBrushApplicator : BrushApplicator<TPixel>
/// <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;
Expand Down
240 changes: 200 additions & 40 deletions src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -96,16 +134,16 @@ public static void Convolve2D<TPixel>(
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);

Expand All @@ -114,11 +152,133 @@ public static void Convolve2D<TPixel>(
}
}

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;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ 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, false).Apply(source, sourceRectangle, configuration);

/// <summary>
/// Create a 1 dimensional Box kernel.
Expand Down
Loading

0 comments on commit 0a78af5

Please sign in to comment.