mirror of https://github.com/SixLabors/ImageSharp
33 changed files with 1150 additions and 456 deletions
@ -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 2x2 Bayer dithering matrix.
|
||||
|
/// </summary>
|
||||
|
public sealed class Bayer2x2Dither : BayerDither |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Bayer2x2Dither"/> class.
|
||||
|
/// </summary>
|
||||
|
public Bayer2x2Dither() |
||||
|
: base(1) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 4x4 Bayer dithering matrix.
|
||||
|
/// </summary>
|
||||
|
public sealed class Bayer4x4Dither : BayerDither |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Bayer4x4Dither"/> class.
|
||||
|
/// </summary>
|
||||
|
public Bayer4x4Dither() |
||||
|
: base(2) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 8x8 Bayer dithering matrix.
|
||||
|
/// </summary>
|
||||
|
public sealed class Bayer8x8Dither : BayerDither |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Bayer8x8Dither"/> class.
|
||||
|
/// </summary>
|
||||
|
public Bayer8x8Dither() |
||||
|
: base(3) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,36 +1,60 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using SixLabors.ImageSharp.Dithering.Base; |
using System.Runtime.CompilerServices; |
||||
using SixLabors.ImageSharp.Memory; |
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Dithering |
namespace SixLabors.ImageSharp.Dithering |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
|
/// Applies order dithering using a Bayer dithering matrix of arbitrary length.
|
||||
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
|
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
||||
/// </summary>
|
/// </summary>
|
||||
public sealed class BayerDither : OrderedDitherBase |
public class BayerDither : OrderedDitherBase |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The threshold matrix.
|
/// Initializes a new instance of the <see cref="BayerDither"/> class.
|
||||
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
|
|
||||
/// </summary>
|
/// </summary>
|
||||
private static readonly Fast2DArray<byte> ThresholdMatrix = |
/// <param name="exponent">
|
||||
new byte[,] |
/// 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++) |
||||
{ |
{ |
||||
{ 15, 143, 47, 175 }, |
for (int x = 0; x < dimension; x++) |
||||
{ 207, 79, 239, 111 }, |
{ |
||||
{ 63, 191, 31, 159 }, |
matrix[y, x] = Bayer(i / dimension, i % dimension, order); |
||||
{ 255, 127, 223, 95 } |
i++; |
||||
}; |
} |
||||
|
} |
||||
|
|
||||
/// <summary>
|
return matrix; |
||||
/// Initializes a new instance of the <see cref="BayerDither"/> class.
|
} |
||||
/// </summary>
|
|
||||
public BayerDither() |
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
: base(ThresholdMatrix) |
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; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,36 +0,0 @@ |
|||||
// 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 4x4 ordered dithering matrix.
|
|
||||
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
|
||||
/// </summary>
|
|
||||
public sealed class OrderedDither : OrderedDitherBase |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The threshold matrix.
|
|
||||
/// This is calculated by multiplying each value in the original matrix by 16
|
|
||||
/// </summary>
|
|
||||
private static readonly Fast2DArray<byte> ThresholdMatrix = |
|
||||
new byte[,] |
|
||||
{ |
|
||||
{ 0, 128, 32, 160 }, |
|
||||
{ 192, 64, 224, 96 }, |
|
||||
{ 48, 176, 16, 144 }, |
|
||||
{ 240, 112, 208, 80 } |
|
||||
}; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="OrderedDither"/> class.
|
|
||||
/// </summary>
|
|
||||
public OrderedDither() |
|
||||
: base(ThresholdMatrix) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,53 +1,48 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
|
||||
using SixLabors.ImageSharp.Memory; |
using SixLabors.ImageSharp.Memory; |
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Dithering.Base |
namespace SixLabors.ImageSharp.Dithering |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The base class for performing ordered dithering using a 4x4 matrix.
|
/// The base class for performing ordered dithering using a dither matrix.
|
||||
/// </summary>
|
/// </summary>
|
||||
public abstract class OrderedDitherBase : IOrderedDither |
public abstract class OrderedDitherBase : IOrderedDither |
||||
{ |
{ |
||||
/// <summary>
|
private readonly Fast2DArray<uint> matrix; |
||||
/// The dithering matrix
|
private readonly Fast2DArray<uint> thresholdMatrix; |
||||
/// </summary>
|
private readonly int modulusX; |
||||
private Fast2DArray<byte> matrix; |
private readonly int modulusY; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
|
/// Initializes a new instance of the <see cref="OrderedDitherBase"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="matrix">The thresholding matrix. </param>
|
/// <param name="matrix">The thresholding matrix. </param>
|
||||
internal OrderedDitherBase(Fast2DArray<byte> matrix) |
internal OrderedDitherBase(Fast2DArray<uint> matrix) |
||||
{ |
{ |
||||
this.matrix = 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 />
|
/// <inheritdoc />
|
||||
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) |
public void Dither<TPixel>(ImageFrame<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte threshold, int x, int y) |
||||
where TPixel : struct, IPixel<TPixel> |
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
source.ToRgba32(ref rgba); |
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; |
||||
switch (index) |
|
||||
{ |
|
||||
case 0: |
|
||||
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.R ? lower : upper; |
|
||||
return; |
|
||||
case 1: |
|
||||
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.G ? lower : upper; |
|
||||
return; |
|
||||
case 2: |
|
||||
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.B ? lower : upper; |
|
||||
return; |
|
||||
case 3: |
|
||||
image[x, y] = this.matrix[y % 3, x % 3] >= rgba.A ? lower : upper; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
throw new ArgumentOutOfRangeException(nameof(index), "Index should be between 0 and 3 inclusive."); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="Image{TPixel}"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class ImageExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold</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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDiffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryErrorDiffusionProcessor<TPixel>(diffuser, threshold, upperColor, lowerColor), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="Image{TPixel}"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class ImageExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using ordered dithering.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using ordered dithering.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using ordered dithering.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to two colors using ordered dithering.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold</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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> BinaryDither<TPixel>(this IImageProcessingContext<TPixel> source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new BinaryOrderedDitherProcessor<TPixel>(dither, upperColor, lowerColor), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="Image{TPixel}"/> type.
|
||||
|
/// </summary>
|
||||
|
public static partial class ImageExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to a web-safe palette using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to a web-safe palette using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to the given palette using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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="palette">The palette to select substitute colors from.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette)); |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dithers the image reducing it to the given palette using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">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="palette">The palette to select substitute colors from.</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{TPixel}"/>.</returns>
|
||||
|
public static IImageProcessingContext<TPixel> Diffuse<TPixel>(this IImageProcessingContext<TPixel> source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
source.ApplyProcessor(new ErrorDiffusionPaletteProcessor<TPixel>(diffuser, threshold, palette), rectangle); |
||||
|
return source; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.Helpers; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs binary threshold filtering against an image using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class BinaryErrorDiffusionProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="diffuser">The error diffuser</param>
|
||||
|
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) |
||||
|
: this(diffuser, .5F) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> 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 BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) |
||||
|
: this(diffuser, threshold, NamedColors<TPixel>.White, NamedColors<TPixel>.Black) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="BinaryErrorDiffusionProcessor{TPixel}"/> 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>
|
||||
|
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
|
||||
|
public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) |
||||
|
{ |
||||
|
Guard.NotNull(diffuser, nameof(diffuser)); |
||||
|
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); |
||||
|
|
||||
|
this.Diffuser = diffuser; |
||||
|
this.Threshold = threshold; |
||||
|
this.UpperColor = upperColor; |
||||
|
this.LowerColor = lowerColor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the error diffuser.
|
||||
|
/// </summary>
|
||||
|
public IErrorDiffuser Diffuser { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the threshold value.
|
||||
|
/// </summary>
|
||||
|
public float Threshold { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color to use for pixels that are above the threshold.
|
||||
|
/// </summary>
|
||||
|
public TPixel UpperColor { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color to use for pixels that fall below the threshold.
|
||||
|
/// </summary>
|
||||
|
public TPixel LowerColor { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
float threshold = this.Threshold * 255F; |
||||
|
var rgba = default(Rgba32); |
||||
|
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
||||
|
|
||||
|
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
||||
|
int startY = interest.Y; |
||||
|
int endY = interest.Bottom; |
||||
|
int startX = interest.X; |
||||
|
int endX = interest.Right; |
||||
|
|
||||
|
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
||||
|
TPixel sourcePixel = source[startX, startY]; |
||||
|
TPixel previousPixel = sourcePixel; |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
|
||||
|
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
||||
|
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
for (int y = startY; y < endY; y++) |
||||
|
{ |
||||
|
Span<TPixel> row = source.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = startX; x < endX; x++) |
||||
|
{ |
||||
|
sourcePixel = row[x]; |
||||
|
|
||||
|
// Check if this is the same as the last pixel. If so use that value
|
||||
|
// rather than calculating it again. This is an inexpensive optimization.
|
||||
|
if (!previousPixel.Equals(sourcePixel)) |
||||
|
{ |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
// Setup the previous pointer
|
||||
|
previousPixel = sourcePixel; |
||||
|
} |
||||
|
|
||||
|
TPixel transformedPixel = luminance >= threshold ? this.UpperColor : this.LowerColor; |
||||
|
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.Helpers; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Performs binary threshold filtering against an image using ordered dithering.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class BinaryOrderedDitherProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
public BinaryOrderedDitherProcessor(IOrderedDither dither) |
||||
|
: this(dither, NamedColors<TPixel>.White, NamedColors<TPixel>.Black) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="BinaryOrderedDitherProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
/// <param name="upperColor">The color to use for pixels that are above the threshold.</param>
|
||||
|
/// <param name="lowerColor">The color to use for pixels that are below the threshold.</param>
|
||||
|
public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor) |
||||
|
{ |
||||
|
Guard.NotNull(dither, nameof(dither)); |
||||
|
|
||||
|
this.Dither = dither; |
||||
|
this.UpperColor = upperColor; |
||||
|
this.LowerColor = lowerColor; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the ditherer.
|
||||
|
/// </summary>
|
||||
|
public IOrderedDither Dither { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color to use for pixels that are above the threshold.
|
||||
|
/// </summary>
|
||||
|
public TPixel UpperColor { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color to use for pixels that fall below the threshold.
|
||||
|
/// </summary>
|
||||
|
public TPixel LowerColor { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
var rgba = default(Rgba32); |
||||
|
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
||||
|
|
||||
|
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
||||
|
int startY = interest.Y; |
||||
|
int endY = interest.Bottom; |
||||
|
int startX = interest.X; |
||||
|
int endX = interest.Right; |
||||
|
|
||||
|
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
||||
|
TPixel sourcePixel = source[startX, startY]; |
||||
|
TPixel previousPixel = sourcePixel; |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
|
||||
|
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
||||
|
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
for (int y = startY; y < endY; y++) |
||||
|
{ |
||||
|
Span<TPixel> row = source.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = startX; x < endX; x++) |
||||
|
{ |
||||
|
sourcePixel = row[x]; |
||||
|
|
||||
|
// Check if this is the same as the last pixel. If so use that value
|
||||
|
// rather than calculating it again. This is an inexpensive optimization.
|
||||
|
if (!previousPixel.Equals(sourcePixel)) |
||||
|
{ |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
// Setup the previous pointer
|
||||
|
previousPixel = sourcePixel; |
||||
|
} |
||||
|
|
||||
|
this.Dither.Dither(source, sourcePixel, this.UpperColor, this.LowerColor, luminance, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,85 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Dithering; |
|
||||
using SixLabors.ImageSharp.Helpers; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal class ErrorDiffusionDitherProcessor<TPixel> : ImageProcessor<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="ErrorDiffusionDitherProcessor{TPixel}"/> 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)); |
|
||||
|
|
||||
this.Diffuser = diffuser; |
|
||||
this.Threshold = threshold; |
|
||||
|
|
||||
// Default to white/black for upper/lower.
|
|
||||
this.UpperColor = NamedColors<TPixel>.White; |
|
||||
this.LowerColor = NamedColors<TPixel>.Black; |
|
||||
} |
|
||||
|
|
||||
/// <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 TPixel UpperColor { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the color to use for pixels that fall below the threshold.
|
|
||||
/// </summary>
|
|
||||
public TPixel LowerColor { get; set; } |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
|
||||
int startY = interest.Y; |
|
||||
int endY = interest.Bottom; |
|
||||
int startX = interest.X; |
|
||||
int endX = interest.Right; |
|
||||
|
|
||||
for (int y = startY; y < endY; y++) |
|
||||
{ |
|
||||
Span<TPixel> row = source.GetPixelRowSpan(y); |
|
||||
|
|
||||
for (int x = startX; x < endX; x++) |
|
||||
{ |
|
||||
TPixel sourceColor = row[x]; |
|
||||
TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; |
|
||||
this.Diffuser.Dither(source, sourceColor, transformedColor, x, y, startX, startY, endX, endY); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,93 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Buffers; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Dithering; |
|
||||
using SixLabors.ImageSharp.Helpers; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal class OrderedDitherProcessor<TPixel> : ImageProcessor<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="OrderedDitherProcessor{TPixel}"/> 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(TPixel) == typeof(Alpha8)) |
|
||||
{ |
|
||||
index = 3; |
|
||||
} |
|
||||
|
|
||||
this.Dither = dither; |
|
||||
this.Index = index; |
|
||||
|
|
||||
// Default to white/black for upper/lower.
|
|
||||
this.UpperColor = NamedColors<TPixel>.White; |
|
||||
this.LowerColor = NamedColors<TPixel>.Black; |
|
||||
} |
|
||||
|
|
||||
/// <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 TPixel UpperColor { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets the color to use for pixels that fall below the threshold.
|
|
||||
/// </summary>
|
|
||||
public TPixel LowerColor { get; set; } |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void BeforeApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
new GrayscaleBt709Processor<TPixel>(1F).Apply(source, sourceRectangle, configuration); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
|
||||
int startY = interest.Y; |
|
||||
int endY = interest.Bottom; |
|
||||
int startX = interest.X; |
|
||||
int endX = interest.Right; |
|
||||
|
|
||||
var rgba = default(Rgba32); |
|
||||
for (int y = startY; y < endY; y++) |
|
||||
{ |
|
||||
Span<TPixel> row = source.GetPixelRowSpan(y); |
|
||||
|
|
||||
for (int x = startX; x < endX; x++) |
|
||||
{ |
|
||||
TPixel sourceColor = row[x]; |
|
||||
this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, ref rgba, this.Index, x, y); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,113 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.Helpers; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="diffuser">The error diffuser</param>
|
||||
|
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) |
||||
|
: this(diffuser, .5F) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> 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 ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) |
||||
|
: this(diffuser, threshold, NamedColors<TPixel>.WebSafePalette) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ErrorDiffusionPaletteProcessor{TPixel}"/> 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>
|
||||
|
/// <param name="palette">The palette to select substitute colors from.</param>
|
||||
|
public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette) |
||||
|
: base(palette) |
||||
|
{ |
||||
|
Guard.NotNull(diffuser, nameof(diffuser)); |
||||
|
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); |
||||
|
|
||||
|
this.Diffuser = diffuser; |
||||
|
this.Threshold = threshold; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the error diffuser.
|
||||
|
/// </summary>
|
||||
|
public IErrorDiffuser Diffuser { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the threshold value.
|
||||
|
/// </summary>
|
||||
|
public float Threshold { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
float threshold = this.Threshold * 255F; |
||||
|
var rgba = default(Rgba32); |
||||
|
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
||||
|
|
||||
|
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
||||
|
int startY = interest.Y; |
||||
|
int endY = interest.Bottom; |
||||
|
int startX = interest.X; |
||||
|
int endX = interest.Right; |
||||
|
|
||||
|
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
||||
|
TPixel sourcePixel = source[startX, startY]; |
||||
|
TPixel previousPixel = sourcePixel; |
||||
|
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
|
||||
|
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
||||
|
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
for (int y = startY; y < endY; y++) |
||||
|
{ |
||||
|
Span<TPixel> row = source.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = startX; x < endX; x++) |
||||
|
{ |
||||
|
sourcePixel = row[x]; |
||||
|
|
||||
|
// Check if this is the same as the last pixel. If so use that value
|
||||
|
// rather than calculating it again. This is an inexpensive optimization.
|
||||
|
if (!previousPixel.Equals(sourcePixel)) |
||||
|
{ |
||||
|
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
// Setup the previous pointer
|
||||
|
previousPixel = sourcePixel; |
||||
|
} |
||||
|
|
||||
|
TPixel transformedPixel = luminance >= threshold ? pair.First : pair.Second; |
||||
|
this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Dithering; |
||||
|
using SixLabors.ImageSharp.Helpers; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
||||
|
/// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class OrderedDitherPaletteProcessor<TPixel> : PaletteDitherProcessorBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
public OrderedDitherPaletteProcessor(IOrderedDither dither) |
||||
|
: this(dither, NamedColors<TPixel>.WebSafePalette) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="OrderedDitherPaletteProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dither">The ordered ditherer.</param>
|
||||
|
/// <param name="palette">The palette to select substitute colors from.</param>
|
||||
|
public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette) |
||||
|
: base(palette) |
||||
|
{ |
||||
|
Guard.NotNull(dither, nameof(dither)); |
||||
|
this.Dither = dither; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the ditherer.
|
||||
|
/// </summary>
|
||||
|
public IOrderedDither Dither { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
var rgba = default(Rgba32); |
||||
|
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
||||
|
|
||||
|
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
||||
|
int startY = interest.Y; |
||||
|
int endY = interest.Bottom; |
||||
|
int startX = interest.X; |
||||
|
int endX = interest.Right; |
||||
|
|
||||
|
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
||||
|
TPixel sourcePixel = source[startX, startY]; |
||||
|
TPixel previousPixel = sourcePixel; |
||||
|
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
|
||||
|
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
||||
|
byte luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
for (int y = startY; y < endY; y++) |
||||
|
{ |
||||
|
Span<TPixel> row = source.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = startX; x < endX; x++) |
||||
|
{ |
||||
|
sourcePixel = row[x]; |
||||
|
|
||||
|
// Check if this is the same as the last pixel. If so use that value
|
||||
|
// rather than calculating it again. This is an inexpensive optimization.
|
||||
|
if (!previousPixel.Equals(sourcePixel)) |
||||
|
{ |
||||
|
pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); |
||||
|
sourcePixel.ToRgba32(ref rgba); |
||||
|
luminance = (byte)(isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B)).Clamp(0, 255); |
||||
|
|
||||
|
// Setup the previous pointer
|
||||
|
previousPixel = sourcePixel; |
||||
|
} |
||||
|
|
||||
|
this.Dither.Dither(source, sourcePixel, pair.First, pair.Second, luminance, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,76 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The base class for dither and diffusion processors that consume a palette.
|
||||
|
/// </summary>
|
||||
|
internal abstract class PaletteDitherProcessorBase<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Dictionary<TPixel, PixelPair<TPixel>> cache = new Dictionary<TPixel, PixelPair<TPixel>>(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PaletteDitherProcessorBase{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="palette">The palette to select substitute colors from.</param>
|
||||
|
public PaletteDitherProcessorBase(TPixel[] palette) |
||||
|
{ |
||||
|
Guard.NotNull(palette, nameof(palette)); |
||||
|
this.Palette = palette; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the palette to select substitute colors from.
|
||||
|
/// </summary>
|
||||
|
public TPixel[] Palette { get; } |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
protected PixelPair<TPixel> GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette) |
||||
|
{ |
||||
|
// Check if the color is in the lookup table
|
||||
|
if (this.cache.ContainsKey(pixel)) |
||||
|
{ |
||||
|
return this.cache[pixel]; |
||||
|
} |
||||
|
|
||||
|
// Not found - loop through the palette and find the nearest match.
|
||||
|
float leastDistance = int.MaxValue; |
||||
|
float secondLeastDistance = int.MaxValue; |
||||
|
var vector = pixel.ToVector4(); |
||||
|
|
||||
|
var closest = default(TPixel); |
||||
|
var secondClosest = default(TPixel); |
||||
|
for (int index = 0; index < colorPalette.Length; index++) |
||||
|
{ |
||||
|
TPixel temp = colorPalette[index]; |
||||
|
var tempVector = temp.ToVector4(); |
||||
|
float distance = Vector4.Distance(vector, tempVector); |
||||
|
|
||||
|
if (distance < leastDistance) |
||||
|
{ |
||||
|
leastDistance = distance; |
||||
|
secondClosest = closest; |
||||
|
closest = temp; |
||||
|
} |
||||
|
else if (distance < secondLeastDistance) |
||||
|
{ |
||||
|
secondLeastDistance = distance; |
||||
|
secondClosest = temp; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Pop it into the cache for next time
|
||||
|
var pair = new PixelPair<TPixel>(closest, secondClosest); |
||||
|
this.cache.Add(pixel, pair); |
||||
|
|
||||
|
return pair; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a composite pair of pixels. Used for caching color distance lookups.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal struct PixelPair<TPixel> : IEquatable<PixelPair<TPixel>> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PixelPair{TPixel}"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="first">The first pixel color</param>
|
||||
|
/// <param name="second">The second pixel color</param>
|
||||
|
public PixelPair(TPixel first, TPixel second) |
||||
|
{ |
||||
|
this.First = first; |
||||
|
this.Second = second; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the first pixel color
|
||||
|
/// </summary>
|
||||
|
public TPixel First { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the second pixel color
|
||||
|
/// </summary>
|
||||
|
public TPixel Second { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool Equals(PixelPair<TPixel> other) |
||||
|
=> this.First.Equals(other.First) && this.Second.Equals(other.Second); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override bool Equals(object obj) |
||||
|
=> obj is PixelPair<TPixel> other && this.First.Equals(other.First) && this.Second.Equals(other.Second); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override int GetHashCode() |
||||
|
=> HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode()); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue