mirror of https://github.com/SixLabors/ImageSharp
22 changed files with 495 additions and 155 deletions
@ -0,0 +1,56 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// Contains reusable static instances of known error diffusion algorithms
|
|||
/// </summary>
|
|||
public static class KnownDiffusers |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Atkinson algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Burks algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Floyd-Steinberg algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Sierra-2 algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Sierra-3 algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Sierra-Lite algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Stevenson-Arce algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the error diffuser that implements the Stucki algorithm.
|
|||
/// </summary>
|
|||
public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// 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 |
|||
{ |
|||
/// <summary>
|
|||
/// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm.
|
|||
/// </summary>
|
|||
public sealed class StevensonArceDiffuser : ErrorDiffuserBase |
|||
{ |
|||
/// <summary>
|
|||
/// The diffusion matrix
|
|||
/// </summary>
|
|||
private static readonly Fast2DArray<float> StevensonArceMatrix = |
|||
new float[,] |
|||
{ |
|||
{ 0, 0, 0, 0, 0, 32, 0 }, |
|||
{ 12, 0, 26, 0, 30, 0, 16 }, |
|||
{ 0, 12, 0, 26, 0, 12, 0 }, |
|||
{ 5, 0, 12, 0, 12, 0, 5 } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="StevensonArceDiffuser"/> class.
|
|||
/// </summary>
|
|||
public StevensonArceDiffuser() |
|||
: base(StevensonArceMatrix, 200) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,60 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// Applies order dithering using a Bayer dithering matrix of arbitrary length.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
|||
/// </summary>
|
|||
public class BayerDither : OrderedDitherBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BayerDither"/> class.
|
|||
/// </summary>
|
|||
/// <param name="exponent">
|
|||
/// 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
|
|||
/// </param>
|
|||
public BayerDither(uint exponent) |
|||
: base(ComputeBayer(exponent)) |
|||
{ |
|||
} |
|||
|
|||
private static Fast2DArray<uint> ComputeBayer(uint order) |
|||
{ |
|||
uint dimension = (uint)(1 << (int)order); |
|||
var matrix = new Fast2DArray<uint>((int)dimension); |
|||
uint i = 0; |
|||
for (int y = 0; y < dimension; y++) |
|||
{ |
|||
for (int x = 0; x < dimension; x++) |
|||
{ |
|||
matrix[y, x] = Bayer(i / dimension, i % dimension, order); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// Contains reusable static instances of known ordered dither matrices
|
|||
/// </summary>
|
|||
public class KnownDitherers |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the order ditherer using the 2x2 Bayer dithering matrix
|
|||
/// </summary>
|
|||
public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the order ditherer using the 3x3 dithering matrix
|
|||
/// </summary>
|
|||
public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the order ditherer using the 4x4 Bayer dithering matrix
|
|||
/// </summary>
|
|||
public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the order ditherer using the 8x8 Bayer dithering matrix
|
|||
/// </summary>
|
|||
public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// An ordered dithering matrix with equal sides of arbitrary length
|
|||
/// </summary>
|
|||
public class OrderedDither : IOrderedDither |
|||
{ |
|||
private readonly Fast2DArray<uint> thresholdMatrix; |
|||
private readonly int modulusX; |
|||
private readonly int modulusY; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
|
|||
/// </summary>
|
|||
/// <param name="length">The length of the matrix sides</param>
|
|||
public OrderedDither(uint length) |
|||
{ |
|||
Fast2DArray<uint> ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); |
|||
this.modulusX = ditherMatrix.Width; |
|||
this.modulusY = ditherMatrix.Height; |
|||
|
|||
// Adjust the matrix range for 0-255
|
|||
// It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2
|
|||
// https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg
|
|||
int multiplier = 256 / ditherMatrix.Count; |
|||
for (int y = 0; y < ditherMatrix.Height; y++) |
|||
{ |
|||
for (int x = 0; x < ditherMatrix.Width; x++) |
|||
{ |
|||
ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; |
|||
} |
|||
} |
|||
|
|||
this.thresholdMatrix = ditherMatrix; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// Applies order dithering using the 3x3 dithering matrix.
|
|||
/// </summary>
|
|||
public sealed class OrderedDither3x3 : OrderedDither |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="OrderedDither3x3"/> class.
|
|||
/// </summary>
|
|||
public OrderedDither3x3() |
|||
: base(3) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// The base class for performing ordered dithering using a dither matrix.
|
|||
/// </summary>
|
|||
public abstract class OrderedDitherBase : IOrderedDither |
|||
{ |
|||
private readonly Fast2DArray<uint> matrix; |
|||
private readonly Fast2DArray<uint> thresholdMatrix; |
|||
private readonly int modulusX; |
|||
private readonly int modulusY; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="matrix">The thresholding matrix. </param>
|
|||
internal OrderedDitherBase(Fast2DArray<uint> matrix) |
|||
{ |
|||
this.matrix = matrix; |
|||
this.modulusX = matrix.Width; |
|||
this.modulusY = matrix.Height; |
|||
this.thresholdMatrix = new Fast2DArray<uint>(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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// A factory for creating ordered dither matrices.
|
|||
/// </summary>
|
|||
internal static class OrderedDitherFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates an ordered dithering matrix with equal sides of arbitrary length.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
|||
/// </summary>
|
|||
/// <param name="length">The length of the matrix sides</param>
|
|||
/// <returns>The <see cref="Fast2DArray{T}"/></returns>
|
|||
public static Fast2DArray<uint> CreateDitherMatrix(uint length) |
|||
{ |
|||
// Calculate the the logarithm of length to the base 2
|
|||
uint exponent = 0; |
|||
uint bayerLength = 0; |
|||
do |
|||
{ |
|||
exponent++; |
|||
bayerLength = (uint)(1 << (int)exponent); |
|||
} |
|||
while (length > bayerLength); |
|||
|
|||
// Create our Bayer matrix that matches the given exponent and dimensions
|
|||
var matrix = new Fast2DArray<uint>((int)length); |
|||
uint i = 0; |
|||
for (int y = 0; y < length; y++) |
|||
{ |
|||
for (int x = 0; x < length; x++) |
|||
{ |
|||
matrix[y, x] = Bayer(i / length, i % length, exponent); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
// If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm,
|
|||
// we need to convert the numbers so that the resulting range is un-gapped.
|
|||
// We generated: We saved: We compress the number range:
|
|||
// 0 8 2 10 0 8 2 0 5 2
|
|||
// 12 4 14 6 12 4 14 7 4 8
|
|||
// 3 11 1 9 3 11 1 3 6 1
|
|||
// 15 7 13 5
|
|||
uint maxValue = bayerLength * bayerLength; |
|||
uint missing = 0; |
|||
for (uint v = 0; v < maxValue; ++v) |
|||
{ |
|||
bool found = false; |
|||
for (int y = 0; y < length; ++y) |
|||
{ |
|||
for (int x = 0; x < length; x++) |
|||
{ |
|||
if (matrix[y, x] == v) |
|||
{ |
|||
matrix[y, x] -= missing; |
|||
found = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!found) |
|||
{ |
|||
++missing; |
|||
} |
|||
} |
|||
|
|||
return matrix; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static uint Bayer(uint x, uint y, uint order) |
|||
{ |
|||
uint result = 0; |
|||
for (uint i = 0; i < order; ++i) |
|||
{ |
|||
uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1); |
|||
uint xOdd = x & 1; |
|||
result = ((result << 1 | xOdd_XOR_yOdd) << 1) | xOdd; |
|||
x >>= 1; |
|||
y >>= 1; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
List of error diffusion schemes. |
|||
|
|||
Quantization error of *current* pixel is added to the pixels |
|||
on the right and below according to the formulas below. |
|||
This works nicely for most static pictures, but causes |
|||
an avalanche of jittering artifacts if used in animation. |
|||
|
|||
Floyd-Steinberg: |
|||
|
|||
* 7 |
|||
3 5 1 / 16 |
|||
|
|||
Jarvis-Judice-Ninke: |
|||
|
|||
* 7 5 |
|||
3 5 7 5 3 |
|||
1 3 5 3 1 / 48 |
|||
|
|||
Stucki: |
|||
|
|||
* 8 4 |
|||
2 4 8 4 2 |
|||
1 2 4 2 1 / 42 |
|||
|
|||
Burkes: |
|||
|
|||
* 8 4 |
|||
2 4 8 4 2 / 32 |
|||
|
|||
|
|||
Sierra3: |
|||
|
|||
* 5 3 |
|||
2 4 5 4 2 |
|||
2 3 2 / 32 |
|||
|
|||
Sierra2: |
|||
|
|||
* 4 3 |
|||
1 2 3 2 1 / 16 |
|||
|
|||
Sierra-2-4A: |
|||
|
|||
* 2 |
|||
1 1 / 4 |
|||
|
|||
Stevenson-Arce: |
|||
|
|||
* . 32 |
|||
12 . 26 . 30 . 16 |
|||
. 12 . 26 . 12 . |
|||
5 . 12 . 12 . 5 / 200 |
|||
|
|||
Atkinson: |
|||
|
|||
* 1 1 / 8 |
|||
1 1 1 |
|||
1 |
|||
@ -0,0 +1,102 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Dithering; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Binarization |
|||
{ |
|||
public class OrderedDitherFactoryTests |
|||
{ |
|||
private static readonly Fast2DArray<uint> Expected2x2Matrix = new Fast2DArray<uint>( |
|||
new uint[2, 2] |
|||
{ |
|||
{ 0, 2 }, |
|||
{ 3, 1 } |
|||
}); |
|||
|
|||
private static readonly Fast2DArray<uint> Expected3x3Matrix = new Fast2DArray<uint>( |
|||
new uint[3, 3] |
|||
{ |
|||
{ 0, 5, 2 }, |
|||
{ 7, 4, 8 }, |
|||
{ 3, 6, 1 } |
|||
}); |
|||
|
|||
private static readonly Fast2DArray<uint> Expected4x4Matrix = new Fast2DArray<uint>( |
|||
new uint[4, 4] |
|||
{ |
|||
{ 0, 8, 2, 10 }, |
|||
{ 12, 4, 14, 6 }, |
|||
{ 3, 11, 1, 9 }, |
|||
{ 15, 7, 13, 5 } |
|||
}); |
|||
|
|||
private static readonly Fast2DArray<uint> Expected8x8Matrix = new Fast2DArray<uint>( |
|||
new uint[8, 8] |
|||
{ |
|||
{ 0, 32, 8, 40, 2, 34, 10, 42 }, |
|||
{ 48, 16, 56, 24, 50, 18, 58, 26 }, |
|||
{ 12, 44, 4, 36, 14, 46, 6, 38 }, |
|||
{ 60, 28, 52, 20, 62, 30, 54, 22 }, |
|||
{ 3, 35, 11, 43, 1, 33, 9, 41 }, |
|||
{ 51, 19, 59, 27, 49, 17, 57, 25 }, |
|||
{ 15, 47, 7, 39, 13, 45, 5, 37 }, |
|||
{ 63, 31, 55, 23, 61, 29, 53, 21 } |
|||
}); |
|||
|
|||
|
|||
[Fact] |
|||
public void OrderedDitherFactoryCreatesCorrect2x2Matrix() |
|||
{ |
|||
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(2); |
|||
for (int y = 0; y < actual.Height; y++) |
|||
{ |
|||
for (int x = 0; x < actual.Width; x++) |
|||
{ |
|||
Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void OrderedDitherFactoryCreatesCorrect3x3Matrix() |
|||
{ |
|||
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(3); |
|||
for (int y = 0; y < actual.Height; y++) |
|||
{ |
|||
for (int x = 0; x < actual.Width; x++) |
|||
{ |
|||
Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void OrderedDitherFactoryCreatesCorrect4x4Matrix() |
|||
{ |
|||
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(4); |
|||
for (int y = 0; y < actual.Height; y++) |
|||
{ |
|||
for (int x = 0; x < actual.Width; x++) |
|||
{ |
|||
Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void OrderedDitherFactoryCreatesCorrect8x8Matrix() |
|||
{ |
|||
Fast2DArray<uint> actual = OrderedDitherFactory.CreateDitherMatrix(8); |
|||
for (int y = 0; y < actual.Height; y++) |
|||
{ |
|||
for (int x = 0; x < actual.Width; x++) |
|||
{ |
|||
Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue