use crate::common::error::{JpegError, Result};
const LOOKUP_BITS: usize = 10;
const LOOKUP_SIZE: usize = 1 << LOOKUP_BITS;
#[derive(Debug, Clone)]
pub struct HuffmanTable {
fast: Box<[u32; LOOKUP_SIZE]>,
maxcode: [i32; 18],
valoffset: [i32; 18],
values: Vec<u8>,
count: usize,
min_slow_length: u8,
}
impl HuffmanTable {
#[inline(always)]
fn pack_fast_entry(symbol: u8, length: u8) -> u16 {
((symbol as u16) << 8) | length as u16
}
#[inline(always)]
fn unpack_fast_entry(entry: u16) -> (u8, u8) {
((entry >> 8) as u8, entry as u8)
}
pub fn build(bits: &[u8; 17], values: &[u8]) -> Result<Self> {
let total_symbols: usize = bits[1..=16].iter().map(|&b| b as usize).sum();
if values.len() < total_symbols {
return Err(JpegError::CorruptData(
"Huffman table: insufficient symbol data".into(),
));
}
let mut huffcode = Vec::with_capacity(total_symbols);
let mut code: u32 = 0;
for (length, &bit_count) in bits.iter().enumerate().skip(1) {
for _ in 0..bit_count {
huffcode.push((code, length));
code += 1;
}
code <<= 1;
}
let mut maxcode = [-1i32; 18];
let mut valoffset = [0i32; 18];
let mut symbol_index: usize = 0;
let mut min_slow_length: u8 = 17; for length in 1..=16usize {
let count = bits[length] as usize;
if count > 0 {
valoffset[length] = symbol_index as i32 - huffcode[symbol_index].0 as i32;
symbol_index += count;
maxcode[length] = huffcode[symbol_index - 1].0 as i32;
if length > LOOKUP_BITS && (min_slow_length as usize) > length {
min_slow_length = length as u8;
}
}
}
let mut fast: Box<[u32; LOOKUP_SIZE]> = vec![0u32; LOOKUP_SIZE]
.into_boxed_slice()
.try_into()
.unwrap();
for (i, &(code_val, code_len)) in huffcode.iter().enumerate() {
if code_len <= LOOKUP_BITS {
let code_shifted: usize = (code_val as usize) << (LOOKUP_BITS - code_len);
let fill_count: usize = 1 << (LOOKUP_BITS - code_len);
let symbol: u8 = values[i];
let base_entry: u32 = Self::pack_fast_entry(symbol, code_len as u8) as u32;
let mag_bits: u8 = symbol & 0x0F;
let total_bits: u8 = code_len as u8 + mag_bits;
let ac_eligible: bool = mag_bits > 0 && (total_bits as usize) <= LOOKUP_BITS;
if ac_eligible {
let run: u8 = symbol >> 4;
let shift: usize = LOOKUP_BITS - total_bits as usize;
for j in 0..fill_count {
let idx: usize = code_shifted | j;
let extra: i16 =
((idx >> shift) & ((1usize << mag_bits as usize) - 1)) as i16;
let threshold: i16 = 1i16 << (mag_bits - 1);
let value: i16 = if extra >= threshold {
extra
} else {
extra + ((!0i16) << mag_bits) + 1
};
let entry: u32 = if (-128i16..=127i16).contains(&value) {
let ac_packed: i16 =
(value << 8) | ((run as i16) << 4) | total_bits as i16;
base_entry | ((ac_packed as u16 as u32) << 16)
} else {
base_entry
};
fast[idx] = entry;
}
} else {
for j in 0..fill_count {
fast[code_shifted | j] = base_entry;
}
}
}
}
Ok(Self {
fast,
maxcode,
valoffset,
values: values[..total_symbols].to_vec(),
count: total_symbols,
min_slow_length,
})
}
#[inline(always)]
pub fn lookup(&self, bits_msb: u16) -> Result<(u8, u8)> {
let entry: u32 = self.fast[(bits_msb >> (16 - LOOKUP_BITS)) as usize];
let lower: u16 = entry as u16;
if lower != 0 {
return Ok(Self::unpack_fast_entry(lower));
}
self.lookup_slow(bits_msb)
}
#[inline(always)]
pub fn lookup_fast(&self, bits_msb: u16) -> (u8, u8) {
let entry: u32 = self.fast[(bits_msb >> (16 - LOOKUP_BITS)) as usize];
Self::unpack_fast_entry(entry as u16)
}
#[inline(always)]
pub fn lookup_combined(&self, bits_msb: u16) -> (i16, u8, u8) {
let entry: u32 = self.fast[(bits_msb >> (16 - LOOKUP_BITS)) as usize];
let ac: i16 = (entry >> 16) as i16;
let (symbol, code_len) = Self::unpack_fast_entry(entry as u16);
(ac, symbol, code_len)
}
#[cold]
#[inline(never)]
fn lookup_slow(&self, bits_msb: u16) -> Result<(u8, u8)> {
let start = self.min_slow_length.max(1) as usize;
if start > 16 {
return Err(JpegError::CorruptData("invalid Huffman code".into()));
}
let mut code = (bits_msb >> (16 - start)) as i32;
for length in start..=16usize {
if code <= self.maxcode[length] {
let idx = (code + self.valoffset[length]) as usize;
if idx < self.values.len() {
return Ok((self.values[idx], length as u8));
}
}
if length < 16 {
code = (code << 1) | ((bits_msb >> (15 - length)) & 1) as i32;
}
}
Err(JpegError::CorruptData("invalid Huffman code".into()))
}
pub fn num_symbols(&self) -> usize {
self.count
}
}
#[cfg(test)]
mod tests_sym16 {
use super::*;
#[test]
fn table_with_symbol_16() {
let bits: [u8; 17] = [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let values: &[u8] = &[14, 0, 16];
let table = HuffmanTable::build(&bits, values).expect("build failed");
let (sym, len) = table.lookup_fast(0x0000);
assert_eq!((sym, len), (14, 1), "0 → symbol 14, len 1");
let (sym, len) = table.lookup_fast(0x8000);
assert_eq!((sym, len), (0, 2), "10 → symbol 0, len 2");
let (sym, len) = table.lookup_fast(0xC000);
assert_eq!((sym, len), (16, 3), "110 → symbol 16, len 3");
let (sym, len) = table.lookup(0xC000).expect("lookup failed");
assert_eq!((sym, len), (16, 3));
}
}