diff --git a/src/ImageSharp/Formats/Jxl/IO/JxlHuffman.cs b/src/ImageSharp/Formats/Jxl/IO/JxlHuffman.cs index 640f667401..678321bab8 100644 --- a/src/ImageSharp/Formats/Jxl/IO/JxlHuffman.cs +++ b/src/ImageSharp/Formats/Jxl/IO/JxlHuffman.cs @@ -1,9 +1,184 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Jxl.IO.Entropy; + namespace SixLabors.ImageSharp.Formats.Jxl.IO; +/// +/// Shared Huffman I/O utilities. +/// internal static class JxlHuffman { - + /// + /// Returns Reverse(Reverse(Key, Len) + 1, Len). The + /// Reverse(Key, Len) function performs bitwise reversal + /// of the len least significant bits of the key value. + /// + public static uint GetNextKey(uint key, int len) + { + uint step = 1u << (len - 1); + while ((key & step) != 0) + { + step >>= 1; + } + + return (key & (step - 1)) + step; + } + + /// + /// Replicates into every + /// times with the upper bound of . + /// + public static void ReplicateValue(Span table, int step, int end, JxlHuffmanCode code) + { + do + { + end -= step; + table[end] = code; + } + while (end > 0); + } + + /// + /// Returns the table width of the next 2nd level table. + /// + /// The histogram of bit lengths for remaining symbols + /// Code length of the next processed symbol + /// Amount of bits for the root symbol + /// Table width for the 2nd level table. + public static int NextTableBitSize(ReadOnlySpan count, int length, int rootBits) + { + uint left = 1u << (length - rootBits); + + while (length < JxlAnsConstants.PrefixMaxBits) + { + if (left <= count[length]) + { + break; + } + + left -= count[length]; + length++; + left <<= 1; + } + + return length - rootBits; + } + + public static uint BuildHuffmanTable( + Span rootTable, + int rootBits, + ReadOnlySpan codeLengths, + Span count) + { + if (codeLengths.Length > (1u << JxlAnsConstants.PrefixMaxBits)) + { + return 0u; + } + + Span offset = stackalloc ushort[JxlAnsConstants.PrefixMaxBits + 1]; + + Span sortedStorage = stackalloc ushort[codeLengths.Length]; + + int maxLength = 1; + ushort sum = 0; + int len, symbol; + for (len = 1; len <= JxlAnsConstants.PrefixMaxBits; len++) + { + offset[len] = sum; + + if (count[len] != 0) + { + sum = (ushort)(sum + count[len]); + maxLength = len; + } + } + + for (symbol = 0; symbol < codeLengths.Length; symbol++) + { + if (codeLengths[symbol] != 0) + { + sortedStorage[offset[codeLengths[symbol]]++] = (ushort)symbol; + } + } + + Span table = rootTable; + int tableBits = rootBits; + uint tableSize = 1u << tableBits; + uint totalSize = tableSize; + + JxlHuffmanCode code = default; + + if (offset[JxlAnsConstants.PrefixMaxBits] == 1) + { + code.Bits = 0; + code.Value = sortedStorage[0]; + + for (int i = 0; i < totalSize; i++) + { + table[i] = code; + } + } + + if (tableBits > maxLength) + { + tableBits = maxLength; + tableSize = 1u << tableBits; + } + + int key = 0; + code.Bits = 0; + int step = 2; + + do + { + for (; count[code.Bits] != 0; --count[code.Bits]) + { + code.Value = sortedStorage[symbol++]; + ReplicateValue(table[key..], step, (int)tableSize, code); + key = (int)GetNextKey((uint)key, code.Bits); + } + + step <<= 1; + } + while (++code.Bits <= tableBits); + + while (totalSize != tableSize) + { + table[..(int)tableSize].CopyTo(table[(int)tableSize..]); + tableSize <<= 1; + } + + uint mask = totalSize - 1u; + int low = -1; + + uint tableOffset = 0; + for (step = 2; len <= maxLength; len++, step <<= 1) + { + for (; count[len] != 0; --count[len]) + { + if ((key & mask) != low) + { + tableOffset += tableSize; + table = table[(int)tableSize..]; + tableBits = NextTableBitSize(count, len, rootBits); + tableSize = 1u << tableBits; + totalSize += tableSize; + low = key & (int)mask; + + rootTable[low].Bits = (byte)(tableBits + rootBits); + rootTable[low].Value = (ushort)(tableOffset - low); + } + + code.Bits = (byte)(len - rootBits); + code.Value = sortedStorage[symbol++]; + + ReplicateValue(table[(key >> rootBits)..], step, (int)tableSize, code); + key = (int)GetNextKey((uint)key, len); + } + } + + return totalSize; + } }