Browse Source

Add ordered dithering

pull/106/head
James Jackson-South 9 years ago
parent
commit
04b1e6dece
  1. 36
      src/ImageSharp.Processing/Binarization/Dither.cs
  2. 3
      src/ImageSharp.Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  3. 119
      src/ImageSharp.Processing/Processors/Binarization/OrderedDitherProcessor.cs
  4. 2
      src/ImageSharp/Colors/PackedPixel/Alpha8.cs
  5. 0
      src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs
  6. 0
      src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs
  7. 0
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
  8. 0
      src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs
  9. 0
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  10. 0
      src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs
  11. 0
      src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs
  12. 0
      src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs
  13. 0
      src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs
  14. 0
      src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs
  15. 38
      src/ImageSharp/Dithering/Ordered/Bayer.cs
  16. 37
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  17. 38
      src/ImageSharp/Dithering/Ordered/Ordered.cs
  18. 1
      tests/ImageSharp.Tests/Colors/PackedPixelTests.cs
  19. 38
      tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs

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

@ -16,7 +16,39 @@ namespace ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// Alters the alpha component of the image.
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TColor">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>
/// <returns>The <see cref="Image{TColor}"/>.</returns>
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IOrderedDither dither, int index = 0)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
return Dither(source, dither, source.Bounds, index);
}
/// <summary>
/// Dithers the image reducing it to two colors using ordered dithering.
/// </summary>
/// <typeparam name="TColor">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>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image<TColor> Dither<TColor>(this Image<TColor> source, IOrderedDither dither, Rectangle rectangle, int index = 0)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
source.ApplyProcessor(new OrderedDitherProcessor<TColor>(dither, index), rectangle);
return source;
}
/// <summary>
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -30,7 +62,7 @@ namespace ImageSharp
}
/// <summary>
/// Alters the alpha component of the image.
/// Dithers the image reducing it to two colors using error diffusion.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>

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

@ -25,9 +25,6 @@ namespace ImageSharp.Processing.Processors
{
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;

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

@ -0,0 +1,119 @@
// <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 System.Buffers;
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 OrderedDitherProcessor<TColor> : ImageProcessor<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TColor}"/> 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(TColor) == typeof(Alpha8))
{
index = 3;
}
this.Dither = dither;
this.Index = index;
// 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 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 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;
byte[] bytes = ArrayPool<byte>.Shared.Rent(4);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TColor sourceColor = sourcePixels[offsetX, offsetY];
this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY);
}
ArrayPool<byte>.Shared.Return(bytes);
}
}
}
}
}

2
src/ImageSharp/Colors/PackedPixel/Alpha8.cs

@ -10,7 +10,7 @@ namespace ImageSharp
using System.Runtime.CompilerServices;
/// <summary>
/// Packed pixel type containing a single 8 bit normalized W values that is ranging from 0 to 1.
/// Packed pixel type containing a single 8 bit normalized W values ranging from 0 to 1.
/// </summary>
public struct Alpha8 : IPackedPixel<byte>, IEquatable<Alpha8>
{

0
src/ImageSharp/Dithering/Atkinson.cs → src/ImageSharp/Dithering/ErrorDiffusion/Atkinson.cs

0
src/ImageSharp/Dithering/Burks.cs → src/ImageSharp/Dithering/ErrorDiffusion/Burks.cs

0
src/ImageSharp/Dithering/ErrorDiffuser.cs → src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs

0
src/ImageSharp/Dithering/FloydSteinberg.cs → src/ImageSharp/Dithering/ErrorDiffusion/FloydSteinberg.cs

0
src/ImageSharp/Dithering/IErrorDiffuser.cs → src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

0
src/ImageSharp/Dithering/JarvisJudiceNinke.cs → src/ImageSharp/Dithering/ErrorDiffusion/JarvisJudiceNinke.cs

0
src/ImageSharp/Dithering/Sierra2.cs → src/ImageSharp/Dithering/ErrorDiffusion/Sierra2.cs

0
src/ImageSharp/Dithering/Sierra3.cs → src/ImageSharp/Dithering/ErrorDiffusion/Sierra3.cs

0
src/ImageSharp/Dithering/SierraLite.cs → src/ImageSharp/Dithering/ErrorDiffusion/SierraLite.cs

0
src/ImageSharp/Dithering/Stucki.cs → src/ImageSharp/Dithering/ErrorDiffusion/Stucki.cs

38
src/ImageSharp/Dithering/Ordered/Bayer.cs

@ -0,0 +1,38 @@
// <copyright file="Bayer.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering.Ordered
{
using System;
/// <summary>
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Bayer : IOrderedDither
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
/// </summary>
private static readonly byte[,] ThresholdMatrix = {
{ 15, 143, 47, 175 },
{ 207, 79, 239, 111 },
{ 63, 191, 31, 159 },
{ 255, 127, 223, 95 }
};
/// <inheritdoc />
public byte[,] Matrix { get; } = ThresholdMatrix;
/// <inheritdoc />
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
source.ToXyzwBytes(bytes, 0);
pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower;
}
}
}

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

@ -0,0 +1,37 @@
// <copyright file="IOrderedDither.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
using System;
/// <summary>
/// Encapsulates properties and methods required to perfom ordered dithering on an image.
/// </summary>
public interface IOrderedDither
{
/// <summary>
/// Gets the dithering matrix
/// </summary>
byte[,] Matrix { get; }
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <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="bytes">The byte array to pack/unpack to. Must have a length of 4. Bytes are unpacked to Xyzw order.</param>
/// <param name="index">The component index to test the threshold against. Must range from 0 to 3.</param>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <typeparam name="TColor">The pixel format.</typeparam>
void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>;
}
}

38
src/ImageSharp/Dithering/Ordered/Ordered.cs

@ -0,0 +1,38 @@
// <copyright file="Ordered.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering.Ordered
{
using System;
/// <summary>
/// Applies error diffusion based dithering using the 4x4 ordered dithering matrix.
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
/// </summary>
public class Ordered : IOrderedDither
{
/// <summary>
/// The threshold matrix.
/// This is calculated by multiplying each value in the original matrix by 16
/// </summary>
private static readonly byte[,] ThresholdMatrix = {
{ 0, 128, 32, 160 },
{ 192, 64, 224, 96 },
{ 48, 176, 16, 144 },
{ 240, 112, 208, 80 }
};
/// <inheritdoc />
public byte[,] Matrix { get; } = ThresholdMatrix;
/// <inheritdoc />
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
source.ToXyzwBytes(bytes, 0);
pixels[x, y] = ThresholdMatrix[x % 3, y % 3] >= bytes[index] ? upper : lower;
}
}
}

1
tests/ImageSharp.Tests/Colors/PackedPixelTests.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Tests.Colors
{
using System;
using System.Diagnostics;
using System.Numerics;
using Xunit;

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

@ -8,6 +8,7 @@ namespace ImageSharp.Tests
using System.IO;
using ImageSharp.Dithering;
using ImageSharp.Dithering.Ordered;
using Xunit;
@ -16,14 +17,14 @@ namespace ImageSharp.Tests
[Fact]
public void ImageShouldApplyDitherFilter()
{
string path = this.CreateOutputDirectory("Dither");
string path = this.CreateOutputDirectory("Dither", "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);
image.Dither(new Bayer()).Save(output);
}
}
}
@ -31,7 +32,38 @@ namespace ImageSharp.Tests
[Fact]
public void ImageShouldApplyDitherFilterInBox()
{
string path = this.CreateOutputDirectory("Dither");
string path = this.CreateOutputDirectory("Dither", "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 Bayer(), new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output);
}
}
}
[Fact]
public void ImageShouldApplyDiffusionFilter()
{
string path = this.CreateOutputDirectory("Dither", "Diffusion");
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 ImageShouldApplyDiffusionFilterInBox()
{
string path = this.CreateOutputDirectory("Dither", "Diffusion");
foreach (TestFile file in Files)
{

Loading…
Cancel
Save