Browse Source

Add dithering to quantizers. FIx #15

af/merge-core
James Jackson-South 9 years ago
parent
commit
f2d507eab5
  1. 32
      src/ImageSharp/Dithering/Atkinson.cs
  2. 31
      src/ImageSharp/Dithering/Burks.cs
  3. 107
      src/ImageSharp/Dithering/ErrorDiffusion.cs
  4. 31
      src/ImageSharp/Dithering/FloydSteinberg.cs
  5. 34
      src/ImageSharp/Dithering/IErrorDiffusion.cs
  6. 32
      src/ImageSharp/Dithering/JarvisJudiceNinke.cs
  7. 31
      src/ImageSharp/Dithering/Sierra2.cs
  8. 32
      src/ImageSharp/Dithering/Sierra3.cs
  9. 31
      src/ImageSharp/Dithering/SierraLite.cs
  10. 32
      src/ImageSharp/Dithering/Stucki.cs
  11. 20
      src/ImageSharp/Quantizers/IQuantizer.cs
  12. 79
      src/ImageSharp/Quantizers/Octree/Quantizer.cs
  13. 2
      src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs

32
src/ImageSharp/Dithering/Atkinson.cs

@ -0,0 +1,32 @@
// <copyright file="Atkinson.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Atkinson image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Atkinson : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] AtkinsonMatrix =
{
{ 0, 0, 1, 1 },
{ 1, 1, 1, 0 },
{ 0, 1, 0, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Atkinson"/> class.
/// </summary>
public Atkinson()
: base(AtkinsonMatrix, 8)
{
}
}
}

31
src/ImageSharp/Dithering/Burks.cs

@ -0,0 +1,31 @@
// <copyright file="Burks.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Burks image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Burks : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] BurksMatrix =
{
{ 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Burks"/> class.
/// </summary>
public Burks()
: base(BurksMatrix, 32)
{
}
}
}

107
src/ImageSharp/Dithering/ErrorDiffusion.cs

@ -0,0 +1,107 @@
// <copyright file="ErrorDiffusion.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;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// The base class for performing effor diffusion based dithering.
/// </summary>
public abstract class ErrorDiffusion : IErrorDiffusion
{
/// <summary>
/// The vector to perform division.
/// </summary>
private readonly Vector4 divisorVector;
/// <summary>
/// The matrix width
/// </summary>
private readonly byte matrixHeight;
/// <summary>
/// The matrix height
/// </summary>
private readonly byte matrixWidth;
/// <summary>
/// The offset at which to start the dithering operation.
/// </summary>
private readonly int startingOffset;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorDiffusion"/> class.
/// </summary>
/// <param name="matrix">The dithering matrix.</param>
/// <param name="divisor">The divisor.</param>
protected ErrorDiffusion(byte[,] matrix, byte divisor)
{
Guard.NotNull(matrix, nameof(matrix));
Guard.MustBeGreaterThan(divisor, 0, nameof(divisor));
this.Matrix = matrix;
this.matrixWidth = (byte)(matrix.GetUpperBound(1) + 1);
this.matrixHeight = (byte)(matrix.GetUpperBound(0) + 1);
this.divisorVector = new Vector4(divisor);
this.startingOffset = 0;
for (int i = 0; i < this.matrixWidth; i++)
{
if (matrix[0, i] != 0)
{
this.startingOffset = (byte)(i - 1);
break;
}
}
}
/// <inheritdoc />
public byte[,] Matrix { get; }
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TColor>(PixelAccessor<TColor> pixels, TColor source, TColor transformed, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
// Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4();
// Loop through and distribute the error amongst neighbouring pixels.
for (int row = 0; row < this.matrixHeight; row++)
{
int matrixY = y + row;
for (int col = 0; col < this.matrixWidth; col++)
{
int matrixX = x + (col - this.startingOffset);
if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height)
{
byte coefficient = this.Matrix[row, col];
if (coefficient == 0)
{
continue;
}
Vector4 coefficientVector = new Vector4(this.Matrix[row, col]);
Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4();
Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor;
result.W = offsetColor.W;
TColor packed = default(TColor);
packed.PackFromVector4(result);
pixels[matrixX, matrixY] = packed;
}
}
}
}
}
}

31
src/ImageSharp/Dithering/FloydSteinberg.cs

@ -0,0 +1,31 @@
// <copyright file="FloydSteinberg.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class FloydSteinberg : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] FloydSteinbergMatrix =
{
{ 0, 0, 7 },
{ 3, 5, 1 }
};
/// <summary>
/// Initializes a new instance of the <see cref="FloydSteinberg"/> class.
/// </summary>
public FloydSteinberg()
: base(FloydSteinbergMatrix, 16)
{
}
}
}

34
src/ImageSharp/Dithering/IErrorDiffusion.cs

@ -0,0 +1,34 @@
// <copyright file="IErrorDiffusion.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 diffused error dithering on an image.
/// </summary>
public interface IErrorDiffusion
{
/// <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="transformed">The transformed pixel</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 transformed, int x, int y, int width, int height)
where TColor : struct, IPackedPixel, IEquatable<TColor>;
}
}

32
src/ImageSharp/Dithering/JarvisJudiceNinke.cs

@ -0,0 +1,32 @@
// <copyright file="JarvisJudiceNinke.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class JarvisJudiceNinke : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] JarvisJudiceNinkeMatrix =
{
{ 0, 0, 0, 7, 5 },
{ 3, 5, 7, 5, 3 },
{ 1, 3, 5, 3, 1 }
};
/// <summary>
/// Initializes a new instance of the <see cref="JarvisJudiceNinke"/> class.
/// </summary>
public JarvisJudiceNinke()
: base(JarvisJudiceNinkeMatrix, 48)
{
}
}
}

31
src/ImageSharp/Dithering/Sierra2.cs

@ -0,0 +1,31 @@
// <copyright file="Sierra2.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Sierra2 image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Sierra2 : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] Sierra2Matrix =
{
{ 0, 0, 0, 4, 3 },
{ 1, 2, 3, 2, 1 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra2"/> class.
/// </summary>
public Sierra2()
: base(Sierra2Matrix, 16)
{
}
}
}

32
src/ImageSharp/Dithering/Sierra3.cs

@ -0,0 +1,32 @@
// <copyright file="Sierra3.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Sierra3 image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Sierra3 : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] Sierra3Matrix =
{
{ 0, 0, 0, 5, 3 },
{ 2, 4, 5, 4, 2 },
{ 0, 2, 3, 2, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Sierra3"/> class.
/// </summary>
public Sierra3()
: base(Sierra3Matrix, 32)
{
}
}
}

31
src/ImageSharp/Dithering/SierraLite.cs

@ -0,0 +1,31 @@
// <copyright file="SierraLite.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the SierraLite image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class SierraLite : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] SierraLiteMatrix =
{
{ 0, 0, 2 },
{ 1, 1, 0 }
};
/// <summary>
/// Initializes a new instance of the <see cref="SierraLite"/> class.
/// </summary>
public SierraLite()
: base(SierraLiteMatrix, 4)
{
}
}
}

32
src/ImageSharp/Dithering/Stucki.cs

@ -0,0 +1,32 @@
// <copyright file="Stucki.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Dithering
{
/// <summary>
/// Applies error diffusion based dithering using the Stucki image dithering algorithm.
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
/// </summary>
public class Stucki : ErrorDiffusion
{
/// <summary>
/// The diffusion matrix
/// </summary>
private static readonly byte[,] StuckiMatrix =
{
{ 0, 0, 0, 8, 4 },
{ 2, 4, 8, 4, 2 },
{ 1, 2, 4, 2, 1 }
};
/// <summary>
/// Initializes a new instance of the <see cref="Stucki"/> class.
/// </summary>
public Stucki()
: base(StuckiMatrix, 4)
{
}
}
}

20
src/ImageSharp/Quantizers/IQuantizer.cs

@ -7,6 +7,8 @@ namespace ImageSharp.Quantizers
{
using System;
using ImageSharp.Dithering;
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>
@ -25,6 +27,24 @@ namespace ImageSharp.Quantizers
QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors);
}
/// <summary>
/// Provides methods for allowing dithering of quantized image pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public interface IDitheredQuantizer<TColor> : IQuantizer<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
/// Gets or sets a value indicating whether to apply dithering to the output image.
/// </summary>
bool Dither { get; set; }
/// <summary>
/// Gets or sets the dithering algorithm to apply to the output image.
/// </summary>
IErrorDiffusion DitherType { get; set; }
}
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>

79
src/ImageSharp/Quantizers/Octree/Quantizer.cs

@ -6,12 +6,16 @@
namespace ImageSharp.Quantizers
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImageSharp.Dithering;
/// <summary>
/// Encapsulates methods to calculate the color palette of an image.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public abstract class Quantizer<TColor> : IQuantizer<TColor>
public abstract class Quantizer<TColor> : IDitheredQuantizer<TColor>
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
/// <summary>
@ -19,6 +23,11 @@ namespace ImageSharp.Quantizers
/// </summary>
private readonly bool singlePass;
/// <summary>
/// The reduced image palette
/// </summary>
private TColor[] palette;
/// <summary>
/// Initializes a new instance of the <see cref="Quantizer{TColor}"/> class.
/// </summary>
@ -35,6 +44,12 @@ namespace ImageSharp.Quantizers
this.singlePass = singlePass;
}
/// <inheritdoc />
public bool Dither { get; set; } = true;
/// <inheritdoc />
public IErrorDiffusion DitherType { get; set; } = new SierraLite();
/// <inheritdoc/>
public virtual QuantizedImage<TColor> Quantize(ImageBase<TColor> image, int maxColors)
{
@ -44,7 +59,6 @@ namespace ImageSharp.Quantizers
int height = image.Height;
int width = image.Width;
byte[] quantizedPixels = new byte[width * height];
TColor[] palette;
using (PixelAccessor<TColor> pixels = image.Lock())
{
@ -57,12 +71,24 @@ namespace ImageSharp.Quantizers
}
// Get the palette
palette = this.GetPalette();
this.palette = this.GetPalette();
this.SecondPass(pixels, quantizedPixels, width, height);
if (this.Dither)
{
// We clone the image as we don't want to alter the original.
using (Image<TColor> clone = new Image<TColor>(image))
using (PixelAccessor<TColor> clonedPixels = clone.Lock())
{
this.SecondPass(clonedPixels, quantizedPixels, width, height);
}
}
else
{
this.SecondPass(pixels, quantizedPixels, width, height);
}
}
return new QuantizedImage<TColor>(width, height, palette, quantizedPixels);
return new QuantizedImage<TColor>(width, height, this.palette, quantizedPixels);
}
/// <summary>
@ -99,6 +125,14 @@ namespace ImageSharp.Quantizers
// And loop through each column
for (int x = 0; x < width; x++)
{
if (this.Dither)
{
// Apply the dithering matrix
TColor sourcePixel = source[x, y];
TColor transformedPixel = this.palette[GetClosestColor(sourcePixel, this.palette)];
this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height);
}
output[(y * source.Width) + x] = this.QuantizePixel(source[x, y]);
}
}
@ -129,8 +163,41 @@ namespace ImageSharp.Quantizers
/// Retrieve the palette for the quantized image
/// </summary>
/// <returns>
/// The new color palette
/// <see cref="T:TColor[]"/>
/// </returns>
protected abstract TColor[] GetPalette();
/// <summary>
/// Returns the closest color from the palette to the given color by calculating the Euclidean distance.
/// </summary>
/// <param name="pixel">The color.</param>
/// <param name="palette">The color palette.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetClosestColor(TColor pixel, TColor[] palette)
{
float leastDistance = int.MaxValue;
Vector4 vector = pixel.ToVector4();
byte colorIndex = 0;
for (int index = 0; index < palette.Length; index++)
{
float distance = Vector4.Distance(vector, palette[index].ToVector4());
if (distance < leastDistance)
{
colorIndex = (byte)index;
leastDistance = distance;
// And if it's an exact match, exit the loop
if (Math.Abs(distance) < Constants.Epsilon)
{
break;
}
}
}
return colorIndex;
}
}
}

2
src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs

@ -97,7 +97,7 @@ namespace ImageSharp.Quantizers
leastDistance = distance;
// And if it's an exact match, exit the loop
if (Math.Abs(distance) < .0001F)
if (Math.Abs(distance) < Constants.Epsilon)
{
break;
}

Loading…
Cancel
Save