use std::io::Write;
use super::error::{Jp2Error, Result};
pub mod marker {
pub const SOC: u16 = 0xFF4F; pub const SOT: u16 = 0xFF90; pub const SOD: u16 = 0xFF93; pub const EOC: u16 = 0xFFD9; pub const SIZ: u16 = 0xFF51; pub const COD: u16 = 0xFF52; pub const COC: u16 = 0xFF53; pub const RGN: u16 = 0xFF5E; pub const QCD: u16 = 0xFF5C; pub const QCC: u16 = 0xFF5D; pub const POC: u16 = 0xFF5F; pub const TLM: u16 = 0xFF55; pub const PLM: u16 = 0xFF57; pub const PLT: u16 = 0xFF58; pub const PPM: u16 = 0xFF60; pub const PPT: u16 = 0xFF61; pub const CME: u16 = 0xFF64; pub const CRG: u16 = 0xFF63; }
#[derive(Debug, Clone)]
pub struct Siz {
pub rsiz: u16,
pub xsiz: u32,
pub ysiz: u32,
pub x_osiz: u32,
pub y_osiz: u32,
pub x_tsiz: u32,
pub y_tsiz: u32,
pub xt_osiz: u32,
pub yt_osiz: u32,
pub components: Vec<SizComponent>,
}
#[derive(Debug, Clone, Copy)]
pub struct SizComponent {
pub ssiz: u8, pub xrsiz: u8, pub yrsiz: u8, }
impl SizComponent {
pub fn new(bits: u8, signed: bool) -> Self {
let ssiz = if signed { 0x80 | (bits - 1) } else { bits - 1 };
Self { ssiz, xrsiz: 1, yrsiz: 1 }
}
pub fn bits(&self) -> u8 { (self.ssiz & 0x7F) + 1 }
pub fn signed(&self) -> bool { self.ssiz & 0x80 != 0 }
}
impl Siz {
pub fn new(width: u32, height: u32, bits: u8, signed: bool, num_components: u16) -> Self {
Self {
rsiz: 0,
xsiz: width, ysiz: height,
x_osiz: 0, y_osiz: 0,
x_tsiz: width, y_tsiz: height,
xt_osiz: 0, yt_osiz: 0,
components: (0..num_components)
.map(|_| SizComponent::new(bits, signed))
.collect(),
}
}
pub fn with_tiles(mut self, tw: u32, th: u32) -> Self {
self.x_tsiz = tw; self.y_tsiz = th; self
}
pub fn tiles_x(&self) -> u32 {
(self.xsiz - self.xt_osiz + self.x_tsiz - 1) / self.x_tsiz
}
pub fn tiles_y(&self) -> u32 {
(self.ysiz - self.yt_osiz + self.y_tsiz - 1) / self.y_tsiz
}
pub fn num_tiles(&self) -> u32 { self.tiles_x() * self.tiles_y() }
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 38 {
return Err(Jp2Error::InvalidCodestream { offset: 0, message: "SIZ too short".into() });
}
let rsiz = u16::from_be_bytes(data[0..2].try_into().unwrap());
let xsiz = u32::from_be_bytes(data[2..6].try_into().unwrap());
let ysiz = u32::from_be_bytes(data[6..10].try_into().unwrap());
let x_osiz = u32::from_be_bytes(data[10..14].try_into().unwrap());
let y_osiz = u32::from_be_bytes(data[14..18].try_into().unwrap());
let x_tsiz = u32::from_be_bytes(data[18..22].try_into().unwrap());
let y_tsiz = u32::from_be_bytes(data[22..26].try_into().unwrap());
let xt_osiz = u32::from_be_bytes(data[26..30].try_into().unwrap());
let yt_osiz = u32::from_be_bytes(data[30..34].try_into().unwrap());
let csiz = u16::from_be_bytes(data[34..36].try_into().unwrap());
if data.len() < 36 + csiz as usize * 3 {
return Err(Jp2Error::InvalidCodestream { offset: 0, message: "SIZ component data truncated".into() });
}
let mut components = Vec::with_capacity(csiz as usize);
for i in 0..csiz as usize {
let off = 36 + i * 3;
components.push(SizComponent { ssiz: data[off], xrsiz: data[off+1], yrsiz: data[off+2] });
}
Ok(Self { rsiz, xsiz, ysiz, x_osiz, y_osiz, x_tsiz, y_tsiz, xt_osiz, yt_osiz, components })
}
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
let csiz = self.components.len() as u16;
let lsiz = 38u16 + csiz * 3;
w.write_all(&marker::SIZ.to_be_bytes())?;
w.write_all(&lsiz.to_be_bytes())?;
w.write_all(&self.rsiz.to_be_bytes())?;
w.write_all(&self.xsiz.to_be_bytes())?;
w.write_all(&self.ysiz.to_be_bytes())?;
w.write_all(&self.x_osiz.to_be_bytes())?;
w.write_all(&self.y_osiz.to_be_bytes())?;
w.write_all(&self.x_tsiz.to_be_bytes())?;
w.write_all(&self.y_tsiz.to_be_bytes())?;
w.write_all(&self.xt_osiz.to_be_bytes())?;
w.write_all(&self.yt_osiz.to_be_bytes())?;
w.write_all(&csiz.to_be_bytes())?;
for c in &self.components {
w.write_all(&[c.ssiz, c.xrsiz, c.yrsiz])?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ProgressionOrder {
#[default]
Lrcp = 0,
Rlcp = 1,
Rpcl = 2,
Pcrl = 3,
Cprl = 4,
}
impl ProgressionOrder {
pub fn from_u8(v: u8) -> Self {
match v { 1=>Self::Rlcp, 2=>Self::Rpcl, 3=>Self::Pcrl, 4=>Self::Cprl, _=>Self::Lrcp }
}
}
#[derive(Debug, Clone)]
pub struct Cod {
pub scod: u8,
pub progression: ProgressionOrder,
pub num_layers: u16,
pub mc_transform: u8,
pub num_decomps: u8,
pub xcb: u8,
pub ycb: u8,
pub cblk_style: u8,
pub wavelet: u8,
pub precincts: Vec<u8>,
}
impl Cod {
pub fn lossless(num_decomps: u8, num_components: u16) -> Self {
Self {
scod: 0,
progression: ProgressionOrder::Lrcp,
num_layers: 1,
mc_transform: if num_components == 3 { 1 } else { 0 }, num_decomps,
xcb: 4, ycb: 4,
cblk_style: 0,
wavelet: 1, precincts: Vec::new(),
}
}
pub fn lossy(num_decomps: u8, num_components: u16) -> Self {
Self {
scod: 0,
progression: ProgressionOrder::Lrcp,
num_layers: 1,
mc_transform: if num_components == 3 { 2 } else { 0 }, num_decomps,
xcb: 4,
ycb: 4,
cblk_style: 0,
wavelet: 0, precincts: Vec::new(),
}
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 10 {
return Err(Jp2Error::InvalidCodestream { offset: 0, message: "COD too short".into() });
}
let scod = data[0];
let progression = ProgressionOrder::from_u8(data[1]);
let num_layers = u16::from_be_bytes(data[2..4].try_into().unwrap());
let mc_transform = data[4];
let num_decomps = data[5];
let xcb = data[6];
let ycb = data[7];
let cblk_style = data[8];
let wavelet = data[9];
let precincts = if scod & 0x01 != 0 { data[10..].to_vec() } else { Vec::new() };
Ok(Self { scod, progression, num_layers, mc_transform, num_decomps, xcb, ycb, cblk_style, wavelet, precincts })
}
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
let has_precincts = !self.precincts.is_empty();
let scod = if has_precincts { self.scod | 0x01 } else { self.scod & !0x01 };
let len = 12u16 + self.precincts.len() as u16;
w.write_all(&marker::COD.to_be_bytes())?;
w.write_all(&len.to_be_bytes())?;
w.write_all(&[scod, self.progression as u8])?;
w.write_all(&self.num_layers.to_be_bytes())?;
w.write_all(&[self.mc_transform, self.num_decomps, self.xcb, self.ycb, self.cblk_style, self.wavelet])?;
w.write_all(&self.precincts)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Qcd {
pub sqcd: u8,
pub step_sizes: Vec<u16>,
}
impl Qcd {
pub fn no_quantisation(num_decomps: u8, bit_depth: u8) -> Self {
let num_subbands = 3 * num_decomps as usize + 1;
let mut step_sizes = Vec::with_capacity(num_subbands);
for i in 0..=num_decomps as usize {
let exp = (bit_depth + num_decomps as u8).saturating_sub(i as u8);
if i == 0 {
step_sizes.push((exp as u16) << 11);
} else {
for _ in 0..3 {
let e = exp.saturating_sub(1);
step_sizes.push((e as u16) << 11);
}
}
}
Self { sqcd: 0, step_sizes }
}
pub fn scalar_expounded(num_decomps: u8, bit_depth: u8, quality_db: f32) -> Self {
let num_subbands = 3 * num_decomps as usize + 1;
let base_step = (2.0f32.powf(-(quality_db / 20.0).max(0.1))) as f64;
let mut step_sizes = Vec::with_capacity(num_subbands);
for i in 0..=num_decomps as usize {
let level_step = base_step * (2.0f64.powi(i as i32));
let exp = level_step.log2().floor() as i32 + bit_depth as i32;
let exp = exp.clamp(0, 31) as u16;
let mantissa = ((level_step / 2.0f64.powi(exp as i32 - bit_depth as i32 + 1)) * 2048.0) as u16 & 0x7FF;
if i == 0 {
step_sizes.push((exp << 11) | mantissa);
} else {
for _ in 0..3 { step_sizes.push((exp << 11) | mantissa); }
}
}
Self { sqcd: 2, step_sizes }
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(Jp2Error::InvalidCodestream { offset: 0, message: "QCD empty".into() });
}
let sqcd = data[0];
let step_sizes: Vec<u16> = match sqcd & 0x1F {
0 => data[1..].iter().map(|&b| (b as u16) << 8).collect(), _ => data[1..].chunks_exact(2).map(|c| u16::from_be_bytes(c.try_into().unwrap())).collect(),
};
Ok(Self { sqcd, step_sizes })
}
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
let is_noquant = self.sqcd & 0x1F == 0;
let body_len = if is_noquant { self.step_sizes.len() } else { self.step_sizes.len() * 2 };
let len = (2 + body_len) as u16;
w.write_all(&marker::QCD.to_be_bytes())?;
w.write_all(&len.to_be_bytes())?;
w.write_all(&[self.sqcd])?;
if is_noquant {
for &s in &self.step_sizes { w.write_all(&[(s >> 8) as u8])?; }
} else {
for &s in &self.step_sizes { w.write_all(&s.to_be_bytes())?; }
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Poc {
pub changes: Vec<PocChange>,
}
#[derive(Debug, Clone, Copy)]
pub struct PocChange {
pub res_start: u8,
pub comp_start: u16,
pub layer_end: u16,
pub res_end: u8,
pub comp_end: u16,
pub progression: ProgressionOrder,
}
impl Poc {
pub fn parse(data: &[u8], num_components: u16) -> Result<Self> {
if data.is_empty() {
return Err(Jp2Error::InvalidCodestream {
offset: 0,
message: "POC marker is empty".into(),
});
}
let mut changes = Vec::new();
let mut pos = 0;
let cpoc_size = if num_components > 256 { 2 } else { 1 };
let entry_size = 1 + cpoc_size + 2 + 1 + cpoc_size + 1;
if data.len() % entry_size != 0 {
return Err(Jp2Error::InvalidCodestream {
offset: 0,
message: format!("POC marker data length {} is not a multiple of {} (num_components={})",
data.len(), entry_size, num_components),
});
}
while pos < data.len() {
if pos + entry_size > data.len() { break; }
let res_start = data[pos];
pos += 1;
let comp_start = if cpoc_size == 2 {
u16::from_be_bytes([data[pos], data[pos+1]])
} else {
data[pos] as u16
};
pos += cpoc_size;
let layer_end = u16::from_be_bytes([data[pos], data[pos+1]]);
pos += 2;
let res_end = data[pos];
pos += 1;
let comp_end = if cpoc_size == 2 {
u16::from_be_bytes([data[pos], data[pos+1]])
} else {
data[pos] as u16
};
pos += cpoc_size;
let progression = ProgressionOrder::from_u8(data[pos]);
pos += 1;
changes.push(PocChange {
res_start,
comp_start,
layer_end,
res_end,
comp_end,
progression,
});
}
Ok(Self { changes })
}
pub fn is_empty(&self) -> bool {
self.changes.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Sot {
pub isot: u16,
pub psot: u32,
pub tpsot: u8,
pub tnsot: u8,
}
impl Sot {
pub fn write<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
w.write_all(&marker::SOT.to_be_bytes())?;
w.write_all(&10u16.to_be_bytes())?; w.write_all(&self.isot.to_be_bytes())?;
w.write_all(&self.psot.to_be_bytes())?;
w.write_all(&[self.tpsot, self.tnsot])?;
Ok(())
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 8 {
return Err(Jp2Error::InvalidCodestream { offset: 0, message: "SOT too short".into() });
}
Ok(Self {
isot: u16::from_be_bytes(data[0..2].try_into().unwrap()),
psot: u32::from_be_bytes(data[2..6].try_into().unwrap()),
tpsot: data[6],
tnsot: data[7],
})
}
}
pub fn write_comment<W: Write>(w: &mut W, text: &str) -> std::io::Result<()> {
let bytes = text.as_bytes();
let len = (4 + bytes.len()) as u16;
w.write_all(&marker::CME.to_be_bytes())?;
w.write_all(&len.to_be_bytes())?;
w.write_all(&1u16.to_be_bytes())?; w.write_all(bytes)?;
Ok(())
}
#[derive(Debug)]
pub struct MarkerSegment {
pub marker: u16,
pub data: Vec<u8>,
pub offset: usize,
}
pub fn parse_codestream_markers(cs: &[u8]) -> Result<Vec<MarkerSegment>> {
let mut segments = Vec::new();
let mut i = 0;
if cs.len() < 2 || cs[0] != 0xFF || cs[1] != 0x4F {
return Err(Jp2Error::InvalidCodestream {
offset: 0,
message: "Missing SOC marker".into(),
});
}
segments.push(MarkerSegment { marker: marker::SOC, data: Vec::new(), offset: 0 });
i = 2;
while i < cs.len() {
if cs[i] != 0xFF { i += 1; continue; }
if i + 1 >= cs.len() { break; }
let m = u16::from_be_bytes([cs[i], cs[i+1]]);
i += 2;
match m {
marker::SOC | marker::SOD | marker::EOC => {
segments.push(MarkerSegment { marker: m, data: Vec::new(), offset: i - 2 });
if m == marker::SOD { break; } continue;
}
_ => {}
}
if i + 2 > cs.len() { break; }
let lseg = u16::from_be_bytes([cs[i], cs[i+1]]) as usize;
if lseg < 2 || i + lseg > cs.len() {
return Err(Jp2Error::InvalidCodestream {
offset: i,
message: format!("Marker 0x{:04X} has invalid length {}", m, lseg),
});
}
let data = cs[i+2..i+lseg].to_vec();
segments.push(MarkerSegment { marker: m, data, offset: i - 2 });
i += lseg;
}
Ok(segments)
}