use super::bitreader::BitReader;
use super::bitwriter::BitWriter;
use super::{JxsError, JxsResult};
pub(crate) const RUN_ESCAPE_BASE: u32 = 8;
pub(crate) const MAG_ESCAPE_BASE: i32 = 9;
const ESCAPE_NBITS_FIELD: u8 = 6;
pub(crate) fn write_escape_value(writer: &mut BitWriter, e: u32) {
let nbits: u8 = if e == 0 {
0
} else {
(32 - e.leading_zeros()) as u8
};
writer.write_bits_u32(u32::from(nbits), ESCAPE_NBITS_FIELD);
if nbits > 0 {
writer.write_bits_u32(e, nbits);
}
}
pub(crate) fn read_escape_value(reader: &mut BitReader<'_>) -> JxsResult<u32> {
let nbits = reader.read_bits_u32(ESCAPE_NBITS_FIELD)? as u8;
if nbits == 0 {
return Ok(0);
}
if nbits > 32 {
return Err(JxsError::VlcError(format!(
"escape continuation nbits={nbits} exceeds 32"
)));
}
reader.read_bits_u32(nbits)
}
pub(crate) fn write_run(writer: &mut BitWriter, run: u32) {
if run < RUN_ESCAPE_BASE {
for _ in 0..run {
writer.write_bit(1);
}
writer.write_bit(0);
} else {
writer.write_bits_u32(0xFF, 8);
write_escape_value(writer, run - RUN_ESCAPE_BASE);
}
}
fn write_magnitude(writer: &mut BitWriter, level: i32) {
if level < MAG_ESCAPE_BASE {
for _ in 0..(level - 1) {
writer.write_bit(1);
}
writer.write_bit(0);
} else {
writer.write_bits_u32(0xFF, 8);
write_escape_value(writer, (level - MAG_ESCAPE_BASE) as u32);
}
}
fn write_nonzero_coeff(writer: &mut BitWriter, value: i32) {
let level = value.unsigned_abs() as i32;
write_magnitude(writer, level);
writer.write_bit(if value < 0 { 1 } else { 0 });
}
pub fn encode_subband(writer: &mut BitWriter, coeffs: &[i32]) {
let num = coeffs.len();
let mut zero_run: u32 = 0;
let mut last_nonzero: Option<usize> = None;
for (idx, &c) in coeffs.iter().enumerate() {
if c == 0 {
zero_run += 1;
} else {
write_run(writer, zero_run);
write_nonzero_coeff(writer, c);
zero_run = 0;
last_nonzero = Some(idx);
}
}
let trailing = match last_nonzero {
Some(pos) => (num - (pos + 1)) as u32,
None => num as u32, };
if trailing > 0 {
write_run(writer, trailing);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jpegxs::entropy::decode_subband;
use crate::jpegxs::vlc::{default_magnitude_table, default_run_table};
fn roundtrip(coeffs: &[i32], width: usize, height: usize) {
assert_eq!(coeffs.len(), width * height);
let mut w = BitWriter::new();
encode_subband(&mut w, coeffs);
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
let run_t = default_run_table();
let mag_t = default_magnitude_table();
let decoded = decode_subband(&mut r, &run_t, &mag_t, width, height).expect("decode");
assert_eq!(decoded.coeffs, coeffs, "subband round-trip mismatch");
}
#[test]
fn escape_value_roundtrip_small() {
for e in 0u32..=300 {
let mut w = BitWriter::new();
write_escape_value(&mut w, e);
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
assert_eq!(read_escape_value(&mut r).unwrap(), e, "escape e={e}");
}
}
#[test]
fn escape_value_roundtrip_powers_of_two() {
for k in 0u32..32 {
let e = 1u32 << k;
let mut w = BitWriter::new();
write_escape_value(&mut w, e);
let bytes = w.finish();
let mut r = BitReader::new(&bytes);
assert_eq!(read_escape_value(&mut r).unwrap(), e, "escape e=2^{k}");
}
}
#[test]
fn run_then_value_roundtrips() {
roundtrip(&[0, 0, 0, 5, 0, 0, -2], 7, 1);
}
#[test]
fn all_levels_1_to_8_roundtrip() {
let mut coeffs = Vec::new();
for level in 1..=8 {
coeffs.push(level);
coeffs.push(0);
}
let n = coeffs.len();
roundtrip(&coeffs, n, 1);
}
#[test]
fn levels_above_escape_roundtrip() {
let coeffs: Vec<i32> = (9..40).collect();
let n = coeffs.len();
roundtrip(&coeffs, n, 1);
}
#[test]
fn negative_values_roundtrip() {
roundtrip(&[-1, -8, -9, -100, -255], 5, 1);
}
#[test]
fn long_zero_run_uses_escape() {
let mut coeffs = vec![0i32; 50];
coeffs.push(7);
let n = coeffs.len();
roundtrip(&coeffs, n, 1);
}
#[test]
fn all_zero_subband_roundtrips() {
roundtrip(&vec![0i32; 64], 8, 8);
}
#[test]
fn constant_nonzero_subband_roundtrips() {
roundtrip(&vec![128i32; 16], 4, 4);
}
#[test]
fn last_coeff_nonzero_at_end_roundtrips() {
roundtrip(&[0, 0, 3, 0, 5], 5, 1);
}
#[test]
fn mixed_large_values_2d_roundtrip() {
let coeffs: Vec<i32> = vec![255, 0, -200, 0, 0, 0, 0, 0, 0, 17, 0, -9, 8, -8, 1, -1];
roundtrip(&coeffs, 4, 4);
}
}