use super::error::{Jp2Error, Result};
use std::io::Write;
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)
}