use super::JxsError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VlcResult {
pub value: i16,
pub bits_consumed: u8,
}
pub struct VlcTable {
table: Vec<(i16, u8)>,
index_bits: u8,
}
impl VlcTable {
pub fn build(entries: &[(u32, u8, i16)]) -> Self {
let max_len = entries.iter().map(|&(_, l, _)| l).max().unwrap_or(1);
let index_bits = max_len.max(1);
let table_size = 1usize << index_bits;
let mut table = vec![(0i16, 0u8); table_size];
for &(code, len, value) in entries {
if len == 0 || len > index_bits {
continue;
}
let shift = index_bits - len;
let base = (code as usize) << shift;
let span = 1usize << shift;
for offset in 0..span {
let idx = base | offset;
if idx < table_size {
if table[idx].1 == 0 {
table[idx] = (value, len);
}
}
}
}
Self { table, index_bits }
}
pub fn lookup(&self, bits: u32) -> Option<VlcResult> {
let idx = (bits >> (32u8.saturating_sub(self.index_bits)) as u32) as usize;
if idx >= self.table.len() {
return None;
}
let (value, len) = self.table[idx];
if len == 0 {
None
} else {
Some(VlcResult {
value,
bits_consumed: len,
})
}
}
pub fn index_bits(&self) -> u8 {
self.index_bits
}
}
pub fn default_run_table() -> VlcTable {
let entries: &[(u32, u8, i16)] = &[
(0b0, 1, 0), (0b10, 2, 1), (0b110, 3, 2), (0b1110, 4, 3), (0b11110, 5, 4), (0b111110, 6, 5), (0b1111110, 7, 6), (0b11111110, 8, 7), (0b11111111, 8, -1), ];
VlcTable::build(entries)
}
pub fn default_magnitude_table() -> VlcTable {
let entries: &[(u32, u8, i16)] = &[
(0b0, 1, 1), (0b10, 2, 2), (0b110, 3, 3), (0b1110, 4, 4), (0b11110, 5, 5), (0b111110, 6, 6), (0b1111110, 7, 7), (0b11111110, 8, 8), (0b11111111, 8, -1), ];
VlcTable::build(entries)
}
pub fn default_significance_table() -> VlcTable {
let entries: &[(u32, u8, i16)] = &[
(0b0, 1, 0), (0b1, 1, 1), ];
VlcTable::build(entries)
}
pub fn build_from_cwd(codes: &[(u32, u8, i16)]) -> Result<VlcTable, JxsError> {
if codes.is_empty() {
return Err(JxsError::VlcError("CWD code table is empty".to_string()));
}
Ok(VlcTable::build(codes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_table_run0_is_single_zero_bit() {
let table = default_run_table();
let result = table.lookup(0x0000_0000u32); assert!(result.is_some(), "run=0 lookup failed");
let r = result.unwrap();
assert_eq!(r.value, 0);
assert_eq!(r.bits_consumed, 1);
}
#[test]
fn run_table_run1_starts_with_10() {
let table = default_run_table();
let result = table.lookup(0b1000_0000_0000_0000_0000_0000_0000_0000u32);
assert!(result.is_some(), "run=1 lookup failed");
let r = result.unwrap();
assert_eq!(r.value, 1);
assert_eq!(r.bits_consumed, 2);
}
#[test]
fn magnitude_table_level1_is_single_zero_bit() {
let table = default_magnitude_table();
let result = table.lookup(0x0000_0000u32);
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.value, 1); assert_eq!(r.bits_consumed, 1);
}
#[test]
fn significance_table_zero_bit() {
let table = default_significance_table();
let r = table.lookup(0x0000_0000).unwrap();
assert_eq!(r.value, 0);
assert_eq!(r.bits_consumed, 1);
let r = table.lookup(0x8000_0000).unwrap();
assert_eq!(r.value, 1);
assert_eq!(r.bits_consumed, 1);
}
#[test]
fn vlc_table_empty_entries_returns_none() {
let table = VlcTable::build(&[]);
assert!(table.lookup(0).is_none());
}
#[test]
fn vlc_table_single_entry_roundtrip() {
let entries = [(0b10u32, 2u8, 99i16)];
let table = VlcTable::build(&entries);
let result = table.lookup(0b10_00_0000_0000_0000_0000_0000_0000_0000u32);
assert_eq!(
result,
Some(VlcResult {
value: 99,
bits_consumed: 2
})
);
}
#[test]
fn build_from_cwd_rejects_empty() {
let result = build_from_cwd(&[]);
assert!(result.is_err());
}
#[test]
fn build_from_cwd_accepts_valid_codes() {
let codes: Vec<(u32, u8, i16)> = vec![(0b0, 1, 0), (0b1, 1, 1)];
let table = build_from_cwd(&codes).unwrap();
assert_eq!(table.index_bits(), 1);
}
}