diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
index 46bafcc0c..9fde27908 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs
@@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Dither(ImageFrame pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
+ public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel
{
- this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true);
- }
-
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
- where TPixel : struct, IPixel
- {
- 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);
}
}
diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
index c538d643c..dabc4e682 100644
--- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
+++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
@@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
///
- /// 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.
///
public interface IErrorDiffuser
{
@@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering
/// The pixel format.
void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY)
where TPixel : struct, IPixel;
-
- ///
- /// Transforms the image applying the dither matrix. This method alters the input pixels array
- ///
- /// The image
- /// The source pixel
- /// The transformed pixel
- /// The column index.
- /// The row index.
- /// The minimum column value.
- /// The minimum row value.
- /// The maximum column value.
- /// The maximum row value.
- ///
- /// 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.
- ///
- /// The pixel format.
- void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel)
- where TPixel : struct, IPixel;
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs b/src/ImageSharp/Dithering/Ordered/Bayer2x2Dither.cs
new file mode 100644
index 000000000..e96a9c4d3
--- /dev/null
+++ b/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
+{
+ ///
+ /// Applies order dithering using the 2x2 Bayer dithering matrix.
+ ///
+ public sealed class Bayer2x2Dither : BayerDither
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Bayer2x2Dither()
+ : base(1)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs b/src/ImageSharp/Dithering/Ordered/Bayer4x4Dither.cs
new file mode 100644
index 000000000..ad72c164f
--- /dev/null
+++ b/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
+{
+ ///
+ /// Applies order dithering using the 4x4 Bayer dithering matrix.
+ ///
+ public sealed class Bayer4x4Dither : BayerDither
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Bayer4x4Dither()
+ : base(2)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs b/src/ImageSharp/Dithering/Ordered/Bayer8x8Dither.cs
new file mode 100644
index 000000000..9077dc2cc
--- /dev/null
+++ b/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
+{
+ ///
+ /// Applies order dithering using the 8x8 Bayer dithering matrix.
+ ///
+ public sealed class Bayer8x8Dither : BayerDither
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Bayer8x8Dither()
+ : base(3)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither.cs
index 685dca5fe..3bac601ae 100644
--- a/src/ImageSharp/Dithering/Ordered/BayerDither.cs
+++ b/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
{
///
- /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
- ///
+ /// Applies order dithering using a Bayer dithering matrix of arbitrary length.
+ ///
///
- public sealed class BayerDither : OrderedDitherBase
+ public class BayerDither : OrderedDitherBase
{
///
- /// 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 class.
///
- private static readonly Fast2DArray ThresholdMatrix =
- new byte[,]
+ ///
+ /// The exponent used to raise the base value (2).
+ /// The value given determines the dimensions of the matrix with each dimension a power of 2. e.g 2^2 = 4, 2^3 = 8
+ ///
+ public BayerDither(uint exponent)
+ : base(ComputeBayer(exponent))
+ {
+ }
+
+ private static Fast2DArray ComputeBayer(uint order)
+ {
+ uint dimension = (uint)(1 << (int)order);
+ var matrix = new Fast2DArray((int)dimension);
+ uint i = 0;
+ for (int y = 0; y < dimension; y++)
{
- { 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++;
+ }
+ }
- ///
- /// Initializes a new instance of the class.
- ///
- 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;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
index 689c9a85b..5d05be370 100644
--- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
+++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
@@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Dithering
{
///
- /// Encapsulates properties and methods required to perfom ordered dithering on an image.
+ /// Encapsulates properties and methods required to perform ordered dithering on an image.
///
public interface IOrderedDither
{
@@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering
/// The source pixel
/// The color to apply to the pixels above the threshold.
/// The color to apply to the pixels below the threshold.
- /// The to pack/unpack to.
- /// The component index to test the threshold against. Must range from 0 to 3.
+ /// The threshold to split the image. Must be between 0 and 1.
/// The column index.
/// The row index.
/// The pixel format.
- void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
+ void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y)
where TPixel : struct, IPixel;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs
deleted file mode 100644
index 12968914d..000000000
--- a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix.
- ///
- ///
- public sealed class OrderedDither : OrderedDitherBase
- {
- ///
- /// The threshold matrix.
- /// This is calculated by multiplying each value in the original matrix by 16
- ///
- private static readonly Fast2DArray ThresholdMatrix =
- new byte[,]
- {
- { 0, 128, 32, 160 },
- { 192, 64, 224, 96 },
- { 48, 176, 16, 144 },
- { 240, 112, 208, 80 }
- };
-
- ///
- /// Initializes a new instance of the class.
- ///
- public OrderedDither()
- : base(ThresholdMatrix)
- {
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
index 818a24d5d..cf7a14239 100644
--- a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs
+++ b/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
{
///
- /// The base class for performing ordered dithering using a 4x4 matrix.
+ /// The base class for performing ordered dithering using a dither matrix.
///
public abstract class OrderedDitherBase : IOrderedDither
{
- ///
- /// The dithering matrix
- ///
- private Fast2DArray matrix;
+ private readonly Fast2DArray matrix;
+ private readonly Fast2DArray thresholdMatrix;
+ private readonly int modulusX;
+ private readonly int modulusY;
///
/// Initializes a new instance of the class.
///
/// The thresholding matrix.
- internal OrderedDitherBase(Fast2DArray matrix)
+ internal OrderedDitherBase(Fast2DArray matrix)
{
this.matrix = matrix;
+ this.modulusX = matrix.Width;
+ this.modulusY = matrix.Height;
+ this.thresholdMatrix = new Fast2DArray(matrix.Width, matrix.Height);
+
+ // Adjust the matrix range for 0-255
+ int multiplier = 256 / (this.modulusX * this.modulusY);
+ for (int y = 0; y < matrix.Height; y++)
+ {
+ for (int x = 0; x < matrix.Width; x++)
+ {
+ this.thresholdMatrix[y, x] = (uint)((matrix[y, x] + 1) * multiplier) - 1;
+ }
+ }
}
///
- public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y)
+ public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y)
where TPixel : struct, IPixel
{
- 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;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs
index 14ac58baf..e0384d208 100644
--- a/src/ImageSharp/Memory/Fast2DArray{T}.cs
+++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs
@@ -28,6 +28,15 @@ namespace SixLabors.ImageSharp.Memory
///
public int Height;
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The length of each dimension.
+ public Fast2DArray(int length)
+ : this(length, length)
+ {
+ }
+
///
/// Initializes a new instance of the struct.
///
@@ -96,7 +105,7 @@ namespace SixLabors.ImageSharp.Memory
///
/// The source array.
///
- /// The represenation on the source data.
+ /// The representation on the source data.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Fast2DArray(T[,] data)
diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs
index f97d3b190..bac05c53d 100644
--- a/src/ImageSharp/PixelFormats/ColorConstants.cs
+++ b/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
{
///
@@ -11,23 +8,17 @@ namespace SixLabors.ImageSharp.PixelFormats
///
public static class ColorConstants
{
- ///
- /// Provides a lazy, one time method of returning the colors.
- ///
- private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors);
-
///
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
///
- public static Rgba32[] WebSafeColors => SafeColors.Value;
+ public static readonly Rgba32[] WebSafeColors = GetWebSafeColors();
///
/// Returns an array of web safe colors.
///
/// The
private static Rgba32[] GetWebSafeColors()
- {
- return new List
+ => new Rgba32[]
{
Rgba32.AliceBlue,
Rgba32.AntiqueWhite,
@@ -171,7 +162,6 @@ namespace SixLabors.ImageSharp.PixelFormats
Rgba32.WhiteSmoke,
Rgba32.Yellow,
Rgba32.YellowGreen
- }.ToArray();
- }
+ };
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
index 45050de72..ccd532bc3 100644
--- a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs
+++ b/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
{
///
@@ -719,5 +721,20 @@ namespace SixLabors.ImageSharp.PixelFormats
/// Represents a matching the W3C definition that has an hex value of #9ACD32.
///
public static readonly TPixel YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255);
+
+ ///
+ /// Represents a matching the W3C definition of web safe colors.
+ ///
+ public static readonly TPixel[] WebSafePalette = GetWebSafePalette();
+
+ private static TPixel[] GetWebSafePalette()
+ {
+ Rgba32[] constants = ColorConstants.WebSafeColors;
+ TPixel[] safe = new TPixel[constants.Length + 1];
+
+ Span constantsBytes = constants.AsSpan().NonPortableCast();
+ PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
+ return safe;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs b/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs
new file mode 100644
index 000000000..eb5008757
--- /dev/null
+++ b/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
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Dithers the image reducing it to two colors using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The .
+ public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle);
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ /// The .
+ public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), rectangle);
+ return source;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Binarization/BinaryDither.cs b/src/ImageSharp/Processing/Binarization/BinaryDither.cs
new file mode 100644
index 000000000..715dff472
--- /dev/null
+++ b/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
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Dithers the image reducing it to two colors using ordered dithering.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The ordered ditherer.
+ /// The .
+ public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using ordered dithering.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The ordered ditherer.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ /// The .
+ public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using ordered dithering.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The ordered ditherer.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle);
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to two colors using ordered dithering.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The ordered ditherer.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle);
+ return source;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs
index 5a165659b..3f86528f5 100644
--- a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs
+++ b/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(threshold), rectangle);
return source;
}
+
+ ///
+ /// Applies binarization to the image splitting the pixels at the given threshold.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ /// The .
+ public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor));
+ return source;
+ }
+
+ ///
+ /// Applies binarization to the image splitting the pixels at the given threshold.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle);
+ return source;
+ }
}
}
diff --git a/src/ImageSharp/Processing/Dithering/Diffuse.cs b/src/ImageSharp/Processing/Dithering/Diffuse.cs
new file mode 100644
index 000000000..e6b82d313
--- /dev/null
+++ b/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
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Dithers the image reducing it to a web-safe palette using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The .
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to a web-safe palette using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle);
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to the given palette using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The palette to select substitute colors from.
+ /// The .
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette));
+ return source;
+ }
+
+ ///
+ /// Dithers the image reducing it to the given palette using error diffusion.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The diffusion algorithm to apply.
+ /// The threshold to apply binarization of the image. Must be between 0 and 1.
+ /// The palette to select substitute colors from.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The .
+ public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle)
+ where TPixel : struct, IPixel
+ {
+ source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle);
+ return source;
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Binarization/Dither.cs b/src/ImageSharp/Processing/Dithering/Dither.cs
similarity index 50%
rename from src/ImageSharp/Processing/Binarization/Dither.cs
rename to src/ImageSharp/Processing/Dithering/Dither.cs
index f21ccf0bd..85fdef24b 100644
--- a/src/ImageSharp/Processing/Binarization/Dither.cs
+++ b/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
{
///
- /// Dithers the image reducing it to two colors using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette using ordered dithering.
///
/// The pixel format.
/// The image this method extends.
@@ -24,27 +23,27 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither)
where TPixel : struct, IPixel
{
- source.ApplyProcessor(new OrderedDitherProcessor(dither, 0));
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither));
return source;
}
///
- /// Dithers the image reducing it to two colors using ordered dithering.
+ /// Dithers the image reducing it to the given palette using ordered dithering.
///
/// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
- /// The component index to test the threshold against. Must range from 0 to 3.
+ /// The palette to select substitute colors from.
/// The .
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, int index)
+ public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette)
where TPixel : struct, IPixel
{
- source.ApplyProcessor(new OrderedDitherProcessor(dither, index));
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette));
return source;
}
///
- /// Dithers the image reducing it to two colors using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette using ordered dithering.
///
/// The pixel format.
/// The image this method extends.
@@ -56,58 +55,25 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle)
where TPixel : struct, IPixel
{
- source.ApplyProcessor(new OrderedDitherProcessor(dither, 0), rectangle);
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle);
return source;
}
///
- /// Dithers the image reducing it to two colors using ordered dithering.
+ /// Dithers the image reducing it to the given palette using ordered dithering.
///
/// The pixel format.
/// The image this method extends.
/// The ordered ditherer.
+ /// The palette to select substitute colors from.
///
/// The structure that specifies the portion of the image object to alter.
///
- /// The component index to test the threshold against. Must range from 0 to 3.
/// The .
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle, int index)
+ public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle)
where TPixel : struct, IPixel
{
- source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle);
- return source;
- }
-
- ///
- /// Dithers the image reducing it to two colors using error diffusion.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The diffusion algorithm to apply.
- /// The threshold to apply binarization of the image. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold)
- where TPixel : struct, IPixel
- {
- source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold));
- return source;
- }
-
- ///
- /// Dithers the image reducing it to two colors using error diffusion.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The diffusion algorithm to apply.
- /// The threshold to apply binarization of the image. Must be between 0 and 1.
- ///
- /// The structure that specifies the portion of the image object to alter.
- ///
- /// The .
- public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
- where TPixel : struct, IPixel
- {
- source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle);
+ source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle);
return source;
}
}
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs
new file mode 100644
index 000000000..70d903f31
--- /dev/null
+++ b/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
+{
+ ///
+ /// Performs binary threshold filtering against an image using error diffusion.
+ ///
+ /// The pixel format.
+ internal class BinaryErrorDiffusionProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser)
+ : this(diffuser, .5F)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ /// The threshold to split the image. Must be between 0 and 1.
+ public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold)
+ : this(diffuser, threshold, NamedColors.White, NamedColors.Black)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ /// The threshold to split the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold.
+ 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;
+ }
+
+ ///
+ /// Gets the error diffuser.
+ ///
+ public IErrorDiffuser Diffuser { get; }
+
+ ///
+ /// Gets the threshold value.
+ ///
+ public float Threshold { get; }
+
+ ///
+ /// Gets the color to use for pixels that are above the threshold.
+ ///
+ public TPixel UpperColor { get; }
+
+ ///
+ /// Gets the color to use for pixels that fall below the threshold.
+ ///
+ public TPixel LowerColor { get; }
+
+ ///
+ protected override void OnApply(ImageFrame 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 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs
new file mode 100644
index 000000000..3cabe378a
--- /dev/null
+++ b/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
+{
+ ///
+ /// Performs binary threshold filtering against an image using ordered dithering.
+ ///
+ /// The pixel format.
+ internal class BinaryOrderedDitherProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ordered ditherer.
+ public BinaryOrderedDitherProcessor(IOrderedDither dither)
+ : this(dither, NamedColors.White, NamedColors.Black)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ordered ditherer.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold.
+ public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor)
+ {
+ Guard.NotNull(dither, nameof(dither));
+
+ this.Dither = dither;
+ this.UpperColor = upperColor;
+ this.LowerColor = lowerColor;
+ }
+
+ ///
+ /// Gets the ditherer.
+ ///
+ public IOrderedDither Dither { get; }
+
+ ///
+ /// Gets the color to use for pixels that are above the threshold.
+ ///
+ public TPixel UpperColor { get; }
+
+ ///
+ /// Gets the color to use for pixels that fall below the threshold.
+ ///
+ public TPixel LowerColor { get; }
+
+ ///
+ protected override void OnApply(ImageFrame 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 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
index 434ed0269..609b09092 100644
--- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
+++ b/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
{
///
- /// An to perform binary threshold filtering against an
- /// . The image will be converted to grayscale before thresholding occurs.
+ /// Performs simple binary threshold filtering against an image.
///
/// The pixel format.
internal class BinaryThresholdProcessor : ImageProcessor
@@ -22,14 +22,22 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
/// The threshold to split the image. Must be between 0 and 1.
public BinaryThresholdProcessor(float threshold)
+ : this(threshold, NamedColors.White, NamedColors.Black)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The threshold to split the image. Must be between 0 and 1.
+ /// The color to use for pixels that are above the threshold.
+ /// The color to use for pixels that are below the threshold.
+ 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.White;
- this.LowerColor = NamedColors.Black;
+ this.UpperColor = upperColor;
+ this.LowerColor = lowerColor;
}
///
@@ -47,55 +55,38 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
public TPixel LowerColor { get; set; }
- ///
- protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration);
- }
-
///
protected override void OnApply(ImageFrame 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 row = source.GetPixelRowSpan(y - startY);
+ Span 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;
}
});
}
diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
deleted file mode 100644
index 01cba15c4..000000000
--- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
+++ /dev/null
@@ -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
-{
- ///
- /// An that dithers an image using error diffusion.
- ///
- /// The pixel format.
- internal class ErrorDiffusionDitherProcessor : ImageProcessor
- where TPixel : struct, IPixel
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The error diffuser
- /// The threshold to split the image. Must be between 0 and 1.
- 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.White;
- this.LowerColor = NamedColors.Black;
- }
-
- ///
- /// Gets the error diffuser.
- ///
- public IErrorDiffuser Diffuser { get; }
-
- ///
- /// Gets the threshold value.
- ///
- public float Threshold { get; }
-
- ///
- /// Gets or sets the color to use for pixels that are above the threshold.
- ///
- public TPixel UpperColor { get; set; }
-
- ///
- /// Gets or sets the color to use for pixels that fall below the threshold.
- ///
- public TPixel LowerColor { get; set; }
-
- ///
- protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration);
- }
-
- ///
- protected override void OnApply(ImageFrame 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 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);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
deleted file mode 100644
index a37d12f18..000000000
--- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
+++ /dev/null
@@ -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
-{
- ///
- /// An that dithers an image using error diffusion.
- ///
- /// The pixel format.
- internal class OrderedDitherProcessor : ImageProcessor
- where TPixel : struct, IPixel
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ordered ditherer.
- /// The component index to test the threshold against. Must range from 0 to 3.
- 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.White;
- this.LowerColor = NamedColors.Black;
- }
-
- ///
- /// Gets the ditherer.
- ///
- public IOrderedDither Dither { get; }
-
- ///
- /// Gets the component index to test the threshold against.
- ///
- public int Index { get; }
-
- ///
- /// Gets or sets the color to use for pixels that are above the threshold.
- ///
- public TPixel UpperColor { get; set; }
-
- ///
- /// Gets or sets the color to use for pixels that fall below the threshold.
- ///
- public TPixel LowerColor { get; set; }
-
- ///
- protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
- {
- new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration);
- }
-
- ///
- protected override void OnApply(ImageFrame 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 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);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs
new file mode 100644
index 000000000..f8ff475d1
--- /dev/null
+++ b/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
+{
+ ///
+ /// An that dithers an image using error diffusion.
+ ///
+ /// The pixel format.
+ internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser)
+ : this(diffuser, .5F)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ /// The threshold to split the image. Must be between 0 and 1.
+ public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold)
+ : this(diffuser, threshold, NamedColors.WebSafePalette)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error diffuser
+ /// The threshold to split the image. Must be between 0 and 1.
+ /// The palette to select substitute colors from.
+ 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;
+ }
+
+ ///
+ /// Gets the error diffuser.
+ ///
+ public IErrorDiffuser Diffuser { get; }
+
+ ///
+ /// Gets the threshold value.
+ ///
+ public float Threshold { get; }
+
+ ///
+ protected override void OnApply(ImageFrame 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 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 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs
new file mode 100644
index 000000000..49455928a
--- /dev/null
+++ b/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
+{
+ ///
+ /// An 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.
+ ///
+ /// The pixel format.
+ internal class OrderedDitherPaletteProcessor : PaletteDitherProcessorBase
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ordered ditherer.
+ public OrderedDitherPaletteProcessor(IOrderedDither dither)
+ : this(dither, NamedColors.WebSafePalette)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ordered ditherer.
+ /// The palette to select substitute colors from.
+ public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette)
+ : base(palette)
+ {
+ Guard.NotNull(dither, nameof(dither));
+ this.Dither = dither;
+ }
+
+ ///
+ /// Gets the ditherer.
+ ///
+ public IOrderedDither Dither { get; }
+
+ ///
+ protected override void OnApply(ImageFrame 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 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 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs
new file mode 100644
index 000000000..c6b80293c
--- /dev/null
+++ b/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
+{
+ ///
+ /// The base class for dither and diffusion processors that consume a palette.
+ ///
+ internal abstract class PaletteDitherProcessorBase : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ private readonly Dictionary> cache = new Dictionary>();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The palette to select substitute colors from.
+ public PaletteDitherProcessorBase(TPixel[] palette)
+ {
+ Guard.NotNull(palette, nameof(palette));
+ this.Palette = palette;
+ }
+
+ ///
+ /// Gets the palette to select substitute colors from.
+ ///
+ public TPixel[] Palette { get; }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected PixelPair 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(closest, secondClosest);
+ this.cache.Add(pixel, pair);
+
+ return pair;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs
new file mode 100644
index 000000000..e3b9c11bd
--- /dev/null
+++ b/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
+{
+ ///
+ /// Represents a composite pair of pixels. Used for caching color distance lookups.
+ ///
+ /// The pixel format.
+ internal struct PixelPair : IEquatable>
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The first pixel color
+ /// The second pixel color
+ public PixelPair(TPixel first, TPixel second)
+ {
+ this.First = first;
+ this.Second = second;
+ }
+
+ ///
+ /// Gets the first pixel color
+ ///
+ public TPixel First { get; }
+
+ ///
+ /// Gets the second pixel color
+ ///
+ public TPixel Second { get; }
+
+ ///
+ public bool Equals(PixelPair other)
+ => this.First.Equals(other.First) && this.Second.Equals(other.Second);
+
+ ///
+ public override bool Equals(object obj)
+ => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second);
+
+ ///
+ public override int GetHashCode()
+ => HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode());
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
index 2d97f6584..fcd7b2e8f 100644
--- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs
@@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors
{
///
- /// 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
///
/// The pixel format.
internal class GrayscaleBt709Processor : FilterProcessor
diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
index d646a680e..8b8db6177 100644
--- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
+++ b/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;
diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
index 0b95c09a6..cd1b4b07b 100644
--- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
@@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Quantizers
{
///
/// 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.
///
///
/// The pixel format.
@@ -31,27 +32,20 @@ namespace SixLabors.ImageSharp.Quantizers
///
/// Initializes a new instance of the class.
///
- ///
- /// The color palette. If none is given this will default to the web safe colors defined
- /// in the CSS Color Module Level 4.
- ///
+ public PaletteQuantizer()
+ : this(NamedColors.WebSafePalette)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The palette to select substitute colors from.
public PaletteQuantizer(TPixel[] palette = null)
: base(true)
{
- if (palette == null)
- {
- Rgba32[] constants = ColorConstants.WebSafeColors;
- TPixel[] safe = new TPixel[constants.Length + 1];
-
- Span constantsBytes = constants.AsSpan().NonPortableCast();
-
- PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length);
- this.colors = safe;
- }
- else
- {
- this.colors = palette;
- }
+ Guard.NotNull(palette, nameof(palette));
+ this.colors = palette;
}
///
@@ -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;
diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
index f08114574..ce2a71da4 100644
--- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
index 221b4a9bf..488b3d18b 100644
--- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs
+++ b/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 p = this.Verify>();
Assert.Equal(.23f, p.Threshold);
+ Assert.Equal(NamedColors.White, p.UpperColor);
+ Assert.Equal(NamedColors.Black, p.LowerColor);
}
[Fact]
public void BinaryThreshold_rect_CorrectProcessor()
{
this.operations.BinaryThreshold(.93f, this.rect);
- var p = this.Verify>(this.rect);
+ BinaryThresholdProcessor p = this.Verify>(this.rect);
Assert.Equal(.93f, p.Threshold);
+ Assert.Equal(NamedColors.White, p.UpperColor);
+ Assert.Equal(NamedColors.Black, p.LowerColor);
+ }
+
+ [Fact]
+ public void BinaryThreshold_CorrectProcessorWithUpperLower()
+ {
+ this.operations.BinaryThreshold(.23f, NamedColors.HotPink, NamedColors.Yellow);
+ BinaryThresholdProcessor p = this.Verify>();
+ Assert.Equal(.23f, p.Threshold);
+ Assert.Equal(NamedColors.HotPink, p.UpperColor);
+ Assert.Equal(NamedColors.Yellow, p.LowerColor);
+ }
+
+ [Fact]
+ public void BinaryThreshold_rect_CorrectProcessorWithUpperLower()
+ {
+ this.operations.BinaryThreshold(.93f, NamedColors.HotPink, NamedColors.Yellow, this.rect);
+ BinaryThresholdProcessor p = this.Verify>(this.rect);
+ Assert.Equal(.93f, p.Threshold);
+ Assert.Equal(NamedColors.HotPink, p.UpperColor);
+ Assert.Equal(NamedColors.Yellow, p.LowerColor);
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs
index 94241d007..ba5cb0cf3 100644
--- a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs
+++ b/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>();
+ this.operations.BinaryDither(this.orderedDither);
+ BinaryOrderedDitherProcessor p = this.Verify>();
Assert.Equal(this.orderedDither, p.Dither);
- Assert.Equal(0, p.Index);
+ Assert.Equal(NamedColors.White, p.UpperColor);
+ Assert.Equal(NamedColors