Browse Source

Add dither scaling and simplify API.

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
ba38de5843
  1. 23
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 5
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 18
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 3
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  5. 5
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  6. 4
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  7. 8
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  8. 7
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs
  9. 18
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  10. 74
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs
  11. 9
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  12. 17
      src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
  13. 25
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  14. 76
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  15. 11
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  16. 60
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  17. 23
      src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs
  18. 42
      src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs
  19. 21
      src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs
  20. 23
      src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs
  21. 26
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  22. 69
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  23. 11
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  24. 7
      tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs
  25. 10
      tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs
  26. 4
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  27. 6
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  28. 2
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  29. 50
      tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs
  30. 48
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs
  31. 149
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  32. 50
      tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs
  33. 36
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  34. 10
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  35. 1
      tests/ImageSharp.Tests/TestImages.cs
  36. 3
      tests/Images/Input/Png/david.png

23
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -1,6 +1,7 @@
// 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 System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -82,6 +83,7 @@ namespace SixLabors.ImageSharp.Advanced
// This is we actually call all the individual methods you need to seed. // This is we actually call all the individual methods you need to seed.
AotCompileOctreeQuantizer<TPixel>(); AotCompileOctreeQuantizer<TPixel>();
AotCompileWuQuantizer<TPixel>(); AotCompileWuQuantizer<TPixel>();
AotCompilePaletteQuantizer<TPixel>();
AotCompileDithering<TPixel>(); AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>(); AotCompilePixelOperations<TPixel>();
@ -109,7 +111,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>() private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer(false))) using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
{ {
test.AotGetPalette(); test.AotGetPalette();
} }
@ -122,7 +124,22 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>() private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer(false))) using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
{
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds());
test.AotGetPalette();
}
}
/// <summary>
/// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompilePaletteQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
using (var test = (PaletteFrameQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreateFrameQuantizer<TPixel>(Configuration.Default))
{ {
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds()); test.QuantizeFrame(frame, frame.Bounds());
@ -141,7 +158,7 @@ namespace SixLabors.ImageSharp.Advanced
TPixel pixel = default; TPixel pixel = default;
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1)) using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{ {
test.Dither(image, default, pixel, pixel, 0, 0, 0); test.Dither(image, default, pixel, pixel, 0, 0, 0, 0);
} }
} }

5
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Bmp namespace SixLabors.ImageSharp.Formats.Bmp
@ -87,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel; this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency; this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256); this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
} }
/// <summary> /// <summary>
@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette) private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, 256); using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using IQuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds()); using IQuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;

18
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -144,13 +144,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
using (IFrameQuantizer<TPixel> paletteFrameQuantizer = using (IFrameQuantizer<TPixel> paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette))
new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Dither, quantized.Palette)) using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()))
{ {
using (IQuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) this.WriteImageData(paletteQuantized, stream);
{
this.WriteImageData(paletteQuantized, stream);
}
} }
} }
} }
@ -171,7 +168,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength
&& frameMetadata.ColorTableLength > 0) && frameMetadata.ColorTableLength > 0)
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, frameMetadata.ColorTableLength)) var options = new QuantizerOptions
{
Dither = this.quantizer.Options.Dither,
DitherScale = this.quantizer.Options.DitherScale,
MaxColors = frameMetadata.ColorTableLength
};
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options))
{ {
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
} }

3
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Formats.Png
// Use the metadata to determine what quantization depth to use if no quantizer has been set. // Use the metadata to determine what quantization depth to use if no quantizer has been set.
if (options.Quantizer is null) if (options.Quantizer is null)
{ {
options.Quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); var maxColors = ImageMaths.GetColorCountForBitDepth(bits);
options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors });
} }
// Create quantized frame returning the palette and set the bit depth. // Create quantized frame returning the palette and set the bit depth.

5
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -38,7 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
TPixel transformed, TPixel transformed,
int x, int x,
int y, int y,
int bitDepth) int bitDepth,
float scale)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Equal? Break out as there's no error to pass. // Equal? Break out as there's no error to pass.
@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
// Calculate the error // Calculate the error
Vector4 error = source.ToVector4() - transformed.ToVector4(); Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale;
int offset = this.offset; int offset = this.offset;
DenseMatrix<float> matrix = this.matrix; DenseMatrix<float> matrix = this.matrix;

4
src/ImageSharp/Processing/Processors/Dithering/IDither.cs

@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <param name="x">The column index.</param> /// <param name="x">The column index.</param>
/// <param name="y">The row index.</param> /// <param name="y">The row index.</param>
/// <param name="bitDepth">The bit depth of the target palette.</param> /// <param name="bitDepth">The bit depth of the target palette.</param>
/// <param name="scale">The dithering scale used to adjust the amount of dither. Range 0..1.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The dithered result for the source pixel.</returns> /// <returns>The dithered result for the source pixel.</returns>
TPixel Dither<TPixel>( TPixel Dither<TPixel>(
@ -37,7 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
TPixel transformed, TPixel transformed,
int x, int x,
int y, int y,
int bitDepth) int bitDepth,
float scale)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

8
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -54,20 +54,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
TPixel transformed, TPixel transformed,
int x, int x,
int y, int y,
int bitDepth) int bitDepth,
float scale)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Should we consider a pixel format with a larger coror range?
Rgba32 rgba = default; Rgba32 rgba = default;
source.ToRgba32(ref rgba); source.ToRgba32(ref rgba);
Rgba32 attempt; Rgba32 attempt;
// Srpead assumes an even colorspace distribution and precision. // Spread assumes an even colorspace distribution and precision.
// Calculated as 0-255/component count. 256 / bitDepth // Calculated as 0-255/component count. 256 / bitDepth
// https://bisqwit.iki.fi/story/howto/dither/jy/ // https://bisqwit.iki.fi/story/howto/dither/jy/
// https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
int spread = 256 / bitDepth; int spread = 256 / bitDepth;
float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX]; float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale;
attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue); attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue);
attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue); attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue);

7
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs

@ -32,10 +32,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
/// <summary> /// <summary>
/// Gets the dithering algorithm. /// Gets the dithering algorithm to apply to the output image.
/// </summary> /// </summary>
public IDither Dither { get; } public IDither Dither { get; }
/// <summary>
/// Gets the dithering scale used to adjust the amount of dither. Range 0..1.
/// </summary>
public float DitherScale { get; }
/// <summary> /// <summary>
/// Gets the palette to select substitute colors from. /// Gets the palette to select substitute colors from.
/// </summary> /// </summary>

18
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly int paletteLength; private readonly int paletteLength;
private readonly int bitDepth; private readonly int bitDepth;
private readonly IDither dither; private readonly IDither dither;
private readonly float ditherScale;
private readonly ReadOnlyMemory<Color> sourcePalette; private readonly ReadOnlyMemory<Color> sourcePalette;
private IMemoryOwner<TPixel> palette; private IMemoryOwner<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
@ -38,6 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.paletteLength = definition.Palette.Span.Length; this.paletteLength = definition.Palette.Span.Length;
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.paletteLength); this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.paletteLength);
this.dither = definition.Dither; this.dither = definition.Dither;
this.ditherScale = definition.DitherScale;
this.sourcePalette = definition.Palette; this.sourcePalette = definition.Palette;
} }
@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
{ {
TPixel sourcePixel = row[x]; TPixel sourcePixel = row[x];
this.pixelMap.GetClosestColor(sourcePixel, out TPixel transformed); this.pixelMap.GetClosestColor(sourcePixel, out TPixel transformed);
this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth); this.dither.Dither(source, interest, sourcePixel, transformed, x, y, this.bitDepth, this.ditherScale);
row[x] = transformed; row[x] = transformed;
} }
} }
@ -67,7 +69,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
} }
// Ordered dithering. We are only operating on a single pixel so we can work in parallel. // Ordered dithering. We are only operating on a single pixel so we can work in parallel.
var ditherOperation = new DitherRowIntervalOperation(source, interest, this.pixelMap, this.dither, this.bitDepth); var ditherOperation = new DitherRowIntervalOperation(
source,
interest,
this.pixelMap,
this.dither,
this.ditherScale,
this.bitDepth);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
this.Configuration, this.Configuration,
interest, interest,
@ -114,6 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly IDither dither; private readonly IDither dither;
private readonly float scale;
private readonly int bitDepth; private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -122,12 +132,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
Rectangle bounds, Rectangle bounds,
EuclideanPixelMap<TPixel> pixelMap, EuclideanPixelMap<TPixel> pixelMap,
IDither dither, IDither dither,
float scale,
int bitDepth) int bitDepth)
{ {
this.source = source; this.source = source;
this.bounds = bounds; this.bounds = bounds;
this.pixelMap = pixelMap; this.pixelMap = pixelMap;
this.dither = dither; this.dither = dither;
this.scale = scale;
this.bitDepth = bitDepth; this.bitDepth = bitDepth;
} }
@ -143,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth); TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth, this.scale);
this.pixelMap.GetClosestColor(dithered, out transformed); this.pixelMap.GetClosestColor(dithered, out transformed);
row[x] = transformed; row[x] = transformed;
} }

74
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs

@ -17,11 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public abstract class FrameQuantizer<TPixel> : IFrameQuantizer<TPixel> public abstract class FrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary>
/// Flag used to indicate whether a single pass or two passes are needed for quantization.
/// </summary>
private readonly bool singlePass; private readonly bool singlePass;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private bool isDisposed; private bool isDisposed;
@ -29,57 +25,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The quantizer.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="singlePass"> /// <param name="singlePass">
/// If true, the quantization process only needs to loop through the source pixels once. /// If <see langword="true"/>, the quantization process only needs to loop through the source pixels once.
/// </param> /// </param>
/// <remarks> /// <remarks>
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will /// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Rectangle, Memory{byte}, ReadOnlyMemory{TPixel})"/> method. /// only call the <see cref="SecondPass(ImageFrame{TPixel}, Rectangle, Memory{byte}, ReadOnlyMemory{TPixel})"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, Rectangle)"/>. /// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks> /// </remarks>
protected FrameQuantizer(Configuration configuration, IQuantizer quantizer, bool singlePass) protected FrameQuantizer(Configuration configuration, QuantizerOptions options, bool singlePass)
{ {
Guard.NotNull(quantizer, nameof(quantizer)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration; this.Configuration = configuration;
this.Dither = quantizer.Dither; this.Options = options;
this.DoDither = this.Dither != null; this.IsDitheringQuantizer = options.Dither != null;
this.singlePass = singlePass; this.singlePass = singlePass;
} }
/// <summary> /// <inheritdoc/>
/// Initializes a new instance of the <see cref="FrameQuantizer{TPixel}"/> class. public QuantizerOptions Options { get; }
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="diffuser">The diffuser</param>
/// <param name="singlePass">
/// If true, the quantization process only needs to loop through the source pixels once
/// </param>
/// <remarks>
/// If you construct this class with a <value>true</value> for <paramref name="singlePass"/>, then the code will
/// only call the <see cref="SecondPass(ImageFrame{TPixel}, Rectangle, Memory{byte}, ReadOnlyMemory{TPixel})"/> method.
/// If two passes are required, the code will also call <see cref="FirstPass(ImageFrame{TPixel}, Rectangle)"/>.
/// </remarks>
protected FrameQuantizer(Configuration configuration, IDither diffuser, bool singlePass)
{
this.Configuration = configuration;
this.Dither = diffuser;
this.DoDither = this.Dither != null;
this.singlePass = singlePass;
}
/// <inheritdoc />
public IDither Dither { get; }
/// <inheritdoc />
public bool DoDither { get; }
/// <summary> /// <summary>
/// Gets the configuration which allows altering default behaviour or extending the library. /// Gets the configuration which allows altering default behaviour or extending the library.
/// </summary> /// </summary>
protected Configuration Configuration { get; } protected Configuration Configuration { get; }
/// <summary>
/// Gets a value indicating whether the frame quantizer utilizes a dithering method.
/// </summary>
protected bool IsDitheringQuantizer { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
@ -109,7 +87,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette); var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
Memory<byte> output = quantizedFrame.GetWritablePixelMemory(); Memory<byte> output = quantizedFrame.GetWritablePixelMemory();
if (this.DoDither) if (this.Options.Dither is null)
{
this.SecondPass(image, interest, output, palette);
}
else
{ {
// We clone the image as we don't want to alter the original via error diffusion based dithering. // We clone the image as we don't want to alter the original via error diffusion based dithering.
using (ImageFrame<TPixel> clone = image.Clone()) using (ImageFrame<TPixel> clone = image.Clone())
@ -117,10 +99,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.SecondPass(clone, interest, output, palette); this.SecondPass(clone, interest, output, palette);
} }
} }
else
{
this.SecondPass(image, interest, output, palette);
}
return quantizedFrame; return quantizedFrame;
} }
@ -162,7 +140,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlyMemory<TPixel> palette) ReadOnlyMemory<TPixel> palette)
{ {
ReadOnlySpan<TPixel> paletteSpan = palette.Span; ReadOnlySpan<TPixel> paletteSpan = palette.Span;
if (!this.DoDither) IDither dither = this.Options.Dither;
if (dither is null)
{ {
var operation = new RowIntervalOperation(source, output, bounds, this, palette); var operation = new RowIntervalOperation(source, output, bounds, this, palette);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
@ -179,8 +159,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Span<byte> outputSpan = output.Span; Span<byte> outputSpan = output.Span;
int bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSpan.Length); int bitDepth = ImageMaths.GetBitsNeededForColorDepth(paletteSpan.Length);
if (this.Dither.DitherType == DitherType.ErrorDiffusion) if (dither.DitherType == DitherType.ErrorDiffusion)
{ {
float ditherScale = this.Options.DitherScale;
int width = bounds.Width; int width = bounds.Width;
int offsetY = bounds.Top; int offsetY = bounds.Top;
int offsetX = bounds.Left; int offsetX = bounds.Left;
@ -193,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
TPixel sourcePixel = row[x]; TPixel sourcePixel = row[x];
outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed); outputSpan[rowStart + x - offsetX] = this.GetQuantizedColor(sourcePixel, paletteSpan, out TPixel transformed);
this.Dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth); dither.Dither(source, bounds, sourcePixel, transformed, x, y, bitDepth, ditherScale);
} }
} }
@ -306,7 +287,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int width = this.bounds.Width; int width = this.bounds.Width;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
IDither dither = this.quantizer.Dither; IDither dither = this.quantizer.Options.Dither;
float scale = this.quantizer.Options.DitherScale;
TPixel transformed = default; TPixel transformed = default;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -316,7 +298,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{ {
TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth); TPixel dithered = dither.Dither(this.source, this.bounds, row[x], transformed, x, y, this.bitDepth, scale);
outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _); outputSpan[rowStart + x - offsetX] = this.quantizer.GetQuantizedColor(dithered, paletteSpan, out TPixel _);
} }
} }

9
src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs

@ -15,14 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether to apply dithering to the output image. /// Gets the quantizer options defining quantization rules.
/// </summary> /// </summary>
bool DoDither { get; } QuantizerOptions Options { get; }
/// <summary>
/// Gets the algorithm to apply to the output image.
/// </summary>
IDither Dither { get; }
/// <summary> /// <summary>
/// Quantize an image frame and return the resulting output pixels. /// Quantize an image frame and return the resulting output pixels.

17
src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
@ -12,27 +11,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public interface IQuantizer public interface IQuantizer
{ {
/// <summary> /// <summary>
/// Gets the dithering algorithm to apply to the output image. /// Gets the quantizer options defining quantization rules.
/// </summary> /// </summary>
IDither Dither { get; } QuantizerOptions Options { get; }
/// <summary> /// <summary>
/// Creates the generic frame quantizer /// Creates the generic frame quantizer.
/// </summary> /// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param> /// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns> /// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
/// <summary> /// <summary>
/// Creates the generic frame quantizer /// Creates the generic frame quantizer.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param> /// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param> /// <param name="options">The options to create the quantizer with.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/></returns> /// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors) IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

25
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs

@ -39,30 +39,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The octree quantizer</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <remarks> /// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree /// the second pass quantizes a color based on the nodes in the tree
/// </remarks> /// </remarks>
public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer) public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options)
: this(configuration, quantizer, quantizer.MaxColors) : base(configuration, options, false)
{ {
} this.colors = this.Options.MaxColors;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The octree quantizer.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <remarks>
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
/// the second pass quantizes a color based on the nodes in the tree
/// </remarks>
public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer, int maxColors)
: base(configuration, quantizer, false)
{
this.colors = maxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
} }
@ -95,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
// Octree only maps the RGB component of a color // Octree only maps the RGB component of a color
// so cannot tell the difference between a fully transparent // so cannot tell the difference between a fully transparent
// pixel and a black one. // pixel and a black one.
if (!this.DoDither && !color.Equals(default)) if (!this.IsDitheringQuantizer && !color.Equals(default))
{ {
var index = (byte)this.octree.GetPaletteIndex(color); var index = (byte)this.octree.GetPaletteIndex(color);
match = palette[index]; match = palette[index];

76
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -2,97 +2,45 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Allows the quantization of images pixels using Octrees. /// Allows the quantization of images pixels using Octrees.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> /// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// <para>
/// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value>
/// </para>
/// </summary> /// </summary>
public class OctreeQuantizer : IQuantizer public class OctreeQuantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>.
/// </summary> /// </summary>
public OctreeQuantizer() public OctreeQuantizer()
: this(true) : this(new QuantizerOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
public OctreeQuantizer(int maxColors)
: this(GetDiffuser(true), maxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image.</param>
public OctreeQuantizer(bool dither)
: this(GetDiffuser(dither), QuantizerConstants.MaxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
public OctreeQuantizer(bool dither, int maxColors)
: this(GetDiffuser(dither), maxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
/// <param name="diffuser">The dithering algorithm, if any, to apply to the output image.</param>
public OctreeQuantizer(IDither diffuser)
: this(diffuser, QuantizerConstants.MaxColors)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="dither">The dithering algorithm, if any, to apply to the output image.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param> public OctreeQuantizer(QuantizerOptions options)
public OctreeQuantizer(IDither dither, int maxColors)
{ {
this.Dither = dither; Guard.NotNull(options, nameof(options));
this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); this.Options = options;
} }
/// <inheritdoc /> /// <inheritdoc />
public IDither Dither { get; } public QuantizerOptions Options { get; }
/// <summary>
/// Gets the maximum number of colors to hold in the color palette.
/// </summary>
public int MaxColors { get; }
/// <param name="configuration"></param>
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> new OctreeFrameQuantizer<TPixel>(configuration, this); => this.CreateFrameQuantizer<TPixel>(configuration, this.Options);
/// <inheritdoc/> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => new OctreeFrameQuantizer<TPixel>(configuration, options);
maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
return new OctreeFrameQuantizer<TPixel>(configuration, this, maxColors);
}
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null;
} }
} }

11
src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
@ -25,13 +24,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="diffuser">The palette quantizer.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="colors">An array of all colors in the palette.</param> /// <param name="colors">A <see cref="ReadOnlyMemory{TPixel}"/> containing all colors in the palette.</param>
public PaletteFrameQuantizer(Configuration configuration, IDither diffuser, ReadOnlyMemory<TPixel> colors) public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> colors)
: base(configuration, diffuser, true) => this.palette = colors; : base(configuration, options, true) => this.palette = colors;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette() => this.palette; protected override ReadOnlyMemory<TPixel> GenerateQuantizedPalette() => this.palette;
internal ReadOnlyMemory<TPixel> AotGetPalette() => this.GenerateQuantizedPalette();
} }
} }

60
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -2,80 +2,62 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Allows the quantization of images pixels using color palettes. /// Allows the quantization of images pixels using color palettes.
/// Override this class to provide your own palette.
/// <para>
/// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering.
/// </para>
/// </summary> /// </summary>
public class PaletteQuantizer : IQuantizer public class PaletteQuantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="palette">The palette.</param> /// <param name="palette">The color palette.</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette) public PaletteQuantizer(ReadOnlyMemory<Color> palette)
: this(palette, true) : this(palette, new QuantizerOptions())
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="palette">The palette.</param> /// <param name="palette">The color palette.</param>
/// <param name="dither">Whether to apply dithering to the output image</param> /// <param name="options">The quantizer options defining quantization rules.</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette, bool dither) public PaletteQuantizer(ReadOnlyMemory<Color> palette, QuantizerOptions options)
: this(palette, GetDiffuser(dither))
{ {
} Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette));
Guard.NotNull(options, nameof(options));
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary>
/// <param name="palette">The palette.</param>
/// <param name="dither">The dithering algorithm, if any, to apply to the output image</param>
public PaletteQuantizer(ReadOnlyMemory<Color> palette, IDither dither)
{
this.Palette = palette; this.Palette = palette;
this.Dither = dither; this.Options = options;
} }
/// <inheritdoc />
public IDither Dither { get; }
/// <summary> /// <summary>
/// Gets the palette. /// Gets the color palette.
/// </summary> /// </summary>
public ReadOnlyMemory<Color> Palette { get; } public ReadOnlyMemory<Color> Palette { get; }
/// <inheritdoc />
public QuantizerOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => this.CreateFrameQuantizer<TPixel>(configuration, this.Options);
var palette = new TPixel[this.Palette.Length];
Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan());
return new PaletteFrameQuantizer<TPixel>(configuration, this.Dither, palette);
}
/// <inheritdoc/> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); Guard.NotNull(options, nameof(options));
int max = Math.Min(maxColors, this.Palette.Length);
var palette = new TPixel[max]; int length = Math.Min(this.Palette.Span.Length, options.MaxColors);
Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan()); var palette = new TPixel[length];
return new PaletteFrameQuantizer<TPixel>(configuration, this.Dither, palette);
}
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null; Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan());
return new PaletteFrameQuantizer<TPixel>(configuration, options, palette);
}
} }
} }

23
src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs

@ -1,12 +1,14 @@
// 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.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Contains color quantization specific constants. /// Contains color quantization specific constants.
/// </summary> /// </summary>
internal static class QuantizerConstants public static class QuantizerConstants
{ {
/// <summary> /// <summary>
/// The minimum number of colors to use when quantizing an image. /// The minimum number of colors to use when quantizing an image.
@ -17,5 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The maximum number of colors to use when quantizing an image. /// The maximum number of colors to use when quantizing an image.
/// </summary> /// </summary>
public const int MaxColors = 256; public const int MaxColors = 256;
/// <summary>
/// The minumim dithering scale used to adjust the amount of dither.
/// </summary>
public const float MinDitherScale = 0;
/// <summary>
/// The max dithering scale used to adjust the amount of dither.
/// </summary>
public const float MaxDitherScale = 1F;
/// <summary>
/// Gets the default dithering algorithm to use.
/// </summary>
public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg;
} }
} }

42
src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Defines options for quantization.
/// </summary>
public class QuantizerOptions
{
private float ditherScale = QuantizerConstants.MaxDitherScale;
private int maxColors = QuantizerConstants.MaxColors;
/// <summary>
/// Gets or sets the algorithm to apply to the output image.
/// Defaults to <see cref="QuantizerConstants.DefaultDither"/>; set to <see langword="null"/> for no dithering.
/// </summary>
public IDither Dither { get; set; } = QuantizerConstants.DefaultDither;
/// <summary>
/// Gets or sets the dithering scale used to adjust the amount of dither. Range 0..1.
/// Defaults to <see cref="QuantizerConstants.MaxDitherScale"/>.
/// </summary>
public float DitherScale
{
get { return this.ditherScale; }
set { this.ditherScale = value.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); }
}
/// <summary>
/// Gets or sets the maximum number of colors to hold in the color palette. Range 0..256.
/// Defaults to <see cref="QuantizerConstants.MaxColors"/>.
/// </summary>
public int MaxColors
{
get { return this.maxColors; }
set { this.maxColors = value.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); }
}
}
}

21
src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs

@ -1,4 +1,4 @@
// 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.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -14,26 +14,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary> /// </summary>
public WebSafePaletteQuantizer() public WebSafePaletteQuantizer()
: this(true) : this(new QuantizerOptions())
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary> /// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param> /// <param name="options">The quantizer options defining quantization rules.</param>
public WebSafePaletteQuantizer(bool dither) public WebSafePaletteQuantizer(QuantizerOptions options)
: base(Color.WebSafePalette, dither) : base(Color.WebSafePalette, options)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSafePaletteQuantizer" /> class.
/// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param>
public WebSafePaletteQuantizer(IDither diffuser)
: base(Color.WebSafePalette, diffuser)
{ {
} }
} }
} }

23
src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs

@ -1,8 +1,6 @@
// 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.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
@ -15,26 +13,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary> /// </summary>
public WernerPaletteQuantizer() public WernerPaletteQuantizer()
: this(true) : this(new QuantizerOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WernerPaletteQuantizer(bool dither)
: base(Color.WernerPalette, dither)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class. /// Initializes a new instance of the <see cref="WernerPaletteQuantizer" /> class.
/// </summary> /// </summary>
/// <param name="diffuser">The error diffusion algorithm, if any, to apply to the output image</param> /// <param name="options">The quantizer options defining quantization rules.</param>
public WernerPaletteQuantizer(IDither diffuser) public WernerPaletteQuantizer(QuantizerOptions options)
: base(Color.WernerPalette, diffuser) : base(Color.WernerPalette, options)
{ {
} }
} }
} }

26
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -96,33 +96,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The Wu quantizer</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <remarks> /// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram. /// the second pass quantizes a color based on the position in the histogram.
/// </remarks> /// </remarks>
public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer) public WuFrameQuantizer(Configuration configuration, QuantizerOptions options)
: this(configuration, quantizer, quantizer.MaxColors) : base(configuration, options, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="quantizer">The Wu quantizer.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette.</param>
/// <remarks>
/// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
/// the second pass quantizes a color based on the position in the histogram.
/// </remarks>
public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer, int maxColors)
: base(configuration, quantizer, false)
{ {
this.memoryAllocator = this.Configuration.MemoryAllocator; this.memoryAllocator = this.Configuration.MemoryAllocator;
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean); this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean); this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.colors = maxColors; this.colors = this.Options.MaxColors;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -185,9 +170,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match) protected override byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{ {
if (!this.DoDither) if (!this.IsDitheringQuantizer)
{ {
// Expected order r->g->b->a
Rgba32 rgba = default; Rgba32 rgba = default;
color.ToRgba32(ref rgba); color.ToRgba32(ref rgba);

69
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs

@ -2,89 +2,44 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/> /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer <see href="http://www.ece.mcmaster.ca/~xwu/cq.c"/>
/// <para>
/// By default the quantizer uses <see cref="KnownDitherings.FloydSteinberg"/> dithering and a color palette of a maximum length of <value>255</value>
/// </para>
/// </summary> /// </summary>
public class WuQuantizer : IQuantizer public class WuQuantizer : IQuantizer
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class. /// Initializes a new instance of the <see cref="WuQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>.
/// </summary> /// </summary>
public WuQuantizer() public WuQuantizer()
: this(true) : this(new QuantizerOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param>
public WuQuantizer(int maxColors)
: this(GetDiffuser(true), maxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="dither">Whether to apply dithering to the output image</param>
public WuQuantizer(bool dither)
: this(GetDiffuser(dither), QuantizerConstants.MaxColors)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary>
/// <param name="diffuser">The dithering algorithm, if any, to apply to the output image</param>
public WuQuantizer(IDither diffuser)
: this(diffuser, QuantizerConstants.MaxColors)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer"/> class. /// Initializes a new instance of the <see cref="WuQuantizer"/> class.
/// </summary> /// </summary>
/// <param name="dither">The dithering algorithm, if any, to apply to the output image</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="maxColors">The maximum number of colors to hold in the color palette</param> public WuQuantizer(QuantizerOptions options)
public WuQuantizer(IDither dither, int maxColors)
{ {
this.Dither = dither; Guard.NotNull(options, nameof(options));
this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); this.Options = options;
} }
/// <inheritdoc /> /// <inheritdoc />
public IDither Dither { get; } public QuantizerOptions Options { get; }
/// <summary>
/// Gets the maximum number of colors to hold in the color palette.
/// </summary>
public int MaxColors { get; }
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => this.CreateFrameQuantizer<TPixel>(configuration, this.Options);
Guard.NotNull(configuration, nameof(configuration));
return new WuFrameQuantizer<TPixel>(configuration, this);
}
/// <inheritdoc/> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, int maxColors) public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => new WuFrameQuantizer<TPixel>(configuration, options);
Guard.NotNull(configuration, nameof(configuration));
maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors);
return new WuFrameQuantizer<TPixel>(configuration, this, maxColors);
}
private static IDither GetDiffuser(bool dither) => dither ? KnownDitherings.FloydSteinberg : null;
} }
} }

11
tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs

@ -1,4 +1,4 @@
// 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.Drawing.Imaging; using System.Drawing.Imaging;
@ -6,6 +6,7 @@ using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image; using SDImage = System.Drawing.Image;
@ -53,11 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void GifCore() public void GifCore()
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; var options = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 })
};
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
this.bmpCore.SaveAsGif(memoryStream, options); this.bmpCore.SaveAsGif(memoryStream, options);
} }
} }
} }
} }

7
tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
@ -23,7 +24,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
this.ForEachImageSharpImage((img, ms) => this.ForEachImageSharpImage((img, ms) =>
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; var options = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.BayerDither4x4 })
};
img.Save(ms, options); img.Save(ms, options);
return null; return null;
}); });

10
tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs

@ -1,4 +1,4 @@
// 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.IO; using System.IO;
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }
@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }
@ -95,9 +95,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }
} }
} }

4
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
var encoder = new BmpEncoder var encoder = new BmpEncoder
{ {
BitsPerPixel = BmpBitsPerPixel.Pixel8, BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new WuQuantizer(256) Quantizer = new WuQuantizer()
}; };
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
var encoder = new BmpEncoder var encoder = new BmpEncoder
{ {
BitsPerPixel = BmpBitsPerPixel.Pixel8, BitsPerPixel = BmpBitsPerPixel.Pixel8,
Quantizer = new OctreeQuantizer(256) Quantizer = new OctreeQuantizer()
}; };
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);

6
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
{ {
// Use the palette quantizer without dithering to ensure results // Use the palette quantizer without dithering to ensure results
// are consistent // are consistent
Quantizer = new WebSafePaletteQuantizer(false) Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null })
}; };
// Always save as we need to compare the encoded output. // Always save as we need to compare the encoded output.
@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var encoder = new GifEncoder var encoder = new GifEncoder
{ {
ColorTableMode = GifColorTableMode.Global, ColorTableMode = GifColorTableMode.Global,
Quantizer = new OctreeQuantizer(false) Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null })
}; };
// Always save as we need to compare the encoded output. // Always save as we need to compare the encoded output.
@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
var encoder = new GifEncoder var encoder = new GifEncoder
{ {
ColorTableMode = colorMode, ColorTableMode = colorMode,
Quantizer = new OctreeQuantizer(frameMetadata.ColorTableLength) Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength })
}; };
image.Save(outStream, encoder); image.Save(outStream, encoder);

2
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -428,7 +428,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FilterMethod = pngFilterMethod, FilterMethod = pngFilterMethod,
CompressionLevel = compressionLevel, CompressionLevel = compressionLevel,
BitDepth = bitDepth, BitDepth = bitDepth,
Quantizer = new WuQuantizer(paletteSize), Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }),
InterlaceMethod = interlaceMode InterlaceMethod = interlaceMode
}; };

50
tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs

@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
[Fact] [Fact]
public void OctreeQuantizerConstructor() public void OctreeQuantizerConstructor()
{ {
var quantizer = new OctreeQuantizer(128); var expected = new QuantizerOptions { MaxColors = 128 };
var quantizer = new OctreeQuantizer(expected);
Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
quantizer = new OctreeQuantizer(false);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); expected = new QuantizerOptions { Dither = null };
Assert.Null(quantizer.Dither); quantizer = new OctreeQuantizer(expected);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); Assert.Null(quantizer.Options.Dither);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson };
quantizer = new OctreeQuantizer(expected);
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson, 128); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(128, quantizer.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 };
quantizer = new OctreeQuantizer(expected);
Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
} }
[Fact] [Fact]
@ -38,23 +42,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.NotNull(frameQuantizer.Options);
Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(false); quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Options.Dither);
Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(KnownDitherings.Atkinson); quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
} }
} }

48
tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs

@ -10,49 +10,55 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
{ {
public class PaletteQuantizerTests public class PaletteQuantizerTests
{ {
private static readonly Color[] Rgb = new Color[] { Color.Red, Color.Green, Color.Blue }; private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue };
[Fact] [Fact]
public void PaletteQuantizerConstructor() public void PaletteQuantizerConstructor()
{ {
var quantizer = new PaletteQuantizer(Rgb); var expected = new QuantizerOptions { MaxColors = 128 };
var quantizer = new PaletteQuantizer(Palette, expected);
Assert.Equal(Rgb, quantizer.Palette); Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
quantizer = new PaletteQuantizer(Rgb, false); expected = new QuantizerOptions { Dither = null };
Assert.Equal(Rgb, quantizer.Palette); quantizer = new PaletteQuantizer(Palette, expected);
Assert.Null(quantizer.Dither); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Null(quantizer.Options.Dither);
quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson };
Assert.Equal(Rgb, quantizer.Palette); quantizer = new PaletteQuantizer(Palette, expected);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 };
quantizer = new PaletteQuantizer(Palette, expected);
Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
} }
[Fact] [Fact]
public void PaletteQuantizerCanCreateFrameQuantizer() public void PaletteQuantizerCanCreateFrameQuantizer()
{ {
var quantizer = new PaletteQuantizer(Rgb); var quantizer = new PaletteQuantizer(Palette);
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.NotNull(frameQuantizer.Options);
Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, false); quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Options.Dither);
Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Rgb, KnownDitherings.Atkinson); quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
} }
@ -60,14 +66,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
public void KnownQuantizersWebSafeTests() public void KnownQuantizersWebSafeTests()
{ {
IQuantizer quantizer = KnownQuantizers.WebSafe; IQuantizer quantizer = KnownQuantizers.WebSafe;
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
} }
[Fact] [Fact]
public void KnownQuantizersWernerTests() public void KnownQuantizersWernerTests()
{ {
IQuantizer quantizer = KnownQuantizers.Werner; IQuantizer quantizer = KnownQuantizers.Werner;
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
} }
} }
} }

149
tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs

@ -17,21 +17,128 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
TestImages.Png.Bike TestImages.Png.Bike
}; };
private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null };
private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg };
private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.BayerDither8x8 };
private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = 0F
};
private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .25F
};
private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .5F
};
private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.FloydSteinberg,
DitherScale = .75F
};
private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
DitherScale = 0F
};
private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
DitherScale = .25F
};
private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
DitherScale = .5F
};
private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions
{
Dither = KnownDitherings.BayerDither8x8,
DitherScale = .75F
};
public static readonly TheoryData<IQuantizer> Quantizers public static readonly TheoryData<IQuantizer> Quantizers
= new TheoryData<IQuantizer> = new TheoryData<IQuantizer>
{ {
// Known uses error diffusion by default.
KnownQuantizers.Octree, KnownQuantizers.Octree,
KnownQuantizers.WebSafe, KnownQuantizers.WebSafe,
KnownQuantizers.Werner, KnownQuantizers.Werner,
KnownQuantizers.Wu, KnownQuantizers.Wu,
new OctreeQuantizer(false), new OctreeQuantizer(NoDitherOptions),
new WebSafePaletteQuantizer(false), new WebSafePaletteQuantizer(NoDitherOptions),
new WernerPaletteQuantizer(false), new WernerPaletteQuantizer(NoDitherOptions),
new WuQuantizer(false), new WuQuantizer(NoDitherOptions),
new OctreeQuantizer(KnownDitherings.BayerDither8x8), new OctreeQuantizer(OrderedDitherOptions),
new WebSafePaletteQuantizer(KnownDitherings.BayerDither8x8), new WebSafePaletteQuantizer(OrderedDitherOptions),
new WernerPaletteQuantizer(KnownDitherings.BayerDither8x8), new WernerPaletteQuantizer(OrderedDitherOptions),
new WuQuantizer(KnownDitherings.BayerDither8x8) new WuQuantizer(OrderedDitherOptions)
};
public static readonly TheoryData<IQuantizer> DitherScaleQuantizers
= new TheoryData<IQuantizer>
{
new OctreeQuantizer(Diffuser0_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions),
new WuQuantizer(Diffuser0_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions),
new WuQuantizer(Diffuser0_25_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions),
new WuQuantizer(Diffuser0_5_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions),
new WuQuantizer(Diffuser0_75_ScaleDitherOptions),
new OctreeQuantizer(DiffuserDitherOptions),
new WebSafePaletteQuantizer(DiffuserDitherOptions),
new WernerPaletteQuantizer(DiffuserDitherOptions),
new WuQuantizer(DiffuserDitherOptions),
new OctreeQuantizer(Ordered0_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions),
new WuQuantizer(Ordered0_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_25_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions),
new WuQuantizer(Ordered0_25_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_5_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions),
new WuQuantizer(Ordered0_5_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_75_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions),
new WuQuantizer(Ordered0_75_ScaleDitherOptions),
new OctreeQuantizer(OrderedDitherOptions),
new WebSafePaletteQuantizer(OrderedDitherOptions),
new WernerPaletteQuantizer(OrderedDitherOptions),
new WuQuantizer(OrderedDitherOptions),
}; };
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f);
@ -42,8 +149,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
string quantizerName = quantizer.GetType().Name; string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither";
string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty;
string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}";
provider.RunRectangleConstrainedValidatingProcessorTest( provider.RunRectangleConstrainedValidatingProcessorTest(
@ -59,8 +166,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
string quantizerName = quantizer.GetType().Name; string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Dither?.GetType()?.Name ?? "noDither"; string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "noDither";
string ditherType = quantizer.Dither?.DitherType.ToString() ?? string.Empty; string ditherType = quantizer.Options.Dither?.DitherType.ToString() ?? string.Empty;
string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}"; string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}";
provider.RunValidatingProcessorTest( provider.RunValidatingProcessorTest(
@ -69,5 +176,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
} }
[Theory]
[WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)]
public void ApplyQuantizationWithDitheringScale<TPixel>(TestImageProvider<TPixel> provider, IQuantizer quantizer)
where TPixel : struct, IPixel<TPixel>
{
string quantizerName = quantizer.GetType().Name;
string ditherName = quantizer.Options.Dither.GetType().Name;
string ditherType = quantizer.Options.Dither.DitherType.ToString();
float ditherScale = quantizer.Options.DitherScale;
string testOutputDetails = $"{quantizerName}_{ditherName}_{ditherType}_{ditherScale}";
provider.RunValidatingProcessorTest(
x => x.Quantize(quantizer),
comparer: ValidatorComparer,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: false);
}
} }
} }

50
tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs

@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
[Fact] [Fact]
public void WuQuantizerConstructor() public void WuQuantizerConstructor()
{ {
var quantizer = new WuQuantizer(128); var expected = new QuantizerOptions { MaxColors = 128 };
var quantizer = new WuQuantizer(expected);
Assert.Equal(128, quantizer.MaxColors);
Assert.Equal(KnownDitherings.FloydSteinberg, quantizer.Dither); Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
quantizer = new WuQuantizer(false);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); expected = new QuantizerOptions { Dither = null };
Assert.Null(quantizer.Dither); quantizer = new WuQuantizer(expected);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
quantizer = new WuQuantizer(KnownDitherings.Atkinson); Assert.Null(quantizer.Options.Dither);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson };
quantizer = new WuQuantizer(expected);
quantizer = new WuQuantizer(KnownDitherings.Atkinson, 128); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(128, quantizer.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Dither);
expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 };
quantizer = new WuQuantizer(expected);
Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
} }
[Fact] [Fact]
@ -38,23 +42,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.NotNull(frameQuantizer.Options);
Assert.Equal(KnownDitherings.FloydSteinberg, frameQuantizer.Dither); Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new WuQuantizer(false); quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.False(frameQuantizer.DoDither); Assert.Null(frameQuantizer.Options.Dither);
Assert.Null(frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new WuQuantizer(KnownDitherings.Atkinson); quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.True(frameQuantizer.DoDither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
} }
} }

36
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -22,29 +22,29 @@ namespace SixLabors.ImageSharp.Tests
var octree = new OctreeQuantizer(); var octree = new OctreeQuantizer();
var wu = new WuQuantizer(); var wu = new WuQuantizer();
Assert.NotNull(werner.Dither); Assert.NotNull(werner.Options.Dither);
Assert.NotNull(webSafe.Dither); Assert.NotNull(webSafe.Options.Dither);
Assert.NotNull(octree.Dither); Assert.NotNull(octree.Options.Dither);
Assert.NotNull(wu.Dither); Assert.NotNull(wu.Options.Dither);
using (IFrameQuantizer<Rgba32> quantizer = werner.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IFrameQuantizer<Rgba32> quantizer = werner.CreateFrameQuantizer<Rgba32>(this.Configuration))
{ {
Assert.True(quantizer.DoDither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = webSafe.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IFrameQuantizer<Rgba32> quantizer = webSafe.CreateFrameQuantizer<Rgba32>(this.Configuration))
{ {
Assert.True(quantizer.DoDither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = octree.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IFrameQuantizer<Rgba32> quantizer = octree.CreateFrameQuantizer<Rgba32>(this.Configuration))
{ {
Assert.True(quantizer.DoDither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = wu.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IFrameQuantizer<Rgba32> quantizer = wu.CreateFrameQuantizer<Rgba32>(this.Configuration))
{ {
Assert.True(quantizer.DoDither); Assert.NotNull(quantizer.Options.Dither);
} }
} }
@ -58,9 +58,15 @@ namespace SixLabors.ImageSharp.Tests
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
Assert.True(image[0, 0].Equals(default(TPixel))); Assert.True(image[0, 0].Equals(default));
var quantizer = new OctreeQuantizer(dither); var options = new QuantizerOptions();
if (!dither)
{
options.Dither = null;
}
var quantizer = new OctreeQuantizer(options);
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
@ -82,9 +88,15 @@ namespace SixLabors.ImageSharp.Tests
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
Assert.True(image[0, 0].Equals(default(TPixel))); Assert.True(image[0, 0].Equals(default));
var options = new QuantizerOptions();
if (!dither)
{
options.Dither = null;
}
var quantizer = new WuQuantizer(dither); var quantizer = new WuQuantizer(options);
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {

10
tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
public void SinglePixelOpaque() public void SinglePixelOpaque()
{ {
Configuration config = Configuration.Default; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
using var image = new Image<Rgba32>(config, 1, 1, Color.Black); using var image = new Image<Rgba32>(config, 1, 1, Color.Black);
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
public void SinglePixelTransparent() public void SinglePixelTransparent()
{ {
Configuration config = Configuration.Default; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
using var image = new Image<Rgba32>(config, 1, 1, default(Rgba32)); using var image = new Image<Rgba32>(config, 1, 1, default(Rgba32));
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
} }
Configuration config = Configuration.Default; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
Configuration config = Configuration.Default; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
ImageFrame<TPixel> frame = image.Frames.RootFrame; ImageFrame<TPixel> frame = image.Frames.RootFrame;
using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config); using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config);
@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
} }
Configuration config = Configuration.Default; Configuration config = Configuration.Default;
var quantizer = new WuQuantizer(false); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config)) using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config))

1
tests/ImageSharp.Tests/TestImages.cs

@ -56,6 +56,7 @@ namespace SixLabors.ImageSharp.Tests
public const string LowColorVariance = "Png/low-variance.png"; public const string LowColorVariance = "Png/low-variance.png";
public const string PngWithMetadata = "Png/PngWithMetaData.png"; public const string PngWithMetadata = "Png/PngWithMetaData.png";
public const string InvalidTextData = "Png/InvalidTextData.png"; public const string InvalidTextData = "Png/InvalidTextData.png";
public const string David = "Png/david.png";
// Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html
public const string Filter0 = "Png/filter0.png"; public const string Filter0 = "Png/filter0.png";

3
tests/Images/Input/Png/david.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0e7e3b46a2f62251950f8c17f94c9d9a434ae643a98c058679644b5a0c5633b6
size 27218
Loading…
Cancel
Save