Browse Source

Refactor 2D and cleanup

js/color-alpha-handling
James Jackson-South 6 years ago
parent
commit
ccdf9c26a1
  1. 16
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  2. 18
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  3. 54
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs
  4. 16
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  5. 14
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  6. 45
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs
  7. 103
      src/ImageSharp/Processing/Processors/Convolution/Convolver.cs
  8. 4
      src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
  9. 63
      src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs

16
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see typeparam="T"/> at the specified position.</returns>
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
/// <returns>
/// The <see cref="DenseMatrix{T}"/> representation on the source data.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator DenseMatrix<T>(T[,] data) => new DenseMatrix<T>(data);
/// <summary>
@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp
/// <returns>
/// The <see cref="T:T[,]"/> representation on the source data.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#pragma warning disable SA1008 // Opening parenthesis should be spaced correctly
public static implicit operator T[,](in DenseMatrix<T> 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.
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DenseMatrix<T> Transpose()
{
var result = new DenseMatrix<T>(this.Rows, this.Columns);
@ -196,13 +196,13 @@ namespace SixLabors.ImageSharp
/// Fills the matrix with the given value
/// </summary>
/// <param name="value">The value to fill each item with</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Fill(T value) => this.Span.Fill(value);
/// <summary>
/// Clears the matrix setting each value to the default value for the element type
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear() => this.Span.Clear();
/// <summary>
@ -232,14 +232,14 @@ namespace SixLabors.ImageSharp
=> obj is DenseMatrix<T> other && this.Equals(other);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(DenseMatrix<T> other) =>
this.Columns == other.Columns
&& this.Rows == other.Rows
&& this.Span.SequenceEqual(other.Span);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
HashCode code = default;

18
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -43,12 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
/// <summary>
/// Gets the horizontal gradient operator.
/// Gets the horizontal convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// Gets the vertical convolution kernel.
/// </summary>
public DenseMatrix<float> KernelY { get; }
@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
Span<int> yOffsets = this.map.GetYOffsetSpan();
Span<int> 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,

54
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
{
/// <summary>
/// A stack only struct used for reducing reference indirection during 2D convolution operations.
/// </summary>
internal readonly ref struct Convolution2DState
{
private readonly Span<int> rowOffsetMap;
private readonly Span<int> columnOffsetMap;
private readonly int kernelHeight;
private readonly int kernelWidth;
public Convolution2DState(
in DenseMatrix<float> kernelY,
in DenseMatrix<float> 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);
}
}

16
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -42,12 +42,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
/// <summary>
/// Gets the horizontal gradient operator.
/// Gets the horizontal convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// Gets the vertical convolution kernel.
/// </summary>
public DenseMatrix<float> KernelY { get; }
@ -143,8 +143,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
Span<int> yOffsets = this.map.GetYOffsetSpan();
Span<int> 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,

14
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
/// <summary>
/// Gets the 2d gradient operator.
/// Gets the 2d convolution kernel.
/// </summary>
public DenseMatrix<float> KernelXY { get; }
@ -110,8 +110,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
Span<int> yOffsets = this.map.GetYOffsetSpan();
Span<int> 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,

45
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
{
/// <summary>
/// A stack only struct used for reducing reference indirection during convolution operations.
/// </summary>
internal readonly ref struct ConvolutionState
{
private readonly Span<int> rowOffsetMap;
private readonly Span<int> columnOffsetMap;
private readonly int kernelHeight;
private readonly int kernelWidth;
public ConvolutionState(
in DenseMatrix<float> 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);
}
}

103
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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="kernelY">The vertical convolution kernel.</param>
/// <param name="kernelX">The horizontal convolution kernel.</param>
/// <param name="rowSampleOffsets">The span containing precalculated kernel y-sampling offsets.</param>
/// <param name="columnSampleOffsets">The span containing precalculated kernel x-sampling offsets.</param>
/// <param name="state">The 2D convolution kernels state.</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>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve2D3<TPixel>(
in DenseMatrix<float> kernelY,
in DenseMatrix<float> kernelX,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in Convolution2DState state,
Buffer2D<TPixel> 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="kernelY">The vertical convolution kernel.</param>
/// <param name="kernelX">The horizontal convolution kernel.</param>
/// <param name="rowSampleOffsets">The span containing precalculated kernel y-sampling offsets.</param>
/// <param name="columnSampleOffsets">The span containing precalculated kernel x-sampling offsets.</param>
/// <param name="state">The 2D convolution kernels state.</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>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve2D4<TPixel>(
in DenseMatrix<float> kernelY,
in DenseMatrix<float> kernelX,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in Convolution2DState state,
Buffer2D<TPixel> 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<TPixel>(
in DenseMatrix<float> kernelY,
in DenseMatrix<float> kernelX,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in Convolution2DState state,
Buffer2D<TPixel> sourcePixels,
int row,
int column,
ref Vector4 targetVector)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="kernel">The convolution kernel.</param>
/// <param name="rowSampleOffsets">The span containing precalculated kernel y-sampling offsets.</param>
/// <param name="columnSampleOffsets">The span containing precalculated kernel x-sampling offsets.</param>
/// <param name="state">The convolution kernel state.</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>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve3<TPixel>(
in DenseMatrix<float> kernel,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in ConvolutionState state,
Buffer2D<TPixel> 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="kernel">The convolution kernel.</param>
/// <param name="rowSampleOffsets">The span containing precalculated kernel y-offsets.</param>
/// <param name="columnSampleOffsets">The span containing precalculated kernel x-offsets.</param>
/// <param name="state">The convolution kernel state.</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>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Convolve4<TPixel>(
in DenseMatrix<float> kernel,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in ConvolutionState state,
Buffer2D<TPixel> 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<TPixel>(
in DenseMatrix<float> kernel,
Span<int> rowSampleOffsets,
Span<int> columnSampleOffsets,
in ConvolutionState state,
Buffer2D<TPixel> sourcePixels,
int row,
int column,
ref Vector4 targetVector)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel> 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;
}

4
src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs → src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs

@ -76,10 +76,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<int> GetYOffsetSpan() => this.yOffsets.GetSpan();
public Span<int> GetRowOffsetSpan() => this.yOffsets.GetSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<int> GetXOffsetSpan() => this.xOffsets.GetSpan();
public Span<int> GetColumnOffsetSpan() => this.xOffsets.GetSpan();
/// <inheritdoc/>
public void Dispose()

63
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
{
/// <summary>
/// A stack only, readonly, kernel matrix that can be indexed without
/// bounds checks when compiled in release mode.
/// </summary>
internal readonly ref struct ReadOnlyKernel
{
private readonly ReadOnlySpan<float> values;
public ReadOnlyKernel(DenseMatrix<float> 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.");
}
}
}
}
Loading…
Cancel
Save