Browse Source

Add error diffusion binerization

af/merge-core
James Jackson-South 9 years ago
parent
commit
6d9d6616fc
  1. 50
      src/ImageSharp.Processing/Binarization/Dither.cs
  2. 16
      src/ImageSharp.Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  3. 111
      src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  4. 54
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  5. 47
      tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs

50
src/ImageSharp.Processing/Binarization/Dither.cs

@ -0,0 +1,50 @@
// <copyright file="Dither.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using ImageSharp.Dithering;
using ImageSharp.Processing.Processors;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Alters the alpha component of the image.
/// </summary>
/// <typeparam name="TColor">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{TColor}"/>.</returns>
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IErrorDiffuser diffuser, float threshold)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
return Dither(source, diffuser, threshold, source.Bounds);
}
/// <summary>
/// Alters the alpha component of the image.
/// </summary>
/// <typeparam name="TColor">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"/>.</returns>
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
source.ApplyProcessor(new ErrorDiffusionDitherProcessor<TColor>(diffuser, threshold), rectangle);
return source;
}
}
}

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

@ -20,28 +20,26 @@ namespace ImageSharp.Processing.Processors
/// Initializes a new instance of the <see cref="BinaryThresholdProcessor{TColor}"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="System.ArgumentException">
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
/// </exception>
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;
}
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Value { get; }
public float Threshold { get; }
/// <summary>
/// Gets or sets the color to use for pixels that are above the threshold.
@ -62,7 +60,7 @@ namespace ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
float threshold = this.Value;
float threshold = this.Threshold;
TColor upper = this.UpperColor;
TColor lower = this.LowerColor;

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

@ -0,0 +1,111 @@
// <copyright file="ErrorDiffusionDitherProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Processing.Processors
{
using System;
using ImageSharp.Dithering;
/// <summary>
/// An <see cref="IImageProcessor{TColor}"/> that dithers an image using error diffusion.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public class ErrorDiffusionDitherProcessor<TColor> : ImageProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TColor}"/> 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));
// 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;
}
/// <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 TColor UpperColor { get; set; }
/// <summary>
/// Gets or sets the color to use for pixels that fall below the threshold.
/// </summary>
public TColor LowerColor { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
new GrayscaleBt709Processor<TColor>().Apply(source, sourceRectangle);
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> 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<TColor> 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);
}
}
}
}
}
}

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

47
tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs

@ -0,0 +1,47 @@
// <copyright file="DitherTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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);
}
}
}
}
}
Loading…
Cancel
Save