Browse Source

Merge pull request #1654 from SixLabors/js/quantizer

Quantization: reduce memory footprint without compromising speed and quality
pull/1669/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
f1bb0e40ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 13
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 10
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  4. 16
      src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
  5. 136
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  6. 5
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  7. 134
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  8. 98
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  9. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  10. 12
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  11. 44
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  12. 61
      src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
  13. 29
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  14. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  15. 38
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  16. 13
      tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs
  17. 4
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  18. 2
      tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp
  19. 2
      tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp
  20. 4
      tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png
  21. 4
      tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
  22. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png
  23. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png
  24. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png
  25. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png
  26. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png
  27. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png
  28. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png
  29. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png
  30. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png
  31. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png
  32. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png
  33. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png
  34. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png
  35. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png
  36. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png
  37. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png
  38. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png
  39. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png
  40. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png
  41. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png
  42. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png
  43. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png
  44. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png
  45. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png
  46. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png
  47. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png
  48. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png
  49. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png
  50. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png
  51. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png
  52. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png
  53. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png
  54. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png
  55. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png
  56. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png
  57. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png
  58. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png
  59. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png
  60. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png
  61. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png
  62. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png
  63. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png
  64. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png
  65. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png
  66. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png
  67. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png
  68. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png
  69. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png
  70. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png
  71. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png
  72. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
  73. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png
  74. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png
  75. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
  76. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png
  77. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png
  78. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
  79. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png
  80. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png
  81. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png
  82. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png
  83. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png
  84. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png
  85. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png
  86. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png
  87. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png
  88. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png
  89. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png
  90. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png
  91. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png
  92. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.25.png
  93. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.5.png
  94. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.75.png
  95. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_0.png
  96. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_ErrorDither_1.png
  97. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png
  98. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png
  99. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png
  100. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.png

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

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
}
/// <summary>

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

@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default;
bool pixelMapSet = false;
bool pixelMapHasValue = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
@ -166,17 +166,22 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
if (!pixelMapSet)
if (!pixelMapHasValue)
{
pixelMapSet = true;
pixelMapHasValue = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
}
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap, true);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
if (pixelMapHasValue)
{
pixelMap.Dispose();
}
}
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)

10
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -131,10 +131,6 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes)
{
ThrowInvalidAllocationException<T>(length);
}
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
@ -171,9 +167,9 @@ namespace SixLabors.ImageSharp.Memory
}
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowInvalidAllocationException<T>(int length) =>
private static void ThrowInvalidAllocationException<T>(int length, int max) =>
throw new InvalidMemoryOperationException(
$"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator.");
$"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator.");
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{

16
src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8);
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// Dithers the image reducing it to a web-safe palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither));
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// Dithers the image reducing it to a web-safe palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale));
/// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering.
/// Dithers the image reducing it to the given palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette));
/// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering.
/// Dithers the image reducing it to the given palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8, rectangle);
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// Dithers the image reducing it to a web-safe palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle);
/// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering.
/// Dithers the image reducing it to a web-safe palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle);
/// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering.
/// Dithers the image reducing it to the given palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>
@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle);
/// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering.
/// Dithers the image reducing it to the given palette.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param>

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

@ -3,7 +3,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -110,17 +109,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>(
ref quantizer,
in Unsafe.AsRef(this),
source,
destination,
bounds);
int spread = CalculatePaletteSpread(destination.Palette.Length);
float scale = quantizer.Options.DitherScale;
ParallelRowIterator.IterateRows(
quantizer.Configuration,
bounds,
in ditherOperation);
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
ReadOnlySpan<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
Span<byte> destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length);
for (int x = 0; x < sourceRow.Length; x++)
{
TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale);
destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _);
}
}
}
/// <inheritdoc/>
@ -132,16 +134,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
var ditherOperation = new PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel>(
in processor,
in Unsafe.AsRef(this),
source,
bounds);
int spread = CalculatePaletteSpread(processor.Palette.Length);
float scale = processor.DitherScale;
ParallelRowIterator.IterateRows(
processor.Configuration,
bounds,
in ditherOperation);
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
for (int x = 0; x < row.Length; x++)
{
ref TPixel sourcePixel = ref row[x];
TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale);
sourcePixel = processor.GetPaletteColor(dithered);
}
}
}
// Spread assumes an even colorspace distribution and precision.
@ -195,95 +201,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
private readonly struct QuantizeDitherRowOperation<TFrameQuantizer, TPixel> : IRowOperation
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowOperation(
ref TFrameQuantizer quantizer,
in OrderedDither dither,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
this.dither = dither;
this.source = source;
this.destination = destination;
this.bounds = bounds;
this.spread = CalculatePaletteSpread(destination.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
ref TFrameQuantizer quantizer = ref Unsafe.AsRef(this.quantizer);
int spread = this.spread;
float scale = this.quantizer.Options.DitherScale;
ReadOnlySpan<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
Span<byte> destRow =
this.destination.GetWritablePixelRowSpanUnsafe(y - this.bounds.Y).Slice(0, sourceRow.Length);
for (int x = 0; x < sourceRow.Length; x++)
{
TPixel dithered = this.dither.Dither(sourceRow[x], x, y, spread, scale);
destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _);
}
}
}
private readonly struct PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel> : IRowOperation
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TPaletteDitherImageProcessor processor;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds;
private readonly float scale;
private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowOperation(
in TPaletteDitherImageProcessor processor,
in OrderedDither dither,
ImageFrame<TPixel> source,
Rectangle bounds)
{
this.processor = processor;
this.dither = dither;
this.source = source;
this.bounds = bounds;
this.scale = processor.DitherScale;
this.spread = CalculatePaletteSpread(processor.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
ref TPaletteDitherImageProcessor processor = ref Unsafe.AsRef(this.processor);
int spread = this.spread;
float scale = this.scale;
Span<TPixel> row = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
for (int x = 0; x < row.Length; x++)
{
ref TPixel sourcePixel = ref row[x];
TPixel dithered = this.dither.Dither(sourcePixel, x, y, spread, scale);
sourcePixel = processor.GetPaletteColor(dithered);
}
}
}
}
}

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

@ -62,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
if (disposing)
{
this.paletteOwner.Dispose();
this.ditherProcessor.Dispose();
}
this.paletteOwner = null;
@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel)"/>.
/// </summary>
/// <remarks>Internal for AOT</remarks>
internal readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel>
internal readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel>, IDisposable
{
private readonly EuclideanPixelMap<TPixel> pixelMap;
@ -101,6 +102,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.pixelMap.GetClosestColor(color, out TPixel match);
return match;
}
public void Dispose() => this.pixelMap.Dispose();
}
}
}

134
src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Concurrent;
using System.Numerics;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -14,11 +14,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Gets the closest color to the supplied color based upon the Euclidean distance.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal readonly struct EuclideanPixelMap<TPixel>
/// <para>
/// This class is not threadsafe and should not be accessed in parallel.
/// Doing so will result in non-idempotent results.
/// </para>
internal readonly struct EuclideanPixelMap<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Vector4[] vectorCache;
private readonly ConcurrentDictionary<TPixel, int> distanceCache;
private readonly Rgba32[] rgbaPalette;
private readonly ColorDistanceCache cache;
/// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
@ -29,11 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{
this.Palette = palette;
this.vectorCache = new Vector4[palette.Length];
// Use the same rules across all target frameworks.
this.distanceCache = new ConcurrentDictionary<TPixel, int>(Environment.ProcessorCount, 31);
PixelOperations<TPixel>.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache);
this.rgbaPalette = new Rgba32[palette.Length];
this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
PixelOperations<TPixel>.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette);
}
/// <summary>
@ -57,11 +59,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public int GetClosestColor(TPixel color, out TPixel match)
{
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span);
Unsafe.SkipInit(out Rgba32 rgba);
color.ToRgba32(ref rgba);
// Check if the color is in the lookup table
if (!this.distanceCache.TryGetValue(color, out int index))
if (!this.cache.TryGetValue(rgba, out short index))
{
return this.GetClosestColorSlow(color, ref paletteRef, out match);
return this.GetClosestColorSlow(rgba, ref paletteRef, out match);
}
match = Unsafe.Add(ref paletteRef, index);
@ -69,17 +73,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match)
private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
{
// Loop through the palette and find the nearest match.
int index = 0;
float leastDistance = float.MaxValue;
var vector = color.ToVector4();
ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference<Vector4>(this.vectorCache);
for (int i = 0; i < this.Palette.Length; i++)
for (int i = 0; i < this.rgbaPalette.Length; i++)
{
Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i);
float distance = Vector4.DistanceSquared(vector, candidate);
Rgba32 candidate = this.rgbaPalette[i];
int distance = DistanceSquared(rgba, candidate);
// If it's an exact match, exit the loop
if (distance == 0)
@ -97,9 +99,103 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
// Now I have the index, pop it into the cache for next time
this.distanceCache[color] = index;
this.cache.Add(rgba, (byte)index);
match = Unsafe.Add(ref paletteRef, index);
return index;
}
/// <summary>
/// Returns the Euclidean distance squared between two specified points.
/// </summary>
/// <param name="a">The first point.</param>
/// <param name="b">The second point.</param>
/// <returns>The distance squared.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int DistanceSquared(Rgba32 a, Rgba32 b)
{
int deltaR = a.R - b.R;
int deltaG = a.G - b.G;
int deltaB = a.B - b.B;
int deltaA = a.A - b.A;
return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA);
}
public void Dispose() => this.cache.Dispose();
/// <summary>
/// A cache for storing color distance matching results.
/// </summary>
/// <remarks>
/// <para>
/// The granularity of the cache has been determined based upon the current
/// suite of test images and provides the lowest possible memory usage while
/// providing enough match accuracy.
/// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB).
/// </para>
/// </remarks>
private unsafe struct ColorDistanceCache : IDisposable
{
private const int IndexBits = 5;
private const int IndexAlphaBits = 5;
private const int IndexCount = (1 << IndexBits) + 1;
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
private const int RgbShift = 8 - IndexBits;
private const int AlphaShift = 8 - IndexAlphaBits;
private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
private MemoryHandle tableHandle;
private readonly IMemoryOwner<short> table;
private readonly short* tablePointer;
public ColorDistanceCache(MemoryAllocator allocator)
{
this.table = allocator.Allocate<short>(Entries);
this.table.GetSpan().Fill(-1);
this.tableHandle = this.table.Memory.Pin();
this.tablePointer = (short*)this.tableHandle.Pointer;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Add(Rgba32 rgba, byte index)
{
int r = rgba.R >> RgbShift;
int g = rgba.G >> RgbShift;
int b = rgba.B >> RgbShift;
int a = rgba.A >> AlphaShift;
int idx = GetPaletteIndex(r, g, b, a);
this.tablePointer[idx] = index;
}
[MethodImpl(InliningOptions.ShortMethod)]
public bool TryGetValue(Rgba32 rgba, out short match)
{
int r = rgba.R >> RgbShift;
int g = rgba.G >> RgbShift;
int b = rgba.B >> RgbShift;
int a = rgba.A >> AlphaShift;
int idx = GetPaletteIndex(r, g, b, a);
match = this.tablePointer[idx];
return match > -1;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
=> (r << ((IndexBits << 1) + IndexAlphaBits))
+ (r << (IndexBits + IndexAlphaBits + 1))
+ (g << (IndexBits + IndexAlphaBits))
+ (r << (IndexBits << 1))
+ (r << (IndexBits + 1))
+ (g << IndexBits)
+ ((r + g + b) << IndexAlphaBits)
+ r + g + b + a;
public void Dispose()
{
if (this.table != null)
{
this.tableHandle.Dispose();
this.table.Dispose();
}
}
}
}
}

98
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -20,10 +20,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly int maxColors;
private readonly int bitDepth;
private readonly Octree octree;
private IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@ -42,10 +44,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Options = options;
this.maxColors = this.Options.MaxColors;
this.octree = new Octree(Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8));
this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8);
this.octree = new Octree(this.bitDepth);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.palette = default;
this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false;
}
@ -67,37 +71,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion)
{
Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer;
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan();
// Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++)
using (IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width))
{
Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
Span<Rgba32> bufferSpan = buffer.GetSpan();
for (int x = 0; x < bufferSpan.Length; x++)
// Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++)
{
Rgba32 rgba = bufferSpan[x];
Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
for (int x = 0; x < bufferSpan.Length; x++)
{
Rgba32 rgba = bufferSpan[x];
// Add the color to the Octree
this.octree.AddColor(rgba);
// Add the color to the Octree
this.octree.AddColor(rgba);
}
}
}
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
int paletteIndex = 0;
this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
// Length of reduced palette + transparency.
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
// On very rare occasions, (blur.png), the quantizer does not preserve a
// transparent entry when palletizing the captured colors.
// To workaround this we ensure the palette ends with the default color
// for higher bit depths. Lower bit depths will correctly reduce the palette.
// TODO: Investigate more evenly reduced palette reduction.
int max = this.maxColors;
if (this.bitDepth == 8)
{
max--;
}
this.octree.Palletize(paletteSpan, max, ref paletteIndex);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
{
this.pixelMap.Dispose();
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
this.palette = result;
}
@ -118,8 +141,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return (byte)this.pixelMap.GetClosestColor(color, out match);
}
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span);
var index = (byte)this.octree.GetPaletteIndex(color);
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span);
byte index = (byte)this.octree.GetPaletteIndex(color);
match = Unsafe.Add(ref paletteRef, index);
return index;
}
@ -132,6 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.isDisposed = true;
this.paletteOwner.Dispose();
this.paletteOwner = null;
this.pixelMap.Dispose();
}
}
@ -176,21 +200,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.previousNode = null;
}
/// <summary>
/// Gets the mask used when getting the appropriate pixels for a given node.
/// </summary>
private static ReadOnlySpan<byte> Mask => new byte[]
{
0b10000000,
0b1000000,
0b100000,
0b10000,
0b1000,
0b100,
0b10,
0b1
};
/// <summary>
/// Gets or sets the number of leaves in the tree
/// </summary>
@ -251,7 +260,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)]
public void Palletize(Span<TPixel> palette, int colorCount, ref int paletteIndex)
{
while (this.Leaves > colorCount - 1)
while (this.Leaves > colorCount)
{
this.Reduce();
}
@ -269,7 +278,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)]
public int GetPaletteIndex(TPixel color)
{
Rgba32 rgba = default;
Unsafe.SkipInit(out Rgba32 rgba);
color.ToRgba32(ref rgba);
return this.root.GetPaletteIndex(ref rgba, 0);
}
@ -468,7 +477,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Vector3.Zero,
new Vector3(255));
TPixel pixel = default;
Unsafe.SkipInit(out TPixel pixel);
pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue));
palette[index] = pixel;
@ -517,7 +526,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
child = this.children[i];
if (child != null)
{
var childIndex = child.GetPaletteIndex(ref pixel, level + 1);
int childIndex = child.GetPaletteIndex(ref pixel, level + 1);
if (childIndex != 0)
{
return childIndex;
@ -538,15 +547,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetColorIndex(ref Rgba32 color, int level)
{
DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level));
int shift = 7 - level;
ref byte maskRef = ref MemoryMarshal.GetReference(Mask);
byte mask = Unsafe.Add(ref maskRef, level);
byte mask = (byte)(1 << shift);
return ((color.R & mask) >> shift)
| ((color.G & mask) >> (shift - 1))
| ((color.B & mask) >> (shift - 2));
| (((color.G & mask) >> shift) << 1)
| (((color.B & mask) >> shift) << 2);
}
/// <summary>

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

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteQuantizer<TPixel>(configuration, options, pixelMap);
return new PaletteQuantizer<TPixel>(configuration, options, pixelMap, false);
}
}
}

12
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly bool leaveMap;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
@ -24,11 +25,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="pixelMap">The pixel map for looking up color matches from a predefined palette.</param>
/// <param name="leaveMap">
/// <see langword="true"/> to leave the pixel map undisposed after disposing the <see cref="PaletteQuantizer{TPixel}"/> object; otherwise, <see langword="false"/>.
/// </param>
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteQuantizer(
Configuration configuration,
QuantizerOptions options,
EuclideanPixelMap<TPixel> pixelMap)
EuclideanPixelMap<TPixel> pixelMap,
bool leaveMap)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
@ -36,6 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration;
this.Options = options;
this.pixelMap = pixelMap;
this.leaveMap = leaveMap;
}
/// <inheritdoc/>
@ -66,6 +72,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/>
public void Dispose()
{
if (!this.leaveMap)
{
this.pixelMap.Dispose();
}
}
}
}

44
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -41,46 +41,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest);
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
ParallelRowIterator.IterateRowIntervals(
configuration,
interest,
in operation);
}
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> quantized;
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
int offsetY = interest.Top;
int offsetX = interest.Left;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> quantized)
for (int y = interest.Y; y < interest.Height; y++)
{
this.bounds = bounds;
this.source = source;
this.quantized = quantized;
}
Span<TPixel> row = source.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedRow = quantized.GetPixelRowSpan(y - offsetY);
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
for (int x = interest.Left; x < interest.Right; x++)
{
Span<TPixel> row = this.source.GetPixelRowSpan(y);
ReadOnlySpan<byte> quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
row[x] = paletteSpan[quantizedRow[x - offsetX]];
}
row[x] = paletteSpan[quantizedRow[x - offsetX]];
}
}
}

61
src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs

@ -3,7 +3,6 @@
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -126,62 +125,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (dither is null)
{
var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(
ref quantizer,
source,
destination,
bounds);
int offsetY = bounds.Top;
int offsetX = bounds.Left;
ParallelRowIterator.IterateRowIntervals(
quantizer.Configuration,
bounds,
in operation);
return;
}
dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds);
}
private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
this.source = source;
this.destination = destination;
this.bounds = bounds;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
for (int y = bounds.Y; y < bounds.Height; y++)
{
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y);
Span<byte> destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY);
Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<byte> destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
for (int x = bounds.Left; x < bounds.Right; x++)
{
destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _);
destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _);
}
}
return;
}
dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds);
}
}
}

29
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -73,6 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private int maxColors;
private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@ -94,10 +94,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tagsOwner = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.paletteOwner = this.memoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.palette = default;
this.colorCube = new Box[this.maxColors];
this.isDisposed = false;
this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null);
}
@ -127,9 +128,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Get3DMoments(this.memoryAllocator);
this.BuildCube();
// Slice again since maxColors has been updated since the buffer was created.
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan().Slice(0, this.maxColors);
ReadOnlySpan<Moment> momentsSpan = this.momentsOwner.GetSpan();
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
for (int k = 0; k < this.maxColors; k++)
for (int k = 0; k < paletteSpan.Length; k++)
{
this.Mark(ref this.colorCube[k], (byte)k);
@ -142,8 +144,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
}
}
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, this.maxColors);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
if (this.isDithering)
{
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
{
this.pixelMap.Dispose();
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
}
this.palette = result;
}
@ -170,7 +184,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<byte> tagSpan = this.tagsOwner.GetSpan();
byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span);
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span);
match = Unsafe.Add(ref paletteRef, index);
return index;
}
@ -187,6 +201,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = null;
this.tagsOwner = null;
this.paletteOwner = null;
this.pixelMap.Dispose();
}
}

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

@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Wu
Quantizer = quantizer ?? KnownQuantizers.Octree
};
// Does DebugSave & load reference CompareToReferenceInput():

38
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -416,7 +416,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)]
[WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
@ -425,6 +424,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, photometricInterpretation, compression);
[Theory]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)]
public void TiffEncoder_StripLength_WithPalette<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, photometricInterpretation, compression, false, 0.01f);
[Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)]
public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
@ -432,7 +437,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
//// CcittGroup3Fax compressed data length can be larger than the original length.
Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, photometricInterpretation, compression));
private static void TestStripLength<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
[Theory]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)]
public void TiffEncode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder);
}
private static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
@ -480,10 +504,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
// Compare with reference.
TestTiffEncoderCore(
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression);
provider,
inputMeta.BitsPerPixel,
photometricInterpretation,
inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
}
private static void TestTiffEncoderCore<TPixel>(

13
tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs

@ -223,19 +223,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
Assert.Equal(0, buffer.Memory.Length);
}
[Theory]
[InlineData(101)]
[InlineData((int.MaxValue / SizeOfLargeStruct) - 1)]
[InlineData(int.MaxValue / SizeOfLargeStruct)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 137)]
public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length)
{
this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct;
Assert.Throws<InvalidMemoryOperationException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
}
[Theory]
[InlineData(-1)]
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)

4
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -172,8 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
provider.RunBufferCapacityLimitProcessorTest(
41,
c => c.Dither(dither),
name,
ImageComparer.TolerantPercentage(0.001f));
name);
}
}
}

2
tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6dba331639d724f198d7d11af971156d34076b57bba0f2d0d45e699104a3a674
oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d
size 9270

2
tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp

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

4
tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1aa62e798c085eb7b0e8e5ce5e4cb2cccfe925dd8ac3e29659f9afd53fca977c
size 329912
oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4
size 266391

4
tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900
size 326504
oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b
size 262887

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb
size 727
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb
size 727
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb
size 727
oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24191da3ce18438edefa7a189d9beadaa3057b5e4d4c550254e3a81ed159c0f8
size 723
oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b
size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:087148425a048f33c6ae063064cfe374f7fb88f075d767e62c73675ec52a3e0a
size 100066
oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934
size 52070

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b2a90c8463632606b40461ad91d80d44826f7b468ba5f1a905acfc85ad0344c9
size 114413
oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03
size 61447

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307
size 114842
oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5
size 61183

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:50c12659dc05b3ce8a6692cdbc72971bbc691cc7fd26c34df65b4bd71d190e5b
size 108799
oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa
size 56070

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a1c408687899b57b96e9f01ea889bc6f16d9a479386346c6bd9babc45ef99d0
size 109095
oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870
size 58502

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d3d8aead7f9f69dac7eec1b2d8e2200331bf28218c98b7fa3435c9610ff88264
size 110221
oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46
size 58480

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1fed9d8f58e38cfa938d7735cbdfcbac0aa02f58eda0dddfe29a5ebed0e74eb
size 117802
oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264
size 62418

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbeeda3f4e505c46e1ec218001f0f1aade4eb64c3932e2c374a74c4b0702d7a8
size 103735
oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c
size 54464

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8003014c90f6c3a722c75e9cafb397d1be3818bb84c3484e28ee79ae273d7d0b
size 109707
oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa
size 60074

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f02a9465aaaa62b6fc0e9e0578362ccf65ce57bf7a8e1e2899f254863e72807f
size 100060
oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2
size 57501

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f50c240c062cb66e820e8f632107e63bc0de85013143a81975e56f0b72499d8f
size 102871
oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a
size 59377

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318
size 104334
oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295
size 60377

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_JarvisJudiceNinke.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5bddfb2d8b8aa83fd532b3157fe78e75199d591d415524f04f688baafd1744a8
size 101155
oid sha256:90fc8048141b2182e4851a48ac5a79c96210eab9e56468fe06f90e7e70a7c180
size 58539

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4ce16c4b23075e784927143d4077063be3b42bd8a88ca1358082c00974c40150
size 102434
oid sha256:b312bd18eba03a37121bbcfb3b285f97fe22283b51256883ce0235bb8605b757
size 58616

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Sierra3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ddbd3cc8250205b38fbedef85c938920608826d5a39e5e9ecfc835b6b2583453
size 101438
oid sha256:750ccd26984a4d5a370c1af6ca5dd1c9c5c6c66e693f7645130fd1669e3b7b4e
size 58923

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_SierraLite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a880481e38ca282f29a8d00fb041de67c5231304ecdc8cef9167efb58dd482ff
size 105295
oid sha256:f9d3777a936883a2177a964f24d9ac86c8a106c375583bc9a8fbeb0ec39a7dc6
size 60610

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_StevensonArce.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:df9945efaa843da6b95c883f109075f116009bec688191d7dae5429a7fa157fc
size 100713
oid sha256:f638821c29d852d6fabe4cc4cfe802e386024835ad07ee496a7bec7a930e851b
size 57886

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Stucki.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c58aeb0e9bcc20b405b5700ec1ac12c7759e77e16da7887186b8d61903e9d906
size 101013
oid sha256:c6e86bfc1594ec4cb8f89a1c92a42778c59aa755ce170a97afb8cab3e623aa79
size 58376

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 1049
oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5
size 865

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 1049
oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5
size 865

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d55bf31ae306fcf91993b488444e83ad0f684f4a2642879e38e27e7b9fb1fa56
size 1051
oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5
size 865

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 1049
oid sha256:f7e849620a297e29ba11014c54430db01d851e4192650f6e39e0410591244cb5
size 865

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:208a0b9a189c8801e97495a93302814679441bbbe1769810eb37bcb52a78518f
size 83344
oid sha256:97cfbef27319988b67aeac87d469d044edd925c90e4774170465f51eed85c16a
size 42915

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c95ae441b8b090a0c838db5ed3e9b3ae1040225420e79b76c806f88b96716b8f
size 80344
oid sha256:3a799b69938507e3fd2a74ffa7c6c6ad6574acb25861a0a50cb8361520d468de
size 41809

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e5ab9eb0b80de50f117446c46025918893c431c228e212bef9371f4f788cee14
size 82652
oid sha256:9932db58eeb966cd293b1b7a375e9c1b17b6d09153c679ebf03d42a08d2ce9b3
size 43332

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f76c909b7e804c8dd80b07fd5346d2036d2fded2bf9a855bd20f7da154a111f3
size 83554
oid sha256:67ebf42bc82483d1778254d95a376230437611dce91c80f8ecda608de56bffe7
size 43108

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:656dfb6c9a53830d915a8c8810d09872333a9230073e25b4f0668269afb15e00
size 83188
oid sha256:8d5cdda990ac146a7580f58cc2bcab72f903dde564a394de7df4cc37e6dcf2dd
size 43906

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b56aa9a03e7f6733fac6b6ceddba50e85727201c4f79aea64540cc79f7fd942e
size 88333
oid sha256:c11e6c197bd1c227ae8f4af7e8c232cfe75db6929ab12bddf5e6554fbaed3f01
size 50716

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:863debcf1bc4a4e3fb0e3c29b8b3f8b98bb7ac47901e89a90a57a2dde5d81f53
size 90431
oid sha256:69ff9654eb61f2bfdd44fb25aff959c5b831015e283cc91a90e3abf6f681dc88
size 52429

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a92785de634c09d73dc91d1a33e52dedd7d5dea79d269753d959f2a1f81afb2b
size 89207
oid sha256:6ee945ac5120e4198d1f94e6467cc0f77c90869bf5a09942e7720dddcfdfbe07
size 51262

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e0aeb15a04553142cade051d523bbc18b2e63997efa0b0c5f5b8bab8662074f7
size 88550
oid sha256:1b023505175ae39a93fa55c85aa31466f0aca76fab0ee54f9667648b91f9aeb9
size 50789

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4801d48fc6691bc2fd555a4bed8a7abdde7edac3dc13b33da580688d11bc4eb4
size 89543
oid sha256:6b18e8b80035a3c5985ebedab5eaf1b0e580d26dd2a8167e687e7b3dd6536751
size 51922

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24acf8421048a6b1a95c8fd31e8b03c1a0b0f3b2ff155c0b9747fabb44060c25
size 319596
oid sha256:df15b095693880ec25f4fda378c8404a55064d83a40fc889f4e7ebb251dd88cf
size 272529

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:26204cd4a30538a667b17e68319747ec0a9726f6955d154c3f9f8fcd73774bd3
size 304297
oid sha256:fd18f2ba17869695efda6acf7daa0f4def11a4f5ba6cee95e06cee505f076c77
size 263994

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:adc156f6010679f2ff076405557d0a34cd50464240bbafafbf44edf37b5a1186
size 321968
oid sha256:7bcd315c4f140b55b294216de83f7835dcdf027acbd9cdb5e8bcbd89360c4781
size 272971

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:07986a3bde930100c20a93e8fa8b03f0f9c822853ddc07d71ebf2be5a36c4620
size 308767
oid sha256:fb9b649fd0b217ce548d46b0e7958f5ab74b5862678d34839d7b7ab29e3722ee
size 255871

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:3774888a23cd3be4d0d3edad8ddfeab86fd52e5605803eacbb50d3eac2f9caaa
size 291234
oid sha256:c0374d786d726692e83022a5d8642807ad24f9d484393d564a4cc73a3f8971f8
size 250230

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:ec93dd8fc45e9eb3b1ad13bd89dfc487f5d6eccd2ad8fa1fede67fa7819a263a
size 299393
oid sha256:a8a9f1fab68b71ae87b7f8f8fa61cd73c6e868359bff60e91c1246eb04c92740
size 252981

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:1de5e7da6659b9d235b0c9b0d55bdd71a3608d72e7a38259b34936a166c11d77
size 292205
oid sha256:216d096da3a1e5df9cffa1dddc2c136c4ad0db1ca3ff930a46193352680e91d6
size 257442

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:a6e11c3e422be8aae08f3d741ec4b45ce79af3603518784d22ff646cbd00c312
size 291259
oid sha256:8c15a5b6114825ff1f118209831a89d8619ea2c956ad52f9564dfc41be94c6cb
size 255797

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:8f3ef9dab0169bd262408a30ce2a1d20da5acb331fd56ce66de2f7efe4555a9a
size 299734
oid sha256:9694b6b29e33c5b0b5a8f662246f5ad0af03b900d52615fa61cad6d16cebb31c
size 259740

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:dd9d03b02c51eadc9b27f165771c1407391bc1d29c2b10a4175324ab29152cbb
size 329877
oid sha256:cc776a1039f25212cbe983ae41de4bc3d8e53dd3f692c327da42d91fe983fe5d
size 275846

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:2eba143227c5fe09d407e9ece1be5480fc55edab5f8464393d13c642b01791f3
size 321299
oid sha256:8aced00a35f19ccb7011cc7ef04bcbe79b064078a5b7b1649ecab789da13160e
size 273774

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f3d8d9e978668ae8f76004dc2a8440ffe2f55875ee92046ca2be02f426def1a6
size 333260
oid sha256:f4fe9d03e33808cf97e6ee3a4a877160b04746e46a3e3c56c0cdf7ab617e90d9
size 276397

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:37a2c548b78e117848d294ab55c6b8f4cf85ad2c6bdf84f9eec8f6eefc07b0fe
size 349177
oid sha256:2358c7b0c3de1f13d9d7840108ffd1b65751946ba28a697d6ae48b7445541807
size 308226

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bfdf8fa9d082c88dd902d927a525698e9752a3738771ba2a0b6ff67568b2f116
size 344607
oid sha256:38c112f9edef86df31b8ccec63bffdd3d4426eb5fd44b774bef4166c70f31a90
size 303086

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bdb6866053be7dbe1e56e6972b50bc030d30a050f73a4429993e3c639e06d345
size 349125
oid sha256:93fd2a28153ec292c0d6b2651830566fa3ee0cdcad7f6978ff8b49cd7fb2ac27
size 308104

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:23be2cf98ea2cd0e5b00fc1b771ea7ba490a3ac9e1de40540fd0d20a61af820c
size 330677
oid sha256:faf061e22dd0e34c62929e9e742c279f400293b87fca15e2e6423115b3e02862
size 290244

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:f032414fd20b82c0bdbaaa3e905c296961f9cdd605408f59cba7de657d8421b0
size 324042
oid sha256:f9a368ff9fbb4d462a99b9eaab8e2ec81e4b1ae1d120cf5abc0cc5fe02ea941c
size 285759

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:0adaaae399376c94af866adfcb2c5777c0dd91d2d4424f24490909e68d2483c9
size 326321
oid sha256:1926eec3a84dd8601ce0de5d8b1b70d25ebd120f4b9877b33266c18404a051fe
size 286469

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:c06a456d0c38121051d91d2cbfa4fcdcf8df4bc6ece89a0bda4b0f7e2a06b6f4
size 333368
oid sha256:2c45b7993e7019efae493f738d6fd441446d9ff5fdf14200003a1a8a90d67b97
size 292334

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:e0e2edc030a20998d3a14bb6715417bb6b561599601710497372ed90b27a5493
size 332861
oid sha256:94edf1b16733a2632406f70b61bcb4f95bc9044706f63b1840cede693330814d
size 291415

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:0571bde66f19b41cf1ba6f3b63f3d380a1025ae2f92dda8b9c494f8869c325e4
size 334758
oid sha256:93ac2cc58c94e036287e76cda3970f070d15c4ded5dc2e553177772d327d56f6
size 292742

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:f8a04e02cdac3b2ce2db20c5108636d40ce13e8d165c4b859cc4794f89cf7f4a
size 352342
oid sha256:307cd34267e96ca51d82873138e319830d13743c2085788ffcdec9bf60d45671
size 310380

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:3e03a52138efa504252053f39f36dbfbe6a477a9ffdd0f8bba633ab74d0088ed
size 351591
oid sha256:a8c296a49104edbd0ccb237c0333d3ab403e8ad5cc15c91f1734d2c3d78cf135
size 309488

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:8cc6c263430489a8866fab47c26f399a034b0dd583d27b12edc68244919321d0
size 353592
oid sha256:1874dab1b45fd976751395e1e9336ffb4d58e2e3d1643f48beea42f39245c98e
size 311280

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
size 13097

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:3c7d3da0ced1c66c6351d530565a190cfc1fdb7f3b7b05d39844f61fb87871ad
size 13758

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:2a6bb9a04f0663eb8a95d6d46c72557078de35ac935499d5ec4ab591d7f59eb9
size 13940

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
size 13097

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:f0facae77f6022c92cdaaa7f27efb424962933c0e86ec4e8a7d62237a0f58d03
size 13919

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ec01d4ee9173d01f92b5643782f4b6c7e0b4342b530acf6062f5f17c6d7b1e9a
size 36290
oid sha256:ec99338895bdada5cabe504afdcb0c0c95d8951e4404d31615a406b9956995c0
size 14154

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2ed04ff17bc4d7c57a9594bb4872f430cc3df4d92c7199d5c5db2420ecc20a95
size 38303
oid sha256:9699207803467b8718a719c7581e1ed6bf0c923a5adaf325aea8358d274fece5
size 18334

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f8e53d995f27780851c044d552473ee52ec9dcc2e0dfa9a806c9f8d2fd62692
size 39251
oid sha256:0e3acfa5b7c6ef3bec68b5fa8db91b2e6160e01d1f952a055831cea2f0a58b0f
size 18675

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf5300ab994c466cde0568afbad510f076a1d5aa16e78249645c202a7b285f4
size 32583
oid sha256:97805a6a6de3cf1e97026a4913afa573f7ec40f82e718dd9c5e4df69482a6e19
size 13097

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a7a1cefa7e70387ccb9e90c5633725ce936635da39c131a59cec7089392c358
size 39744
oid sha256:fc1c1b5d0d0abec9b52ae7a83946a46020d2394a5f49f42e2ddb50fac988e974
size 18874

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:733b748c42d4bc7103e8edf264fad4af268f2ee7ad7bab84f4ade6e8d91227e9
size 17206
oid sha256:2eac7954110e82c7c9cb1c0d3734467b7e46745ea19b2fd10d0af7df0aad552c
size 9007

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b44413544d4286aff611c94bb026562b0b0913db6d804ec7c9c82a595d2cd00
size 18474
oid sha256:51b06fc436e322ff9fc9e367b8117eb1178e112eb90fbd41a87847ab64a24136
size 8801

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8f597c6b7abc7cd729c034d8e34a0aeef19666f8accf997767f0d963e3818ec
size 20022
oid sha256:e46c5f17ef76f11ca1dfe70dd4b38858de049832c26add1e9f987f87319a3491
size 11029

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e970fa92294f6eb9e20f9d780f046a85f0e569660b4500ad4c8fca284b4fa27d
size 15992
oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8
size 7702

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b0586ae018f98d26298d3dc4af329eeca044c1cdc5ed5a71ff22e1b9ca46c122
size 22701
oid sha256:e73014c6698526f3341e1f6001938bf5c60501bd6114451903a654c43c5f1997
size 11719

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3b56c451b5e7461782dec2f5dccab18e7ad33efe3d9f1906421c32c75923648
size 17790
oid sha256:420d8ff32aa8ffa789e0c5dd00151856a016bc4f83ad035fdb4a8a22c338e247
size 8952

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4d81ab162bd065f438504ea2a44be93cefd7f1b31d7d983e23108e8e19b86fa
size 18390
oid sha256:93f3be15cb660c7c74c0de12d459c390c5f3c950d09dc4bcf617f5093e2b818b
size 8606

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d2cb1111d2a3915072ca53404215052bbff42ff9639e8e3c2b4f6a70591fd0e
size 19145
oid sha256:a035a0b97ac471500a9dbead47a0d13deb449136980b89795b671b3e14481c9e
size 9716

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e970fa92294f6eb9e20f9d780f046a85f0e569660b4500ad4c8fca284b4fa27d
size 15992
oid sha256:3527a0577720e7e8abf36b534540e72d17854d7b3b7d70cf3cdb519318e9e3c8
size 7702

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save