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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Dithering.Base; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix.
|
|||
/// <see href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT"/>
|
|||
/// Applies order dithering using a Bayer dithering matrix of arbitrary length.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Ordered_dithering"/>
|
|||
/// </summary>
|
|||
public sealed class BayerDither : OrderedDitherBase |
|||
public class BayerDither : OrderedDitherBase |
|||
{ |
|||
/// <summary>
|
|||
/// The threshold matrix.
|
|||
/// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1
|
|||
/// Initializes a new instance of the <see cref="BayerDither"/> class.
|
|||
/// </summary>
|
|||
private static readonly Fast2DArray<byte> ThresholdMatrix = |
|||
new byte[,] |
|||
/// <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++) |
|||
{ |
|||
{ 15, 143, 47, 175 }, |
|||
{ 207, 79, 239, 111 }, |
|||
{ 63, 191, 31, 159 }, |
|||
{ 255, 127, 223, 95 } |
|||
}; |
|||
for (int x = 0; x < dimension; x++) |
|||
{ |
|||
matrix[y, x] = Bayer(i / dimension, i % dimension, order); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BayerDither"/> class.
|
|||
/// </summary>
|
|||
public BayerDither() |
|||
: base(ThresholdMatrix) |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Dithering.Base |
|||
namespace SixLabors.ImageSharp.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// The base class for performing ordered dithering using a 4x4 matrix.
|
|||
/// The base class for performing ordered dithering using a dither matrix.
|
|||
/// </summary>
|
|||
public abstract class OrderedDitherBase : IOrderedDither |
|||
{ |
|||
/// <summary>
|
|||
/// The dithering matrix
|
|||
/// </summary>
|
|||
private Fast2DArray<byte> matrix; |
|||
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<byte> matrix) |
|||
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, 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> |
|||
{ |
|||
source.ToRgba32(ref rgba); |
|||
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."); |
|||
image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; |
|||
} |
|||
} |
|||
} |
|||
@ -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