use super::{JlsError, JlsResult};
pub const SOI: u16 = 0xFFD8;
pub const EOI: u16 = 0xFFD9;
pub const SOF55: u16 = 0xFFF7;
pub const LSE: u16 = 0xFFF8;
pub const SOS: u16 = 0xFFDA;
pub const DNL: u16 = 0xFFDC;
pub const DRI: u16 = 0xFFDD;
#[derive(Debug, Clone)]
pub struct FrameParams {
pub precision: u8,
pub height: u16,
pub width: u16,
pub num_components: u8,
pub components: Vec<ComponentSpec>,
}
#[derive(Debug, Clone)]
pub struct ComponentSpec {
pub id: u8,
pub h_factor: u8,
pub v_factor: u8,
pub quant_table_idx: u8,
}
#[derive(Debug, Clone)]
pub struct JlsPresetParams {
pub max_val: u16,
pub t1: i32,
pub t2: i32,
pub t3: i32,
pub reset: u16,
}
impl JlsPresetParams {
#[must_use]
pub fn default_for_precision(precision: u8) -> Self {
let max_val = (1u32 << precision) - 1;
let (t1, t2, t3) = if max_val >= 128 {
let factor = (max_val as i32 + 127) / 256;
let t1 = (factor * 1 + 10).max(2).min(max_val as i32 + 1);
let t2 = (factor * 4 + 11).max(3).min(max_val as i32 + 1);
let t3 = (factor * 17 + 12).max(4).min(max_val as i32 + 1);
(t1, t2, t3)
} else {
let mv = max_val as i32;
((mv / 4).max(2), (mv / 2).max(3), (3 * mv / 4).max(4))
};
Self {
max_val: max_val as u16,
t1,
t2,
t3,
reset: 64,
}
}
}
#[derive(Debug, Clone)]
pub struct ScanHeader {
pub near: u8,
pub ilv: u8,
pub pt: u8,
pub component_ids: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct JlsHeaders {
pub frame: FrameParams,
pub presets: JlsPresetParams,
pub scan: ScanHeader,
pub scan_data_start: usize,
}
fn read_u16(data: &[u8], pos: &mut usize) -> JlsResult<u16> {
if *pos + 2 > data.len() {
return Err(JlsError::Truncated {
context: "marker read_u16",
});
}
let v = u16::from_be_bytes([data[*pos], data[*pos + 1]]);
*pos += 2;
Ok(v)
}
fn read_u8(data: &[u8], pos: &mut usize) -> JlsResult<u8> {
if *pos >= data.len() {
return Err(JlsError::Truncated {
context: "marker read_u8",
});
}
let v = data[*pos];
*pos += 1;
Ok(v)
}
fn parse_sof55(data: &[u8], pos: &mut usize, len: usize) -> JlsResult<FrameParams> {
let start = *pos;
let precision = read_u8(data, pos)?;
let height = read_u16(data, pos)?;
let width = read_u16(data, pos)?;
let num_components = read_u8(data, pos)?;
let mut components = Vec::with_capacity(num_components as usize);
for _ in 0..num_components {
let id = read_u8(data, pos)?;
let hv = read_u8(data, pos)?;
let tq = read_u8(data, pos)?;
components.push(ComponentSpec {
id,
h_factor: hv >> 4,
v_factor: hv & 0x0F,
quant_table_idx: tq,
});
}
let consumed = *pos - start;
if consumed != len - 2 {
*pos = start + len - 2;
}
Ok(FrameParams {
precision,
height,
width,
num_components,
components,
})
}
fn parse_lse(data: &[u8], pos: &mut usize, len: usize) -> JlsResult<Option<JlsPresetParams>> {
if len < 2 + 11 {
*pos += len - 2;
return Ok(None);
}
let id = read_u8(data, pos)?;
if id != 1 {
*pos += len - 3;
return Ok(None);
}
let max_val = read_u16(data, pos)?;
let t1 = read_u16(data, pos)? as i32;
let t2 = read_u16(data, pos)? as i32;
let t3 = read_u16(data, pos)? as i32;
let reset = read_u16(data, pos)?;
Ok(Some(JlsPresetParams {
max_val,
t1,
t2,
t3,
reset,
}))
}
fn parse_sos(data: &[u8], pos: &mut usize, len: usize) -> JlsResult<ScanHeader> {
let start = *pos;
let ns = read_u8(data, pos)?;
let mut component_ids = Vec::with_capacity(ns as usize);
for _ in 0..ns {
let cs = read_u8(data, pos)?;
let _td_ta = read_u8(data, pos)?; component_ids.push(cs);
}
let near = read_u8(data, pos)?;
let ilv = read_u8(data, pos)?;
let pt = read_u8(data, pos)?;
let consumed = *pos - start;
if consumed < len - 2 {
*pos = start + len - 2;
}
Ok(ScanHeader {
near,
ilv,
pt,
component_ids,
})
}
fn walk_markers(data: &[u8], start_pos: usize) -> JlsResult<JlsHeaders> {
let mut pos = start_pos;
let mut frame_opt: Option<FrameParams> = None;
let mut presets_opt: Option<JlsPresetParams> = None;
let scan = loop {
if pos + 2 > data.len() {
return Err(JlsError::Truncated {
context: "marker scan",
});
}
if data[pos] != 0xFF {
return Err(JlsError::InvalidMarker(0xFF00 | data[pos] as u16));
}
let marker_byte = data[pos + 1];
let marker = 0xFF00u16 | marker_byte as u16;
pos += 2;
if marker_byte == 0x00 || marker_byte == 0xFF {
continue;
}
if marker == SOI || marker == EOI {
continue;
}
if pos + 2 > data.len() {
return Err(JlsError::Truncated {
context: "marker length",
});
}
let seg_len = read_u16(data, &mut pos)? as usize;
if seg_len < 2 {
return Err(JlsError::InvalidMarker(marker));
}
let seg_start = pos;
let seg_data_len = seg_len - 2;
match marker {
SOF55 => {
if data.len() < pos + seg_data_len {
return Err(JlsError::Truncated { context: "SOF55" });
}
frame_opt = Some(parse_sof55(data, &mut pos, seg_len)?);
}
LSE => {
if data.len() < pos + seg_data_len {
return Err(JlsError::Truncated { context: "LSE" });
}
if let Some(p) = parse_lse(data, &mut pos, seg_len)? {
presets_opt = Some(p);
}
}
SOS => {
if data.len() < pos + seg_data_len {
return Err(JlsError::Truncated { context: "SOS" });
}
break parse_sos(data, &mut pos, seg_len)?;
}
_ => {
pos = seg_start + seg_data_len;
}
}
if pos >= data.len() {
return Err(JlsError::Truncated {
context: "marker walk",
});
}
};
let frame = frame_opt.ok_or(JlsError::NotJpegLs)?;
let presets =
presets_opt.unwrap_or_else(|| JlsPresetParams::default_for_precision(frame.precision));
Ok(JlsHeaders {
presets,
scan,
scan_data_start: pos,
frame,
})
}
pub fn parse_headers(data: &[u8]) -> JlsResult<JlsHeaders> {
if data.len() < 4 {
return Err(JlsError::NotJpegLs);
}
let soi = u16::from_be_bytes([data[0], data[1]]);
if soi != SOI {
return Err(JlsError::NotJpegLs);
}
walk_markers(data, 2usize)
}