Browse Source

Refactor and fix dithering API + algorithm.

af/merge-core
James Jackson-South 8 years ago
parent
commit
789e52c367
  1. 18
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
  2. 24
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  3. 19
      src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs
  4. 19
      src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs
  5. 19
      src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs
  6. 60
      src/ImageSharp/Dithering/Ordered/BayerDither.cs
  7. 7
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  8. 36
      src/ImageSharp/Dithering/Ordered/OrderedDither.cs
  9. 49
      src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
  10. 11
      src/ImageSharp/Memory/Fast2DArray{T}.cs
  11. 18
      src/ImageSharp/PixelFormats/ColorConstants.cs
  12. 17
      src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
  13. 86
      src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs
  14. 82
      src/ImageSharp/Processing/Binarization/BinaryDither.cs
  15. 36
      src/ImageSharp/Processing/Binarization/BinaryThreshold.cs
  16. 84
      src/ImageSharp/Processing/Dithering/Diffuse.cs
  17. 58
      src/ImageSharp/Processing/Dithering/Dither.cs
  18. 123
      src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
  19. 103
      src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs
  20. 73
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  21. 85
      src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  22. 93
      src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
  23. 113
      src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
  24. 93
      src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
  25. 76
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
  26. 49
      src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs
  27. 2
      src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
  28. 2
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  29. 32
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  30. 2
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  31. 28
      tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
  32. 70
      tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs
  33. 19
      tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs

18
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();
@ -117,6 +105,8 @@ namespace SixLabors.ImageSharp.Dithering.Base
var offsetColor = pixel.ToVector4();
Vector4 result = ((error * coefficient) / this.divisorVector) + offsetColor;
// result.W = offsetColor.W;
pixel.PackFromVector4(result);
}
}

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>;
}
}
}

19
src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.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 Bayer2x2Dither : BayerDither
{
/// <summary>
/// Initializes a new instance of the <see cref="Bayer2x2Dither"/> class.
/// </summary>
public Bayer2x2Dither()
: base(1)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.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 Bayer4x4Dither : BayerDither
{
/// <summary>
/// Initializes a new instance of the <see cref="Bayer4x4Dither"/> class.
/// </summary>
public Bayer4x4Dither()
: base(2)
{
}
}
}

19
src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.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 Bayer8x8Dither : BayerDither
{
/// <summary>
/// Initializes a new instance of the <see cref="Bayer8x8Dither"/> class.
/// </summary>
public Bayer8x8Dither()
: base(3)
{
}
}
}

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

@ -1,36 +1,60 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Dithering.Base;
using System.Runtime.CompilerServices;
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"/>
/// Applies order dithering using a Bayer dithering matrix of arbitrary length.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
public sealed class BayerDither : OrderedDitherBase
public class BayerDither : OrderedDitherBase
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// Initializes a new instance of the <see cref="BayerDither"/> class.
/// </summary>
private static readonly Fast2DArray<byte> ThresholdMatrix =
new byte[,]
/// <param name="exponent">
/// The exponent used to raise the base value (2).
/// The value given determines the dimensions of the matrix with each dimension a power of 2. e.g 2^2 = 4, 2^3 = 8
/// </param>
public BayerDither(uint exponent)
: base(ComputeBayer(exponent))
{
}
private static Fast2DArray<uint> ComputeBayer(uint order)
{
uint dimension = (uint)(1 << (int)order);
var matrix = new Fast2DArray<uint>((int)dimension);
uint i = 0;
for (int y = 0; y < dimension; y++)
{
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
};
for (int x = 0; x < dimension; x++)
{
matrix[y, x] = Bayer(i / dimension, i % dimension, order);
i++;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="BayerDither"/> class.
/// </summary>
public BayerDither()
: base(ThresholdMatrix)
return matrix;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint Bayer(uint x, uint y, uint order)
{
uint res = 0;
for (uint i = 0; i < order; ++i)
{
uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1);
uint xOdd = x & 1;
res = ((res << 1 | xOdd_XOR_yOdd) << 1) | xOdd;
x >>= 1;
y >>= 1;
}
return res;
}
}
}

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, byte threshold, int x, int y)
where TPixel : struct, IPixel<TPixel>;
}
}

36
src/ImageSharp/Dithering/Ordered/OrderedDither.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 ordered dithering matrix.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
public sealed class OrderedDither : OrderedDitherBase
{
/// <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 }
};
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
/// </summary>
public OrderedDither()
: base(ThresholdMatrix)
{
}
}
}

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

@ -1,53 +1,48 @@
// 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
namespace SixLabors.ImageSharp.Dithering
{
/// <summary>
/// The base class for performing ordered dithering using a 4x4 matrix.
/// The base class for performing ordered dithering using a dither matrix.
/// </summary>
public abstract class OrderedDitherBase : IOrderedDither
{
/// <summary>
/// The dithering matrix
/// </summary>
private Fast2DArray<byte> matrix;
private readonly Fast2DArray<uint> matrix;
private readonly Fast2DArray<uint> thresholdMatrix;
private readonly int modulusX;
private readonly int modulusY;
/// <summary>
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
/// </summary>
/// <param name="matrix">The thresholding matrix. </param>
internal OrderedDitherBase(Fast2DArray<byte> matrix)
internal OrderedDitherBase(Fast2DArray<uint> matrix)
{
this.matrix = matrix;
this.modulusX = matrix.Width;
this.modulusY = matrix.Height;
this.thresholdMatrix = new Fast2DArray<uint>(matrix.Width, matrix.Height);
// Adjust the matrix range for 0-255
int multiplier = 256 / (this.modulusX * this.modulusY);
for (int y = 0; y < matrix.Height; y++)
{
for (int x = 0; x < matrix.Width; x++)
{
this.thresholdMatrix[y, x] = (uint)((matrix[y, x] + 1) * multiplier) - 1;
}
}
}
/// <inheritdoc />
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte threshold, 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.");
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper;
}
}
}

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

@ -28,6 +28,15 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public int Height;
/// <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>
@ -96,7 +105,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();
}
};
}
}
}

17
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>
@ -719,5 +721,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>
/// Represents a <see cref="T:TPixel[]"/> matching the W3C definition of web safe colors.
/// </summary>
public static readonly TPixel[] WebSafePalette = GetWebSafePalette();
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
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
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 = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
// 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
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
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 = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
// 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
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
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 = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
// Setup the previous pointer
previousPixel = sourcePixel;
}
TPixel transformedPixel = luminance >= threshold ? pair.First : pair.Second;
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
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
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 = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255);
// Setup the previous pointer
previousPixel = sourcePixel;
}
this.Dither.Dither(source, sourcePixel, pair.First, pair.Second, luminance, x, y);
}
}
}
}
}

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

@ -0,0 +1,76 @@
// 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];
var tempVector = temp.ToVector4();
float distance = Vector4.Distance(vector, tempVector);
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/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;

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);
}
}
}

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

@ -5,7 +5,6 @@ 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
@ -23,55 +22,84 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization
[Fact]
public void Dither_CorrectProcessor()
{
this.operations.Dither(orderedDither);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
this.operations.BinaryDither(this.orderedDither);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>();
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void Dither_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
this.operations.BinaryDither(this.orderedDither, this.rect);
BinaryOrderedDitherProcessor<Rgba32> p = this.Verify<BinaryOrderedDitherProcessor<Rgba32>>(this.rect);
Assert.Equal(this.orderedDither, p.Dither);
Assert.Equal(0, p.Index);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void Dither_index_CorrectProcessor()
{
this.operations.Dither(orderedDither, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>();
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(2, p.Index);
Assert.Equal(NamedColors<Rgba32>.Yellow, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void Dither_index_rect_CorrectProcessor()
{
this.operations.Dither(orderedDither, this.rect, 2);
var p = this.Verify<OrderedDitherProcessor<Rgba32>>(this.rect);
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(2, p.Index);
Assert.Equal(NamedColors<Rgba32>.HotPink, p.LowerColor);
}
[Fact]
public void Dither_ErrorDifuser_CorrectProcessor()
public void Dither_ErrorDiffuser_CorrectProcessor()
{
this.operations.Dither(errorDiffuser, 4);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>();
this.operations.BinaryDiffuse(this.errorDiffuser, .4F);
BinaryErrorDiffusionProcessor<Rgba32> p = this.Verify<BinaryErrorDiffusionProcessor<Rgba32>>();
Assert.Equal(this.errorDiffuser, p.Diffuser);
Assert.Equal(4, p.Threshold);
Assert.Equal(.4F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void Dither_ErrorDifuser_rect_CorrectProcessor()
public void Dither_ErrorDiffuser_rect_CorrectProcessor()
{
this.operations.Dither(this.errorDiffuser, 3, this.rect);
var p = this.Verify<ErrorDiffusionDitherProcessor<Rgba32>>(this.rect);
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(3, p.Threshold);
Assert.Equal(.3F, p.Threshold);
Assert.Equal(NamedColors<Rgba32>.White, p.UpperColor);
Assert.Equal(NamedColors<Rgba32>.Black, p.LowerColor);
}
[Fact]
public void Dither_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 Dither_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);
}
}
}

19
tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs

@ -22,8 +22,9 @@ 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", new Bayer8x8Dither() },
{ "Bayer4x4", new Bayer4x4Dither() },
{ "Bayer2x2", new Bayer2x2Dither() }
};
public static readonly TheoryData<string, IErrorDiffuser> ErrorDiffusers = new TheoryData<string, IErrorDiffuser>
@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
};
private static IOrderedDither DefaultDitherer => new OrderedDither();
private static IOrderedDither DefaultDitherer => new Bayer4x4Dither();
private static IErrorDiffuser DefaultErrorDiffuser => new AtkinsonDiffuser();
@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(ditherer));
image.Mutate(x => x.BinaryDither(ditherer));
image.DebugSave(provider, name);
}
}
@ -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.BinaryDiffuse(diffuser, .5F));
image.DebugSave(provider, name);
}
}
@ -76,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Dither(DefaultDitherer));
image.Mutate(x => x.BinaryDither(DefaultDitherer));
image.DebugSave(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.BinaryDiffuse(DefaultErrorDiffuser, 0.5f));
image.DebugSave(provider);
}
}
@ -103,7 +104,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(DefaultDitherer, bounds));
image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);
@ -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.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds));
image.DebugSave(provider);
ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds);

Loading…
Cancel
Save