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;
+ }
}