diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs index 9fde27908..8f448198b 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs @@ -105,8 +105,6 @@ namespace SixLabors.ImageSharp.Dithering.Base var offsetColor = pixel.ToVector4(); Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor; - - // result.W = offsetColor.W; pixel.PackFromVector4(result); } } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs new file mode 100644 index 000000000..c75530b8e --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known error diffusion algorithms + /// + public static class KnownDiffusers + { + /// + /// Gets the error diffuser that implements the Atkinson algorithm. + /// + public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); + + /// + /// Gets the error diffuser that implements the Burks algorithm. + /// + public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); + + /// + /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. + /// + public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); + + /// + /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-2 algorithm. + /// + public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-3 algorithm. + /// + public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-Lite algorithm. + /// + public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); + + /// + /// Gets the error diffuser that implements the Stevenson-Arce algorithm. + /// + public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); + + /// + /// Gets the error diffuser that implements the Stucki algorithm. + /// + public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs new file mode 100644 index 000000000..0f0338ac7 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering.Base; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public sealed class StevensonArceDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray StevensonArceMatrix = + new float[,] + { + { 0, 0, 0, 0, 0, 32, 0 }, + { 12, 0, 26, 0, 30, 0, 16 }, + { 0, 12, 0, 26, 0, 12, 0 }, + { 5, 0, 12, 0, 12, 0, 5 } + }; + + /// + /// Initializes a new instance of the class. + /// + public StevensonArceDiffuser() + : base(StevensonArceMatrix, 200) + { + } + } +} diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither.cs deleted file mode 100644 index 3bac601ae..000000000 --- a/src/ImageSharp/Dithering/Ordered/BayerDither.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Dithering -{ - /// - /// Applies order dithering using a Bayer dithering matrix of arbitrary length. - /// - /// - public class BayerDither : OrderedDitherBase - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The exponent used to raise the base value (2). - /// The value given determines the dimensions of the matrix with each dimension a power of 2. e.g 2^2 = 4, 2^3 = 8 - /// - public BayerDither(uint exponent) - : base(ComputeBayer(exponent)) - { - } - - private static Fast2DArray ComputeBayer(uint order) - { - uint dimension = (uint)(1 << (int)order); - var matrix = new Fast2DArray((int)dimension); - uint i = 0; - for (int y = 0; y < dimension; y++) - { - for (int x = 0; x < dimension; x++) - { - matrix[y, x] = Bayer(i / dimension, i % dimension, order); - i++; - } - } - - return matrix; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Bayer(uint x, uint y, uint order) - { - uint res = 0; - for (uint i = 0; i < order; ++i) - { - uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1); - uint xOdd = x & 1; - res = ((res << 1 | xOdd_XOR_yOdd) << 1) | xOdd; - x >>= 1; - y >>= 1; - } - - return res; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs similarity index 63% rename from src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs rename to src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs index e96a9c4d3..1d844c8a7 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs +++ b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs @@ -6,13 +6,13 @@ namespace SixLabors.ImageSharp.Dithering /// /// Applies order dithering using the 2x2 Bayer dithering matrix. /// - public sealed class Bayer2x2Dither : BayerDither + public sealed class BayerDither2x2 : OrderedDither { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Bayer2x2Dither() - : base(1) + public BayerDither2x2() + : base(2) { } } diff --git a/src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs similarity index 63% rename from src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs rename to src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs index ad72c164f..4e9f20beb 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs +++ b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs @@ -6,13 +6,13 @@ namespace SixLabors.ImageSharp.Dithering /// /// Applies order dithering using the 4x4 Bayer dithering matrix. /// - public sealed class Bayer4x4Dither : BayerDither + public sealed class BayerDither4x4 : OrderedDither { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Bayer4x4Dither() - : base(2) + public BayerDither4x4() + : base(4) { } } diff --git a/src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs similarity index 63% rename from src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs rename to src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs index 9077dc2cc..3ff179a06 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs +++ b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs @@ -6,13 +6,13 @@ namespace SixLabors.ImageSharp.Dithering /// /// Applies order dithering using the 8x8 Bayer dithering matrix. /// - public sealed class Bayer8x8Dither : BayerDither + public sealed class BayerDither8x8 : OrderedDither { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public Bayer8x8Dither() - : base(3) + public BayerDither8x8() + : base(8) { } } diff --git a/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs new file mode 100644 index 000000000..e58cbad8a --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known ordered dither matrices + /// + public class KnownDitherers + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs new file mode 100644 index 000000000..8f8210a8b --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// An ordered dithering matrix with equal sides of arbitrary length + /// + public class OrderedDither : IOrderedDither + { + private readonly Fast2DArray thresholdMatrix; + private readonly int modulusX; + private readonly int modulusY; + + /// + /// Initializes a new instance of the class. + /// + /// The length of the matrix sides + public OrderedDither(uint length) + { + Fast2DArray ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + this.modulusX = ditherMatrix.Width; + this.modulusY = ditherMatrix.Height; + + // Adjust the matrix range for 0-255 + // It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 + // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg + int multiplier = 256 / ditherMatrix.Count; + for (int y = 0; y < ditherMatrix.Height; y++) + { + for (int x = 0; x < ditherMatrix.Width; x++) + { + ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + } + } + + this.thresholdMatrix = ditherMatrix; + } + + /// + public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y) + where TPixel : struct, IPixel + { + image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs new file mode 100644 index 000000000..0436b35e9 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 3x3 dithering matrix. + /// + public sealed class OrderedDither3x3 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public OrderedDither3x3() + : base(3) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs deleted file mode 100644 index cf7a14239..000000000 --- a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Dithering -{ - /// - /// The base class for performing ordered dithering using a dither matrix. - /// - public abstract class OrderedDitherBase : IOrderedDither - { - private readonly Fast2DArray matrix; - private readonly Fast2DArray thresholdMatrix; - private readonly int modulusX; - private readonly int modulusY; - - /// - /// Initializes a new instance of the class. - /// - /// The thresholding matrix. - internal OrderedDitherBase(Fast2DArray matrix) - { - this.matrix = matrix; - this.modulusX = matrix.Width; - this.modulusY = matrix.Height; - this.thresholdMatrix = new Fast2DArray(matrix.Width, matrix.Height); - - // Adjust the matrix range for 0-255 - int multiplier = 256 / (this.modulusX * this.modulusY); - for (int y = 0; y < matrix.Height; y++) - { - for (int x = 0; x < matrix.Width; x++) - { - this.thresholdMatrix[y, x] = (uint)((matrix[y, x] + 1) * multiplier) - 1; - } - } - } - - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y) - where TPixel : struct, IPixel - { - image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs new file mode 100644 index 000000000..fc9ac2551 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// A factory for creating ordered dither matrices. + /// + internal static class OrderedDitherFactory + { + /// + /// Creates an ordered dithering matrix with equal sides of arbitrary length. + /// + /// + /// The length of the matrix sides + /// The + public static Fast2DArray CreateDitherMatrix(uint length) + { + // Calculate the the logarithm of length to the base 2 + uint exponent = 0; + uint bayerLength = 0; + do + { + exponent++; + bayerLength = (uint)(1 << (int)exponent); + } + while (length > bayerLength); + + // Create our Bayer matrix that matches the given exponent and dimensions + var matrix = new Fast2DArray((int)length); + uint i = 0; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + matrix[y, x] = Bayer(i / length, i % length, exponent); + i++; + } + } + + // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, + // we need to convert the numbers so that the resulting range is un-gapped. + // We generated: We saved: We compress the number range: + // 0 8 2 10 0 8 2 0 5 2 + // 12 4 14 6 12 4 14 7 4 8 + // 3 11 1 9 3 11 1 3 6 1 + // 15 7 13 5 + uint maxValue = bayerLength * bayerLength; + uint missing = 0; + for (uint v = 0; v < maxValue; ++v) + { + bool found = false; + for (int y = 0; y < length; ++y) + { + for (int x = 0; x < length; x++) + { + if (matrix[y, x] == v) + { + matrix[y, x] -= missing; + found = true; + break; + } + } + } + + if (!found) + { + ++missing; + } + } + + return matrix; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Bayer(uint x, uint y, uint order) + { + uint result = 0; + for (uint i = 0; i < order; ++i) + { + uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1); + uint xOdd = x & 1; + result = ((result << 1 | xOdd_XOR_yOdd) << 1) | xOdd; + x >>= 1; + y >>= 1; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/error_diffusion.txt b/src/ImageSharp/Dithering/error_diffusion.txt new file mode 100644 index 000000000..ea412f635 --- /dev/null +++ b/src/ImageSharp/Dithering/error_diffusion.txt @@ -0,0 +1,58 @@ +List of error diffusion schemes. + +Quantization error of *current* pixel is added to the pixels +on the right and below according to the formulas below. +This works nicely for most static pictures, but causes +an avalanche of jittering artifacts if used in animation. + +Floyd-Steinberg: + + * 7 + 3 5 1 / 16 + +Jarvis-Judice-Ninke: + + * 7 5 + 3 5 7 5 3 + 1 3 5 3 1 / 48 + +Stucki: + + * 8 4 + 2 4 8 4 2 + 1 2 4 2 1 / 42 + +Burkes: + + * 8 4 + 2 4 8 4 2 / 32 + + +Sierra3: + + * 5 3 + 2 4 5 4 2 + 2 3 2 / 32 + +Sierra2: + + * 4 3 + 1 2 3 2 1 / 16 + +Sierra-2-4A: + + * 2 + 1 1 / 4 + +Stevenson-Arce: + + * . 32 + 12 . 26 . 30 . 16 + . 12 . 26 . 12 . + 5 . 12 . 12 . 5 / 200 + +Atkinson: + + * 1 1 / 8 + 1 1 1 + 1 diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs index e0384d208..38ccdd279 100644 --- a/src/ImageSharp/Memory/Fast2DArray{T}.cs +++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs @@ -28,6 +28,11 @@ namespace SixLabors.ImageSharp.Memory /// public int Height; + /// + /// Gets the number of items in the 2D array + /// + public int Count; + /// /// Initializes a new instance of the struct. /// @@ -50,7 +55,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.Data = new T[this.Width * this.Height]; + this.Count = width * height; + this.Data = new T[this.Count]; } /// @@ -66,7 +72,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); - this.Data = new T[this.Width * this.Height]; + this.Count = this.Width * this.Height; + this.Data = new T[this.Count]; for (int y = 0; y < this.Height; y++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs index f8ff475d1..1102a48e4 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.Processing.Processors previousPixel = sourcePixel; } - TPixel transformedPixel = luminance >= threshold ? pair.First : pair.Second; + TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs index 49455928a..0a49f99cf 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors previousPixel = sourcePixel; } - this.Dither.Dither(source, sourcePixel, pair.First, pair.Second, luminance, x, y); + this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs index c6b80293c..b3c564edb 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs @@ -50,8 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors for (int index = 0; index < colorPalette.Length; index++) { TPixel temp = colorPalette[index]; - var tempVector = temp.ToVector4(); - float distance = Vector4.Distance(vector, tempVector); + float distance = Vector4.Distance(vector, temp.ToVector4()); if (distance < leastDistance) { diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 20ba2e637..31e424060 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base public bool Dither { get; set; } = true; /// - public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); + public IErrorDiffuser DitherType { get; set; } = KnownDiffusers.FloydSteinberg; /// public virtual QuantizedImage Quantize(ImageFrame image, int maxColors) diff --git a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs index 5cdbe638a..a5364db72 100644 --- a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs +++ b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(null); + var fast = new Fast2DArray(null); }); } @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(0, 10); + var fast = new Fast2DArray(0, 10); }); } @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(10, 0); + var fast = new Fast2DArray(10, 0); }); } @@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(new float[0, 0]); + var fast = new Fast2DArray(new float[0, 0]); }); } [Fact] public void Fast2DArrayReturnsCorrectDimensions() { - Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + var fast = new Fast2DArray(FloydSteinbergMatrix); Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1)); Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0)); } @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void Fast2DArrayGetSetReturnsCorrectResults() { - Fast2DArray fast = new Fast2DArray(4, 4); + var fast = new Fast2DArray(4, 4); const float Val = 5F; fast[3, 3] = Val; diff --git a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs index ba5cb0cf3..f801b2031 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; -using Moq; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization @@ -16,9 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { - this.orderedDither = new Mock().Object; - this.errorDiffuser = new Mock().Object; + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; } + [Fact] public void Dither_CorrectProcessor() { diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs new file mode 100644 index 000000000..a0ddc2c7c --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class OrderedDitherFactoryTests + { + private static readonly Fast2DArray Expected2x2Matrix = new Fast2DArray( + new uint[2, 2] + { + { 0, 2 }, + { 3, 1 } + }); + + private static readonly Fast2DArray Expected3x3Matrix = new Fast2DArray( + new uint[3, 3] + { + { 0, 5, 2 }, + { 7, 4, 8 }, + { 3, 6, 1 } + }); + + private static readonly Fast2DArray Expected4x4Matrix = new Fast2DArray( + new uint[4, 4] + { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }); + + private static readonly Fast2DArray Expected8x8Matrix = new Fast2DArray( + new uint[8, 8] + { + { 0, 32, 8, 40, 2, 34, 10, 42 }, + { 48, 16, 56, 24, 50, 18, 58, 26 }, + { 12, 44, 4, 36, 14, 46, 6, 38 }, + { 60, 28, 52, 20, 62, 30, 54, 22 }, + { 3, 35, 11, 43, 1, 33, 9, 41 }, + { 51, 19, 59, 27, 49, 17, 57, 25 }, + { 15, 47, 7, 39, 13, 45, 5, 37 }, + { 63, 31, 55, 23, 61, 29, 53, 21 } + }); + + + [Fact] + public void OrderedDitherFactoryCreatesCorrect2x2Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(2); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect3x3Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(3); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect4x4Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(4); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect8x8Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(8); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs index 3ddf9d0fe..6db1434c6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -11,8 +11,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - using System.Linq; - public class DitherTests : FileTestBase { public static readonly string[] CommonTestImages = @@ -22,27 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData Ditherers = new TheoryData { - { "Bayer8x8", new Bayer8x8Dither() }, - { "Bayer4x4", new Bayer4x4Dither() }, - { "Bayer2x2", new Bayer2x2Dither() } + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } }; public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", new AtkinsonDiffuser() }, - { "Burks", new BurksDiffuser() }, - { "FloydSteinberg", new FloydSteinbergDiffuser() }, - { "JarvisJudiceNinke", new JarvisJudiceNinkeDiffuser() }, - { "Sierra2", new Sierra2Diffuser() }, - { "Sierra3", new Sierra3Diffuser() }, - { "SierraLite", new SierraLiteDiffuser() }, - { "Stucki", new StuckiDiffuser() }, + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, }; - private static IOrderedDither DefaultDitherer => new Bayer4x4Dither(); + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IErrorDiffuser DefaultErrorDiffuser => new AtkinsonDiffuser(); + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization image.DebugSave(provider); } } - + [Theory] [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider)