diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
index f4664a5c0f..296ed71b72 100644
--- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8);
///
- /// Dithers the image reducing it to a web-safe palette using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither));
///
- /// Dithers the image reducing it to a web-safe palette using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale));
///
- /// Dithers the image reducing it to the given palette using ordered dithering.
+ /// Dithers the image reducing it to the given palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette));
///
- /// Dithers the image reducing it to the given palette using ordered dithering.
+ /// Dithers the image reducing it to the given palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8, rectangle);
///
- /// Dithers the image reducing it to a web-safe palette using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle);
///
- /// Dithers the image reducing it to a web-safe palette using ordered dithering.
+ /// Dithers the image reducing it to a web-safe palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle);
///
- /// Dithers the image reducing it to the given palette using ordered dithering.
+ /// Dithers the image reducing it to the given palette.
///
/// The image this method extends.
/// The ordered ditherer.
@@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle);
///
- /// Dithers the image reducing it to the given palette using ordered dithering.
+ /// Dithers the image reducing it to the given palette.
///
/// The image this method extends.
/// The ordered ditherer.
diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
index dbd5194947..1342de9dc7 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
@@ -121,13 +121,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// significantly negatively affect performance.
///
///
- /// The cache is limited to 2471625 entries at 4MB.
- /// This could be halfed by reducing the alpha accuracy but this treats
- /// gradients less well in gifs than our previous cache implementation.
+ /// The cache is limited to 646866 entries at 0.62MB.
///
private struct ColorDistanceCache
{
- private const int IndexBits = 6;
+ private const int IndexBits = 5;
private const int IndexAlphaBits = 3;
private const int IndexCount = (1 << IndexBits) + 1;
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
index aaf9a0cecc..fab462e2ef 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
@@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel
{
private readonly int maxColors;
+ private readonly int bitDepth;
private readonly Octree octree;
private IMemoryOwner paletteOwner;
private ReadOnlyMemory palette;
@@ -41,9 +42,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration;
this.Options = options;
- this.maxColors = Math.Min(byte.MaxValue, this.Options.MaxColors);
- this.octree = new Octree(Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8));
- this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors + 1, AllocationOptions.Clean);
+ this.maxColors = this.Options.MaxColors;
+ this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8);
+ this.octree = new Octree(this.bitDepth);
+ this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean);
this.palette = default;
this.pixelMap = default;
this.isDithering = !(this.Options.Dither is null);
@@ -92,8 +94,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int paletteIndex = 0;
Span paletteSpan = this.paletteOwner.GetSpan();
- this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
+ // 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 result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
this.pixelMap = new EuclideanPixelMap(this.Configuration, result);
this.palette = result;
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
index 80b2c3ef4b..b5d840f9dd 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
@@ -126,9 +126,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 paletteSpan = this.paletteOwner.GetSpan().Slice(0, this.maxColors);
ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan();
- Span 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);