Browse Source

Use Fast2DAarray for ordered dither.

pull/106/head
James Jackson-South 9 years ago
parent
commit
489c04be7b
  1. 102
      src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs
  2. 19
      src/ImageSharp/Dithering/Ordered/Bayer.cs
  3. 2
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  4. 19
      src/ImageSharp/Dithering/Ordered/Ordered.cs
  5. 12
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  6. 57
      tests/ImageSharp.Benchmarks/General/Array2D.cs
  7. 5
      tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
  8. 71
      tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs

102
src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs

@ -0,0 +1,102 @@
// <copyright file="Fast2DArray{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
/// <summary>
/// Provides fast access to 2D arrays.
/// </summary>
/// <typeparam name="T">The type of elements in the array.</typeparam>
public 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>
/// 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.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];
}
}
}
/// <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>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>
/// <param name="row">The row-coordinate of the item. Must be greater than zero and smaller than the height of the array.</param>
/// <param name="column">The column-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.");
}
}
}
}

19
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
/// </summary>
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<byte> ThresholdMatrix =
new Fast2DArray<byte>(new byte[,]
{
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
});
/// <inheritdoc />
public byte[][] Matrix { get; } = ThresholdMatrix;
public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix;
/// <inheritdoc />
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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;
}
}
}

2
src/ImageSharp/Dithering/Ordered/IOrderedDither.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Dithering
/// <summary>
/// Gets the dithering matrix
/// </summary>
byte[][] Matrix { get; }
Fast2DArray<byte> Matrix { get; }
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array

19
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
/// </summary>
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<byte> ThresholdMatrix =
new Fast2DArray<byte>(new byte[,]
{
{ 0, 128, 32, 160 },
{ 192, 64, 224, 96 },
{ 48, 176, 16, 144 },
{ 240, 112, 208, 80 }
});
/// <inheritdoc />
public byte[][] Matrix { get; } = ThresholdMatrix;
public Fast2DArray<byte> Matrix { get; } = ThresholdMatrix;
/// <inheritdoc />
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
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;
}
}
}

12
src/ImageSharp/Image/PixelAccessor{TColor}.cs

@ -153,8 +153,8 @@ namespace ImageSharp
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TColor"/> at the specified position.</returns>
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.
/// </summary>
/// <param name="area">The area.</param>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the dimensions are not within the bounds of the image.
/// </exception>
@ -649,8 +649,8 @@ namespace ImageSharp
/// <summary>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and smaller than the width of the pixel.</param>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the coordinates are not within the bounds of the image.
/// </exception>

57
tests/ImageSharp.Benchmarks/General/Array2D.cs

@ -0,0 +1,57 @@
// <copyright file="Array2D.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks.General
{
using BenchmarkDotNet.Attributes;
public class Array2D
{
private float[,] data;
private float[][] jaggedData;
private Fast2DArray<float> 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<float>(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];
}
}
}

5
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.
// </copyright>
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()

71
tests/ImageSharp.Tests/Common/Fast2DArrayTests.cs

@ -0,0 +1,71 @@
// <copyright file="Fast2DArrayTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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<ArgumentNullException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(null);
});
}
[Fact]
public void Fast2DArrayThrowsOnEmptyInitializer()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(new float[0, 0]);
});
}
[Fact]
public void Fast2DArrayReturnsCorrectDimensions()
{
Fast2DArray<float> fast = new Fast2DArray<float>(FloydSteinbergMatrix);
Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1));
Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0));
}
[Fact]
public void Fast2DArrayGetReturnsCorrectResults()
{
Fast2DArray<float> fast = new Fast2DArray<float>(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<float> fast = new Fast2DArray<float>(new float[4, 4]);
const float Val = 5F;
fast[3, 3] = Val;
Assert.True(Math.Abs(Val - fast[3, 3]) < Constants.Epsilon);
}
}
}
Loading…
Cancel
Save