use crate::bits::BitWriterMsb;
use crate::jpeg::huffman::HuffmanTables;
use crate::jpeg::quantize::zigzag_reorder;
#[derive(Debug, Clone, Copy)]
pub struct ScanParams {
pub ss: u8,
pub se: u8,
pub al: u8,
pub is_luminance: bool,
}
#[derive(Debug, Clone)]
pub struct ScanSpec {
pub components: Vec<u8>,
pub ss: u8,
pub se: u8,
pub ah: u8,
pub al: u8,
}
impl ScanSpec {
pub fn new(components: Vec<u8>, ss: u8, se: u8, ah: u8, al: u8) -> Self {
Self {
components,
ss,
se,
ah,
al,
}
}
pub fn is_dc_scan(&self) -> bool {
self.ss == 0 && self.se == 0
}
pub fn is_first_scan(&self) -> bool {
self.ah == 0
}
pub fn is_refinement_scan(&self) -> bool {
self.ah > 0
}
}
pub fn default_progressive_script() -> Vec<ScanSpec> {
vec![
ScanSpec::new(vec![0], 0, 0, 0, 1), ScanSpec::new(vec![1], 0, 0, 0, 1), ScanSpec::new(vec![2], 0, 0, 0, 1), ScanSpec::new(vec![0], 1, 5, 0, 2), ScanSpec::new(vec![0], 6, 14, 0, 2), ScanSpec::new(vec![0], 15, 63, 0, 1), ScanSpec::new(vec![1], 1, 63, 0, 1), ScanSpec::new(vec![2], 1, 63, 0, 1), ScanSpec::new(vec![0], 0, 0, 1, 0), ScanSpec::new(vec![1], 0, 0, 1, 0), ScanSpec::new(vec![2], 0, 0, 1, 0), ScanSpec::new(vec![0], 1, 5, 2, 1), ScanSpec::new(vec![0], 1, 5, 1, 0), ScanSpec::new(vec![0], 6, 14, 2, 1), ScanSpec::new(vec![0], 6, 14, 1, 0), ScanSpec::new(vec![0], 15, 63, 1, 0), ScanSpec::new(vec![1], 1, 63, 1, 0), ScanSpec::new(vec![2], 1, 63, 1, 0), ]
}
pub fn simple_progressive_script() -> Vec<ScanSpec> {
vec![
ScanSpec::new(vec![0], 0, 0, 0, 0), ScanSpec::new(vec![1], 0, 0, 0, 0), ScanSpec::new(vec![2], 0, 0, 0, 0), ScanSpec::new(vec![0], 1, 10, 0, 0), ScanSpec::new(vec![0], 11, 63, 0, 0), ScanSpec::new(vec![1], 1, 63, 0, 0), ScanSpec::new(vec![2], 1, 63, 0, 0), ]
}
pub fn encode_dc_first(
writer: &mut BitWriterMsb,
dc_diff: i16,
al: u8,
dc_code: (u16, u8), ) {
let shifted_dc = dc_diff >> al;
writer.write_bits(dc_code.0 as u32, dc_code.1);
if shifted_dc != 0 {
let cat = category(shifted_dc);
let (val_bits, val_len) = encode_value(shifted_dc);
writer.write_bits(val_bits as u32, val_len);
let _ = cat; }
}
pub fn encode_dc_refine(writer: &mut BitWriterMsb, dc_coef: i16, al: u8) {
let bit = ((dc_coef.unsigned_abs() >> al) & 1) as u32;
writer.write_bits(bit, 1);
}
pub fn encode_ac_first(
writer: &mut BitWriterMsb,
block: &[i16; 64],
scan: &ScanParams,
eob_run: &mut u16,
tables: &HuffmanTables,
) {
let zigzag = zigzag_reorder(block);
let mut k = scan.se as usize;
while k >= scan.ss as usize && (zigzag[k] >> scan.al) == 0 {
if k == scan.ss as usize {
break;
}
k -= 1;
}
let last_nonzero = k;
let all_zero = last_nonzero == scan.ss as usize && (zigzag[scan.ss as usize] >> scan.al) == 0;
if all_zero {
*eob_run += 1;
if *eob_run == 0x7FFF {
flush_eob_run(writer, eob_run, tables, scan.is_luminance);
}
return;
}
if *eob_run > 0 {
flush_eob_run(writer, eob_run, tables, scan.is_luminance);
}
let mut zero_run = 0u8;
for k in (scan.ss as usize)..=(last_nonzero) {
let coef = zigzag[k] >> scan.al;
if coef == 0 {
zero_run += 1;
continue;
}
while zero_run >= 16 {
let zrl_code = get_ac_code(tables, 0xF0, scan.is_luminance);
writer.write_bits(zrl_code.0 as u32, zrl_code.1);
zero_run -= 16;
}
let ac_cat = category(coef);
let rs = (zero_run << 4) | ac_cat;
let ac_code = get_ac_code(tables, rs, scan.is_luminance);
writer.write_bits(ac_code.0 as u32, ac_code.1);
let (val_bits, val_len) = encode_value(coef);
writer.write_bits(val_bits as u32, val_len);
zero_run = 0;
}
if last_nonzero < scan.se as usize {
*eob_run = 1;
}
}
pub fn encode_ac_refine(
writer: &mut BitWriterMsb,
block: &[i16; 64],
scan: &ScanParams,
eob_run: &mut u16,
tables: &HuffmanTables,
) {
let zigzag = zigzag_reorder(block);
let mut correction_bits: Vec<u8> = Vec::new();
let mut zero_run = 0u8;
let mut pending_eob = false;
let mut end = scan.se as usize;
while end > scan.ss as usize {
let coef = zigzag[end];
if coef.abs() > (1 << scan.al) {
break;
}
if (coef.unsigned_abs() >> scan.al) & 1 != 0 {
break;
}
end -= 1;
}
for k in (scan.ss as usize)..=(scan.se as usize) {
let coef = zigzag[k];
let abs_coef = coef.unsigned_abs();
if abs_coef > (1 << scan.al) {
correction_bits.push(((abs_coef >> scan.al) & 1) as u8);
} else if (abs_coef >> scan.al) & 1 != 0 {
if *eob_run > 0 {
flush_eob_run(writer, eob_run, tables, scan.is_luminance);
}
while zero_run >= 16 {
let zrl_code = get_ac_code(tables, 0xF0, scan.is_luminance);
writer.write_bits(zrl_code.0 as u32, zrl_code.1);
for &bit in &correction_bits {
writer.write_bits(bit as u32, 1);
}
correction_bits.clear();
zero_run -= 16;
}
let rs = (zero_run << 4) | 1;
let ac_code = get_ac_code(tables, rs, scan.is_luminance);
writer.write_bits(ac_code.0 as u32, ac_code.1);
let sign_bit = if coef < 0 { 0u32 } else { 1u32 };
writer.write_bits(sign_bit, 1);
for &bit in &correction_bits {
writer.write_bits(bit as u32, 1);
}
correction_bits.clear();
zero_run = 0;
} else {
zero_run += 1;
}
}
if zero_run > 0 || !correction_bits.is_empty() {
pending_eob = true;
}
if pending_eob {
*eob_run += 1;
if *eob_run == 0x7FFF {
flush_eob_run(writer, eob_run, tables, scan.is_luminance);
}
}
for &bit in &correction_bits {
writer.write_bits(bit as u32, 1);
}
}
pub fn flush_eob_run_public(
writer: &mut BitWriterMsb,
eob_run: &mut u16,
tables: &HuffmanTables,
is_luminance: bool,
) {
flush_eob_run(writer, eob_run, tables, is_luminance);
}
fn flush_eob_run(
writer: &mut BitWriterMsb,
eob_run: &mut u16,
tables: &HuffmanTables,
is_luminance: bool,
) {
if *eob_run == 0 {
return;
}
let mut temp = *eob_run;
let mut nbits = 0u8;
while temp > 0 {
temp >>= 1;
nbits += 1;
}
nbits = nbits.saturating_sub(1);
let symbol = nbits << 4;
let ac_code = get_ac_code(tables, symbol, is_luminance);
writer.write_bits(ac_code.0 as u32, ac_code.1);
if nbits > 0 {
let extra_bits = *eob_run - (1 << nbits);
writer.write_bits(extra_bits as u32, nbits);
}
*eob_run = 0;
}
pub fn get_dc_code(tables: &HuffmanTables, category: u8, is_luminance: bool) -> (u16, u8) {
if is_luminance {
get_code_from_table(&tables.dc_lum_bits, &tables.dc_lum_vals, category)
} else {
get_code_from_table(&tables.dc_chrom_bits, &tables.dc_chrom_vals, category)
}
}
fn get_ac_code(tables: &HuffmanTables, rs: u8, is_luminance: bool) -> (u16, u8) {
if is_luminance {
get_code_from_table(&tables.ac_lum_bits, &tables.ac_lum_vals, rs)
} else {
get_code_from_table(&tables.ac_chrom_bits, &tables.ac_chrom_vals, rs)
}
}
fn get_code_from_table(bits: &[u8; 16], vals: &[u8], symbol: u8) -> (u16, u8) {
let mut code = 0u16;
let mut val_idx = 0;
for (length, &count) in bits.iter().enumerate() {
for _ in 0..count {
if val_idx < vals.len() && vals[val_idx] == symbol {
return (code, (length + 1) as u8);
}
val_idx += 1;
code += 1;
}
code <<= 1;
}
(0, 4)
}
fn category(value: i16) -> u8 {
let abs_val = value.unsigned_abs();
if abs_val == 0 {
0
} else {
16 - abs_val.leading_zeros() as u8
}
}
fn encode_value(value: i16) -> (u16, u8) {
let cat = category(value);
if cat == 0 {
return (0, 0);
}
let bits = if value < 0 {
(value - 1) as u16
} else {
value as u16
};
(bits & ((1 << cat) - 1), cat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_spec_properties() {
let dc_scan = ScanSpec::new(vec![0], 0, 0, 0, 1);
assert!(dc_scan.is_dc_scan());
assert!(dc_scan.is_first_scan());
assert!(!dc_scan.is_refinement_scan());
let ac_refine = ScanSpec::new(vec![0], 1, 63, 1, 0);
assert!(!ac_refine.is_dc_scan());
assert!(ac_refine.is_refinement_scan());
}
#[test]
fn test_default_script_coverage() {
let script = default_progressive_script();
for scan in &script {
for &c in &scan.components {
assert!(c < 3, "Invalid component index");
}
assert!(scan.ss <= scan.se);
assert!(scan.se <= 63);
}
}
#[test]
fn test_simple_script_coverage() {
let script = simple_progressive_script();
for scan in &script {
for &c in &scan.components {
assert!(c < 3, "Invalid component index");
}
assert!(scan.ss <= scan.se);
assert!(scan.se <= 63);
}
assert!(script.len() < default_progressive_script().len());
}
#[test]
fn test_category() {
assert_eq!(category(0), 0);
assert_eq!(category(1), 1);
assert_eq!(category(-1), 1);
assert_eq!(category(127), 7);
assert_eq!(category(-128), 8);
}
#[test]
fn test_encode_value() {
assert_eq!(encode_value(0), (0, 0));
let (bits, len) = encode_value(1);
assert_eq!(len, 1);
assert_eq!(bits, 1);
let (bits, len) = encode_value(127);
assert_eq!(len, 7);
assert_eq!(bits, 127);
let (bits, len) = encode_value(-1);
assert_eq!(len, 1);
assert_eq!(bits, 0);
let (_bits, len) = encode_value(-127);
assert_eq!(len, 7);
}
#[test]
fn test_scan_spec_first_vs_refinement() {
let first = ScanSpec::new(vec![0], 0, 0, 0, 1);
assert!(first.is_first_scan());
assert!(!first.is_refinement_scan());
let refine = ScanSpec::new(vec![0], 0, 0, 1, 0);
assert!(!refine.is_first_scan());
assert!(refine.is_refinement_scan());
}
#[test]
fn test_get_code_from_table_fallback() {
let bits = [0u8; 16];
let vals: [u8; 0] = [];
let (code, len) = get_code_from_table(&bits, &vals, 0);
assert_eq!((code, len), (0, 4));
}
#[test]
fn test_encode_dc_first_zero() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let dc_code = get_dc_code(&tables, 0, true); encode_dc_first(&mut writer, 0, 0, dc_code);
assert!(!writer.is_empty());
}
#[test]
fn test_encode_dc_first_nonzero() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let dc_diff = 100;
let cat = category(dc_diff);
let dc_code = get_dc_code(&tables, cat, true);
encode_dc_first(&mut writer, dc_diff, 0, dc_code);
assert!(!writer.is_empty());
}
#[test]
fn test_encode_dc_first_with_successive_approximation() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let dc_diff = 128; let al = 2; let shifted = dc_diff >> al; let cat = category(shifted);
let dc_code = get_dc_code(&tables, cat, true);
encode_dc_first(&mut writer, dc_diff, al, dc_code);
assert!(!writer.is_empty());
}
#[test]
fn test_encode_dc_refine() {
let mut writer = BitWriterMsb::new();
encode_dc_refine(&mut writer, 5, 0); encode_dc_refine(&mut writer, 4, 0); assert!(!writer.is_empty());
}
#[test]
fn test_encode_dc_refine_higher_bit() {
let mut writer = BitWriterMsb::new();
encode_dc_refine(&mut writer, 6, 1); encode_dc_refine(&mut writer, 5, 1); assert!(!writer.is_empty());
}
#[test]
fn test_encode_ac_first_all_zeros() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let block = [0i16; 64];
let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
assert_eq!(eob_run, 1);
}
#[test]
fn test_encode_ac_first_with_coefficients() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[1] = 10;
block[8] = 5;
let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty() || eob_run > 0);
}
#[test]
fn test_encode_ac_first_successive_approximation() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[1] = 8; let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 10,
al: 2,
is_luminance: true,
};
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty() || eob_run > 0);
}
#[test]
fn test_flush_eob_run_zero() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut eob_run = 0u16;
flush_eob_run_public(&mut writer, &mut eob_run, &tables, true);
assert!(writer.is_empty());
assert_eq!(eob_run, 0);
}
#[test]
fn test_flush_eob_run_small() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut eob_run = 3u16;
flush_eob_run_public(&mut writer, &mut eob_run, &tables, true);
assert!(!writer.is_empty());
assert_eq!(eob_run, 0);
}
#[test]
fn test_flush_eob_run_large() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut eob_run = 100u16;
flush_eob_run_public(&mut writer, &mut eob_run, &tables, true);
assert!(!writer.is_empty());
assert_eq!(eob_run, 0);
}
#[test]
fn test_eob_run_max() {
let tables = HuffmanTables::new();
let block = [0i16; 64];
let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
let mut eob_run = 0u16;
for _ in 0..100 {
let mut writer = BitWriterMsb::new();
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
}
assert!(eob_run > 0);
}
#[test]
fn test_get_dc_code_luminance_vs_chrominance() {
let tables = HuffmanTables::new();
let lum_code = get_dc_code(&tables, 1, true);
let chrom_code = get_dc_code(&tables, 1, false);
assert!(lum_code.1 > 0);
assert!(chrom_code.1 > 0);
}
#[test]
fn test_category_boundary_values() {
assert_eq!(category(1), 1);
assert_eq!(category(-1), 1);
assert_eq!(category(2), 2);
assert_eq!(category(-2), 2);
assert_eq!(category(3), 2);
assert_eq!(category(4), 3);
assert_eq!(category(7), 3);
assert_eq!(category(8), 4);
assert_eq!(category(15), 4);
assert_eq!(category(16), 5);
}
#[test]
fn test_encode_value_boundary() {
let (bits, len) = encode_value(1);
assert_eq!((bits, len), (1, 1));
let (bits, len) = encode_value(2);
assert_eq!((bits, len), (2, 2));
let (bits, len) = encode_value(3);
assert_eq!((bits, len), (3, 2));
let (bits, len) = encode_value(4);
assert_eq!((bits, len), (4, 3));
}
#[test]
fn test_scan_spec_ac_scan() {
let ac_scan = ScanSpec::new(vec![0], 1, 63, 0, 0);
assert!(!ac_scan.is_dc_scan());
assert!(ac_scan.is_first_scan());
assert!(!ac_scan.is_refinement_scan());
}
#[test]
fn test_scan_spec_partial_ac() {
let partial = ScanSpec::new(vec![0], 1, 10, 0, 0);
assert!(!partial.is_dc_scan());
assert_eq!(partial.ss, 1);
assert_eq!(partial.se, 10);
}
#[test]
fn test_default_script_has_all_components() {
let script = default_progressive_script();
let mut has_y = false;
let mut has_cb = false;
let mut has_cr = false;
for scan in &script {
if scan.components.contains(&0) {
has_y = true;
}
if scan.components.contains(&1) {
has_cb = true;
}
if scan.components.contains(&2) {
has_cr = true;
}
}
assert!(has_y, "Missing Y component scans");
assert!(has_cb, "Missing Cb component scans");
assert!(has_cr, "Missing Cr component scans");
}
#[test]
fn test_simple_script_complete_coverage() {
let script = simple_progressive_script();
let mut dc_covered = [false; 3];
let mut ac_covered = [false; 3];
for scan in &script {
for &c in &scan.components {
if scan.is_dc_scan() {
dc_covered[c as usize] = true;
} else {
ac_covered[c as usize] = true;
}
}
}
assert!(
dc_covered.iter().all(|&x| x),
"Not all DC components covered"
);
assert!(
ac_covered.iter().all(|&x| x),
"Not all AC components covered"
);
}
#[test]
fn test_encode_ac_refine_basic() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[1] = 10; let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 10,
al: 0,
is_luminance: true,
};
encode_ac_refine(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty() || eob_run > 0);
}
#[test]
fn test_encode_ac_first_long_zero_run() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[32] = 100; let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty());
}
#[test]
fn test_encode_ac_first_multiple_zrl() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[48] = 50;
let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty());
}
#[test]
fn test_encode_ac_refine_long_zero_run() {
let tables = HuffmanTables::new();
let mut writer = BitWriterMsb::new();
let mut block = [0i16; 64];
block[1] = 8; block[48] = 8; let mut eob_run = 0u16;
let scan = ScanParams {
ss: 1,
se: 63,
al: 2,
is_luminance: true,
};
encode_ac_refine(&mut writer, &block, &scan, &mut eob_run, &tables);
assert!(!writer.is_empty() || eob_run > 0);
}
#[test]
fn test_eob_run_accumulation_to_max() {
let tables = HuffmanTables::new();
let block = [0i16; 64]; let scan = ScanParams {
ss: 1,
se: 63,
al: 0,
is_luminance: true,
};
let mut eob_run = 0u16;
let mut total_writes = 0;
for _ in 0..0x8000 {
let mut writer = BitWriterMsb::new();
encode_ac_first(&mut writer, &block, &scan, &mut eob_run, &tables);
if !writer.is_empty() {
total_writes += 1;
}
}
assert!(total_writes > 0 || eob_run > 0);
}
#[test]
fn test_encode_with_default_script_produces_output() {
let script = default_progressive_script();
assert!(!script.is_empty());
assert!(script[0].is_dc_scan());
assert!(script[1].is_dc_scan());
assert!(script[2].is_dc_scan());
let has_ac_scans = script.iter().any(|s| !s.is_dc_scan());
assert!(has_ac_scans);
}
#[test]
fn test_default_script_successive_approximation() {
let script = default_progressive_script();
let dc_scans: Vec<_> = script.iter().filter(|s| s.is_dc_scan()).collect();
assert!(!dc_scans.is_empty());
let has_sa = dc_scans.iter().any(|s| s.al > 0);
assert!(
has_sa,
"Default script should use successive approximation for DC"
);
}
}