diff --git a/src/ImageSharp.Processing/Binarization/Dither.cs b/src/ImageSharp.Processing/Binarization/Dither.cs
new file mode 100644
index 000000000..f481ac4df
--- /dev/null
+++ b/src/ImageSharp.Processing/Binarization/Dither.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ using ImageSharp.Dithering;
+ using ImageSharp.Processing.Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Alters the alpha component of the image.
+ ///
+ /// 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 Image Dither(this Image source, IErrorDiffuser diffuser, float threshold)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ return Dither(source, diffuser, threshold, source.Bounds);
+ }
+
+ ///
+ /// Alters the alpha component of the image.
+ ///
+ /// 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 Image Dither(this Image source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle);
+ return source;
+ }
+ }
+}
diff --git a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs
index 2eb5225f8..cb3758748 100644
--- a/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs
+++ b/src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs
@@ -20,28 +20,26 @@ namespace ImageSharp.Processing.Processors
/// Initializes a new instance of the class.
///
/// The threshold to split the image. Must be between 0 and 1.
- ///
- /// is less than 0 or is greater than 1.
- ///
public BinaryThresholdProcessor(float threshold)
{
- // TODO: Check limit.
+ // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties.
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
- this.Value = threshold;
+ this.Threshold = threshold;
+ // Default to white/black for upper/lower.
TColor upper = default(TColor);
- upper.PackFromVector4(Color.White.ToVector4());
+ upper.PackFromBytes(255, 255, 255, 255);
this.UpperColor = upper;
TColor lower = default(TColor);
- lower.PackFromVector4(Color.Black.ToVector4());
+ lower.PackFromBytes(0, 0, 0, 255);
this.LowerColor = lower;
}
///
/// Gets the threshold value.
///
- public float Value { get; }
+ public float Threshold { get; }
///
/// Gets or sets the color to use for pixels that are above the threshold.
@@ -62,7 +60,7 @@ namespace ImageSharp.Processing.Processors
///
protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
{
- float threshold = this.Value;
+ float threshold = this.Threshold;
TColor upper = this.UpperColor;
TColor lower = this.LowerColor;
diff --git a/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
new file mode 100644
index 000000000..c5b78b639
--- /dev/null
+++ b/src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
@@ -0,0 +1,111 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Processing.Processors
+{
+ using System;
+
+ using ImageSharp.Dithering;
+
+ ///
+ /// An that dithers an image using error diffusion.
+ ///
+ /// The pixel format.
+ public class ErrorDiffusionDitherProcessor : ImageProcessor
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ ///
+ /// 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));
+
+ // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties.
+ Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
+
+ this.Diffuser = diffuser;
+ this.Threshold = threshold;
+
+ // Default to white/black for upper/lower.
+ TColor upper = default(TColor);
+ upper.PackFromBytes(255, 255, 255, 255);
+ this.UpperColor = upper;
+
+ TColor lower = default(TColor);
+ lower.PackFromBytes(0, 0, 0, 255);
+ this.LowerColor = lower;
+ }
+
+ ///
+ /// 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 TColor UpperColor { get; set; }
+
+ ///
+ /// Gets or sets the color to use for pixels that fall below the threshold.
+ ///
+ public TColor LowerColor { get; set; }
+
+ ///
+ protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle)
+ {
+ new GrayscaleBt709Processor().Apply(source, sourceRectangle);
+ }
+
+ ///
+ protected override void OnApply(ImageBase source, Rectangle sourceRectangle)
+ {
+ 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;
+ }
+
+ if (minY > 0)
+ {
+ startY = 0;
+ }
+
+ using (PixelAccessor sourcePixels = source.Lock())
+ {
+ for (int y = minY; y < maxY; y++)
+ {
+ int offsetY = y - startY;
+ for (int x = minX; x < maxX; x++)
+ {
+ int offsetX = x - startX;
+ TColor sourceColor = sourcePixels[offsetX, offsetY];
+ TColor transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor;
+ this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
index 97bd34def..7e47501f3 100644
--- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
+++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
@@ -111,26 +111,27 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files)
{
- Image image = file.CreateImage();
-
- using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif"))
+ using (Image image = file.CreateImage())
{
- image.SaveAsGif(output);
- }
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp"))
+ {
+ image.SaveAsBmp(output);
+ }
- using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.bmp"))
- {
- image.SaveAsBmp(output);
- }
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg"))
+ {
+ image.SaveAsJpeg(output);
+ }
- using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.jpg"))
- {
- image.SaveAsJpeg(output);
- }
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
+ {
+ image.SaveAsPng(output);
+ }
- using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
- {
- image.SaveAsPng(output);
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.gif"))
+ {
+ image.SaveAsGif(output);
+ }
}
}
}
@@ -142,22 +143,25 @@ namespace ImageSharp.Tests
foreach (TestFile file in Files)
{
- Image image = file.CreateImage();
-
byte[] serialized;
- using (MemoryStream memoryStream = new MemoryStream())
+ using (Image image = file.CreateImage())
{
- image.Save(memoryStream);
- memoryStream.Flush();
- serialized = memoryStream.ToArray();
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ image.Save(memoryStream);
+ memoryStream.Flush();
+ serialized = memoryStream.ToArray();
+ }
}
using (MemoryStream memoryStream = new MemoryStream(serialized))
{
- Image image2 = new Image(memoryStream);
- using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
+ using (Image image2 = new Image(memoryStream))
{
- image2.Save(output);
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
+ {
+ image2.Save(output);
+ }
}
}
}
diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs
new file mode 100644
index 000000000..3de2481e5
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System.IO;
+
+ using ImageSharp.Dithering;
+
+ using Xunit;
+
+ public class DitherTest : FileTestBase
+ {
+ [Fact]
+ public void ImageShouldApplyDitherFilter()
+ {
+ string path = this.CreateOutputDirectory("Dither");
+
+ foreach (TestFile file in Files)
+ {
+ using (Image image = file.CreateImage())
+ using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
+ {
+ image.Dither(new SierraLite(), .5F).Save(output);
+ }
+ }
+ }
+
+ [Fact]
+ public void ImageShouldApplyDitherFilterInBox()
+ {
+ string path = this.CreateOutputDirectory("Dither");
+
+ foreach (TestFile file in Files)
+ {
+ string filename = file.GetFileName("-InBox");
+ using (Image image = file.CreateImage())
+ using (FileStream output = File.OpenWrite($"{path}/{filename}"))
+ {
+ image.Dither(new SierraLite(), .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file