From 489c04be7bb544db045a687148a440aa5ae15b50 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 17 Feb 2017 09:40:32 +1100 Subject: [PATCH] Use Fast2DAarray for ordered dither. --- .../Common/Helpers/Fast2DArray{T}.cs | 102 ++++++++++++++++++ src/ImageSharp/Dithering/Ordered/Bayer.cs | 19 ++-- .../Dithering/Ordered/IOrderedDither.cs | 2 +- src/ImageSharp/Dithering/Ordered/Ordered.cs | 19 ++-- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 12 +-- .../ImageSharp.Benchmarks/General/Array2D.cs | 57 ++++++++++ .../General/ArrayCopy.cs | 5 +- .../Common/Fast2DArrayTests.cs | 71 ++++++++++++ 8 files changed, 260 insertions(+), 27 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Array2D.cs create mode 100644 tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs 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