diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
index 471e4f6b22..de95ba3482 100644
--- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs
+++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
@@ -96,6 +96,27 @@ namespace SixLabors.ImageSharp
}
}
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The number of columns.
+ /// The number of rows.
+ /// The array to provide access to.
+ public DenseMatrix(int columns, int rows, Span data)
+ {
+ Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows));
+ Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns));
+ Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns");
+
+ this.Rows = rows;
+ this.Columns = columns;
+ this.Size = new Size(columns, rows);
+ this.Count = this.Columns * this.Rows;
+ this.Data = new T[this.Columns * this.Rows];
+
+ data.CopyTo(this.Data);
+ }
+
///
/// Gets a span wrapping the .
///
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
new file mode 100644
index 0000000000..dbf345caf4
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// A stack only, readonly, kernel matrix that can be indexed without
+ /// bounds checks when compiled in release mode.
+ ///
+ internal readonly ref struct Kernel
+ where T : struct, IEquatable
+ {
+ private readonly Span values;
+
+ public Kernel(DenseMatrix matrix)
+ {
+ this.Columns = matrix.Columns;
+ this.Rows = matrix.Rows;
+ this.values = matrix.Span;
+ }
+
+ public int Columns
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ public int Rows
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ public ReadOnlySpan Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return this.values;
+ }
+ }
+
+ public T this[int row, int column]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ this.CheckCoordinates(row, column);
+ ref T vBase = ref MemoryMarshal.GetReference(this.values);
+ return Unsafe.Add(ref vBase, (row * this.Columns) + column);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetValue(int row, int column, T value)
+ {
+ this.SetValue((row * this.Columns) + column, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetValue(int index, T value)
+ {
+ ref T vBase = ref MemoryMarshal.GetReference(this.values);
+ Unsafe.Add(ref vBase, index) = value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear()
+ {
+ this.values.Clear();
+ }
+
+ [Conditional("DEBUG")]
+ private void CheckCoordinates(int row, int column)
+ {
+ if (row < 0 || row >= this.Rows)
+ {
+ throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds.");
+ }
+
+ if (column < 0 || column >= this.Columns)
+ {
+ throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds.");
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs
new file mode 100644
index 0000000000..f4aa68e22f
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// A stack only struct used for reducing reference indirection during convolution operations.
+ ///
+ internal readonly ref struct MedianConvolutionState
+ {
+ private readonly Span rowOffsetMap;
+ private readonly Span columnOffsetMap;
+ private readonly int kernelHeight;
+ private readonly int kernelWidth;
+
+ public MedianConvolutionState(
+ in DenseMatrix kernel,
+ KernelSamplingMap map)
+ {
+ this.Kernel = new Kernel(kernel);
+ this.kernelHeight = kernel.Rows;
+ this.kernelWidth = kernel.Columns;
+ this.rowOffsetMap = map.GetRowOffsetSpan();
+ this.columnOffsetMap = map.GetColumnOffsetSpan();
+ }
+
+ public readonly Kernel Kernel
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly ref int GetSampleRow(int row)
+ => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly ref int GetSampleColumn(int column)
+ => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
index a926abb803..4aa2ef7ec4 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
@@ -59,31 +59,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Span yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount);
Span zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount);
- Span xOffsets = this.map.GetColumnOffsetSpan();
- Span yOffsets = this.map.GetRowOffsetSpan();
- int baseXOffsetIndex = 0;
- int baseYOffsetIndex = (y - this.bounds.Top) * this.kernelSize;
+ DenseMatrix kernel = new DenseMatrix(this.kernelSize, this.kernelSize, kernelBuffer);
+
+ int row = y - this.bounds.Y;
+ MedianConvolutionState state = new MedianConvolutionState(in kernel, this.map);
+ ref int sampleRowBase = ref state.GetSampleRow(row);
+ ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer);
if (this.preserveAlpha)
{
for (int x = 0; x < boundsWidth; x++)
{
int index = 0;
- for (int kY = 0; kY < this.kernelSize; kY++)
+ ref int sampleColumnBase = ref state.GetSampleColumn(x);
+ ref Vector4 target = ref Unsafe.Add(ref targetBase, x);
+ for (int kY = 0; kY < state.Kernel.Rows; kY++)
{
- int currentY = yOffsets[baseYOffsetIndex + kY];
- Span row = this.sourcePixels.DangerousGetRowSpan(currentY);
- for (int kX = 0; kX < this.kernelSize; kX++)
+ int currentYIndex = Unsafe.Add(ref sampleRowBase, kY);
+ Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth);
+ ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourceRow);
+ for (int kX = 0; kX < state.Kernel.Columns; kX++)
{
- int currentX = xOffsets[baseXOffsetIndex + kX];
- TPixel pixel = row[currentX];
- kernelBuffer[index] = pixel.ToVector4();
+ int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX;
+ TPixel pixel = Unsafe.Add(ref sourceRowBase, currentXIndex);
+ state.Kernel.SetValue(index, pixel.ToVector4());
index++;
}
}
- targetBuffer[x] = this.FindMedian3(kernelBuffer, xChannel, yChannel, zChannel, kernelCount);
- baseXOffsetIndex += this.kernelSize;
+ target = this.FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel, kernelCount);
+ state.Kernel.Clear();
}
}
else
@@ -92,21 +97,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int x = 0; x < boundsWidth; x++)
{
int index = 0;
- for (int kY = 0; kY < this.kernelSize; kY++)
+ ref int sampleColumnBase = ref state.GetSampleColumn(x);
+ ref Vector4 target = ref Unsafe.Add(ref targetBase, x);
+ for (int kY = 0; kY < state.Kernel.Rows; kY++)
{
- int j = yOffsets[baseYOffsetIndex + kY];
- Span row = this.sourcePixels.DangerousGetRowSpan(j);
- for (int kX = 0; kX < this.kernelSize; kX++)
+ int currentYIndex = Unsafe.Add(ref sampleRowBase, kY);
+ Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth);
+ ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourceRow);
+ for (int kX = 0; kX < state.Kernel.Columns; kX++)
{
- int currentX = xOffsets[baseXOffsetIndex + kX];
- TPixel pixel = row[currentX];
- kernelBuffer[index] = pixel.ToVector4();
+ int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX;
+ TPixel pixel = Unsafe.Add(ref sourceRowBase, currentXIndex);
+ state.Kernel.SetValue(index, pixel.ToVector4());
index++;
}
}
- targetBuffer[x] = this.FindMedian4(kernelBuffer, xChannel, yChannel, zChannel, wChannel, kernelCount);
- baseXOffsetIndex += this.kernelSize;
+ target = this.FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel, kernelCount);
+ state.Kernel.Clear();
}
}
@@ -115,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private Vector4 FindMedian3(Span kernelSpan, Span xChannel, Span yChannel, Span zChannel, int stride)
+ private Vector4 FindMedian3(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, int stride)
{
int halfLength = (kernelSpan.Length + 1) >> 1;
@@ -138,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private Vector4 FindMedian4(Span kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel, int stride)
+ private Vector4 FindMedian4(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel, int stride)
{
int halfLength = (kernelSpan.Length + 1) >> 1;