📷 A modern, cross-platform, 2D Graphics library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

221 lines
7.9 KiB

// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.WebP
{
/// <summary>
/// Utility functions related to creating the huffman tables.
/// </summary>
internal static class HuffmanUtils
{
public const int HuffmanTableBits = 8;
public const int HuffmanPackedBits = 6;
public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1;
public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits;
public static int BuildHuffmanTable(Span<HuffmanCode> table, int rootBits, int[] codeLengths, int codeLengthsSize)
{
Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
Guard.NotNull(codeLengths, nameof(codeLengths));
Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
// sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
var sorted = new int[codeLengthsSize];
int totalSize = 1 << rootBits; // total size root table + 2nd level table.
int len; // current code length.
int symbol; // symbol index in original or sorted table.
var counts = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
var offsets = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
var codeLengthOfSymbol = codeLengths[symbol];
if (codeLengthOfSymbol > WebPConstants.MaxAllowedCodeLength)
{
return 0;
}
counts[codeLengthOfSymbol]++;
}
// Error, all code lengths are zeros.
if (counts[0] == codeLengthsSize)
{
return 0;
}
// Generate offsets into sorted symbol table by code length.
offsets[1] = 0;
for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
{
int codesOfLength = counts[len];
if (codesOfLength > (1 << len))
{
return 0;
}
offsets[len + 1] = offsets[len] + codesOfLength;
}
// Sort symbols by length, by symbol order within each length.
for (symbol = 0; symbol < codeLengthsSize; ++symbol)
{
int symbolCodeLength = codeLengths[symbol];
if (symbolCodeLength > 0)
{
sorted[offsets[symbolCodeLength]++] = symbol;
}
}
// Special case code with only one value.
if (offsets[WebPConstants.MaxAllowedCodeLength] == 1)
{
var huffmanCode = new HuffmanCode()
{
BitsUsed = 0,
Value = (uint)sorted[0]
};
ReplicateValue(table, 1, totalSize, huffmanCode);
return totalSize;
}
int step; // step size to replicate values in current table
int low = -1; // low bits for current root entry
int mask = totalSize - 1; // mask for low bits
int key = 0; // reversed prefix code
int numNodes = 1; // number of Huffman tree nodes
int numOpen = 1; // number of open branches in current tree level
int tableBits = rootBits; // key length of current table
int tableSize = 1 << tableBits; // size of current table
symbol = 0;
// Fill in root table.
for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
{
var countsLen = counts[len];
numOpen <<= 1;
numNodes += numOpen;
numOpen -= counts[len];
if (numOpen < 0)
{
return 0;
}
for (; countsLen > 0; countsLen--)
{
var huffmanCode = new HuffmanCode()
{
BitsUsed = len,
Value = (uint)sorted[symbol++]
};
ReplicateValue(table.Slice(key), step, tableSize, huffmanCode);
key = GetNextKey(key, len);
}
counts[len] = countsLen;
}
// Fill in 2nd level tables and add pointers to root table.
Span<HuffmanCode> tableSpan = table;
int tablePos = 0;
for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1)
{
numOpen <<= 1;
numNodes += numOpen;
numOpen -= counts[len];
if (numOpen < 0)
{
return 0;
}
for (; counts[len] > 0; --counts[len])
{
if ((key & mask) != low)
{
tableSpan = tableSpan.Slice(tableSize);
tablePos += tableSize;
tableBits = NextTableBitSize(counts, len, rootBits);
tableSize = 1 << tableBits;
totalSize += tableSize;
low = key & mask;
uint v = (uint)(tablePos - low);
table[low] = new HuffmanCode
{
BitsUsed = tableBits + rootBits,
Value = (uint)(tablePos - low)
};
}
var huffmanCode = new HuffmanCode
{
BitsUsed = len - rootBits,
Value = (uint)sorted[symbol++]
};
ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode);
key = GetNextKey(key, len);
}
}
return totalSize;
}
/// <summary>
/// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
/// len is the code length of the next processed symbol.
/// </summary>
private static int NextTableBitSize(int[] count, int len, int rootBits)
{
int left = 1 << (len - rootBits);
while (len < WebPConstants.MaxAllowedCodeLength)
{
left -= count[len];
if (left <= 0)
{
break;
}
++len;
left <<= 1;
}
return len - rootBits;
}
/// <summary>
/// Stores code in table[0], table[step], table[2*step], ..., table[end-step].
/// Assumes that end is an integer multiple of step.
/// </summary>
private static void ReplicateValue(Span<HuffmanCode> table, int step, int end, HuffmanCode code)
{
Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step");
do
{
end -= step;
table[end] = code;
}
while (end > 0);
}
/// <summary>
/// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
/// bit-wise reversal of the len least significant bits of key.
/// </summary>
private static int GetNextKey(int key, int len)
{
int step = 1 << (len - 1);
while ((key & step) != 0)
{
step >>= 1;
}
return step != 0 ? (key & (step - 1)) + step : key;
}
}
}