use crate::error::JpegError;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ScanComponent {
pub(crate) id: u8,
pub(crate) dc_table: u8,
pub(crate) ac_table: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParsedScan {
pub(crate) components: Vec<ScanComponent>,
pub(crate) ss: u8,
pub(crate) se: u8,
pub(crate) ah: u8,
pub(crate) al: u8,
}
pub(crate) fn parse_scan_header(payload: &[u8], offset: usize) -> Result<ParsedScan, JpegError> {
if payload.is_empty() {
return Err(JpegError::InvalidSegmentLength {
offset,
marker: 0xDA,
length: 2,
});
}
let ns = payload[0] as usize;
let expected = 1 + ns * 2 + 3;
if payload.len() != expected {
return Err(JpegError::InvalidSegmentLength {
offset,
marker: 0xDA,
length: (payload.len() + 2) as u16,
});
}
let mut components = Vec::with_capacity(ns);
for i in 0..ns {
let base = 1 + i * 2;
let id = payload[base];
let td_ta = payload[base + 1];
let dc_table = td_ta >> 4;
let ac_table = td_ta & 0x0F;
if dc_table > 3 || ac_table > 3 {
return Err(JpegError::InvalidSegmentLength {
offset: offset + base + 1,
marker: 0xDA,
length: (payload.len() + 2) as u16,
});
}
components.push(ScanComponent {
id,
dc_table,
ac_table,
});
}
let last = 1 + ns * 2;
let ss = payload[last];
let se = payload[last + 1];
let ahal = payload[last + 2];
Ok(ParsedScan {
components,
ss,
se,
ah: ahal >> 4,
al: ahal & 0x0F,
})
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn parses_three_component_baseline_scan() {
let payload = vec![3u8, 1, 0x00, 2, 0x11, 3, 0x11, 0, 63, 0];
let scan = parse_scan_header(&payload, 0).unwrap();
assert_eq!(scan.components.len(), 3);
assert_eq!(
scan.components[0],
ScanComponent {
id: 1,
dc_table: 0,
ac_table: 0
}
);
assert_eq!(
scan.components[1],
ScanComponent {
id: 2,
dc_table: 1,
ac_table: 1
}
);
assert_eq!(
scan.components[2],
ScanComponent {
id: 3,
dc_table: 1,
ac_table: 1
}
);
assert_eq!((scan.ss, scan.se, scan.ah, scan.al), (0, 63, 0, 0));
}
#[test]
fn parses_single_component_grayscale_scan() {
let payload = vec![1u8, 1, 0x00, 0, 63, 0];
let scan = parse_scan_header(&payload, 0).unwrap();
assert_eq!(scan.components.len(), 1);
assert_eq!(scan.components[0].id, 1);
}
#[test]
fn rejects_empty_payload() {
let err = parse_scan_header(&[], 10).unwrap_err();
assert!(matches!(err, JpegError::InvalidSegmentLength { .. }));
}
#[test]
fn rejects_length_mismatch() {
let payload = vec![2u8, 1, 0x00, 0, 63, 0];
let err = parse_scan_header(&payload, 0).unwrap_err();
assert!(matches!(err, JpegError::InvalidSegmentLength { .. }));
}
#[test]
fn rejects_out_of_range_table_selector() {
let payload = vec![1u8, 1, 0x05, 0, 63, 0];
let err = parse_scan_header(&payload, 0).unwrap_err();
assert!(matches!(err, JpegError::InvalidSegmentLength { .. }));
}
}