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;