use super::markers::{COD, EOC, QCD, SIZ, SOC, SOD, SOT};
use super::{Jp2Error, Jp2Result};
#[derive(Debug, Clone, Copy)]
pub struct ComponentSpec {
pub ssiz: u8,
pub xr_siz: u8,
pub yr_siz: u8,
}
impl ComponentSpec {
#[must_use]
pub fn unsigned(bit_depth: u8) -> Self {
Self {
ssiz: bit_depth.saturating_sub(1) & 0x7F,
xr_siz: 1,
yr_siz: 1,
}
}
}
pub fn write_soc(out: &mut Vec<u8>) {
out.extend_from_slice(&SOC.to_be_bytes());
}
pub fn write_siz(
out: &mut Vec<u8>,
width: u32,
height: u32,
tile_width: u32,
tile_height: u32,
components: &[ComponentSpec],
) -> Jp2Result<()> {
if components.is_empty() {
return Err(Jp2Error::InternalError(
"SIZ requires at least one component".to_string(),
));
}
let csiz = u16::try_from(components.len())
.map_err(|_| Jp2Error::InternalError("too many components for SIZ".to_string()))?;
let lsiz_usize = 2 + 36 + 3 * components.len();
let lsiz = u16::try_from(lsiz_usize)
.map_err(|_| Jp2Error::InternalError("SIZ marker too long".to_string()))?;
out.extend_from_slice(&SIZ.to_be_bytes());
out.extend_from_slice(&lsiz.to_be_bytes());
out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&width.to_be_bytes()); out.extend_from_slice(&height.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&tile_width.to_be_bytes()); out.extend_from_slice(&tile_height.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&csiz.to_be_bytes()); for c in components {
out.push(c.ssiz);
out.push(c.xr_siz);
out.push(c.yr_siz);
}
Ok(())
}
pub fn write_cod(out: &mut Vec<u8>, num_decomp_levels: u8, xcb: u8, ycb: u8) {
write_cod_with_filter(out, num_decomp_levels, xcb, ycb, 1);
}
pub fn write_cod_lossy(out: &mut Vec<u8>, num_decomp_levels: u8, xcb: u8, ycb: u8) {
write_cod_with_filter(out, num_decomp_levels, xcb, ycb, 0);
}
fn write_cod_with_filter(
out: &mut Vec<u8>,
num_decomp_levels: u8,
xcb: u8,
ycb: u8,
wavelet_filter: u8,
) {
let lcod: u16 = 12; out.extend_from_slice(&COD.to_be_bytes());
out.extend_from_slice(&lcod.to_be_bytes());
out.push(0); out.push(0); out.extend_from_slice(&1u16.to_be_bytes()); out.push(0); out.push(num_decomp_levels); out.push(xcb); out.push(ycb); out.push(0); out.push(wavelet_filter); }
pub fn write_qcd(out: &mut Vec<u8>, num_decomp_levels: u8) -> Jp2Result<()> {
let num_subbands = 1usize + 3 * usize::from(num_decomp_levels);
let lqcd_usize = 2 + 1 + num_subbands;
let lqcd = u16::try_from(lqcd_usize)
.map_err(|_| Jp2Error::InternalError("QCD marker too long".to_string()))?;
out.extend_from_slice(&QCD.to_be_bytes());
out.extend_from_slice(&lqcd.to_be_bytes());
out.push(0); for _ in 0..num_subbands {
out.push(0x00); }
Ok(())
}
pub fn write_qcd_lossy(
out: &mut Vec<u8>,
num_decomp_levels: u8,
epsilon_mu: &[(u8, u16)],
) -> Jp2Result<()> {
let num_subbands = 1usize + 3 * usize::from(num_decomp_levels);
if epsilon_mu.len() != num_subbands {
return Err(Jp2Error::InternalError(format!(
"write_qcd_lossy expects {num_subbands} (ε, μ) pairs, got {}",
epsilon_mu.len()
)));
}
let lqcd_usize = 2 + 1 + 2 * num_subbands;
let lqcd = u16::try_from(lqcd_usize)
.map_err(|_| Jp2Error::InternalError("QCD marker too long".to_string()))?;
out.extend_from_slice(&QCD.to_be_bytes());
out.extend_from_slice(&lqcd.to_be_bytes());
out.push(0x02);
for &(eps, mu) in epsilon_mu {
if eps > 0x1F {
return Err(Jp2Error::InternalError(format!(
"QCD ε value {eps} out of range (0..=31)"
)));
}
if mu > 0x07FF {
return Err(Jp2Error::InternalError(format!(
"QCD μ value {mu} out of range (0..=2047)"
)));
}
let raw: u16 = (u16::from(eps) << 11) | (mu & 0x07FF);
out.extend_from_slice(&raw.to_be_bytes());
}
Ok(())
}
pub fn write_sot(out: &mut Vec<u8>, isot: u16, psot: u32, tpsot: u8, tnsot: u8) {
let lsot: u16 = 10; out.extend_from_slice(&SOT.to_be_bytes());
out.extend_from_slice(&lsot.to_be_bytes());
out.extend_from_slice(&isot.to_be_bytes());
out.extend_from_slice(&psot.to_be_bytes());
out.push(tpsot);
out.push(tnsot);
}
pub fn write_sod(out: &mut Vec<u8>) {
out.extend_from_slice(&SOD.to_be_bytes());
}
pub fn write_eoc(out: &mut Vec<u8>) {
out.extend_from_slice(&EOC.to_be_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jpeg2000::markers::{parse_codestream, MarkerSegment};
#[test]
fn write_and_parse_roundtrip() {
let mut v = Vec::new();
write_soc(&mut v);
write_siz(&mut v, 32, 24, 32, 24, &[ComponentSpec::unsigned(8)]).expect("siz");
write_cod(&mut v, 2, 2, 2);
write_qcd(&mut v, 2).expect("qcd");
write_sot(&mut v, 0, 0, 0, 1);
write_sod(&mut v);
v.push(0x00); write_eoc(&mut v);
let segments = parse_codestream(&v).expect("parse");
let siz = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Siz(z) => Some(z),
_ => None,
})
.expect("SIZ");
assert_eq!(siz.image_width(), 32);
assert_eq!(siz.image_height(), 24);
assert_eq!(siz.csiz, 1);
assert_eq!(siz.components[0].bit_depth(), 8);
assert!(!siz.components[0].is_signed());
let cod = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Cod(c) => Some(c),
_ => None,
})
.expect("COD");
assert_eq!(cod.num_decomp_levels, 2);
assert!(cod.is_lossless_wavelet());
assert_eq!(cod.num_layers, 1);
assert_eq!(cod.xcb, 2);
assert_eq!(cod.ycb, 2);
let qcd = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Qcd(q) => Some(q),
_ => None,
})
.expect("QCD");
assert_eq!(qcd.quant_style(), 0);
assert!(segments.iter().any(|s| matches!(s, MarkerSegment::Eoc)));
}
#[test]
fn lossy_cod_and_qcd_roundtrip() {
let mut v = Vec::new();
write_soc(&mut v);
write_siz(&mut v, 32, 32, 32, 32, &[ComponentSpec::unsigned(8)]).expect("siz");
write_cod_lossy(&mut v, 2, 3, 3);
let pairs: Vec<(u8, u16)> = (0..7).map(|_| (8u8, 0u16)).collect();
write_qcd_lossy(&mut v, 2, &pairs).expect("qcd lossy");
write_sot(&mut v, 0, 0, 0, 1);
write_sod(&mut v);
v.push(0x00);
write_eoc(&mut v);
let segments = parse_codestream(&v).expect("parse");
let cod = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Cod(c) => Some(c),
_ => None,
})
.expect("COD");
assert!(cod.is_irreversible_97(), "wavelet filter must be 9-7");
assert!(!cod.is_lossless_wavelet());
assert_eq!(cod.num_decomp_levels, 2);
let qcd = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Qcd(q) => Some(q),
_ => None,
})
.expect("QCD");
assert_eq!(qcd.quant_style(), 2, "Sqcd style must be expounded");
assert_eq!(qcd.guard_bits(), 0);
assert_eq!(qcd.step_sizes.len(), 7);
for &raw in &qcd.step_sizes {
let eps = (raw >> 11) & 0x1F;
let mu = raw & 0x07FF;
assert_eq!(eps, 8);
assert_eq!(mu, 0);
}
}
#[test]
fn write_qcd_lossy_rejects_wrong_length() {
let mut v = Vec::new();
let err = write_qcd_lossy(&mut v, 2, &[(8u8, 0u16); 6]);
assert!(err.is_err());
}
#[test]
fn write_qcd_lossy_rejects_out_of_range() {
let mut v = Vec::new();
let err = write_qcd_lossy(
&mut v,
1,
&[(32u8, 0u16), (8u8, 0u16), (8u8, 0u16), (8u8, 0u16)],
);
assert!(err.is_err());
let err = write_qcd_lossy(
&mut v,
1,
&[(8u8, 2048u16), (8u8, 0u16), (8u8, 0u16), (8u8, 0u16)],
);
assert!(err.is_err());
}
#[test]
fn siz_multi_component() {
let mut v = Vec::new();
write_soc(&mut v);
let comps = [ComponentSpec::unsigned(8); 3];
write_siz(&mut v, 16, 16, 16, 16, &comps).expect("siz");
write_cod(&mut v, 1, 2, 2);
write_qcd(&mut v, 1).expect("qcd");
write_sot(&mut v, 0, 0, 0, 1);
write_sod(&mut v);
v.push(0x00);
write_eoc(&mut v);
let segments = parse_codestream(&v).expect("parse");
let siz = segments
.iter()
.find_map(|s| match s {
MarkerSegment::Siz(z) => Some(z),
_ => None,
})
.expect("SIZ");
assert_eq!(siz.csiz, 3);
assert_eq!(siz.components.len(), 3);
}
}