diff --git a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs
new file mode 100644
index 0000000000..26ec816ce3
--- /dev/null
+++ b/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Diagnostics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Provides fast access to 2D arrays.
+ ///
+ /// The type of elements in the array.
+ public struct Fast2DArray
+ {
+ ///
+ /// The 1D representation of the 2D array.
+ ///
+ public T[] Data;
+
+ ///
+ /// Gets the width of the 2D array.
+ ///
+ public int Width;
+
+ ///
+ /// Gets the height of the 2D array.
+ ///
+ public int Height;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The 2D array to provide access to.
+ 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.Data = new T[this.Width * this.Height];
+
+ 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];
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the item at the specified position.
+ ///
+ /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array.
+ /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array.
+ /// The at the specified position.
+ 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;
+ }
+ }
+
+ ///
+ /// Checks the coordinates to ensure they are within bounds.
+ ///
+ /// The row-coordinate of the item. Must be greater than zero and smaller than the height of the array.
+ /// The column-coordinate of the item. Must be greater than zero and smaller than the width of the array.
+ ///
+ /// Thrown if the coordinates are not within the bounds of the array.
+ ///
+ [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.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs
index c560889790..3d3d900233 100644
--- a/src/ImageSharp/Dithering/Ordered/Bayer.cs
+++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs
@@ -17,23 +17,24 @@ namespace ImageSharp.Dithering.Ordered
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
///
- private static readonly byte[][] ThresholdMatrix =
- {
- new byte[] { 15, 143, 47, 175 },
- new byte[] { 207, 79, 239, 111 },
- new byte[] { 63, 191, 31, 159 },
- new byte[] { 255, 127, 223, 95 }
- };
+ private static readonly Fast2DArray ThresholdMatrix =
+ new Fast2DArray(new byte[,]
+ {
+ { 15, 143, 47, 175 },
+ { 207, 79, 239, 111 },
+ { 63, 191, 31, 159 },
+ { 255, 127, 223, 95 }
+ });
///
- public byte[][] Matrix { get; } = ThresholdMatrix;
+ public Fast2DArray Matrix { get; } = ThresholdMatrix;
///
public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable
{
source.ToXyzwBytes(bytes, 0);
- pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper;
+ pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
index 464b9d4199..e74a863a51 100644
--- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
+++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
@@ -15,7 +15,7 @@ namespace ImageSharp.Dithering
///
/// Gets the dithering matrix
///
- byte[][] Matrix { get; }
+ Fast2DArray Matrix { get; }
///
/// Transforms the image applying the dither matrix. This method alters the input pixels array
diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs
index 38f8f21a57..bb1f21060d 100644
--- a/src/ImageSharp/Dithering/Ordered/Ordered.cs
+++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs
@@ -17,23 +17,24 @@ namespace ImageSharp.Dithering.Ordered
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16
///
- private static readonly byte[][] ThresholdMatrix =
- {
- new byte[] { 0, 128, 32, 160 },
- new byte[] { 192, 64, 224, 96 },
- new byte[] { 48, 176, 16, 144 },
- new byte[] { 240, 112, 208, 80 }
- };
+ private static readonly Fast2DArray ThresholdMatrix =
+ new Fast2DArray(new byte[,]
+ {
+ { 0, 128, 32, 160 },
+ { 192, 64, 224, 96 },
+ { 48, 176, 16, 144 },
+ { 240, 112, 208, 80 }
+ });
///
- public byte[][] Matrix { get; } = ThresholdMatrix;
+ public Fast2DArray Matrix { get; } = ThresholdMatrix;
///
public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable
{
source.ToXyzwBytes(bytes, 0);
- pixels[x, y] = ThresholdMatrix[x % 3][y % 3] >= bytes[index] ? lower : upper;
+ pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
index f37ba7496f..55b16235a5 100644
--- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs
+++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
@@ -153,8 +153,8 @@ namespace ImageSharp
///
/// Gets or sets the pixel at the specified position.
///
- /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
- /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
+ /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.
+ /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.
/// The at the specified position.
public TColor this[int x, int y]
{
@@ -625,8 +625,8 @@ namespace ImageSharp
/// Checks that the given area and offset are within the bounds of the image.
///
/// The area.
- /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
- /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
+ /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.
+ /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.
///
/// Thrown if the dimensions are not within the bounds of the image.
///
@@ -649,8 +649,8 @@ namespace ImageSharp
///
/// Checks the coordinates to ensure they are within bounds.
///
- /// The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
- /// The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.
+ /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.
+ /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.
///
/// Thrown if the coordinates are not within the bounds of the image.
///
diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs
new file mode 100644
index 0000000000..a01ba77adc
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Benchmarks.General
+{
+ using BenchmarkDotNet.Attributes;
+
+ public class Array2D
+ {
+ private float[,] data;
+
+ private float[][] jaggedData;
+
+ private Fast2DArray fastData;
+
+ [Params(10, 100, 1000, 10000)]
+ public int Count { get; set; }
+
+ public int Index { get; set; }
+
+ [Setup]
+ public void SetUp()
+ {
+ this.data = new float[this.Count, this.Count];
+ this.jaggedData = new float[this.Count][];
+
+ for (int i = 0; i < this.Count; i++)
+ {
+ this.jaggedData[i] = new float[this.Count];
+ }
+
+ this.fastData = new Fast2DArray(this.data);
+
+ this.Index = this.Count / 2;
+ }
+
+ [Benchmark(Baseline = true, Description = "Array access using 2D array")]
+ public float ArrayIndex()
+ {
+ return this.data[this.Index, this.Index];
+ }
+
+ [Benchmark(Description = "Array access using a jagged array")]
+ public float ArrayJaggedIndex()
+ {
+ return this.jaggedData[this.Index][this.Index];
+ }
+
+ [Benchmark(Description = "Array access using Fast2DArray")]
+ public float ArrayFastIndex()
+ {
+ return this.fastData[this.Index, this.Index];
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
index 88d47db519..dddd83e424 100644
--- a/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
+++ b/tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
@@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
+
namespace ImageSharp.Benchmarks.General
{
using System;
@@ -14,9 +15,9 @@ namespace ImageSharp.Benchmarks.General
[Params(100, 1000, 10000)]
public int Count { get; set; }
- byte[] source;
+ private byte[] source;
- byte[] destination;
+ private byte[] destination;
[Setup]
public void SetUp()
diff --git a/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs
new file mode 100644
index 0000000000..903ea6f8d9
--- /dev/null
+++ b/tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests.Common
+{
+ using System;
+
+ using Xunit;
+
+ public class Fast2DArrayTests
+ {
+ private static readonly float[,] FloydSteinbergMatrix =
+ {
+ { 0, 0, 7 },
+ { 3, 5, 1 }
+ };
+
+ [Fact]
+ public void Fast2DArrayThrowsOnNullInitializer()
+ {
+ Assert.Throws(() =>
+ {
+ Fast2DArray fast = new Fast2DArray(null);
+ });
+ }
+
+ [Fact]
+ public void Fast2DArrayThrowsOnEmptyInitializer()
+ {
+ Assert.Throws(() =>
+ {
+ Fast2DArray fast = new Fast2DArray(new float[0, 0]);
+ });
+ }
+
+ [Fact]
+ public void Fast2DArrayReturnsCorrectDimensions()
+ {
+ Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix);
+ Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1));
+ Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0));
+ }
+
+ [Fact]
+ public void Fast2DArrayGetReturnsCorrectResults()
+ {
+ Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix);
+
+ for (int row = 0; row < fast.Height; row++)
+ {
+ for (int column = 0; column < fast.Width; column++)
+ {
+ Assert.True(Math.Abs(fast[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon);
+ }
+ }
+ }
+
+ [Fact]
+ public void Fast2DArrayGetSetReturnsCorrectResults()
+ {
+ Fast2DArray fast = new Fast2DArray(new float[4, 4]);
+ const float Val = 5F;
+
+ fast[3, 3] = Val;
+
+ Assert.True(Math.Abs(Val - fast[3, 3]) < Constants.Epsilon);
+ }
+ }
+}
\ No newline at end of file