mirror of https://github.com/SixLabors/ImageSharp
47 changed files with 559 additions and 392 deletions
@ -1,173 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Diagnostics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Memory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides fast access to 2D arrays.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The type of elements in the array.</typeparam>
|
|
||||
internal struct Fast2DArray<T> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The 1D representation of the 2D array.
|
|
||||
/// </summary>
|
|
||||
public T[] Data; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the width of the 2D array.
|
|
||||
/// </summary>
|
|
||||
public int Width; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the height of the 2D array.
|
|
||||
/// </summary>
|
|
||||
public int Height; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the number of items in the 2D array
|
|
||||
/// </summary>
|
|
||||
public int Count; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Fast2DArray{T}" /> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="length">The length of each dimension.</param>
|
|
||||
public Fast2DArray(int length) |
|
||||
: this(length, length) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Fast2DArray{T}" /> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="width">The width.</param>
|
|
||||
/// <param name="height">The height.</param>
|
|
||||
public Fast2DArray(int width, int height) |
|
||||
{ |
|
||||
this.Height = height; |
|
||||
this.Width = width; |
|
||||
|
|
||||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|
||||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|
||||
|
|
||||
this.Count = width * height; |
|
||||
this.Data = new T[this.Count]; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Fast2DArray{T}"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="data">The 2D array to provide access to.</param>
|
|
||||
public Fast2DArray(T[,] data) |
|
||||
{ |
|
||||
Guard.NotNull(data, nameof(data)); |
|
||||
this.Height = data.GetLength(0); |
|
||||
this.Width = data.GetLength(1); |
|
||||
|
|
||||
Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); |
|
||||
Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); |
|
||||
|
|
||||
this.Count = this.Width * this.Height; |
|
||||
this.Data = new T[this.Count]; |
|
||||
|
|
||||
for (int y = 0; y < this.Height; y++) |
|
||||
{ |
|
||||
for (int x = 0; x < this.Width; x++) |
|
||||
{ |
|
||||
this.Data[(y * this.Width) + x] = data[y, x]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the item at the specified position.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array.</param>
|
|
||||
/// <param name="column">The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array.</param>
|
|
||||
/// <returns>The <see typeparam="T"/> at the specified position.</returns>
|
|
||||
public T this[int row, int column] |
|
||||
{ |
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
get |
|
||||
{ |
|
||||
this.CheckCoordinates(row, column); |
|
||||
return this.Data[(row * this.Width) + column]; |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
set |
|
||||
{ |
|
||||
this.CheckCoordinates(row, column); |
|
||||
this.Data[(row * this.Width) + column] = value; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Performs an implicit conversion from a 2D array to a <see cref="Fast2DArray{T}" />.
|
|
||||
/// </summary>
|
|
||||
/// <param name="data">The source array.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Fast2DArray{T}"/> representation on the source data.
|
|
||||
/// </returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public static implicit operator Fast2DArray<T>(T[,] data) |
|
||||
{ |
|
||||
return new Fast2DArray<T>(data); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a <see cref="Span{T}"/> representing the row beginning from the the first item on that row.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The y-coordinate of the row. Must be greater than or equal to zero and less than the height of the 2D array.</param>
|
|
||||
/// <returns>The <see cref="Span{T}"/></returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public Span<T> GetRowSpan(int row) |
|
||||
{ |
|
||||
this.CheckCoordinates(row); |
|
||||
return new Span<T>(this.Data, row * this.Width, this.Width); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Checks the coordinates to ensure they are within bounds.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The y-coordinate of the item. Must be greater than zero and smaller than the height of the array.</param>
|
|
||||
/// <param name="column">The x-coordinate of the item. Must be greater than zero and smaller than the width of the array.</param>
|
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||
/// Thrown if the coordinates are not within the bounds of the array.
|
|
||||
/// </exception>
|
|
||||
[Conditional("DEBUG")] |
|
||||
private void CheckCoordinates(int row, int column) |
|
||||
{ |
|
||||
if (row < 0 || row >= this.Height) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds."); |
|
||||
} |
|
||||
|
|
||||
if (column < 0 || column >= this.Width) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the array bounds."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Checks the coordinates to ensure they are within bounds.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The y-coordinate of the item. Must be greater than zero and smaller than the height of the array.</param>
|
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||
/// Thrown if the coordinates are not within the bounds of the image.
|
|
||||
/// </exception>
|
|
||||
[Conditional("DEBUG")] |
|
||||
private void CheckCoordinates(int row) |
|
||||
{ |
|
||||
if (row < 0 || row >= this.Height) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the array bounds."); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,215 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Primitives |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a dense matrix with arbitrary elements.
|
||||
|
/// Components that are adjacent in a column of the matrix are adjacent in the storage array.
|
||||
|
/// The components are said to be stored in column major order.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of elements in the matrix.</typeparam>
|
||||
|
public readonly struct DenseMatrix<T> : IEquatable<DenseMatrix<T>> |
||||
|
where T : struct, IEquatable<T> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The 1D representation of the dense matrix.
|
||||
|
/// </summary>
|
||||
|
public readonly T[] Data; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of columns in the dense matrix.
|
||||
|
/// </summary>
|
||||
|
public readonly int Columns; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of rows in the dense matrix.
|
||||
|
/// </summary>
|
||||
|
public readonly int Rows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of items in the array.
|
||||
|
/// </summary>
|
||||
|
public readonly int Count; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref=" DenseMatrix{T}" /> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="length">The length of each side in the matrix.</param>
|
||||
|
public DenseMatrix(int length) |
||||
|
: this(length, length) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref=" DenseMatrix{T}" /> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="columns">The number of columns.</param>
|
||||
|
/// <param name="rows">The number of rows.</param>
|
||||
|
public DenseMatrix(int columns, int rows) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThan(columns, 0, nameof(columns)); |
||||
|
Guard.MustBeGreaterThan(rows, 0, nameof(rows)); |
||||
|
|
||||
|
this.Rows = rows; |
||||
|
this.Columns = columns; |
||||
|
this.Count = columns * rows; |
||||
|
this.Data = new T[this.Columns * this.Rows]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref=" DenseMatrix{T}"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The 2D array to provide access to.</param>
|
||||
|
public DenseMatrix(T[,] data) |
||||
|
{ |
||||
|
Guard.NotNull(data, nameof(data)); |
||||
|
int rows = data.GetLength(0); |
||||
|
int columns = data.GetLength(1); |
||||
|
|
||||
|
Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); |
||||
|
Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); |
||||
|
|
||||
|
this.Rows = rows; |
||||
|
this.Columns = columns; |
||||
|
this.Count = this.Columns * this.Rows; |
||||
|
this.Data = new T[this.Columns * this.Rows]; |
||||
|
|
||||
|
for (int y = 0; y < this.Rows; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < this.Columns; x++) |
||||
|
{ |
||||
|
ref T value = ref this[y, x]; |
||||
|
value = data[y, x]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the item at the specified position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row">The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array.</param>
|
||||
|
/// <param name="column">The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array.</param>
|
||||
|
/// <returns>The <see typeparam="T"/> at the specified position.</returns>
|
||||
|
public ref T this[int row, int column] |
||||
|
{ |
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
get |
||||
|
{ |
||||
|
this.CheckCoordinates(row, column); |
||||
|
return ref this.Data[(row * this.Columns) + column]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an implicit conversion from a <see cref="T:T[,]" /> to a <see cref=" DenseMatrix{T}" />.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The source array.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="DenseMatrix{T}"/> representation on the source data.
|
||||
|
/// </returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static implicit operator DenseMatrix<T>(T[,] data) => new DenseMatrix<T>(data); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Performs an implicit conversion from a <see cref="DenseMatrix{T}"/> to a <see cref="T:T[,]" />.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The source array.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="T:T[,]"/> representation on the source data.
|
||||
|
/// </returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
#pragma warning disable SA1008 // Opening parenthesis should be spaced correctly
|
||||
|
public static implicit operator T[,] (DenseMatrix<T> data) |
||||
|
#pragma warning restore SA1008 // Opening parenthesis should be spaced correctly
|
||||
|
{ |
||||
|
var result = new T[data.Rows, data.Columns]; |
||||
|
|
||||
|
for (int y = 0; y < data.Rows; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < data.Columns; x++) |
||||
|
{ |
||||
|
ref T value = ref result[y, x]; |
||||
|
value = data[y, x]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Fills the matrix with the given value
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The value to fill each item with</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void Fill(T value) |
||||
|
{ |
||||
|
for (int i = 0; i < this.Data.Length; i++) |
||||
|
{ |
||||
|
this.Data[i] = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clears the matrix setting each value to the default value for the element type
|
||||
|
/// </summary>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public void Clear() => Array.Clear(this.Data, 0, this.Data.Length); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks the coordinates to ensure they are within bounds.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row">The y-coordinate of the item. Must be greater than zero and smaller than the height of the matrix.</param>
|
||||
|
/// <param name="column">The x-coordinate of the item. Must be greater than zero and smaller than the width of the matrix.</param>
|
||||
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||
|
/// Thrown if the coordinates are not within the bounds of the array.
|
||||
|
/// </exception>
|
||||
|
[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."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool Equals(DenseMatrix<T> other) |
||||
|
{ |
||||
|
if (this.Columns != other.Columns) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (this.Rows != other.Rows) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < this.Data.Length; i++) |
||||
|
{ |
||||
|
if (!this.Data[i].Equals(other.Data[i])) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool Equals(object obj) => obj is DenseMatrix<T> matrix && this.Equals(matrix); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override int GetHashCode() => this.Data.GetHashCode(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Primitives; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Primitives |
||||
|
{ |
||||
|
public class DenseMatrixTests |
||||
|
{ |
||||
|
private static readonly float[,] FloydSteinbergMatrix = |
||||
|
{ |
||||
|
{ 0, 0, 7 }, |
||||
|
{ 3, 5, 1 } |
||||
|
}; |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixThrowsOnNullInitializer() |
||||
|
{ |
||||
|
Assert.Throws<ArgumentNullException>(() => |
||||
|
{ |
||||
|
var dense = new DenseMatrix<float>(null); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixThrowsOnEmptyZeroWidth() |
||||
|
{ |
||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => |
||||
|
{ |
||||
|
var dense = new DenseMatrix<float>(0, 10); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixThrowsOnEmptyZeroHeight() |
||||
|
{ |
||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => |
||||
|
{ |
||||
|
var dense = new DenseMatrix<float>(10, 0); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixThrowsOnEmptyInitializer() |
||||
|
{ |
||||
|
Assert.Throws<ArgumentOutOfRangeException>(() => |
||||
|
{ |
||||
|
var dense = new DenseMatrix<float>(new float[0, 0]); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixReturnsCorrectDimensions() |
||||
|
{ |
||||
|
var dense = new DenseMatrix<float>(FloydSteinbergMatrix); |
||||
|
Assert.True(dense.Columns == FloydSteinbergMatrix.GetLength(1)); |
||||
|
Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); |
||||
|
Assert.Equal(3, dense.Columns); |
||||
|
Assert.Equal(2, dense.Rows); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixGetReturnsCorrectResults() |
||||
|
{ |
||||
|
DenseMatrix<float> dense = FloydSteinbergMatrix; |
||||
|
|
||||
|
for (int row = 0; row < dense.Rows; row++) |
||||
|
{ |
||||
|
for (int column = 0; column < dense.Columns; column++) |
||||
|
{ |
||||
|
Assert.True(Math.Abs(dense[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixGetSetReturnsCorrectResults() |
||||
|
{ |
||||
|
var dense = new DenseMatrix<int>(4, 4); |
||||
|
const int Val = 5; |
||||
|
|
||||
|
dense[3, 3] = Val; |
||||
|
|
||||
|
Assert.Equal(Val, dense[3, 3]); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DenseMatrixCanFillAndClear() |
||||
|
{ |
||||
|
var dense = new DenseMatrix<int>(9); |
||||
|
dense.Fill(4); |
||||
|
|
||||
|
for (int i = 0; i < dense.Data.Length; i++) |
||||
|
{ |
||||
|
Assert.Equal(4, dense.Data[i]); |
||||
|
} |
||||
|
|
||||
|
dense.Clear(); |
||||
|
|
||||
|
for (int i = 0; i < dense.Data.Length; i++) |
||||
|
{ |
||||
|
Assert.Equal(0, dense.Data[i]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue