Browse Source

Merge pull request #456 from SixLabors/js/dither-all-the-things

Refactor thresholding/dithering API's + bug fixes
af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
af9aeb46be
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
  2. 24
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  3. 56
      src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs
  4. 34
      src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs
  5. 36
      src/ImageSharp/Dithering/Ordered/BayerDither.cs
  6. 19
      src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs
  7. 19
      src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs
  8. 19
      src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs
  9. 7
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  10. 31
      src/ImageSharp/Dithering/Ordered/KnownDitherers.cs
  11. 50
      src/ImageSharp/Dithering/Ordered/OrderedDither.cs
  12. 19
      src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs
  13. 53
      src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
  14. 94
      src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs
  15. 58
      src/ImageSharp/Dithering/error_diffusion.txt
  16. 22
      src/ImageSharp/Memory/Fast2DArray{T}.cs
  17. 18
      src/ImageSharp/PixelFormats/ColorConstants.cs
  18. 22
      src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
  19. 86
      src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs
  20. 82
      src/ImageSharp/Processing/Binarization/BinaryDither.cs
  21. 36
      src/ImageSharp/Processing/Binarization/BinaryThreshold.cs
  22. 84
      src/ImageSharp/Processing/Dithering/Diffuse.cs
  23. 58
      src/ImageSharp/Processing/Dithering/Dither.cs
  24. 123
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
  25. 103
      src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs
  26. 73
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  27. 85
      src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  28. 93
      src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
  29. 113
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
  30. 93
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
  31. 75
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
  32. 49
      src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs
  33. 2
      src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
  34. 2
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  35. 32
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  36. 2
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  37. 2
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  38. 12
      tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs
  39. 3
      tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs
  40. 105
      tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs
  41. 28
      tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
  42. 77
      tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs
  43. 102
      tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs
  44. 104
      tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs
  45. 131
      tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs
  46. 39
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

16
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs

@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>
{
this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
image[x, y] = transformed;
}
image[x, y] = transformed;
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();

24
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Encapsulates properties and methods required to perfom diffused error dithering on an image.
/// Encapsulates properties and methods required to perform diffused error dithering on an image.
/// </summary>
public interface IErrorDiffuser
{
@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="minX">The minimum column value.</param>
/// <param name="minY">The minimum row value.</param>
/// <param name="maxX">The maximum column value.</param>
/// <param name="maxY">The maximum row value.</param>
/// <param name="replacePixel">
/// Whether to replace the pixel at the given coordinates with the transformed value.
/// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
/// </param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
where TPixel : struct, IPixel<TPixel>;
}
}
}

56
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
{
/// <summary>
/// Contains reusable static instances of known error diffusion algorithms
/// </summary>
public static class KnownDiffusers
{
/// <summary>
/// Gets the error diffuser that implements the Atkinson algorithm.
/// </summary>
public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Burks algorithm.
/// </summary>
public static IErrorDiffuser Burks { get; } = new BurksDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Floyd-Steinberg algorithm.
/// </summary>
public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm.
/// </summary>
public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-2 algorithm.
/// </summary>
public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-3 algorithm.
/// </summary>
public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser();
/// <summary>
/// Gets the error diffuser that implements the Sierra-Lite algorithm.
/// </summary>
public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stevenson-Arce algorithm.
/// </summary>
public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser();
/// <summary>
/// Gets the error diffuser that implements the Stucki algorithm.
/// </summary>
public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser();
}
}

34
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
{
/// <summary>
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
/// </summary>
public sealed class StevensonArceDiffuser : ErrorDiffuserBase
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly Fast2DArray<float> 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 }
};
/// <summary>
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
/// </summary>
public StevensonArceDiffuser()
: base(StevensonArceMatrix, 200)
{
}
}
}

36
src/ImageSharp/Dithering/Ordered/BayerDither.cs

@ -1,36 +0,0 @@
// 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
{
/// <summary>
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public sealed class BayerDither : OrderedDitherBase
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
};
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither"/> class.
/// </summary>
public BayerDither()
: base(ThresholdMatrix)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 2x2 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither2x2 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither2x2"/> class.
/// </summary>
public BayerDither2x2()
: base(2)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 4x4 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither4x4 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither4x4"/> class.
/// </summary>
public BayerDither4x4()
: base(4)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies order dithering using the 8x8 Bayer dithering matrix.
/// </summary>
public sealed class BayerDither8x8 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither8x8"/> class.
/// </summary>
public BayerDither8x8()
: base(8)
{
}
}
}

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

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Encapsulates properties and methods required to perfom ordered dithering on an image.
/// Encapsulates properties and methods required to perform ordered dithering on an image.
/// </summary>
public interface IOrderedDither
{
@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering
/// <param name="source">The source pixel</param>
/// <param name="upper">The color to apply to the pixels above the threshold.</param>
/// <param name="lower">The color to apply to the pixels below the threshold.</param>
/// <param name="rgba">The <see cref="Rgba32"/> to pack/unpack to.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>;
}
}

31
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
{
/// <summary>
/// Contains reusable static instances of known ordered dither matrices
/// </summary>
public class KnownDitherers
{
/// <summary>
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2();
/// <summary>
/// Gets the order ditherer using the 3x3 dithering matrix
/// </summary>
public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3();
/// <summary>
/// Gets the order ditherer using the 4x4 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4();
/// <summary>
/// Gets the order ditherer using the 8x8 Bayer dithering matrix
/// </summary>
public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8();
}
}

50
src/ImageSharp/Dithering/Ordered/OrderedDither.cs

@ -1,36 +1,50 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering.Base;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the 4x4 ordered dithering matrix.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// An ordered dithering matrix with equal sides of arbitrary length
/// </summary>
public sealed class OrderedDither : OrderedDitherBase
public class OrderedDither : IOrderedDither
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
{
{ 0, 128, 32, 160 },
{ 192, 64, 224, 96 },
{ 48, 176, 16, 144 },
{ 240, 112, 208, 80 }
};
private readonly Fast2DArray<uint> thresholdMatrix;
private readonly int modulusX;
private readonly int modulusY;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
/// </summary>
public OrderedDither()
: base(ThresholdMatrix)
/// <param name="length">The length of the matrix sides</param>
public OrderedDither(uint length)
{
Fast2DArray<uint> 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;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>
{
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper;
}
}
}

19
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
{
/// <summary>
/// Applies order dithering using the 3x3 dithering matrix.
/// </summary>
public sealed class OrderedDither3x3 : OrderedDither
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither3x3"/> class.
/// </summary>
public OrderedDither3x3()
: base(3)
{
}
}
}

53
src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs

@ -1,53 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering.Base
{
/// <summary>
/// The base class for performing ordered dithering using a 4x4 matrix.
/// </summary>
public abstract class OrderedDitherBase : IOrderedDither
{
/// <summary>
/// The dithering matrix
/// </summary>
private Fast2DArray<byte> matrix;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
/// </summary>
/// <param name="matrix">The thresholding matrix. </param>
internal OrderedDitherBase(Fast2DArray<byte> matrix)
{
this.matrix = matrix;
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
where TPixel : struct, IPixel<TPixel>
{
source.ToRgba32(ref rgba);
switch (index)
{
case 0:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.R ? lower : upper;
return;
case 1:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.G ? lower : upper;
return;
case 2:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.B ? lower : upper;
return;
case 3:
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.A ? lower : upper;
return;
}
throw new ArgumentOutOfRangeException(nameof(index), "Index should be between 0 and 3 inclusive.");
}
}
}

94
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
{
/// <summary>
/// A factory for creating ordered dither matrices.
/// </summary>
internal static class OrderedDitherFactory
{
/// <summary>
/// Creates an ordered dithering matrix with equal sides of arbitrary length.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
/// <param name="length">The length of the matrix sides</param>
/// <returns>The <see cref="Fast2DArray{T}"/></returns>
public static Fast2DArray<uint> 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<uint>((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;
}
}
}

58
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

22
src/ImageSharp/Memory/Fast2DArray{T}.cs

@ -28,6 +28,20 @@ namespace SixLabors.ImageSharp.Memory
/// </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>
@ -41,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];
}
/// <summary>
@ -57,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++)
{
@ -96,7 +112,7 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
/// <param name="data">The source array.</param>
/// <returns>
/// The <see cref="Fast2DArray{T}"/> represenation on the source data.
/// The <see cref="Fast2DArray{T}"/> representation on the source data.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Fast2DArray<T>(T[,] data)

18
src/ImageSharp/PixelFormats/ColorConstants.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
@ -11,23 +8,17 @@ namespace SixLabors.ImageSharp.PixelFormats
/// </summary>
public static class ColorConstants
{
/// <summary>
/// Provides a lazy, one time method of returning the colors.
/// </summary>
private static readonly Lazy<Rgba32[]> SafeColors = new Lazy<Rgba32[]>(GetWebSafeColors);
/// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary>
public static Rgba32[] WebSafeColors => SafeColors.Value;
public static readonly Rgba32[] WebSafeColors = GetWebSafeColors();
/// <summary>
/// Returns an array of web safe colors.
/// </summary>
/// <returns>The <see cref="T:Color[]"/></returns>
private static Rgba32[] GetWebSafeColors()
{
return new List<Rgba32>
=> new Rgba32[]
{
Rgba32.AliceBlue,
Rgba32.AntiqueWhite,
@ -171,7 +162,6 @@ namespace SixLabors.ImageSharp.PixelFormats
Rgba32.WhiteSmoke,
Rgba32.Yellow,
Rgba32.YellowGreen
}.ToArray();
}
};
}
}
}

22
src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
@ -10,6 +12,11 @@ namespace SixLabors.ImageSharp.PixelFormats
public static class NamedColors<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Thread-safe backing field for <see cref="WebSafePalette"/>.
/// </summary>
private static readonly Lazy<TPixel[]> WebSafePaletteLazy = new Lazy<TPixel[]>(GetWebSafePalette, true);
/// <summary>
/// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
@ -719,5 +726,20 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Represents a <see paramref="TPixel"/> matching the W3C definition that has an hex value of #9ACD32.
/// </summary>
public static readonly TPixel YellowGreen = ColorBuilder<TPixel>.FromRGBA(154, 205, 50, 255);
/// <summary>
/// Gets a <see cref="T:TPixel[]"/> matching the W3C definition of web safe colors.
/// </summary>
public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value;
private static TPixel[] GetWebSafePalette()
{
Rgba32[] constants = ColorConstants.WebSafeColors;
TPixel[] safe = new TPixel[constants.Length + 1];
Span<byte> constantsBytes = constants.AsSpan().NonPortableCast<Rgba32, byte>();
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
return safe;
}
}
}

86
src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs

@ -0,0 +1,86 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor), rectangle);
return source;
}
}
}

82
src/ImageSharp/Processing/Binarization/BinaryDither.cs

@ -0,0 +1,82 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor), rectangle);
return source;
}
}
}

36
src/ImageSharp/Processing/Binarization/BinaryThreshold.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
@ -43,5 +42,40 @@ namespace SixLabors.ImageSharp
source.ApplyProcessor(new BinaryThresholdProcessor<TPixel>(threshold), rectangle);
return source;
}
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float threshold, TPixel upperColor, TPixel lowerColor)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryThresholdProcessor<TPixel>(threshold, upperColor, lowerColor));
return source;
}
/// <summary>
/// Applies binarization to the image splitting the pixels at the given threshold.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> BinaryThreshold<TPixel>(this IImageProcessingContext<TPixel> source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BinaryThresholdProcessor<TPixel>(threshold, upperColor, lowerColor), rectangle);
return source;
}
}
}

84
src/ImageSharp/Processing/Dithering/Diffuse.cs

@ -0,0 +1,84 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to a web-safe palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to a web-safe palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to the given palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette));
return source;
}
/// <summary>
/// Dithers the image reducing it to the given palette using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette), rectangle);
return source;
}
}
}

58
src/ImageSharp/Processing/Binarization/Dither.cs → src/ImageSharp/Processing/Dithering/Dither.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -24,27 +23,27 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, 0));
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to the given palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, int index)
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel[] palette)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, index));
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither, palette));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -56,58 +55,25 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, 0), rectangle);
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// Dithers the image reducing it to the given palette using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle, int index)
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new OrderedDitherProcessor<TPixel>(dither, index), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TPixel>(diffuser, threshold));
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="diffuser">The diffusion algorithm to apply.</param>
/// <param name="threshold">The threshold to apply binarization of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Dither<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TPixel>(diffuser, threshold), rectangle);
source.ApplyProcessor(new OrderedDitherPaletteProcessor<TPixel>(dither, palette), rectangle);
return source;
}
}

123
src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs

@ -0,0 +1,123 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Performs binary threshold filtering against an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser)
: this(diffuser, .5F)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold)
: this(diffuser, threshold, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor)
{
Guard.NotNull(diffuser, nameof(diffuser));
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Diffuser = diffuser;
this.Threshold = threshold;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <summary>
/// Gets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; }
/// <summary>
/// Gets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
float threshold = this.Threshold * 255F;
var rgba = default(Rgba32);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
TPixel sourcePixel = source[startX, startY];
TPixel previousPixel = sourcePixel;
sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if (!previousPixel.Equals(sourcePixel))
{
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
// Setup the previous pointer
previousPixel = sourcePixel;
}
TPixel transformedPixel = luminance >= threshold ? this.UpperColor : this.LowerColor;
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY);
}
}
}
}
}

103
src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs

@ -0,0 +1,103 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Performs binary threshold filtering against an image using ordered dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryOrderedDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
public BinaryOrderedDitherProcessor(IOrderedDither dither)
: this(dither, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
{
Guard.NotNull(dither, nameof(dither));
this.Dither = dither;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <summary>
/// Gets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; }
/// <summary>
/// Gets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
var rgba = default(Rgba32);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
TPixel sourcePixel = source[startX, startY];
TPixel previousPixel = sourcePixel;
sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if (!previousPixel.Equals(sourcePixel))
{
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
// Setup the previous pointer
previousPixel = sourcePixel;
}
this.Dither.Dither(source, sourcePixel, this.UpperColor, this.LowerColor, luminance, x, y);
}
}
}
}
}

73
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs

@ -4,14 +4,14 @@
using System;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> to perform binary threshold filtering against an
/// <see cref="Image{TPixel}"/>. The image will be converted to grayscale before thresholding occurs.
/// Performs simple binary threshold filtering against an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BinaryThresholdProcessor<TPixel> : ImageProcessor<TPixel>
@ -22,14 +22,22 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public BinaryThresholdProcessor(float threshold)
: this(threshold, NamedColors<TPixel>.White, NamedColors<TPixel>.Black)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor{TPixel}"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
public BinaryThresholdProcessor(float threshold, TPixel upperColor, TPixel lowerColor)
{
// TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties.
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Threshold = threshold;
// Default to white/black for upper/lower.
this.UpperColor = NamedColors<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
this.UpperColor = upperColor;
this.LowerColor = lowerColor;
}
/// <summary>
@ -47,55 +55,38 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
float threshold = this.Threshold;
float threshold = this.Threshold * 255F;
TPixel upper = this.UpperColor;
TPixel lower = this.LowerColor;
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
// Align start/end positions.
int minX = Math.Max(0, startX);
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
if (minY > 0)
{
startY = 0;
}
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
Parallel.For(
minY,
maxY,
startY,
endY,
configuration.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetPixelRowSpan(y - startY);
Span<TPixel> row = source.GetPixelRowSpan(y);
var rgba = default(Rgba32);
for (int x = minX; x < maxX; x++)
for (int x = startX; x < endX; x++)
{
ref TPixel color = ref row[x - startX];
ref TPixel color = ref row[x];
color.ToRgba32(ref rgba);
// Any channel will do since it's Grayscale.
color = color.ToVector4().X >= threshold ? upper : lower;
// Convert to grayscale using ITU-R Recommendation BT.709 if required
float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
color = luminance >= threshold ? upper : lower;
}
});
}

85
src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs

@ -1,85 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ErrorDiffusionDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public ErrorDiffusionDitherProcessor(IErrorDiffuser diffuser, float threshold)
{
Guard.NotNull(diffuser, nameof(diffuser));
this.Diffuser = diffuser;
this.Threshold = threshold;
// Default to white/black for upper/lower.
this.UpperColor = NamedColors<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <summary>
/// Gets or sets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; set; }
/// <summary>
/// Gets or sets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
TPixel sourceColor = row[x];
TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor;
this.Diffuser.Dither(source, sourceColor, transformedColor, x, y, startX, startY, endX, endY);
}
}
}
}
}

93
src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs

@ -1,93 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class OrderedDitherProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
public OrderedDitherProcessor(IOrderedDither dither, int index)
{
Guard.NotNull(dither, nameof(dither));
Guard.MustBeBetweenOrEqualTo(index, 0, 3, nameof(index));
// Alpha8 only stores the pixel data in the alpha channel.
if (typeof(TPixel) == typeof(Alpha8))
{
index = 3;
}
this.Dither = dither;
this.Index = index;
// Default to white/black for upper/lower.
this.UpperColor = NamedColors<TPixel>.White;
this.LowerColor = NamedColors<TPixel>.Black;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <summary>
/// Gets the component index to test the threshold against.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets or sets the color to use for pixels that are above the threshold.
/// </summary>
public TPixel UpperColor { get; set; }
/// <summary>
/// Gets or sets the color to use for pixels that fall below the threshold.
/// </summary>
public TPixel LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
var rgba = default(Rgba32);
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
TPixel sourceColor = row[x];
this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, ref rgba, this.Index, x, y);
}
}
}
}
}

113
src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs

@ -0,0 +1,113 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser)
: this(diffuser, .5F)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold)
: this(diffuser, threshold, NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="diffuser">The error diffuser</param>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette)
: base(palette)
{
Guard.NotNull(diffuser, nameof(diffuser));
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Diffuser = diffuser;
this.Threshold = threshold;
}
/// <summary>
/// Gets the error diffuser.
/// </summary>
public IErrorDiffuser Diffuser { get; }
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Threshold { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
float threshold = this.Threshold * 255F;
var rgba = default(Rgba32);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
TPixel sourcePixel = source[startX, startY];
TPixel previousPixel = sourcePixel;
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if (!previousPixel.Equals(sourcePixel))
{
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
// Setup the previous pointer
previousPixel = sourcePixel;
}
TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First;
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY);
}
}
}
}
}

93
src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs

@ -0,0 +1,93 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
/// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class OrderedDitherPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
public OrderedDitherPaletteProcessor(IOrderedDither dither)
: this(dither, NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
/// </summary>
/// <param name="dither">The ordered ditherer.</param>
/// <param name="palette">The palette to select substitute colors from.</param>
public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette)
: base(palette)
{
Guard.NotNull(dither, nameof(dither));
this.Dither = dither;
}
/// <summary>
/// Gets the ditherer.
/// </summary>
public IOrderedDither Dither { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
var rgba = default(Rgba32);
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8);
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
int startY = interest.Y;
int endY = interest.Bottom;
int startX = interest.X;
int endX = interest.Right;
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
TPixel sourcePixel = source[startX, startY];
TPixel previousPixel = sourcePixel;
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
sourcePixel.ToRgba32(ref rgba);
// Convert to grayscale using ITU-R Recommendation BT.709 if required
float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
for (int y = startY; y < endY; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y);
for (int x = startX; x < endX; x++)
{
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
if (!previousPixel.Equals(sourcePixel))
{
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette);
sourcePixel.ToRgba32(ref rgba);
luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B);
// Setup the previous pointer
previousPixel = sourcePixel;
}
this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y);
}
}
}
}
}

75
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// The base class for dither and diffusion processors that consume a palette.
/// </summary>
internal abstract class PaletteDitherProcessorBase<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>();
/// <summary>
/// Initializes a new instance of the <see cref="PaletteDitherProcessorBase{TPixel}"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
public PaletteDitherProcessorBase(TPixel[] palette)
{
Guard.NotNull(palette, nameof(palette));
this.Palette = palette;
}
/// <summary>
/// Gets the palette to select substitute colors from.
/// </summary>
public TPixel[] Palette { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected PixelPair<TPixel> GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette)
{
// Check if the color is in the lookup table
if (this.cache.ContainsKey(pixel))
{
return this.cache[pixel];
}
// Not found - loop through the palette and find the nearest match.
float leastDistance = int.MaxValue;
float secondLeastDistance = int.MaxValue;
var vector = pixel.ToVector4();
var closest = default(TPixel);
var secondClosest = default(TPixel);
for (int index = 0; index < colorPalette.Length; index++)
{
TPixel temp = colorPalette[index];
float distance = Vector4.DistanceSquared(vector, temp.ToVector4());
if (distance < leastDistance)
{
leastDistance = distance;
secondClosest = closest;
closest = temp;
}
else if (distance < secondLeastDistance)
{
secondLeastDistance = distance;
secondClosest = temp;
}
}
// Pop it into the cache for next time
var pair = new PixelPair<TPixel>(closest, secondClosest);
this.cache.Add(pixel, pair);
return pair;
}
}
}

49
src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Represents a composite pair of pixels. Used for caching color distance lookups.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal struct PixelPair<TPixel> : IEquatable<PixelPair<TPixel>>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelPair{TPixel}"/> struct.
/// </summary>
/// <param name="first">The first pixel color</param>
/// <param name="second">The second pixel color</param>
public PixelPair(TPixel first, TPixel second)
{
this.First = first;
this.Second = second;
}
/// <summary>
/// Gets the first pixel color
/// </summary>
public TPixel First { get; }
/// <summary>
/// Gets the second pixel color
/// </summary>
public TPixel Second { get; }
/// <inheritdoc/>
public bool Equals(PixelPair<TPixel> other)
=> this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is PixelPair<TPixel> other && this.First.Equals(other.First) && this.Second.Equals(other.Second);
/// <inheritdoc/>
public override int GetHashCode()
=> HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode());
}
}

2
src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Applies a greyscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709
/// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class GrayscaleBt709Processor<TPixel> : FilterProcessor<TPixel>

2
src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs

@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.Dither)
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false);
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height);
}
output[(y * source.Width) + x] = pixelValue;

32
src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs

@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Quantizers
{
/// <summary>
/// Encapsulates methods to create a quantized image based upon the given palette.
/// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -31,27 +32,20 @@ namespace SixLabors.ImageSharp.Quantizers
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">
/// The color palette. If none is given this will default to the web safe colors defined
/// in the CSS Color Module Level 4.
/// </param>
public PaletteQuantizer()
: this(NamedColors<TPixel>.WebSafePalette)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="palette">The palette to select substitute colors from.</param>
public PaletteQuantizer(TPixel[] palette = null)
: base(true)
{
if (palette == null)
{
Rgba32[] constants = ColorConstants.WebSafeColors;
TPixel[] safe = new TPixel[constants.Length + 1];
Span<byte> constantsBytes = constants.AsSpan().NonPortableCast<Rgba32, byte>();
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
this.colors = safe;
}
else
{
this.colors = palette;
}
Guard.NotNull(palette, nameof(palette));
this.colors = palette;
}
/// <inheritdoc/>
@ -102,7 +96,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.Dither)
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false);
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height);
}
output[(y * source.Width) + x] = pixelValue;

2
src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs

@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base
public bool Dither { get; set; } = true;
/// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();
public IErrorDiffuser DitherType { get; set; } = KnownDiffusers.FloydSteinberg;
/// <inheritdoc/>
public virtual QuantizedImage<TPixel> Quantize(ImageFrame<TPixel> image, int maxColors)

2
src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.Dither)
{
// Apply the dithering matrix. We have to reapply the value now as the original has changed.
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false);
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height);
}
output[(y * source.Width) + x] = pixelValue;

12
tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentNullException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(null);
var fast = new Fast2DArray<float>(null);
});
}
@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(0, 10);
var fast = new Fast2DArray<float>(0, 10);
});
}
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(10, 0);
var fast = new Fast2DArray<float>(10, 0);
});
}
@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Memory
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
Fast2DArray<float> fast = new Fast2DArray<float>(new float[0, 0]);
var fast = new Fast2DArray<float>(new float[0, 0]);
});
}
[Fact]
public void Fast2DArrayReturnsCorrectDimensions()
{
Fast2DArray<float> fast = new Fast2DArray<float>(FloydSteinbergMatrix);
var fast = new Fast2DArray<float>(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<float> fast = new Fast2DArray<float>(4, 4);
var fast = new Fast2DArray<float>(4, 4);
const float Val = 5F;
fast[3, 3] = Val;

3
tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs

@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Tests
get
{
var result = new TheoryData<string>();
foreach (string name in typeof(NamedColors<Rgba32>).GetTypeInfo().GetFields().Select(x => x.Name ))
foreach (string name in typeof(NamedColors<Rgba32>).GetTypeInfo()
.GetFields().Where(x => x.Name != nameof(NamedColors<Rgba32>.WebSafePalette)).Select(x => x.Name))
{
result.Add(name);
}

105
tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs

@ -0,0 +1,105 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class BinaryDitherTest : BaseImageOperationsExtensionTest
{
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
public BinaryDitherTest()
{
this.orderedDither = KnownDitherers.BayerDither4x4;
this.errorDiffuser = KnownDiffusers.FloydSteinberg;
}
[Fact]
public void BinaryDither_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_rect_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, this.rect);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_index_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, NamedColors<Rgba32>.Yellow, NamedColors<Rgba32>.HotPink);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void BinaryDither_index_rect_CorrectProcessor()
{
this.operations.BinaryDither(this.orderedDither, NamedColors<Rgba32>.Yellow, NamedColors<Rgba32>.HotPink, this.rect);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_CorrectProcessor()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .4F);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
[Fact]
public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors()
{
this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow, this.rect);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
}
}

28
tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs

@ -15,16 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
public void BinaryThreshold_CorrectProcessor()
{
this.operations.BinaryThreshold(.23f);
var p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryThreshold_rect_CorrectProcessor()
{
this.operations.BinaryThreshold(.93f, this.rect);
var p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void BinaryThreshold_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.23f, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>();
Assert.Equal(.23f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
[Fact]
public void BinaryThreshold_rect_CorrectProcessorWithUpperLower()
{
this.operations.BinaryThreshold(.93f, NamedColors<Rgba32>.HotPink, NamedColors<Rgba32>.Yellow, this.rect);
BinaryThresholdProcessor<Rgba32> p = this.Verify<BinaryThresholdProcessor<Rgba32>>(this.rect);
Assert.Equal(.93f, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.LowerColor);
}
}
}

77
tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs

@ -1,77 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using Moq;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class DitherTest : BaseImageOperationsExtensionTest
{
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
public DitherTest()
{
this.orderedDither = new Mock<IOrderedDither>().Object;
this.errorDiffuser = new Mock<IErrorDiffuser>().Object;
}
[Fact]
public void Dither_CorrectProcessor()
{
this.operations.Dither(orderedDither);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(orderedDither, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(2, p.Index);
}
[Fact]
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(2, p.Index);
}
[Fact]
public void Dither_ErrorDifuser_CorrectProcessor()
{
this.operations.Dither(errorDiffuser, 4);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(4, p.Threshold);
}
[Fact]
public void Dither_ErrorDifuser_rect_CorrectProcessor()
{
this.operations.Dither(this.errorDiffuser, 3, this.rect);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(3, p.Threshold);
}
}
}

102
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<uint> Expected2x2Matrix = new Fast2DArray<uint>(
new uint[2, 2]
{
{ 0, 2 },
{ 3, 1 }
});
private static readonly Fast2DArray<uint> Expected3x3Matrix = new Fast2DArray<uint>(
new uint[3, 3]
{
{ 0, 5, 2 },
{ 7, 4, 8 },
{ 3, 6, 1 }
});
private static readonly Fast2DArray<uint> Expected4x4Matrix = new Fast2DArray<uint>(
new uint[4, 4]
{
{ 0, 8, 2, 10 },
{ 12, 4, 14, 6 },
{ 3, 11, 1, 9 },
{ 15, 7, 13, 5 }
});
private static readonly Fast2DArray<uint> Expected8x8Matrix = new Fast2DArray<uint>(
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<uint> 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<uint> 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<uint> 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<uint> 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]);
}
}
}
}
}

104
tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Binarization
{
public class DitherTest : BaseImageOperationsExtensionTest
{
private readonly IOrderedDither orderedDither;
private readonly IErrorDiffuser errorDiffuser;
private readonly Rgba32[] TestPalette =
{
Rgba32.Red,
Rgba32.Green,
Rgba32.Blue
};
public DitherTest()
{
this.orderedDither = KnownDitherers.BayerDither4x4;
this.errorDiffuser = KnownDiffusers.FloydSteinberg;
}
[Fact]
public void Dither_CorrectProcessor()
{
this.operations.Dither(this.orderedDither);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.rect);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(this.orderedDither, this.TestPalette, this.rect);
OrderedDitherPaletteProcessor<Rgba32> p = this.Verify<OrderedDitherPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .4F);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.4F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.Diffuse(this.errorDiffuser, .3F, this.rect);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.3F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.WebSafePalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);
}
[Fact]
public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors()
{
this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect);
ErrorDiffusionPaletteProcessor<Rgba32> p = this.Verify<ErrorDiffusionPaletteProcessor<Rgba32>>(this.rect);
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(.5F, p.Threshold);
Assert.Equal(this.TestPalette, p.Palette);
}
}
}

131
tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs

@ -0,0 +1,131 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
public class BinaryDitherTests : FileTestBase
{
public static readonly string[] CommonTestImages =
{
TestImages.Png.CalliphoraPartial, TestImages.Png.Bike
};
public static readonly TheoryData<string, IOrderedDither> Ditherers = new TheoryData<string, IOrderedDither>
{
{ "Bayer8x8", KnownDitherers.BayerDither8x8 },
{ "Bayer4x4", KnownDitherers.BayerDither4x4 },
{ "Ordered3x3", KnownDitherers.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherers.BayerDither2x2 }
};
public static readonly TheoryData<string, IErrorDiffuser> ErrorDiffusers = new TheoryData<string, IErrorDiffuser>
{
{ "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 => KnownDitherers.BayerDither4x4;
private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson;
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)]
[WithTestPatternImages(nameof(Ditherers), 100, 100, DefaultPixelType)]
public void BinaryDitherFilter_WorksWithAllDitherers<TPixel>(TestImageProvider<TPixel> provider, string name, IOrderedDither ditherer)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDither(ditherer));
image.DebugSave(provider, name);
}
}
[Theory]
[WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)]
[WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)]
public void DiffusionFilter_WorksWithAllErrorDiffusers<TPixel>(TestImageProvider<TPixel> provider, string name, IErrorDiffuser diffuser)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDiffuse(diffuser, .5F));
image.DebugSave(provider, name);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDither(DefaultDitherer));
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void DiffusionFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f));
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)]
public void ApplyDitherFilterInBox<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
}
}
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)]
public void ApplyDiffusionFilterInBox<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> source = provider.GetImage())
using (Image<TPixel> image = source.Clone())
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
}
}
}
}

39
tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs → tests/ImageSharp.Tests/Processing/Processors/Dithering/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,26 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
public static readonly TheoryData<string, IOrderedDither> Ditherers = new TheoryData<string, IOrderedDither>
{
{ "Ordered", new OrderedDither() },
{ "Bayer", new BayerDither() }
{ "Bayer8x8", KnownDitherers.BayerDither8x8 },
{ "Bayer4x4", KnownDitherers.BayerDither4x4 },
{ "Ordered3x3", KnownDitherers.OrderedDither3x3 },
{ "Bayer2x2", KnownDitherers.BayerDither2x2 }
};
public static readonly TheoryData<string, IErrorDiffuser> ErrorDiffusers = new TheoryData<string, IErrorDiffuser>
{
{ "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 OrderedDither();
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)]
@ -64,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(diffuser, .5F));
image.Mutate(x => x.Diffuse(diffuser, .5F));
image.DebugSave(provider, name);
}
}
@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
image.DebugSave(provider);
}
}
[Theory]
[WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)]
public void DiffusionFilter_ShouldNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(DefaultErrorDiffuser, 0.5f));
image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f));
image.DebugSave(provider);
}
}
@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2);
image.Mutate(x => x.Dither(DefaultErrorDiffuser, .5F, bounds));
image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, .5F, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
Loading…
Cancel
Save