From a99e6cf3800391b2b763b80a09f145c63d8813c5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 29 Apr 2025 20:53:33 +1000 Subject: [PATCH] Revert to original CoarseCache --- .../Quantization/IColorIndexCache.cs | 84 ++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs index 4e4fa5f0ac..e4f6c87839 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -119,6 +119,84 @@ internal unsafe struct HybridCache : IColorIndexCache } } +/// +/// A coarse cache for color distance lookups that uses a fixed-size lookup table. +/// +/// +/// This cache uses a fixed lookup table with 2,097,152 bins, each storing a 2-byte value, +/// resulting in a worst-case memory usage of approximately 4 MB. Lookups and insertions are +/// performed in constant time (O(1)) via direct table indexing. This design is optimized for +/// speed while maintaining a predictable, fixed memory footprint. +/// +internal unsafe struct CoarseCache : IColorIndexCache +{ + private const int IndexRBits = 5; + private const int IndexGBits = 5; + private const int IndexBBits = 5; + private const int IndexABits = 6; + private const int IndexRCount = 1 << IndexRBits; // 32 bins for red + private const int IndexGCount = 1 << IndexGBits; // 32 bins for green + private const int IndexBCount = 1 << IndexBBits; // 32 bins for blue + private const int IndexACount = 1 << IndexABits; // 64 bins for alpha + private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; // 2,097,152 bins + + private readonly IMemoryOwner binsOwner; + private readonly short* binsPointer; + private MemoryHandle binsHandle; + + private CoarseCache(MemoryAllocator allocator) + { + this.binsOwner = allocator.Allocate(TotalBins); + this.binsOwner.GetSpan().Fill(-1); + this.binsHandle = this.binsOwner.Memory.Pin(); + this.binsPointer = (short*)this.binsHandle.Pointer; + } + + /// + public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryAdd(Rgba32 color, short value) + { + this.binsPointer[GetCoarseIndex(color)] = value; + return true; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryGetValue(Rgba32 color, out short value) + { + value = this.binsPointer[GetCoarseIndex(color)]; + return value > -1; // Coarse match found + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetCoarseIndex(Rgba32 color) + { + int rIndex = color.R >> (8 - IndexRBits); + int gIndex = color.G >> (8 - IndexGBits); + int bIndex = color.B >> (8 - IndexBBits); + int aIndex = color.A >> (8 - IndexABits); + + return (aIndex * IndexRCount * IndexGCount * IndexBCount) + + (rIndex * IndexGCount * IndexBCount) + + (gIndex * IndexBCount) + + bIndex; + } + + /// + public readonly void Clear() + => this.binsOwner.GetSpan().Fill(-1); + + /// + public void Dispose() + { + this.binsHandle.Dispose(); + this.binsOwner.Dispose(); + } +} + /// /// /// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, @@ -147,7 +225,7 @@ internal unsafe struct HybridCache : IColorIndexCache /// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). /// /// -internal unsafe struct CoarseCache : IColorIndexCache +internal unsafe struct CoarseCacheLite : IColorIndexCache { // Use 5 bits per channel for R, G, and B: 32 levels each. // Total buckets = 32^3 = 32768. @@ -158,7 +236,7 @@ internal unsafe struct CoarseCache : IColorIndexCache private readonly AlphaBucket* buckets; private MemoryHandle bucketHandle; - private CoarseCache(MemoryAllocator allocator) + private CoarseCacheLite(MemoryAllocator allocator) { this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); this.bucketHandle = this.bucketsOwner.Memory.Pin(); @@ -166,7 +244,7 @@ internal unsafe struct CoarseCache : IColorIndexCache } /// - public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); + public static CoarseCacheLite Create(MemoryAllocator allocator) => new(allocator); /// public readonly bool TryAdd(Rgba32 color, short paletteIndex)