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