From 08c36f23b2a78ad6d214096bba063b89de68715c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Jun 2021 13:25:59 +0100 Subject: [PATCH] Fix Wu palette, reduce memory usage --- .../Extensions/Dithering/DitherExtensions.cs | 16 +++++++------- .../Quantization/EuclideanPixelMap{TPixel}.cs | 6 ++---- .../Quantization/OctreeQuantizer{TPixel}.cs | 21 +++++++++++++++---- .../Quantization/WuQuantizer{TPixel}.cs | 5 +++-- 4 files changed, 30 insertions(+), 18 deletions(-) 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);