use super::{Jp2Error, Jp2Result};
pub const SOC: u16 = 0xFF4F;
pub const SIZ: u16 = 0xFF51;
pub const COD: u16 = 0xFF52;
pub const QCD: u16 = 0xFF5C;
pub const SOT: u16 = 0xFF90;
pub const SOD: u16 = 0xFF93;
pub const EOC: u16 = 0xFFD9;
#[derive(Debug, Clone)]
pub struct ComponentParams {
pub ssiz: u8,
pub xr_siz: u8,
pub yr_siz: u8,
}
impl ComponentParams {
#[must_use]
pub fn bit_depth(&self) -> u8 {
(self.ssiz & 0x7F) + 1
}
#[must_use]
pub fn is_signed(&self) -> bool {
(self.ssiz & 0x80) != 0
}
}
#[derive(Debug, Clone)]
pub struct SizMarker {
pub rsiz: u16,
pub x_siz: u32,
pub y_siz: u32,
pub xo_siz: u32,
pub yo_siz: u32,
pub xt_siz: u32,
pub yt_siz: u32,
pub xto_siz: u32,
pub yto_siz: u32,
pub csiz: u16,
pub components: Vec<ComponentParams>,
}
impl SizMarker {
#[must_use]
pub fn image_width(&self) -> u32 {
self.x_siz.saturating_sub(self.xo_siz)
}
#[must_use]
pub fn image_height(&self) -> u32 {
self.y_siz.saturating_sub(self.yo_siz)
}
#[must_use]
pub fn num_tiles_x(&self) -> u32 {
let effective_xt = if self.xt_siz == 0 {
self.x_siz
} else {
self.xt_siz
};
let width = self.x_siz.saturating_sub(self.xto_siz);
if width == 0 {
return 1;
}
(width + effective_xt - 1) / effective_xt
}
#[must_use]
pub fn num_tiles_y(&self) -> u32 {
let effective_yt = if self.yt_siz == 0 {
self.y_siz
} else {
self.yt_siz
};
let height = self.y_siz.saturating_sub(self.yto_siz);
if height == 0 {
return 1;
}
(height + effective_yt - 1) / effective_yt
}
#[must_use]
pub fn tile_rect(&self, tile_idx: u32) -> (u32, u32, u32, u32) {
let ntx = self.num_tiles_x().max(1);
let col = tile_idx % ntx;
let row = tile_idx / ntx;
let effective_xt = if self.xt_siz == 0 {
self.x_siz
} else {
self.xt_siz
};
let effective_yt = if self.yt_siz == 0 {
self.y_siz
} else {
self.yt_siz
};
let x0 = self.xto_siz + col * effective_xt;
let y0 = self.yto_siz + row * effective_yt;
let x1 = (x0 + effective_xt).min(self.x_siz);
let y1 = (y0 + effective_yt).min(self.y_siz);
let tw = x1.saturating_sub(x0);
let th = y1.saturating_sub(y0);
(x0, y0, tw, th)
}
}
#[derive(Debug, Clone)]
pub struct CodMarker {
pub scod: u8,
pub progression_order: u8,
pub num_layers: u16,
pub mct: u8,
pub num_decomp_levels: u8,
pub xcb: u8,
pub ycb: u8,
pub cb_style: u8,
pub wavelet_filter: u8,
}
impl CodMarker {
#[must_use]
pub fn is_lossless_wavelet(&self) -> bool {
self.wavelet_filter == 1
}
#[must_use]
pub fn is_irreversible_97(&self) -> bool {
self.wavelet_filter == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubbandKind {
Ll,
Hl,
Lh,
Hh,
}
#[derive(Debug, Clone)]
pub struct QcdMarker {
pub sqcd: u8,
pub step_sizes: Vec<u16>,
}
impl QcdMarker {
#[must_use]
pub fn guard_bits(&self) -> u8 {
self.sqcd >> 5
}
#[must_use]
pub fn quant_style(&self) -> u8 {
self.sqcd & 0x1F
}
#[must_use]
pub fn step_size_for_subband(&self, subband_idx: usize, bit_depth: u8) -> f64 {
let style = self.quant_style();
match style {
0 => 1.0, 1 => {
if self.step_sizes.is_empty() {
return 1.0;
}
let raw = self.step_sizes[0];
let epsilon = i32::from((raw >> 11) & 0x1F);
let mu = f64::from(raw & 0x7FF);
let r_b = i32::from(bit_depth);
(2f64).powi(r_b - epsilon) * (1.0 + mu / 2048.0)
}
2 => {
if self.step_sizes.is_empty() {
return 1.0;
}
let idx = subband_idx.min(self.step_sizes.len().saturating_sub(1));
let raw = self.step_sizes[idx];
let epsilon = i32::from((raw >> 11) & 0x1F);
let mu = f64::from(raw & 0x7FF);
let r_b = i32::from(bit_depth);
(2f64).powi(r_b - epsilon) * (1.0 + mu / 2048.0)
}
_ => 1.0, }
}
}
#[derive(Debug, Clone)]
pub struct SotMarker {
pub isot: u16,
pub psot: u32,
pub tpsot: u8,
pub tnsot: u8,
}
#[derive(Debug, Clone)]
pub enum MarkerSegment {
Siz(SizMarker),
Cod(CodMarker),
Qcd(QcdMarker),
Sot(SotMarker),
Sod {
data: Vec<u8>,
},
Eoc,
}
fn read_u8(buf: &[u8], offset: usize, ctx: &'static str) -> Jp2Result<u8> {
buf.get(offset).copied().ok_or(Jp2Error::Truncated {
context: ctx,
needed: offset + 1,
available: buf.len(),
})
}
fn read_u16_be(buf: &[u8], offset: usize, ctx: &'static str) -> Jp2Result<u16> {
if offset + 2 > buf.len() {
return Err(Jp2Error::Truncated {
context: ctx,
needed: offset + 2,
available: buf.len(),
});
}
Ok(u16::from_be_bytes([buf[offset], buf[offset + 1]]))
}
fn read_u32_be(buf: &[u8], offset: usize, ctx: &'static str) -> Jp2Result<u32> {
if offset + 4 > buf.len() {
return Err(Jp2Error::Truncated {
context: ctx,
needed: offset + 4,
available: buf.len(),
});
}
Ok(u32::from_be_bytes([
buf[offset],
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
]))
}
fn parse_siz(payload: &[u8]) -> Jp2Result<SizMarker> {
const FIXED: usize = 36;
if payload.len() < FIXED {
return Err(Jp2Error::Truncated {
context: "SIZ marker",
needed: FIXED,
available: payload.len(),
});
}
let rsiz = read_u16_be(payload, 0, "SIZ.rsiz")?;
let x_siz = read_u32_be(payload, 2, "SIZ.xsiz")?;
let y_siz = read_u32_be(payload, 6, "SIZ.ysiz")?;
let xo_siz = read_u32_be(payload, 10, "SIZ.xosiz")?;
let yo_siz = read_u32_be(payload, 14, "SIZ.yosiz")?;
let xt_siz = read_u32_be(payload, 18, "SIZ.xtsiz")?;
let yt_siz = read_u32_be(payload, 22, "SIZ.ytsiz")?;
let xto_siz = read_u32_be(payload, 26, "SIZ.xtosiz")?;
let yto_siz = read_u32_be(payload, 30, "SIZ.ytosiz")?;
let csiz = read_u16_be(payload, 34, "SIZ.csiz")?;
let needed_len = FIXED + 3 * usize::from(csiz);
if payload.len() < needed_len {
return Err(Jp2Error::Truncated {
context: "SIZ component params",
needed: needed_len,
available: payload.len(),
});
}
let mut components = Vec::with_capacity(usize::from(csiz));
let mut off = FIXED;
for _ in 0..csiz {
let ssiz = read_u8(payload, off, "SIZ.ssiz")?;
let xr_siz = read_u8(payload, off + 1, "SIZ.xrsiz")?;
let yr_siz = read_u8(payload, off + 2, "SIZ.yrsiz")?;
components.push(ComponentParams {
ssiz,
xr_siz,
yr_siz,
});
off += 3;
}
Ok(SizMarker {
rsiz,
x_siz,
y_siz,
xo_siz,
yo_siz,
xt_siz,
yt_siz,
xto_siz,
yto_siz,
csiz,
components,
})
}
fn parse_cod(payload: &[u8]) -> Jp2Result<CodMarker> {
if payload.len() < 10 {
return Err(Jp2Error::Truncated {
context: "COD marker",
needed: 10,
available: payload.len(),
});
}
let scod = payload[0];
let progression_order = payload[1];
let num_layers = u16::from_be_bytes([payload[2], payload[3]]);
let mct = payload[4];
let num_decomp_levels = payload[5];
let xcb = payload[6];
let ycb = payload[7];
let cb_style = payload[8];
let wavelet_filter = payload[9];
Ok(CodMarker {
scod,
progression_order,
num_layers,
mct,
num_decomp_levels,
xcb,
ycb,
cb_style,
wavelet_filter,
})
}
fn parse_qcd(payload: &[u8]) -> Jp2Result<QcdMarker> {
if payload.is_empty() {
return Err(Jp2Error::Truncated {
context: "QCD marker",
needed: 1,
available: 0,
});
}
let sqcd = payload[0];
let quant_style = sqcd & 0x1F;
let mut step_sizes = Vec::new();
match quant_style {
0 => {
for &b in &payload[1..] {
step_sizes.push(u16::from(b));
}
}
1 => {
if payload.len() >= 3 {
let v = u16::from_be_bytes([payload[1], payload[2]]);
step_sizes.push(v);
}
}
2 => {
let mut off = 1;
while off + 1 < payload.len() {
let v = u16::from_be_bytes([payload[off], payload[off + 1]]);
step_sizes.push(v);
off += 2;
}
}
_ => {
for &b in &payload[1..] {
step_sizes.push(u16::from(b));
}
}
}
Ok(QcdMarker { sqcd, step_sizes })
}
fn parse_sot(payload: &[u8]) -> Jp2Result<SotMarker> {
if payload.len() < 8 {
return Err(Jp2Error::Truncated {
context: "SOT marker",
needed: 8,
available: payload.len(),
});
}
let isot = u16::from_be_bytes([payload[0], payload[1]]);
let psot = u32::from_be_bytes([payload[2], payload[3], payload[4], payload[5]]);
let tpsot = payload[6];
let tnsot = payload[7];
Ok(SotMarker {
isot,
psot,
tpsot,
tnsot,
})
}
pub fn parse_codestream(data: &[u8]) -> Jp2Result<Vec<MarkerSegment>> {
let mut segments = Vec::new();
let mut pos = 0;
if data.len() < 2 {
return Err(Jp2Error::Truncated {
context: "codestream SOC",
needed: 2,
available: data.len(),
});
}
let first_marker = u16::from_be_bytes([data[0], data[1]]);
if first_marker != SOC {
return Err(Jp2Error::InvalidMarker {
marker: first_marker,
offset: 0,
});
}
pos += 2;
let mut in_tile = false;
let mut tile_data_start = 0usize;
let mut sot_start = 0usize;
let mut sot_psot = 0u32;
loop {
if pos + 1 >= data.len() {
break;
}
let marker = u16::from_be_bytes([data[pos], data[pos + 1]]);
if in_tile {
if marker == EOC {
let tile_data = data[tile_data_start..pos].to_vec();
segments.push(MarkerSegment::Sod { data: tile_data });
segments.push(MarkerSegment::Eoc);
break;
} else if marker == SOT {
let tile_data = data[tile_data_start..pos].to_vec();
segments.push(MarkerSegment::Sod { data: tile_data });
in_tile = false;
} else {
pos += 1;
continue;
}
}
match marker {
EOC => {
segments.push(MarkerSegment::Eoc);
break;
}
SOD => {
pos += 2;
tile_data_start = pos;
in_tile = true;
if sot_psot > 0 {
let tile_end = sot_start.saturating_add(sot_psot as usize).min(data.len());
if tile_end > tile_data_start {
let tile_data = data[tile_data_start..tile_end].to_vec();
segments.push(MarkerSegment::Sod { data: tile_data });
pos = tile_end;
in_tile = false;
}
}
continue;
}
_ => {}
}
let marker_start = pos;
pos += 2; if pos + 2 > data.len() {
return Err(Jp2Error::Truncated {
context: "marker segment length",
needed: pos + 2,
available: data.len(),
});
}
let lseg = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
if lseg < 2 {
return Err(Jp2Error::InvalidMarker {
marker,
offset: pos - 2,
});
}
let payload_len = lseg - 2;
pos += 2;
if pos + payload_len > data.len() {
return Err(Jp2Error::Truncated {
context: "marker segment payload",
needed: pos + payload_len,
available: data.len(),
});
}
let payload = &data[pos..pos + payload_len];
pos += payload_len;
match marker {
SIZ => {
let siz = parse_siz(payload)?;
segments.push(MarkerSegment::Siz(siz));
}
COD => {
let cod = parse_cod(payload)?;
segments.push(MarkerSegment::Cod(cod));
}
QCD => {
let qcd = parse_qcd(payload)?;
segments.push(MarkerSegment::Qcd(qcd));
}
SOT => {
let sot = parse_sot(payload)?;
sot_start = marker_start;
sot_psot = sot.psot;
segments.push(MarkerSegment::Sot(sot));
}
_ => {
}
}
}
Ok(segments)
}
#[cfg(test)]
mod tests {
use super::*;
fn minimal_codestream() -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&SOC.to_be_bytes());
let siz_payload_len: u16 = 2 + 36 + 3;
v.extend_from_slice(&SIZ.to_be_bytes());
v.extend_from_slice(&siz_payload_len.to_be_bytes());
v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&16u32.to_be_bytes()); v.extend_from_slice(&16u32.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&16u32.to_be_bytes()); v.extend_from_slice(&16u32.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&1u16.to_be_bytes()); v.push(7); v.push(1); v.push(1);
let sot_len: u16 = 10;
v.extend_from_slice(&SOT.to_be_bytes());
v.extend_from_slice(&sot_len.to_be_bytes());
v.extend_from_slice(&0u16.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.push(0); v.push(0);
v.extend_from_slice(&SOD.to_be_bytes());
v.extend_from_slice(&EOC.to_be_bytes());
v
}
#[test]
fn parse_minimal_codestream() {
let data = minimal_codestream();
let segments = parse_codestream(&data).expect("parse");
let has_siz = segments.iter().any(|s| matches!(s, MarkerSegment::Siz(_)));
let has_sot = segments.iter().any(|s| matches!(s, MarkerSegment::Sot(_)));
let has_eoc = segments.iter().any(|s| matches!(s, MarkerSegment::Eoc));
assert!(has_siz, "Expected Siz marker");
assert!(has_sot, "Expected Sot marker");
assert!(has_eoc, "Expected Eoc marker");
}
#[test]
fn siz_fields_correct() {
let data = minimal_codestream();
let segments = parse_codestream(&data).expect("parse");
let siz = segments
.iter()
.find_map(|s| {
if let MarkerSegment::Siz(sz) = s {
Some(sz)
} else {
None
}
})
.expect("SIZ marker");
assert_eq!(siz.image_width(), 16);
assert_eq!(siz.image_height(), 16);
assert_eq!(siz.csiz, 1);
assert_eq!(siz.components[0].bit_depth(), 8);
assert!(!siz.components[0].is_signed());
}
#[test]
fn sot_fields_correct() {
let data = minimal_codestream();
let segments = parse_codestream(&data).expect("parse");
let sot = segments
.iter()
.find_map(|s| {
if let MarkerSegment::Sot(st) = s {
Some(st)
} else {
None
}
})
.expect("SOT marker");
assert_eq!(sot.isot, 0);
assert_eq!(sot.tpsot, 0);
}
#[test]
fn missing_soc_returns_error() {
let data = vec![0x00u8, 0x00];
assert!(parse_codestream(&data).is_err());
}
#[test]
fn truncated_codestream_returns_error() {
let data = vec![0xFF, 0x4F]; let result = parse_codestream(&data);
let _ = result;
}
}