From ccdf9c26a132f4f374a85b9a936d6d47a94a955b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Dec 2020 01:30:47 +0000 Subject: [PATCH] Refactor 2D and cleanup --- src/ImageSharp/Primitives/DenseMatrix{T}.cs | 16 +-- .../Convolution2DProcessor{TPixel}.cs | 18 +-- .../Convolution/Convolution2DState.cs | 54 +++++++++ .../Convolution2PassProcessor{TPixel}.cs | 16 +-- .../ConvolutionProcessor{TPixel}.cs | 14 +-- .../Convolution/ConvolutionState.cs | 45 ++++++++ .../Processors/Convolution/Convolver.cs | 103 ++++++------------ .../{Kernels => }/KernelSamplingMap.cs | 4 +- .../Processors/Convolution/ReadOnlyKernel.cs | 63 +++++++++++ 9 files changed, 225 insertions(+), 108 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs rename src/ImageSharp/Processing/Processors/Convolution/{Kernels => }/KernelSamplingMap.cs (95%) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index e312703368..60dadb617b 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp /// The at the specified position. public ref T this[int row, int column] { - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { this.CheckCoordinates(row, column); @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp /// /// The representation on the source data. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data); /// @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp /// /// The representation on the source data. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly public static implicit operator T[,](in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp /// Transposes the rows and columns of the dense matrix. /// /// The . - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public DenseMatrix Transpose() { var result = new DenseMatrix(this.Rows, this.Columns); @@ -196,13 +196,13 @@ namespace SixLabors.ImageSharp /// Fills the matrix with the given value /// /// The value to fill each item with - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Fill(T value) => this.Span.Fill(value); /// /// Clears the matrix setting each value to the default value for the element type /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() => this.Span.Clear(); /// @@ -232,14 +232,14 @@ namespace SixLabors.ImageSharp => obj is DenseMatrix other && this.Equals(other); /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(DenseMatrix other) => this.Columns == other.Columns && this.Rows == other.Rows && this.Span.SequenceEqual(other.Span); /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { HashCode code = default; diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 8f1d373556..249c73e8d6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -43,12 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// Gets the horizontal gradient operator. + /// Gets the horizontal convolution kernel. /// public DenseMatrix KernelX { get; } /// - /// Gets the vertical gradient operator. + /// Gets the vertical convolution kernel. /// public DenseMatrix KernelY { get; } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new Convolution2DState(this.kernelY, this.kernelX, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -141,10 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve2D3( - in this.kernelY, - in this.kernelX, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -156,10 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve2D4( - in this.kernelY, - in this.kernelX, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs new file mode 100644 index 0000000000..e36d458a4a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only struct used for reducing reference indirection during 2D convolution operations. + /// + internal readonly ref struct Convolution2DState + { + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public Convolution2DState( + in DenseMatrix kernelY, + in DenseMatrix kernelX, + KernelSamplingMap map) + { + // We check the kernels are the same size upstream. + this.KernelY = new ReadOnlyKernel(kernelY); + this.KernelX = new ReadOnlyKernel(kernelX); + this.kernelHeight = kernelY.Rows; + this.kernelWidth = kernelY.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + + public ReadOnlyKernel KernelY + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + public ReadOnlyKernel KernelX + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetRowSampleOffset(int row, int kernelRow) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetColumnSampleOffset(int column, int kernelColumn) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 2ea062e281..95fd3b83cc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -42,12 +42,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// Gets the horizontal gradient operator. + /// Gets the horizontal convolution kernel. /// public DenseMatrix KernelX { get; } /// - /// Gets the vertical gradient operator. + /// Gets the vertical convolution kernel. /// public DenseMatrix KernelY { get; } @@ -143,8 +143,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new ConvolutionState(this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -152,9 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve3( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -166,9 +164,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve4( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 999fba22be..191460f40b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } /// - /// Gets the 2d gradient operator. + /// Gets the 2d convolution kernel. /// public DenseMatrix KernelXY { get; } @@ -110,8 +110,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new ConvolutionState(this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -119,9 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve3( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -133,9 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve4( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs new file mode 100644 index 0000000000..97a3af342e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 ConvolutionState + { + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public ConvolutionState( + in DenseMatrix kernel, + KernelSamplingMap map) + { + this.Kernel = new ReadOnlyKernel(kernel); + this.kernelHeight = kernel.Rows; + this.kernelWidth = kernel.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + + public ReadOnlyKernel Kernel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetRowSampleOffset(int row, int kernelRow) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetColumnSampleOffset(int column, int kernelColumn) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index c9e9d74148..5ddc8e85c6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -1,11 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp { @@ -19,20 +20,14 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical convolution kernel. - /// The horizontal convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The 2D convolution kernels state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2D3( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -42,10 +37,7 @@ namespace SixLabors.ImageSharp Vector4 vector = default; Convolve2DImpl( - in kernelY, - in kernelX, - rowSampleOffsets, - columnSampleOffsets, + in state, sourcePixels, row, column, @@ -63,20 +55,14 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical convolution kernel. - /// The horizontal convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The 2D convolution kernels state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2D4( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -86,10 +72,7 @@ namespace SixLabors.ImageSharp Vector4 vector = default; Convolve2DImpl( - in kernelY, - in kernelX, - rowSampleOffsets, - columnSampleOffsets, + in state, sourcePixels, row, column, @@ -100,34 +83,33 @@ namespace SixLabors.ImageSharp target = vector; } - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2DImpl( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { - Vector4 vectorY = default; - Vector4 vectorX = default; + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; int kernelHeight = kernelY.Rows; int kernelWidth = kernelY.Columns; + Vector4 vectorY = default; + Vector4 vectorX = default; + for (int y = 0; y < kernelHeight; y++) { - int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + int offsetY = state.GetRowSampleOffset(row, y); + ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); for (int x = 0; x < kernelWidth; x++) { - int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; - var sample = sourceRowSpan[offsetX].ToVector4(); + int offsetX = state.GetColumnSampleOffset(column, x); + var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); - vectorX += kernelX[y, x] * sample; vectorY += kernelY[y, x] * sample; } @@ -141,18 +123,14 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The convolution kernel state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve3( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -162,9 +140,7 @@ namespace SixLabors.ImageSharp Vector4 vector = default; ConvolveImpl( - in kernel, - rowSampleOffsets, - columnSampleOffsets, + state, sourcePixels, row, column, @@ -182,18 +158,14 @@ namespace SixLabors.ImageSharp /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The convolution kernel. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve4( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -203,9 +175,7 @@ namespace SixLabors.ImageSharp Vector4 vector = default; ConvolveImpl( - in kernel, - rowSampleOffsets, - columnSampleOffsets, + state, sourcePixels, row, column, @@ -216,29 +186,28 @@ namespace SixLabors.ImageSharp target = vector; } - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ConvolveImpl( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { + ReadOnlyKernel kernel = state.Kernel; int kernelHeight = kernel.Rows; int kernelWidth = kernel.Columns; for (int y = 0; y < kernelHeight; y++) { - int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + int offsetY = state.GetRowSampleOffset(row, y); + ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); for (int x = 0; x < kernelWidth; x++) { - int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; - var sample = sourceRowSpan[offsetX].ToVector4(); + int offsetX = state.GetColumnSampleOffset(column, x); + var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); targetVector += kernel[y, x] * sample; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs similarity index 95% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs rename to src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 493c0d0fd2..73a4fa4004 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -76,10 +76,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetYOffsetSpan() => this.yOffsets.GetSpan(); + public Span GetRowOffsetSpan() => this.yOffsets.GetSpan(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetXOffsetSpan() => this.xOffsets.GetSpan(); + public Span GetColumnOffsetSpan() => this.xOffsets.GetSpan(); /// public void Dispose() diff --git a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs new file mode 100644 index 0000000000..37e0060054 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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 ReadOnlyKernel + { + private readonly ReadOnlySpan values; + + public ReadOnlyKernel(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 float this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(row, column); + ref float vBase = ref MemoryMarshal.GetReference(this.values); + return Unsafe.Add(ref vBase, (row * this.Columns) + column); + } + } + + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); + } + + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); + } + } + } +}