Browse Source

Rename quantizer and update tests

pull/3107/head
James Jackson-South 1 month ago
parent
commit
f0ce591a64
  1. 8
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  3. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  4. 6
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 2
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  6. 2
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  7. 8
      src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs
  8. 8
      src/ImageSharp/Processing/KnownQuantizers.cs
  9. 22
      src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs
  10. 354
      src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs
  11. 12
      tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs
  12. 8
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  13. 2
      tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
  14. 6
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  15. 10
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  16. 20
      tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs
  17. 26
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  18. 14
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  19. 3
      tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp
  20. 4
      tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png
  21. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png
  22. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png
  23. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png
  24. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png
  25. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png
  26. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png
  27. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png
  28. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png
  29. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png
  30. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png
  31. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png
  32. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png
  33. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png
  34. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png
  35. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png
  36. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png
  37. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
  38. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png
  39. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
  40. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png
  41. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png
  42. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
  43. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png
  44. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png
  45. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
  46. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png
  47. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png
  48. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png
  49. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png
  50. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png
  51. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png
  52. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png
  53. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png
  54. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png
  55. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png
  56. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png
  57. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png
  58. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png
  59. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png
  60. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png
  61. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png
  62. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png
  63. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png
  64. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png
  65. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png
  66. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png
  67. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png
  68. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png
  69. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png
  70. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png
  71. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png
  72. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png
  73. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png
  74. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png
  75. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png
  76. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png
  77. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png
  78. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png
  79. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png
  80. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png
  81. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png
  82. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png
  83. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png
  84. 0
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png
  85. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
  86. 3
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png
  87. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
  88. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png
  89. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png
  90. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png

8
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -54,7 +54,7 @@ internal static class AotCompilerTools
/// <remarks>
/// This method doesn't actually do anything but serves an important purpose...
/// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception:
/// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode."
/// "Attempting to JIT compile method... HexadecatreeQuantizer.ConstructPalette... while running in aot-only mode."
/// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT
/// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on
/// iOS so it bombs out.
@ -479,7 +479,7 @@ internal static class AotCompilerTools
private static void AotCompileQuantizers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileQuantizer<TPixel, OctreeQuantizer>();
AotCompileQuantizer<TPixel, HexadecatreeQuantizer>();
AotCompileQuantizer<TPixel, PaletteQuantizer>();
AotCompileQuantizer<TPixel, WebSafePaletteQuantizer>();
AotCompileQuantizer<TPixel, WernerPaletteQuantizer>();
@ -549,8 +549,8 @@ internal static class AotCompilerTools
where TPixel : unmanaged, IPixel<TPixel>
where TDither : struct, IDither
{
OctreeQuantizer<TPixel> octree = default;
default(TDither).ApplyQuantizationDither<OctreeQuantizer<TPixel>, TPixel>(ref octree, default, default, default);
HexadecatreeQuantizer<TPixel> hexadecatree = default;
default(TDither).ApplyQuantizationDither<HexadecatreeQuantizer<TPixel>, TPixel>(ref hexadecatree, default, default, default);
PaletteQuantizer<TPixel> palette = default;
default(TDither).ApplyQuantizationDither<PaletteQuantizer<TPixel>, TPixel>(ref palette, default, default, default);

2
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -13,7 +13,7 @@ public sealed class BmpEncoder : QuantizingImageEncoder
/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoder"/> class.
/// </summary>
public BmpEncoder() => this.Quantizer = KnownQuantizers.Octree;
public BmpEncoder() => this.Quantizer = KnownQuantizers.Hexadecatree;
/// <summary>
/// Gets the number of bits per pixel.

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

@ -116,7 +116,7 @@ internal sealed class BmpEncoderCore
this.bitsPerPixel = encoder.BitsPerPixel;
// TODO: Use a palette quantizer if supplied.
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree;
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Hexadecatree;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
this.transparentColorMode = encoder.TransparentColorMode;
this.infoHeaderType = encoder.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3;

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

@ -117,7 +117,7 @@ internal sealed class GifEncoderCore
if (globalQuantizer is null)
{
// Is this a gif with color information. If so use that, otherwise use octree.
// Is this a gif with color information. If so use that, otherwise use the adaptive hexadecatree quantizer.
if (gifMetadata.ColorTableMode == FrameColorTableMode.Global && gifMetadata.GlobalColorTable?.Length > 0)
{
int ti = GetTransparentIndex(quantized, frameMetadata);
@ -132,12 +132,12 @@ internal sealed class GifEncoderCore
}
else
{
globalQuantizer = new OctreeQuantizer(options);
globalQuantizer = new HexadecatreeQuantizer(options);
}
}
else
{
globalQuantizer = new OctreeQuantizer(options);
globalQuantizer = new HexadecatreeQuantizer(options);
}
}

2
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -15,7 +15,7 @@ public class TiffEncoder : QuantizingImageEncoder
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoder"/> class.
/// </summary>
public TiffEncoder() => this.Quantizer = KnownQuantizers.Octree;
public TiffEncoder() => this.Quantizer = KnownQuantizers.Hexadecatree;
/// <summary>
/// Gets the number of bits per pixel.

2
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -71,7 +71,7 @@ internal sealed class TiffEncoderCore
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
this.PhotometricInterpretation = encoder.PhotometricInterpretation;
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Octree;
this.quantizer = encoder.Quantizer ?? KnownQuantizers.Hexadecatree;
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
this.BitsPerPixel = encoder.BitsPerPixel;
this.HorizontalPredictor = encoder.HorizontalPredictor;

8
src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs

@ -12,12 +12,12 @@ namespace SixLabors.ImageSharp.Processing;
public static class QuantizeExtensions
{
/// <summary>
/// Applies quantization to the image using the <see cref="OctreeQuantizer"/>.
/// Applies quantization to the image using the <see cref="HexadecatreeQuantizer"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Quantize(this IImageProcessingContext source) =>
Quantize(source, KnownQuantizers.Octree);
Quantize(source, KnownQuantizers.Hexadecatree);
/// <summary>
/// Applies quantization to the image.
@ -29,7 +29,7 @@ public static class QuantizeExtensions
source.ApplyProcessor(new QuantizeProcessor(quantizer));
/// <summary>
/// Applies quantization to the image using the <see cref="OctreeQuantizer"/>.
/// Applies quantization to the image using the <see cref="HexadecatreeQuantizer"/>.
/// </summary>
/// <param name="source">The current image processing context.</param>
/// <param name="rectangle">
@ -37,7 +37,7 @@ public static class QuantizeExtensions
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/>.</returns>
public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) =>
Quantize(source, KnownQuantizers.Octree, rectangle);
Quantize(source, KnownQuantizers.Hexadecatree, rectangle);
/// <summary>
/// Applies quantization to the image.

8
src/ImageSharp/Processing/KnownQuantizers.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -6,14 +6,14 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Processing;
/// <summary>
/// Contains reusable static instances of known quantizing algorithms
/// Contains reusable static instances of known quantizing algorithms.
/// </summary>
public static class KnownQuantizers
{
/// <summary>
/// Gets the adaptive Octree quantizer. Fast with good quality.
/// Gets the adaptive hexadecatree quantizer. Fast with good quality.
/// </summary>
public static IQuantizer Octree { get; } = new OctreeQuantizer();
public static IQuantizer Hexadecatree { get; } = new HexadecatreeQuantizer();
/// <summary>
/// Gets the Xiaolin Wu's Color Quantizer which generates high quality output.

22
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs → src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs

@ -6,25 +6,29 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
/// <summary>
/// Allows the quantization of images pixels using Octrees.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// Quantizes images by grouping colors in an adaptive 16-way tree and reducing those groups into a palette.
/// </summary>
public class OctreeQuantizer : IQuantizer
/// <remarks>
/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha. Fully opaque mid-tone colors
/// use RGB-only routing so more branch resolution is spent on visible color detail, while transparent, dark, and
/// light colors use alpha-aware routing so opacity changes can form their own palette buckets.
/// </remarks>
public class HexadecatreeQuantizer : IQuantizer
{
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class
/// Initializes a new instance of the <see cref="HexadecatreeQuantizer"/> class
/// using the default <see cref="QuantizerOptions"/>.
/// </summary>
public OctreeQuantizer()
public HexadecatreeQuantizer()
: this(new QuantizerOptions())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// Initializes a new instance of the <see cref="HexadecatreeQuantizer"/> class.
/// </summary>
/// <param name="options">The quantizer options defining quantization rules.</param>
public OctreeQuantizer(QuantizerOptions options)
/// <param name="options">The quantizer options that control palette size, dithering, and transparency behavior.</param>
public HexadecatreeQuantizer(QuantizerOptions options)
{
Guard.NotNull(options, nameof(options));
this.Options = options;
@ -41,5 +45,5 @@ public class OctreeQuantizer : IQuantizer
/// <inheritdoc />
public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : unmanaged, IPixel<TPixel>
=> new OctreeQuantizer<TPixel>(configuration, options);
=> new HexadecatreeQuantizer<TPixel>(configuration, options);
}

354
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs

@ -12,19 +12,28 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
/// <summary>
/// Encapsulates methods to calculate the color palette if an image using an Octree pattern.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// Quantizes an image by building an adaptive 16-way color tree and reducing it to the requested palette size.
/// </summary>
/// <remarks>
/// <para>
/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha, giving the tree up to 16 children
/// per node and letting transparency participate directly in palette construction.
/// </para>
/// <para>
/// Fully opaque mid-tone colors use RGB-only routing so more branch resolution is spent on visible color detail.
/// Transparent, dark, and light colors use alpha-aware routing so opacity changes can form distinct palette buckets.
/// </para>
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
// See https://github.com/dotnet/roslyn-analyzers/issues/6151
public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
public struct HexadecatreeQuantizer<TPixel> : IQuantizer<TPixel>
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly int maxColors;
private readonly int bitDepth;
private readonly Octree octree;
private readonly Hexadecatree tree;
private readonly IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette;
private PixelMap<TPixel>? pixelMap;
@ -32,19 +41,19 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> struct.
/// Initializes a new instance of the <see cref="HexadecatreeQuantizer{TPixel}"/> struct.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behavior or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="configuration">The configuration that provides memory allocation and pixel conversion services.</param>
/// <param name="options">The quantizer options that control palette size, dithering, and transparency behavior.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public OctreeQuantizer(Configuration configuration, QuantizerOptions options)
public HexadecatreeQuantizer(Configuration configuration, QuantizerOptions options)
{
this.Configuration = configuration;
this.Options = options;
this.maxColors = this.Options.MaxColors;
this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8);
this.octree = new Octree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold);
this.tree = new Hexadecatree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default;
this.palette = default;
@ -76,23 +85,28 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
/// <inheritdoc/>
public readonly void AddPaletteColors(in Buffer2DRegion<TPixel> pixelRegion)
{
PixelRowDelegate pixelRowDelegate = new(this.octree);
QuantizerUtilities.AddPaletteColors<OctreeQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>(
PixelRowDelegate pixelRowDelegate = new(this.tree);
QuantizerUtilities.AddPaletteColors<HexadecatreeQuantizer<TPixel>, TPixel, Rgba32, PixelRowDelegate>(
ref Unsafe.AsRef(in this),
in pixelRegion,
in pixelRowDelegate);
}
/// <summary>
/// Materializes the final palette from the accumulated tree and prepares the dither lookup map when needed.
/// </summary>
private void ResolvePalette()
{
short paletteIndex = 0;
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
this.octree.Palettize(paletteSpan, ref paletteIndex);
this.tree.Palettize(paletteSpan, ref paletteIndex);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory[..paletteSpan.Length];
if (this.isDithering)
{
// Dithered colors often no longer land on a color that was seen during palette construction,
// so the quantization pass switches to nearest-palette matching once the palette is finalized.
this.pixelMap = PixelMapFactory.Create(this.Configuration, result, this.Options.ColorMatchingMode);
}
@ -108,17 +122,15 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
[MethodImpl(InliningOptions.ShortMethod)]
public readonly byte GetQuantizedColor(TPixel color, out TPixel match)
{
// Due to the addition of new colors by dithering that are not part of the original histogram,
// the octree nodes might not match the correct color.
// In this case, we must use the pixel map to get the closest color.
if (this.isDithering)
{
// Dithering introduces adjusted colors that were never inserted into the tree, so tree lookup
// is only reliable for the non-dithered path.
return (byte)this.pixelMap!.GetClosestColor(color, out match);
}
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span);
int index = this.octree.GetPaletteIndex(color);
int index = this.tree.GetPaletteIndex(color);
match = Unsafe.Add(ref paletteRef, (nuint)index);
return (byte)index;
}
@ -132,34 +144,43 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
this.paletteOwner.Dispose();
this.pixelMap?.Dispose();
this.pixelMap = null;
this.octree.Dispose();
this.tree.Dispose();
}
}
/// <summary>
/// Forwards source rows into the tree without creating an intermediate buffer.
/// </summary>
private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate<Rgba32>
{
private readonly Octree octree;
private readonly Hexadecatree tree;
public PixelRowDelegate(Octree octree) => this.octree = octree;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRowDelegate"/> struct.
/// </summary>
/// <param name="tree">The destination tree that should accumulate each visited row.</param>
public PixelRowDelegate(Hexadecatree tree) => this.tree = tree;
public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.octree.AddColors(row);
/// <inheritdoc/>
public void Invoke(ReadOnlySpan<Rgba32> row, int rowIndex) => this.tree.AddColors(row);
}
/// <summary>
/// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation.
/// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores
/// color accumulation data, and supports dynamic node allocation and reduction. It offers near-constant-time insertions
/// and lookups while consuming roughly 240 KB for the node pool.
/// Stores the adaptive 16-way partition tree used to accumulate colors and emit palette entries.
/// </summary>
internal sealed class Octree : IDisposable
/// <remarks>
/// The tree uses a fixed node arena for predictable allocation behavior, keeps per-level reducible node lists so
/// deeper buckets can be merged until the palette fits, and caches the previously inserted leaf so repeated colors
/// can be accumulated cheaply.
/// </remarks>
internal sealed class Hexadecatree : IDisposable
{
// The memory allocator.
private readonly MemoryAllocator allocator;
// Pooled buffer for OctreeNodes.
private readonly IMemoryOwner<OctreeNode> nodesOwner;
private readonly IMemoryOwner<Node> nodesOwner;
// Reducible nodes: one per level; we use an integer index; -1 means “no node.”
// One reducible-node head per level.
// Each entry stores a node index, or -1 when that level currently
// has no reducible nodes.
private readonly short[] reducibleNodes;
// Maximum number of allowable colors.
@ -186,13 +207,13 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
private readonly Stack<short> freeIndices = new();
/// <summary>
/// Initializes a new instance of the <see cref="Octree"/> class.
/// Initializes a new instance of the <see cref="Hexadecatree"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behavior or extending the library.</param>
/// <param name="maxColorBits">The maximum number of significant bits in the image.</param>
/// <param name="maxColors">The maximum number of colors to allow in the palette.</param>
/// <param name="transparencyThreshold">The threshold for transparent colors.</param>
public Octree(
/// <param name="configuration">The configuration that provides the backing memory allocator.</param>
/// <param name="maxColorBits">The number of levels to descend before forcing leaves.</param>
/// <param name="maxColors">The maximum number of palette entries the reduced tree may retain.</param>
/// <param name="transparencyThreshold">The alpha threshold below which generated palette entries become fully transparent.</param>
public Hexadecatree(
Configuration configuration,
int maxColorBits,
int maxColors,
@ -207,8 +228,7 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
// Allocate a conservative buffer for nodes.
const int capacity = 4096;
this.allocator = configuration.MemoryAllocator;
this.nodesOwner = this.allocator.Allocate<OctreeNode>(capacity, AllocationOptions.Clean);
this.nodesOwner = configuration.MemoryAllocator.Allocate<Node>(capacity, AllocationOptions.Clean);
// Create the reducible nodes array (one per level 0 .. maxColorBits-1).
this.reducibleNodes = new short[this.maxColorBits];
@ -216,24 +236,24 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
// Reserve index 0 for the root.
this.rootIndex = 0;
ref OctreeNode root = ref this.Nodes[this.rootIndex];
ref Node root = ref this.Nodes[this.rootIndex];
root.Initialize(0, this.maxColorBits, this, this.rootIndex);
}
/// <summary>
/// Gets or sets the number of leaves in the tree.
/// Gets or sets the number of leaf nodes currently representing palette buckets.
/// </summary>
public int Leaves { get; set; }
/// <summary>
/// Gets the full collection of nodes as a span.
/// Gets the underlying node arena.
/// </summary>
internal Span<OctreeNode> Nodes => this.nodesOwner.Memory.Span;
internal Span<Node> Nodes => this.nodesOwner.Memory.Span;
/// <summary>
/// Adds a span of colors to the octree.
/// Adds a row of colors to the tree.
/// </summary>
/// <param name="row">A span of color values to be added.</param>
/// <param name="row">The colors to accumulate.</param>
public void AddColors(ReadOnlySpan<Rgba32> row)
{
for (int x = 0; x < row.Length; x++)
@ -243,12 +263,13 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
/// <summary>
/// Add a color to the Octree.
/// Adds a single color sample to the tree.
/// </summary>
/// <param name="color">The color to add.</param>
/// <param name="color">The color to accumulate.</param>
private void AddColor(Rgba32 color)
{
// Ensure that the tree is not already full.
// Once the node arena is full and there are no recycled slots available, keep collapsing
// reducible leaves until the tree is small enough to make forward progress again.
if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0)
{
while (this.Leaves > this.maxColors)
@ -257,32 +278,32 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
}
// If the color is the same as the previous color, increment the node.
// Otherwise, add a new node.
// Scanlines often contain long runs of the same color. Caching the previous leaf lets those
// repeats skip the tree walk and just bump the accumulated sums in place.
if (this.previousColor.Equals(color))
{
if (this.previousNode == -1)
{
this.previousColor = color;
OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this);
Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this);
}
else
{
OctreeNode.Increment(this.previousNode, color, this);
Node.Increment(this.previousNode, color, this);
}
}
else
{
this.previousColor = color;
OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this);
Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this);
}
}
/// <summary>
/// Construct the palette from the octree.
/// Reduces the tree to the requested palette size and emits the final palette entries.
/// </summary>
/// <param name="palette">The palette to construct.</param>
/// <param name="paletteIndex">The current palette index.</param>
/// <param name="palette">The destination palette span.</param>
/// <param name="paletteIndex">The running palette index.</param>
public void Palettize(Span<TPixel> palette, ref short paletteIndex)
{
while (this.Leaves > this.maxColors)
@ -294,48 +315,45 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
/// <summary>
/// Get the palette index for the passed color.
/// Gets the palette index selected by the tree for the supplied color.
/// </summary>
/// <param name="color">The color to get the palette index for.</param>
/// <returns>The <see cref="int"/>.</returns>
/// <param name="color">The color to resolve.</param>
/// <returns>The palette index represented by the best matching leaf in the reduced tree.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetPaletteIndex(TPixel color)
=> this.Nodes[this.rootIndex].GetPaletteIndex(color.ToRgba32(), 0, this);
/// <summary>
/// Track the previous node and color.
/// Records the most recently touched leaf so repeated colors can bypass another descent.
/// </summary>
/// <param name="nodeIndex">The node index.</param>
/// <param name="nodeIndex">The leaf node index.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TrackPrevious(int nodeIndex)
=> this.previousNode = nodeIndex;
/// <summary>
/// Reduce the depth of the tree.
/// Collapses the deepest currently reducible node into a single leaf.
/// </summary>
private void Reduce()
{
// Find the deepest level containing at least one reducible node
int index = this.maxColorBits - 1;
while ((index > 0) && (this.reducibleNodes[index] == -1))
{
index--;
}
// Reduce the node most recently added to the list at level 'index'
ref OctreeNode node = ref this.Nodes[this.reducibleNodes[index]];
ref Node node = ref this.Nodes[this.reducibleNodes[index]];
this.reducibleNodes[index] = node.NextReducibleIndex;
// Decrement the leaf count after reducing the node
node.Reduce(this);
// And just in case I've reduced the last color to be added, and the next color to
// be added is the same, invalidate the previousNode...
// If the last inserted leaf was merged away, the next repeated color must walk the tree again.
this.previousNode = -1;
}
// Allocate a new OctreeNode from the pooled buffer.
// First check the freeIndices stack.
/// <summary>
/// Allocates a node index from the free list or from the unused tail of the arena.
/// </summary>
/// <returns>The allocated node index, or <c>-1</c> if no node can be allocated.</returns>
internal short AllocateNode()
{
if (this.freeIndices.Count > 0)
@ -354,9 +372,9 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
/// <summary>
/// Free a node index, making it available for re-allocation.
/// Returns a node index to the free list.
/// </summary>
/// <param name="index">The index to free.</param>
/// <param name="index">The node index to recycle.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void FreeNode(short index)
{
@ -367,8 +385,11 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
/// <inheritdoc/>
public void Dispose() => this.nodesOwner.Dispose();
/// <summary>
/// Represents one node in the hexadecatree node arena.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct OctreeNode
internal struct Node
{
public bool Leaf;
public int PixelCount;
@ -380,19 +401,21 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
public short NextReducibleIndex;
private InlineArray16<short> children;
/// <summary>
/// Gets the 16 child slots for this node.
/// </summary>
[UnscopedRef]
public Span<short> Children => this.children;
/// <summary>
/// Initialize the <see cref="OctreeNode"/>.
/// Initializes a node either as a leaf or as a reducible interior node.
/// </summary>
/// <param name="level">The level of the node.</param>
/// <param name="colorBits">The number of significant color bits in the image.</param>
/// <param name="octree">The parent octree.</param>
/// <param name="index">The index of the node.</param>
public void Initialize(int level, int colorBits, Octree octree, short index)
/// <param name="level">The depth of the node being initialized.</param>
/// <param name="colorBits">The maximum tree depth.</param>
/// <param name="tree">The owning tree.</param>
/// <param name="index">The node index in the arena.</param>
public void Initialize(int level, int colorBits, Hexadecatree tree, short index)
{
// Construct the new node.
this.Leaf = level == colorBits;
this.Red = 0;
this.Green = 0;
@ -401,76 +424,73 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
this.PixelCount = 0;
this.PaletteIndex = 0;
this.NextReducibleIndex = -1;
// Always clear the Children array.
this.Children.Fill(-1);
if (this.Leaf)
{
octree.Leaves++;
tree.Leaves++;
}
else
{
// Add this node to the reducible nodes list for its level.
this.NextReducibleIndex = octree.reducibleNodes[level];
octree.reducibleNodes[level] = index;
// Track reducible nodes per level so palette reduction can always collapse the deepest
// buckets first without scanning the entire arena.
this.NextReducibleIndex = tree.reducibleNodes[level];
tree.reducibleNodes[level] = index;
}
}
/// <summary>
/// Add a color to the Octree.
/// Descends the tree for the supplied color, allocating nodes as needed until a leaf is reached.
/// </summary>
/// <param name="nodeIndex">The node index.</param>
/// <param name="color">The color to add.</param>
/// <param name="colorBits">The number of significant color bits in the image.</param>
/// <param name="level">The level of the node.</param>
/// <param name="octree">The parent octree.</param>
public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Octree octree)
/// <param name="nodeIndex">The current node index.</param>
/// <param name="color">The color being accumulated.</param>
/// <param name="colorBits">The maximum tree depth.</param>
/// <param name="level">The current depth.</param>
/// <param name="tree">The owning tree.</param>
public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Hexadecatree tree)
{
ref OctreeNode node = ref octree.Nodes[nodeIndex];
ref Node node = ref tree.Nodes[nodeIndex];
if (node.Leaf)
{
Increment(nodeIndex, color, octree);
octree.TrackPrevious(nodeIndex);
Increment(nodeIndex, color, tree);
tree.TrackPrevious(nodeIndex);
return;
}
else
{
int index = GetColorIndex(color, level);
short childIndex;
Span<short> children = node.Children;
childIndex = children[index];
int index = GetColorIndex(color, level);
Span<short> children = node.Children;
short childIndex = children[index];
if (childIndex == -1)
{
childIndex = tree.AllocateNode();
if (childIndex == -1)
{
childIndex = octree.AllocateNode();
if (childIndex == -1)
{
// No room in the tree, so increment the count and return.
Increment(nodeIndex, color, octree);
octree.TrackPrevious(nodeIndex);
return;
}
ref OctreeNode child = ref octree.Nodes[childIndex];
child.Initialize(level + 1, colorBits, octree, childIndex);
children[index] = childIndex;
// If the arena is exhausted and no node can be reclaimed yet, fall back to
// accumulating into the current node instead of failing the insert outright.
Increment(nodeIndex, color, tree);
tree.TrackPrevious(nodeIndex);
return;
}
AddColor(childIndex, color, colorBits, level + 1, octree);
ref Node child = ref tree.Nodes[childIndex];
child.Initialize(level + 1, colorBits, tree, childIndex);
children[index] = childIndex;
}
// Keep descending until we reach the leaf bucket that should accumulate this sample.
AddColor(childIndex, color, colorBits, level + 1, tree);
}
/// <summary>
/// Increment the color components of this node.
/// Adds the supplied color sample to an existing node's running sums.
/// </summary>
/// <param name="nodeIndex">The node index.</param>
/// <param name="color">The color to increment by.</param>
/// <param name="octree">The parent octree.</param>
public static void Increment(int nodeIndex, Rgba32 color, Octree octree)
/// <param name="nodeIndex">The node index to update.</param>
/// <param name="color">The color sample being accumulated.</param>
/// <param name="tree">The owning tree.</param>
public static void Increment(int nodeIndex, Rgba32 color, Hexadecatree tree)
{
ref OctreeNode node = ref octree.Nodes[nodeIndex];
ref Node node = ref tree.Nodes[nodeIndex];
node.PixelCount++;
node.Red += color.R;
node.Green += color.G;
@ -479,10 +499,10 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
/// <summary>
/// Reduce this node by ensuring its children are all reduced (i.e. leaves) and then merging their data.
/// Merges all child nodes into this node and turns it into a leaf.
/// </summary>
/// <param name="octree">The parent octree.</param>
public void Reduce(Octree octree)
/// <param name="tree">The owning tree.</param>
public void Reduce(Hexadecatree tree)
{
// If already a leaf, do nothing.
if (this.Leaf)
@ -492,25 +512,27 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
// Now merge the (presumably reduced) children.
int pixelCount = 0;
int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0;
int sumRed = 0;
int sumGreen = 0;
int sumBlue = 0;
int sumAlpha = 0;
Span<short> children = this.Children;
for (int i = 0; i < children.Length; i++)
{
short childIndex = children[i];
if (childIndex != -1)
{
ref OctreeNode child = ref octree.Nodes[childIndex];
ref Node child = ref tree.Nodes[childIndex];
int pixels = child.PixelCount;
sumRed += child.Red;
sumGreen += child.Green;
sumBlue += child.Blue;
sumAlpha += child.Alpha;
pixelCount += pixels;
// Free the child immediately.
children[i] = -1;
octree.FreeNode(childIndex);
tree.FreeNode(childIndex);
}
}
@ -529,16 +551,16 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
this.Leaf = true;
octree.Leaves++;
tree.Leaves++;
}
/// <summary>
/// Traverse the tree to construct the palette.
/// Traverses the reduced tree and emits one palette color per leaf.
/// </summary>
/// <param name="octree">The parent octree.</param>
/// <param name="palette">The palette to construct.</param>
/// <param name="paletteIndex">The current palette index.</param>
public void ConstructPalette(Octree octree, Span<TPixel> palette, ref short paletteIndex)
/// <param name="tree">The owning tree.</param>
/// <param name="palette">The destination palette span.</param>
/// <param name="paletteIndex">The running palette index.</param>
public void ConstructPalette(Hexadecatree tree, Span<TPixel> palette, ref short paletteIndex)
{
if (this.Leaf)
{
@ -549,13 +571,12 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
Vector4.Zero,
new Vector4(255));
if (vector.W < octree.transparencyThreshold255)
if (vector.W < tree.transparencyThreshold255)
{
vector = Vector4.Zero;
}
palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W));
this.PaletteIndex = paletteIndex++;
}
else
@ -566,19 +587,20 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
int childIndex = children[i];
if (childIndex != -1)
{
octree.Nodes[childIndex].ConstructPalette(octree, palette, ref paletteIndex);
tree.Nodes[childIndex].ConstructPalette(tree, palette, ref paletteIndex);
}
}
}
}
/// <summary>
/// Get the palette index for the passed color.
/// Resolves the palette index represented by this node for the supplied color.
/// </summary>
/// <param name="color">The color to get the palette index for.</param>
/// <param name="level">The level of the node.</param>
/// <param name="octree">The parent octree.</param>
public int GetPaletteIndex(Rgba32 color, int level, Octree octree)
/// <param name="color">The color to resolve.</param>
/// <param name="level">The current tree depth.</param>
/// <param name="tree">The owning tree.</param>
/// <returns>The palette index for the best reachable leaf, or <c>-1</c> if no leaf can be reached.</returns>
public int GetPaletteIndex(Rgba32 color, int level, Hexadecatree tree)
{
if (this.Leaf)
{
@ -590,15 +612,16 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
int childIndex = children[colorIndex];
if (childIndex != -1)
{
return octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree);
return tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree);
}
// After reductions the exact branch can disappear, so fall back to the first reachable descendant leaf.
for (int i = 0; i < children.Length; i++)
{
childIndex = children[i];
if (childIndex != -1)
{
int childPaletteIndex = octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree);
int childPaletteIndex = tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree);
if (childPaletteIndex != -1)
{
return childPaletteIndex;
@ -610,37 +633,35 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
/// <summary>
/// Gets the color index at the given level.
/// Computes the child slot for a color at the supplied tree level.
/// </summary>
/// <param name="color">The color to get the index for.</param>
/// <param name="level">The level to get the index at.</param>
/// <param name="color">The color being routed.</param>
/// <param name="level">The tree depth whose bit plane should be sampled.</param>
/// <returns>The child slot index for the color at the supplied level.</returns>
/// <remarks>
/// For fully opaque mid-tone colors the tree ignores alpha and routes on RGB only, preserving more branch
/// resolution for visible color detail. For transparent, dark, and light colors it includes alpha as the
/// most significant routing bit so opacity changes can form their own branches.
/// </remarks>
public static int GetColorIndex(Rgba32 color, int level)
{
// Determine how many bits to shift based on the current tree level.
// At level 0, shift = 7; as level increases, the shift decreases.
// Sample one bit plane per level, starting at the most significant bit and moving downward.
int shift = 7 - level;
byte mask = (byte)(1 << shift);
// Compute the luminance of the RGB components using the BT.709 standard.
// This gives a measure of brightness for the color.
// Use BT.709 luminance as a cheap brightness estimate for deciding whether alpha carries
// useful information at this level for fully opaque colors.
int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B);
// Define thresholds for determining when to include the alpha bit in the index.
// The thresholds are scaled according to the current level.
// 128 is the midpoint of the 8-bit range (0–255), so shifting it right by 'level'
// produces a threshold that scales with the color cube subdivision.
// Scale the brightness thresholds with depth so deeper levels become stricter about when
// to spend a branch bit on alpha instead of RGB detail.
int darkThreshold = 128 >> level;
// The light threshold is set symmetrically: 255 minus the scaled midpoint.
int lightThreshold = 255 - (128 >> level);
// If the pixel is fully opaque and its brightness falls between the dark and light thresholds,
// ignore the alpha channel to maximize RGB resolution.
// Otherwise (if the pixel is dark, light, or semi-transparent), include the alpha bit
// to preserve any gradient that may be present.
if (color.A == 255 && luminance > darkThreshold && luminance < lightThreshold)
{
// Extract one bit each from R, G, and B channels and combine them into a 3-bit index.
// Fully opaque mid-tone colors route on RGB only, which preserves more visible color
// resolution because alpha would contribute no extra separation here.
int rBits = ((color.R & mask) >> shift) << 2;
int gBits = ((color.G & mask) >> shift) << 1;
int bBits = (color.B & mask) >> shift;
@ -648,7 +669,8 @@ public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
}
else
{
// Extract one bit from each channel including alpha (alpha becomes the most significant bit).
// Transparent, dark, and light colors include alpha as the high routing bit so opacity
// changes can form distinct buckets alongside RGB differences.
int aBits = ((color.A & mask) >> shift) << 3;
int rBits = ((color.R & mask) >> shift) << 2;
int gBits = ((color.G & mask) >> shift) << 1;

12
tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs

@ -40,19 +40,19 @@ public class EncodeIndexedPng
this.bmpCore.Dispose();
}
[Benchmark(Baseline = true, Description = "ImageSharp Octree Png")]
public void PngCoreOctree()
[Benchmark(Baseline = true, Description = "ImageSharp Hexadecatree Png")]
public void PngCoreHexadecatree()
{
using MemoryStream memoryStream = new();
PngEncoder options = new() { Quantizer = KnownQuantizers.Octree };
PngEncoder options = new() { Quantizer = KnownQuantizers.Hexadecatree };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Octree NoDither Png")]
public void PngCoreOctreeNoDither()
[Benchmark(Description = "ImageSharp Hexadecatree NoDither Png")]
public void PngCoreHexadecatreeNoDither()
{
using MemoryStream memoryStream = new();
PngEncoder options = new() { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
PngEncoder options = new() { Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}

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

@ -292,7 +292,7 @@ public class BmpEncoderTests
[Theory]
[WithFile(Bit32Rgb, PixelTypes.Rgba32)]
public void Encode_8BitColor_WithOctreeQuantizer<TPixel>(TestImageProvider<TPixel> provider)
public void Encode_8BitColor_WithHexadecatreeQuantizer<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (!TestEnvironment.Is64BitProcess)
@ -304,7 +304,7 @@ public class BmpEncoderTests
BmpEncoder encoder = new()
{
BitsPerPixel = BmpBitsPerPixel.Bit8,
Quantizer = new OctreeQuantizer()
Quantizer = new HexadecatreeQuantizer()
};
string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false);
@ -385,7 +385,7 @@ public class BmpEncoderTests
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = false,
Quantizer = KnownQuantizers.Octree
Quantizer = KnownQuantizers.Hexadecatree
};
image.SaveAsBmp(reencodedStream, encoder);
reencodedStream.Seek(0, SeekOrigin.Begin);
@ -478,7 +478,7 @@ public class BmpEncoderTests
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Octree
Quantizer = quantizer ?? KnownQuantizers.Hexadecatree
};
// Does DebugSave & load reference CompareToReferenceInput():

2
tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs

@ -125,7 +125,7 @@ public class GeneralFormatTests
public static readonly TheoryData<string> QuantizerNames =
new()
{
nameof(KnownQuantizers.Octree),
nameof(KnownQuantizers.Hexadecatree),
nameof(KnownQuantizers.WebSafe),
nameof(KnownQuantizers.Werner),
nameof(KnownQuantizers.Wu)

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

@ -115,7 +115,7 @@ public class GifEncoderTests
GifEncoder encoder = new()
{
ColorTableMode = FrameColorTableMode.Global,
Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null })
Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null })
};
// Always save as we need to compare the encoded output.
@ -124,7 +124,7 @@ public class GifEncoderTests
encoder = new GifEncoder
{
ColorTableMode = FrameColorTableMode.Local,
Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }),
Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }),
};
provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local");
@ -191,7 +191,7 @@ public class GifEncoderTests
GifEncoder encoder = new()
{
ColorTableMode = colorMode,
Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = maxColors })
Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { MaxColors = maxColors })
};
image.Save(outStream, encoder);

10
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -135,9 +135,9 @@ public class WebpEncoderTests
// Alpha thresholding is 64/255F.
GifEncoder gifEncoder = new()
{
Quantizer = new OctreeQuantizer(options)
Quantizer = new HexadecatreeQuantizer(options)
};
provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "octree");
provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "hexadecatree");
gifEncoder = new GifEncoder
{
@ -152,8 +152,8 @@ public class WebpEncoderTests
};
using Image<TPixel> cloned1 = image.Clone();
cloned1.Mutate(c => c.Quantize(new OctreeQuantizer(options)));
provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "octree");
cloned1.Mutate(c => c.Quantize(new HexadecatreeQuantizer(options)));
provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "hexadecatree");
using Image<TPixel> cloned2 = image.Clone();
cloned2.Mutate(c => c.Quantize(new WuQuantizer(options)));
@ -162,7 +162,7 @@ public class WebpEncoderTests
// Now blend the images with a blue background and save as webp.
using Image<Rgba32> background1 = new(image.Width, image.Height, Color.White.ToPixel<Rgba32>());
background1.Mutate(c => c.DrawImage(cloned1, 1));
provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "octree-blended");
provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "hexadecatree-blended");
using Image<Rgba32> background2 = new(image.Width, image.Height, Color.White.ToPixel<Rgba32>());
background2.Mutate(c => c.DrawImage(cloned2, 1));

20
tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs → tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs

@ -8,37 +8,37 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization;
[Trait("Category", "Processors")]
public class OctreeQuantizerTests
public class HexadecatreeQuantizerTests
{
[Fact]
public void OctreeQuantizerConstructor()
public void HexadecatreeQuantizerConstructor()
{
QuantizerOptions expected = new() { MaxColors = 128 };
OctreeQuantizer quantizer = new(expected);
HexadecatreeQuantizer quantizer = new(expected);
Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither);
expected = new QuantizerOptions { Dither = null };
quantizer = new OctreeQuantizer(expected);
quantizer = new HexadecatreeQuantizer(expected);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Null(quantizer.Options.Dither);
expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson };
quantizer = new OctreeQuantizer(expected);
quantizer = new HexadecatreeQuantizer(expected);
Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 };
quantizer = new OctreeQuantizer(expected);
quantizer = new HexadecatreeQuantizer(expected);
Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors);
Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither);
}
[Fact]
public void OctreeQuantizerCanCreateFrameQuantizer()
public void HexadecatreeQuantizerCanCreateFrameQuantizer()
{
OctreeQuantizer quantizer = new();
HexadecatreeQuantizer quantizer = new();
IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
@ -46,14 +46,14 @@ public class OctreeQuantizerTests
Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null });
quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.Null(frameQuantizer.Options.Dither);
frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);

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

@ -74,15 +74,15 @@ public class QuantizerTests
= new()
{
// Known uses error diffusion by default.
KnownQuantizers.Octree,
KnownQuantizers.Hexadecatree,
KnownQuantizers.WebSafe,
KnownQuantizers.Werner,
KnownQuantizers.Wu,
new OctreeQuantizer(NoDitherOptions),
new HexadecatreeQuantizer(NoDitherOptions),
new WebSafePaletteQuantizer(NoDitherOptions),
new WernerPaletteQuantizer(NoDitherOptions),
new WuQuantizer(NoDitherOptions),
new OctreeQuantizer(OrderedDitherOptions),
new HexadecatreeQuantizer(OrderedDitherOptions),
new WebSafePaletteQuantizer(OrderedDitherOptions),
new WernerPaletteQuantizer(OrderedDitherOptions),
new WuQuantizer(OrderedDitherOptions)
@ -91,52 +91,52 @@ public class QuantizerTests
public static readonly TheoryData<IQuantizer> DitherScaleQuantizers
= new()
{
new OctreeQuantizer(Diffuser0_ScaleDitherOptions),
new HexadecatreeQuantizer(Diffuser0_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions),
new WuQuantizer(Diffuser0_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions),
new HexadecatreeQuantizer(Diffuser0_25_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions),
new WuQuantizer(Diffuser0_25_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions),
new HexadecatreeQuantizer(Diffuser0_5_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions),
new WuQuantizer(Diffuser0_5_ScaleDitherOptions),
new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions),
new HexadecatreeQuantizer(Diffuser0_75_ScaleDitherOptions),
new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions),
new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions),
new WuQuantizer(Diffuser0_75_ScaleDitherOptions),
new OctreeQuantizer(DiffuserDitherOptions),
new HexadecatreeQuantizer(DiffuserDitherOptions),
new WebSafePaletteQuantizer(DiffuserDitherOptions),
new WernerPaletteQuantizer(DiffuserDitherOptions),
new WuQuantizer(DiffuserDitherOptions),
new OctreeQuantizer(Ordered0_ScaleDitherOptions),
new HexadecatreeQuantizer(Ordered0_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions),
new WuQuantizer(Ordered0_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_25_ScaleDitherOptions),
new HexadecatreeQuantizer(Ordered0_25_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions),
new WuQuantizer(Ordered0_25_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_5_ScaleDitherOptions),
new HexadecatreeQuantizer(Ordered0_5_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions),
new WuQuantizer(Ordered0_5_ScaleDitherOptions),
new OctreeQuantizer(Ordered0_75_ScaleDitherOptions),
new HexadecatreeQuantizer(Ordered0_75_ScaleDitherOptions),
new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions),
new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions),
new WuQuantizer(Ordered0_75_ScaleDitherOptions),
new OctreeQuantizer(OrderedDitherOptions),
new HexadecatreeQuantizer(OrderedDitherOptions),
new WebSafePaletteQuantizer(OrderedDitherOptions),
new WernerPaletteQuantizer(OrderedDitherOptions),
new WuQuantizer(OrderedDitherOptions),

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

@ -15,12 +15,12 @@ public class QuantizedImageTests
{
WernerPaletteQuantizer werner = new();
WebSafePaletteQuantizer webSafe = new();
OctreeQuantizer octree = new();
HexadecatreeQuantizer hexadecatree = new();
WuQuantizer wu = new();
Assert.NotNull(werner.Options.Dither);
Assert.NotNull(webSafe.Options.Dither);
Assert.NotNull(octree.Options.Dither);
Assert.NotNull(hexadecatree.Options.Dither);
Assert.NotNull(wu.Options.Dither);
using (IQuantizer<Rgba32> quantizer = werner.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
@ -33,7 +33,7 @@ public class QuantizedImageTests
Assert.NotNull(quantizer.Options.Dither);
}
using (IQuantizer<Rgba32> quantizer = octree.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
using (IQuantizer<Rgba32> quantizer = hexadecatree.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
{
Assert.NotNull(quantizer.Options.Dither);
}
@ -47,7 +47,7 @@ public class QuantizedImageTests
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)]
public void OctreeQuantizerYieldsCorrectTransparentPixel<TPixel>(
public void HexadecatreeQuantizerYieldsCorrectTransparentPixel<TPixel>(
TestImageProvider<TPixel> provider,
bool dither)
where TPixel : unmanaged, IPixel<TPixel>
@ -60,7 +60,7 @@ public class QuantizedImageTests
options.Dither = null;
}
OctreeQuantizer quantizer = new(options);
HexadecatreeQuantizer quantizer = new(options);
foreach (ImageFrame<TPixel> frame in image.Frames)
{
@ -103,8 +103,8 @@ public class QuantizedImageTests
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
OctreeQuantizer octreeQuantizer = new();
IQuantizer<TPixel> quantizer = octreeQuantizer.CreatePixelSpecificQuantizer<TPixel>(Configuration.Default, new QuantizerOptions { MaxColors = 128 });
HexadecatreeQuantizer hexadecatreeQuantizer = new();
IQuantizer<TPixel> quantizer = hexadecatreeQuantizer.CreatePixelSpecificQuantizer<TPixel>(Configuration.Default, new QuantizerOptions { MaxColors = 128 });
ImageFrame<TPixel> frame = image.Frames[0];
quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds);
}

3
tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4
size 9270

4
tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3
size 945821
oid sha256:770061fbb29cd20bc700ce3fc57e38a758c632c3e89de51f5fbee3d5d522539e
size 912635

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27f6e8e195c4431dc7354a379152d3a8664582bc2bb1c8960ebf4088aa6505e2
size 248709

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:46b5751dc43e9ad5541913cf851ef1b061aa474a95283c712511531202d7015e
size 239326

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0086044f12a7c58e49733f203af29a8aff2826ea654730274720eada15669254
size 249163

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85ee8479984aa52f837badbc49085c5448597fbfd987438fe25b58bad475e85f
size 239498

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f
size 216030
oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f
size 215873

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f
size 216030
oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f
size 215873

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a
size 226727
oid sha256:5eb87f02c7924b764bbd2c951047b7204c56a0a1a0d6853a0fb3d30a56ed0184
size 226633

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7
size 220192
oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3
size 220099

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7
size 220192
oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3
size 220099

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f
size 230883
oid sha256:c4548abed72e4f833b33eed14392206d7232112fc651becb2351fdee27da5bc1
size 230687

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d
size 263152
oid sha256:832173c8ca6bd7a8bf417d83b459ccddb541daed1c31539bf596cacea455441d
size 263018

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095
size 262298
oid sha256:15a6dc485f0c3fd4c9fbbdb6b50437d58d68210790e37f8aab32e66a864e2746
size 261872

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6eeed563b407940e2a05f068c42b52738e6e1217a1500c9230f7068ca4e9f1e
size 304162

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3dc7dc55af4ef0741a66c569876ad8a2df27164a653baa5bae536e6d121b2c11
size 300528

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0b65e7903fbfa1ed0682221fdd86c6f0448b3f6a886cae5379720cce881a1f1e
size 305962

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce381c2d261b9b1ca61d8f6e2ff07b992283c327dc6b7cf53c7e5c9317abb7d3
size 316443

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2bfc23a95df8a88ac6e2777d67f381e800d23647c162a9a97131a101bbb97143
size 306703

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d3f58a108d933ec9ac0a5271af5b65d0a8ab9d521d54e48312b280cc42d71ac
size 322049

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be
size 269397
oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291
size 268504

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be
size 269397
oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291
size 268504

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441
size 270622
oid sha256:b5fa657236e12cbb2a8d2cd747029723a6b3829b475f28626d7647d7b2150918
size 271579

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c
size 284481
oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8
size 284056

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c
size 284481
oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8
size 284056

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a
size 285370
oid sha256:61ed5f4d77428be46357609d80a66e884dedbb8c255fdcc71d49eeba0eed2bf2
size 285037

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670
size 316544
oid sha256:1cc2ef3cb819b5a82e0af32c3ab44aff0206530e291b00bdade58da2ebe4494a
size 308246

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d
size 317309
oid sha256:575c8d81152642fa0eec0ea9901d1941fea58b7686cfaac1d01e0bf59f393c4b
size 308330

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63
size 323979
oid sha256:ba295a5ddb79bc61f0be9a28a636fdcc63055c26c46872d407fe20ff785f11ed
size 310415

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5
size 13160

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9a87ef109c08411ca61d91ddcf010c272303a17abd90b6ba2204eac021055e5
size 13665

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:81496d88b42edf4b39ab723d0b5414b56140892f45d30fc2435904b630fa9af5
size 13886

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5
size 13160

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:997e5281abd8cf3a587984ec1b7e31487ec5ddf16326d025124833d536e4ac27
size 13910

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5
size 13160

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89
size 13158

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:abfdd1e40c2c1d7fde419bda1da6e534ed989598e790b8ae4de35152a83f77a0
size 13686

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60c28eb1dc3c0416b20cec230917c0e4a70dd2929467bbab796ecbb04fe5a178
size 13886

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89
size 13158

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a523f097bf3b155f3823c5e400190b5d5e0d4470db7136576472c3257db76600
size 13909

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89
size 13158

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf
size 13432
oid sha256:dd31b6fc59e1f9f88230d57b39362b76cedd0bd94e15904f69071ba3f465e48d
size 13656

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d
size 13158
oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489
size 13160

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4ac8b88b317281738d833fc71f52348d9f4f45ea5a1303dd91fdb8b42be4267
size 13186
oid sha256:dd738ee2a397bb1ee305f03c70e185dea6f67827dc15b9df1966cfe8c0f28040
size 13177

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1305d54f2139d4577490317051d6ce94a7fc8dd45b902d87a30fb04098dd4594
size 13407
oid sha256:2a2df64f89df17428415932c2ef0028d8ad408b5276264d99e6038b70473ebde
size 13417

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d
size 13158
oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489
size 13160

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3fc3a7ace123c330ea06072eb36dd5d65ed9154d4d0f55a828fc542c8a422c1
size 13472
oid sha256:234854be2a3f774a58baf79f20e68c7331b6caff486ab4b1e509a96e2a3d70b9
size 13455

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35757f2e0831cae2fbd3cc11ffaaae855e853ebaa9a1a5564b6568a5e1c442e9
size 16031
oid sha256:ef65ce360293ca5659730747087c15735c15df1143204acb60120a5b68cd7cd4
size 15905

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82
size 17520
oid sha256:6618f169cf4b585979f8e9261af88fe4a61c3c40b453a159cb643cc062a6a9dc
size 17517

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d
size 13158
oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489
size 13160

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5
size 18182
oid sha256:4acf21f23978c83c9872bb2575ab45e4f0bbc86c8610c99479b1469fc12df5f2
size 18112

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72
size 44622
oid sha256:4ded8db323023a7c7620bba3b2259a549571442fe0a37883c7755ac69ae9d6d5
size 44646

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3802cfe67638a24869d6cc9ace1d94460b4c0c26f2c91b12b95fa8f979de64bb
size 101579
oid sha256:83c8403f5d0e5457721d992c1e6980134e8a65a1f646163a4f091cf34583ca02
size 101417

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf2021eba9edbb2295924f8394472ac0bb237f0c462c39aa32a2074ef15f9acc
size 81771
oid sha256:e5412b892143bb433804c662750a64a1660b2072520db53d76ec6897c636ec50
size 81742

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d11b18946d373b995ecbb449c8c4cfcc7078aad1c8705997bcbf83131acde03
size 102439
oid sha256:a88a48586502de786aca0b36341cf6033fb3ec3ce7924ce1e2819fd14791ffe4
size 102235

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22920fb2379dee7d12fee52f6a39b8e46e1e99f77b91f879c51bb33a981dfdcb
size 98851

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7c7137e1b87d317d7e139cde8499deafa89f27bddba146cc5736f9c0566778c5
size 81609

0
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png → tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2236e81d33fcfb50afb9d5fd1a38c5ddf5d33fbb52de1c3204a4a9892fd334ce
size 99084

3
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4b59097d1507236af2556ae5f2638360b223b7752cd4c8f760bc14673d811d0
size 81709

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a8d9c0d81525d9f37d2f36946939040aea30edfc2b7ec0bf329fb49f6c7d73f
size 69896
oid sha256:aee197677c3276d4abb8fc027358b38be26462374e364841781626f0aa67e1a4
size 69769

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4474b94e2d563938e10ec0526e7d94ba06b440db51b910604e752f7f9e814d66
size 110757
oid sha256:0b3c8dc7e653ef1846c7359e9a0f719bee91549846f160abb547cd0aab6a8a59
size 110711

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58a61c1d9a1d05acd484948c3e5c0496dbc74c0060f5de71741de39eae04ffa8
size 103875
oid sha256:4b95721a963def9e82dd32e277ed4594213920d7808ad26696d01e4f8fda842e
size 103855

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6649918c0394ead13c016a57b6a08561290651bccac88f7f15ba0e29dc5faa4
size 110422
oid sha256:cb174c104cdcf35433c98522a1d9d52ccf42e8927e0b59fec3556aeee8b15a47
size 110505

Loading…
Cancel
Save