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

Cleanup General Convolution #887

Merged
merged 13 commits into from
Apr 25, 2019
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
97 changes: 58 additions & 39 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 @@ -15,108 +16,126 @@ namespace SixLabors.ImageSharp
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.
/// </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,
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public static void Convolve2D<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,
int offsetColumn)
bool preserveAlpha)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vector = default;
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
Vector4 vectorY = default;
Vector4 vectorX = default;
int matrixHeight = matrixY.Rows;
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);

vector += matrix[y, x] * currentColor;
vectorX += matrixX[y, x] * currentColor;
vectorY += matrixY[y, x] * currentColor;
}
}

ref Vector4 target = ref targetRow[column];
vector.W = target.W;
var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);

if (preserveAlpha)
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
{
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 kernel weight values.
/// </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="matrix">The 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>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
public static void Convolve<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn,
int offsetColumn)
bool preserveAlpha)
where TPixel : struct, IPixel<TPixel>
{
Vector4 vectorY = default;
Vector4 vectorX = default;
int matrixHeight = matrixY.Rows;
int matrixWidth = matrixY.Columns;
Vector4 vector = default;
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.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);

vectorX += matrixX[y, x] * currentColor;
vectorY += matrixY[y, x] * currentColor;
vector += matrix[y, x] * currentColor;
}
}

var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY));
ref Vector4 target = ref targetRow[column];
vector.W = target.W;
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);

if (preserveAlpha)
JimBobSquarePants marked this conversation as resolved.
Show resolved Hide resolved
{
vector.W = target.W;
}

Vector4Utils.UnPremultiply(ref vector);
target = vector;
}
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,11 +24,13 @@ internal class Convolution2DProcessor<TPixel> : ImageProcessor<TPixel>
/// </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>
Expand All @@ -40,6 +43,11 @@ public Convolution2DProcessor(DenseMatrix<float> kernelX, DenseMatrix<float> ker
/// </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,
Expand All @@ -48,6 +56,7 @@ protected override void OnFrameApply(
{
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;
Expand All @@ -71,6 +80,7 @@ protected override void OnFrameApply(
{
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++)
{
Expand All @@ -79,10 +89,21 @@ protected override void OnFrameApply(

for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX);
DenseMatrixUtils.Convolve2D(
in matrixY,
in matrixX,
source.PixelBuffer,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX,
preserveAlpha);
}

PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,10 +24,15 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
/// </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>
Expand All @@ -40,13 +45,16 @@ public Convolution2PassProcessor(DenseMatrix<float> kernelX, DenseMatrix<float>
/// </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);
Expand All @@ -72,6 +80,8 @@ private void ApplyConvolution(
Configuration configuration)
{
DenseMatrix<float> matrix = kernel;
bool preserveAlpha = this.PreserveAlpha;

int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
Expand All @@ -89,6 +99,7 @@ private void ApplyConvolution(
{
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++)
{
Expand All @@ -97,10 +108,20 @@ private void ApplyConvolution(

for (int x = 0; x < width; x++)
{
DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX);
DenseMatrixUtils.Convolve(
in matrix,
sourcePixels,
ref vectorSpanRef,
y,
x,
startY,
maxY,
startX,
maxX,
preserveAlpha);
}

PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan);
}
});
}
Expand Down
Loading