use crate::error::{JpxError, Result};
use crate::DecodeLimits;
use crate::JpxWarning;
const SOC: u16 = 65359;
const SIZ: u16 = 65361;
const COD: u16 = 65362;
const COC: u16 = 65363;
const TLM: u16 = 65365;
const PLM: u16 = 65367;
const PLT: u16 = 65368;
const QCD: u16 = 65372;
const QCC: u16 = 65373;
const RGN: u16 = 65374;
const POC: u16 = 65375;
const PPM: u16 = 65376;
const PPT: u16 = 65377;
const CRG: u16 = 65379;
const COM: u16 = 65380;
const SOT: u16 = 65424;
const SOP: u16 = 65425;
const EPH: u16 = 65426;
const SOD: u16 = 65427;
const EOC: u16 = 65497;
const RESERVED_NO_SEGMENT_FIRST: u16 = 65328;
const RESERVED_NO_SEGMENT_LAST: u16 = 65343;
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Siz {
pub rsiz: u16,
pub xsiz: u32,
pub ysiz: u32,
pub xosiz: u32,
pub yosiz: u32,
pub xtsiz: u32,
pub ytsiz: u32,
pub xtosiz: u32,
pub ytosiz: u32,
pub components: Vec<SizComponent>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct SizComponent {
pub depth: u8,
pub signed: bool,
pub xrsiz: u8,
pub yrsiz: u8,
}
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum ProgressionOrder {
Lrcp,
Rlcp,
Rpcl,
Pcrl,
Cprl,
}
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum WaveletKind {
Irreversible97,
Reversible53,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct PrecinctExponents {
pub ppx: u8,
pub ppy: u8,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct CodingStyle {
pub decomposition_levels: u8,
pub code_block_width_exp: u8,
pub code_block_height_exp: u8,
pub code_block_style: u8,
pub wavelet: WaveletKind,
pub precincts: Vec<PrecinctExponents>,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Cod {
pub sop_markers: bool,
pub eph_markers: bool,
pub progression: ProgressionOrder,
pub layers: u16,
pub mct: u8,
pub style: CodingStyle,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Coc {
pub component: u16,
pub style: CodingStyle,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct QuantStep {
pub exponent: u8,
pub mantissa: u16,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) enum QuantizationStyle {
None {
exponents: Vec<u8>,
},
ScalarDerived {
exponent: u8,
mantissa: u16,
},
ScalarExpounded {
steps: Vec<QuantStep>,
},
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Quantization {
pub guard_bits: u8,
pub style: QuantizationStyle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct BandQuant {
pub exponent: u32,
pub mantissa: u16,
}
impl Quantization {
pub(crate) fn band_quant(&self, levels: u8, level: u8, flat: usize) -> BandQuant {
let derive =
|first: u8| (u32::from(first) + u32::from(level)).saturating_sub(u32::from(levels));
match &self.style {
QuantizationStyle::None { exponents } => match exponents.get(flat) {
Some(&exponent) => BandQuant {
exponent: u32::from(exponent),
mantissa: 0,
},
None => BandQuant {
exponent: derive(exponents.first().copied().unwrap_or(0)),
mantissa: 0,
},
},
QuantizationStyle::ScalarDerived { exponent, mantissa } => BandQuant {
exponent: derive(*exponent),
mantissa: *mantissa,
},
QuantizationStyle::ScalarExpounded { steps } => match steps.get(flat) {
Some(step) => BandQuant {
exponent: u32::from(step.exponent),
mantissa: step.mantissa,
},
None => {
let first = steps.first().copied().unwrap_or(QuantStep {
exponent: 0,
mantissa: 0,
});
BandQuant {
exponent: derive(first.exponent),
mantissa: first.mantissa,
}
}
},
}
}
pub(crate) fn short_for(&self, levels: u8) -> bool {
let needed = 3 * usize::from(levels) + 1;
match &self.style {
QuantizationStyle::None { exponents } => exponents.len() < needed,
QuantizationStyle::ScalarDerived { .. } => false,
QuantizationStyle::ScalarExpounded { steps } => steps.len() < needed,
}
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Qcc {
pub component: u16,
pub quant: Quantization,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct Rgn {
pub component: u16,
pub shift: u8,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct PocSegment {
pub res_start: u8,
pub comp_start: u16,
pub layer_end: u16,
pub res_end: u8,
pub comp_end: u16,
pub order: ProgressionOrder,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Ppm {
pub index: u8,
pub data: Vec<u8>,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct Ppt {
pub index: u8,
pub data: Vec<u8>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Sot {
pub tile_index: u16,
#[allow(dead_code)]
pub tile_part_length: u32,
pub tile_part_index: u8,
pub tile_part_count: u8,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct MainHeader {
pub siz: Siz,
pub cod: Cod,
pub coc: Vec<Coc>,
pub qcd: Quantization,
pub qcc: Vec<Qcc>,
pub rgn: Vec<Rgn>,
pub poc: Vec<PocSegment>,
pub ppm: Vec<Ppm>,
}
#[allow(dead_code)]
#[derive(Debug, Default)]
pub(crate) struct TileOverrides {
pub cod: Option<Cod>,
pub coc: Vec<Coc>,
pub qcd: Option<Quantization>,
pub qcc: Vec<Qcc>,
pub rgn: Vec<Rgn>,
pub poc: Vec<PocSegment>,
pub ppt: Vec<Ppt>,
}
#[derive(Debug)]
pub(crate) struct TilePart<'a> {
pub sot: Sot,
pub overrides: TileOverrides,
pub body: &'a [u8],
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct Codestream<'a> {
pub main: MainHeader,
pub tile_parts: Vec<TilePart<'a>>,
pub warnings: Vec<JpxWarning>,
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Reader { data, pos: 0 }
}
fn remaining(&self) -> usize {
self.data.len() - self.pos
}
fn at_end(&self) -> bool {
self.pos == self.data.len()
}
fn take(&mut self, n: usize, what: &str) -> Result<&'a [u8]> {
if self.remaining() < n {
return Err(truncated(what));
}
let slice = &self.data[self.pos..self.pos + n];
self.pos += n;
Ok(slice)
}
fn u8(&mut self, what: &str) -> Result<u8> {
Ok(self.take(1, what)?[0])
}
fn u16(&mut self, what: &str) -> Result<u16> {
let b = self.take(2, what)?;
Ok(u16::from(b[0]) << 8 | u16::from(b[1]))
}
fn u32(&mut self, what: &str) -> Result<u32> {
let b = self.take(4, what)?;
Ok(u32::from(b[0]) << 24 | u32::from(b[1]) << 16 | u32::from(b[2]) << 8 | u32::from(b[3]))
}
}
fn truncated(what: &str) -> JpxError {
JpxError::Malformed(format!("{what}: truncated"))
}
fn malformed(detail: String) -> JpxError {
JpxError::Malformed(detail)
}
fn marker_name(marker: u16) -> &'static str {
match marker {
SOC => "SOC",
SIZ => "SIZ",
COD => "COD",
COC => "COC",
TLM => "TLM",
PLM => "PLM",
PLT => "PLT",
QCD => "QCD",
QCC => "QCC",
RGN => "RGN",
POC => "POC",
PPM => "PPM",
PPT => "PPT",
CRG => "CRG",
COM => "COM",
SOT => "SOT",
SOP => "SOP",
EPH => "EPH",
SOD => "SOD",
EOC => "EOC",
_ => "unknown marker",
}
}
fn segment_payload<'a>(r: &mut Reader<'a>, what: &str) -> Result<&'a [u8]> {
let lmar = usize::from(r.u16(what)?);
if lmar < 2 {
return Err(malformed(format!(
"{what}: segment length {lmar} below the 2-byte minimum (A.1.2)"
)));
}
r.take(lmar - 2, what)
}
fn progression_from(value: u8, what: &str) -> Result<ProgressionOrder> {
match value {
0 => Ok(ProgressionOrder::Lrcp),
1 => Ok(ProgressionOrder::Rlcp),
2 => Ok(ProgressionOrder::Rpcl),
3 => Ok(ProgressionOrder::Pcrl),
4 => Ok(ProgressionOrder::Cprl),
other => Err(malformed(format!(
"{what}: reserved progression order {other} (Table A.16)"
))),
}
}
fn component_index(r: &mut Reader<'_>, csiz: u16, what: &str) -> Result<u16> {
if csiz < 257 {
Ok(u16::from(r.u8(what)?))
} else {
r.u16(what)
}
}
fn parse_siz(payload: &[u8], limits: &DecodeLimits, warnings: &mut Vec<JpxWarning>) -> Result<Siz> {
let mut r = Reader::new(payload);
let rsiz = r.u16("SIZ")?;
if rsiz > 2 {
warnings.push(JpxWarning::note(format!(
"SIZ: reserved Rsiz capability {rsiz} (Table A.10)"
)));
}
let xsiz = r.u32("SIZ")?;
let ysiz = r.u32("SIZ")?;
let xosiz = r.u32("SIZ")?;
let yosiz = r.u32("SIZ")?;
let xtsiz = r.u32("SIZ")?;
let ytsiz = r.u32("SIZ")?;
let xtosiz = r.u32("SIZ")?;
let ytosiz = r.u32("SIZ")?;
if xsiz == 0 || ysiz == 0 || xtsiz == 0 || ytsiz == 0 {
return Err(malformed(
"SIZ: zero grid or tile dimension (Table A.9)".into(),
));
}
if xosiz >= xsiz || yosiz >= ysiz {
return Err(malformed(
"SIZ: image offset leaves an empty image area (A.5.1)".into(),
));
}
if xtosiz > xosiz || ytosiz > yosiz {
return Err(malformed(
"SIZ: tile offset beyond the image offset (Equation (B-3))".into(),
));
}
if u64::from(xtosiz) + u64::from(xtsiz) <= u64::from(xosiz)
|| u64::from(ytosiz) + u64::from(ytsiz) <= u64::from(yosiz)
{
return Err(malformed(
"SIZ: the first tile misses the image area (Equation (B-4))".into(),
));
}
let csiz = r.u16("SIZ")?;
if csiz == 0 || csiz > 16384 {
return Err(malformed(format!(
"SIZ: Csiz {csiz} outside 1..=16384 (Table A.9)"
)));
}
if u64::from(csiz) > u64::from(limits.max_components) {
return Err(JpxError::LimitExceeded {
what: "max_components",
actual: u64::from(csiz),
limit: u64::from(limits.max_components),
});
}
if r.remaining() != 3 * usize::from(csiz) {
return Err(malformed(format!(
"SIZ: Lsiz disagrees with Csiz {csiz} (Equation (A-1))"
)));
}
let mut components = Vec::with_capacity(usize::from(csiz));
for _ in 0..csiz {
let ssiz = r.u8("SIZ")?;
let xrsiz = r.u8("SIZ")?;
let yrsiz = r.u8("SIZ")?;
let depth_field = ssiz & 127;
if depth_field > 37 {
return Err(malformed(format!(
"SIZ: Ssiz precision {depth_field} beyond 38 bits (Table A.11)"
)));
}
if xrsiz == 0 || yrsiz == 0 {
return Err(malformed("SIZ: zero XRsiz or YRsiz (Table A.9)".into()));
}
components.push(SizComponent {
depth: depth_field + 1,
signed: ssiz & 128 != 0,
xrsiz,
yrsiz,
});
}
Ok(Siz {
rsiz,
xsiz,
ysiz,
xosiz,
yosiz,
xtsiz,
ytsiz,
xtosiz,
ytosiz,
components,
})
}
fn parse_coding_style(
r: &mut Reader<'_>,
precincts_signalled: bool,
what: &str,
) -> Result<CodingStyle> {
let levels = r.u8(what)?;
if levels > 32 {
return Err(malformed(format!(
"{what}: {levels} decomposition levels above 32 (Table A.15)"
)));
}
let xcb0 = r.u8(what)?;
let ycb0 = r.u8(what)?;
if xcb0 > 8 || ycb0 > 8 {
return Err(malformed(format!(
"{what}: code-block exponent offset above 8 (Table A.18)"
)));
}
let width_exp = xcb0 + 2;
let height_exp = ycb0 + 2;
if width_exp + height_exp > 12 {
return Err(malformed(format!(
"{what}: code-block area above 4096 samples (Table A.18)"
)));
}
let code_block_style = r.u8(what)?;
let wavelet = match r.u8(what)? {
0 => WaveletKind::Irreversible97,
1 => WaveletKind::Reversible53,
other => {
return Err(malformed(format!(
"{what}: reserved transformation {other} (Table A.20)"
)));
}
};
let mut precincts = Vec::new();
if precincts_signalled {
for level in 0..=levels {
let byte = r.u8(what)?;
let ppx = byte & 15;
let ppy = byte >> 4;
if level > 0 && (ppx == 0 || ppy == 0) {
return Err(malformed(format!(
"{what}: zero precinct exponent above resolution 0 (Table A.21)"
)));
}
precincts.push(PrecinctExponents { ppx, ppy });
}
}
Ok(CodingStyle {
decomposition_levels: levels,
code_block_width_exp: width_exp,
code_block_height_exp: height_exp,
code_block_style,
wavelet,
precincts,
})
}
fn parse_cod(payload: &[u8]) -> Result<Cod> {
let mut r = Reader::new(payload);
let scod = r.u8("COD")?;
let progression = progression_from(r.u8("COD")?, "COD")?;
let layers = r.u16("COD")?;
if layers == 0 {
return Err(malformed("COD: zero layers (Table A.14)".into()));
}
let mct = r.u8("COD")?;
if mct > 1 {
return Err(malformed(format!(
"COD: reserved component transformation {mct} (Table A.17)"
)));
}
let style = parse_coding_style(&mut r, scod & 1 != 0, "COD")?;
if !r.at_end() {
return Err(malformed(format!(
"COD: {} bytes after SPcod (Equation (A-2))",
r.remaining()
)));
}
Ok(Cod {
sop_markers: scod & 2 != 0,
eph_markers: scod & 4 != 0,
progression,
layers,
mct,
style,
})
}
fn parse_coc(payload: &[u8], csiz: u16) -> Result<Coc> {
let mut r = Reader::new(payload);
let component = component_index(&mut r, csiz, "COC")?;
let scoc = r.u8("COC")?;
let style = parse_coding_style(&mut r, scoc & 1 != 0, "COC")?;
if !r.at_end() {
return Err(malformed(format!(
"COC: {} bytes after SPcoc (Equation (A-3))",
r.remaining()
)));
}
Ok(Coc { component, style })
}
fn parse_quantization(r: &mut Reader<'_>, what: &str) -> Result<Quantization> {
let sq = r.u8(what)?;
let guard_bits = sq >> 5;
let style = match sq & 31 {
0 => {
if r.at_end() {
return Err(malformed(format!(
"{what}: no reversible step sizes (Equation (A-4))"
)));
}
let mut exponents = Vec::with_capacity(r.remaining());
while !r.at_end() {
exponents.push(r.u8(what)? >> 3);
}
QuantizationStyle::None { exponents }
}
1 => {
let word = r.u16(what)?;
QuantizationStyle::ScalarDerived {
exponent: (word >> 11) as u8,
mantissa: word & 2047,
}
}
2 => {
if r.remaining() == 0 || !r.remaining().is_multiple_of(2) {
return Err(malformed(format!(
"{what}: ragged expounded step-size list (Equation (A-4))"
)));
}
let mut steps = Vec::with_capacity(r.remaining() / 2);
while !r.at_end() {
let word = r.u16(what)?;
steps.push(QuantStep {
exponent: (word >> 11) as u8,
mantissa: word & 2047,
});
}
QuantizationStyle::ScalarExpounded { steps }
}
other => {
return Err(malformed(format!(
"{what}: reserved quantization style {other} (Table A.28)"
)));
}
};
if !r.at_end() {
return Err(malformed(format!(
"{what}: trailing bytes after the step size (Equation (A-4))"
)));
}
Ok(Quantization { guard_bits, style })
}
fn parse_qcd(payload: &[u8]) -> Result<Quantization> {
parse_quantization(&mut Reader::new(payload), "QCD")
}
fn parse_qcc(payload: &[u8], csiz: u16) -> Result<Qcc> {
let mut r = Reader::new(payload);
let component = component_index(&mut r, csiz, "QCC")?;
let quant = parse_quantization(&mut r, "QCC")?;
Ok(Qcc { component, quant })
}
fn parse_rgn(payload: &[u8], csiz: u16, warnings: &mut Vec<JpxWarning>) -> Result<Option<Rgn>> {
let mut r = Reader::new(payload);
let component = component_index(&mut r, csiz, "RGN")?;
let srgn = r.u8("RGN")?;
let shift = r.u8("RGN")?;
if !r.at_end() {
return Err(malformed(format!(
"RGN: {} bytes after SPrgn (Table A.24)",
r.remaining()
)));
}
if srgn != 0 {
warnings.push(JpxWarning::loss(format!(
"RGN: reserved ROI style {srgn} skipped (Table A.25)"
)));
return Ok(None);
}
Ok(Some(Rgn { component, shift }))
}
fn parse_poc(payload: &[u8], csiz: u16, component_count: u16) -> Result<Vec<PocSegment>> {
let entry_len = if csiz < 257 { 7 } else { 9 };
if payload.is_empty() || !payload.len().is_multiple_of(entry_len) {
return Err(malformed(
"POC: Lpoc holds no whole progression change (Equation (A-6))".into(),
));
}
let mut r = Reader::new(payload);
let mut segments = Vec::with_capacity(payload.len() / entry_len);
while !r.at_end() {
let res_start = r.u8("POC")?;
if res_start > 32 {
return Err(malformed(format!(
"POC: RSpoc {res_start} above 32 (Table A.32)"
)));
}
let comp_start = component_index(&mut r, csiz, "POC")?;
let layer_end = r.u16("POC")?;
if layer_end == 0 {
return Err(malformed("POC: zero LYEpoc (Table A.32)".into()));
}
let res_end = r.u8("POC")?;
if res_end <= res_start || res_end > 33 {
return Err(malformed(format!(
"POC: REpoc {res_end} outside RSpoc+1..=33 (Table A.32)"
)));
}
let signalled_comp_end = component_index(&mut r, csiz, "POC")?;
let comp_end = if signalled_comp_end == 0 {
component_count
} else {
signalled_comp_end
};
if comp_end <= comp_start {
return Err(malformed(format!(
"POC: CEpoc {comp_end} not above CSpoc {comp_start} (Table A.32)"
)));
}
let order = progression_from(r.u8("POC")?, "POC")?;
segments.push(PocSegment {
res_start,
comp_start,
layer_end,
res_end,
comp_end,
order,
});
}
Ok(segments)
}
fn parse_ppm(payload: &[u8]) -> Result<Ppm> {
match payload.split_first() {
Some((&index, data)) => Ok(Ppm {
index,
data: data.to_vec(),
}),
None => Err(malformed("PPM: missing Zppm (Table A.38)".into())),
}
}
fn parse_ppt(payload: &[u8]) -> Result<Ppt> {
match payload.split_first() {
Some((&index, data)) => Ok(Ppt {
index,
data: data.to_vec(),
}),
None => Err(malformed("PPT: missing Zppt (Table A.39)".into())),
}
}
fn parse_sot(payload: &[u8]) -> Result<Sot> {
if payload.len() != 8 {
return Err(malformed(format!(
"SOT: Lsot {} instead of the fixed 10 (Table A.5)",
payload.len() + 2
)));
}
let mut r = Reader::new(payload);
let tile_index = r.u16("SOT")?;
if tile_index == 65535 {
return Err(malformed("SOT: Isot 65535 above 65534 (Table A.5)".into()));
}
let tile_part_length = r.u32("SOT")?;
if tile_part_length != 0 && tile_part_length < 14 {
return Err(malformed(format!(
"SOT: Psot {tile_part_length} inside the forbidden 1..=13 (Table A.5)"
)));
}
let tile_part_index = r.u8("SOT")?;
if tile_part_index == 255 {
return Err(malformed("SOT: TPsot 255 above 254 (Table A.5)".into()));
}
let tile_part_count = r.u8("SOT")?;
Ok(Sot {
tile_index,
tile_part_length,
tile_part_index,
tile_part_count,
})
}
fn record_override<T>(
list: &mut Vec<T>,
parsed: T,
key: impl Fn(&T) -> u16,
csiz: u16,
kind: &str,
context: &str,
warnings: &mut Vec<JpxWarning>,
) {
let component = key(&parsed);
if component >= csiz {
warnings.push(JpxWarning::note(format!(
"{context}: {kind} for component {component} beyond Csiz = {csiz} dropped"
)));
return;
}
if let Some(existing) = list.iter_mut().find(|entry| key(entry) == component) {
warnings.push(JpxWarning::note(format!(
"{context}: duplicate {kind} for component {component}; the last wins"
)));
*existing = parsed;
} else {
list.push(parsed);
}
}
fn scan_tile_part_header(
r: &mut Reader<'_>,
csiz: u16,
sot: Sot,
warnings: &mut Vec<JpxWarning>,
) -> Result<TileOverrides> {
let mut overrides = TileOverrides::default();
let context = format!("tile {} part {}", sot.tile_index, sot.tile_part_index);
let late_check = |kind: &str, warnings: &mut Vec<JpxWarning>| {
if sot.tile_part_index > 0 {
warnings.push(JpxWarning::note(format!(
"{context}: {kind} in a non-first tile-part header honoured leniently (A.6)"
)));
}
};
loop {
let marker = r.u16("tile-part header")?;
match marker {
SOD => return Ok(overrides),
COD => {
late_check("COD", warnings);
let parsed = parse_cod(segment_payload(r, "COD")?)?;
if overrides.cod.replace(parsed).is_some() {
warnings.push(JpxWarning::note(format!(
"{context}: duplicate COD; the last wins (A.6.1)"
)));
}
}
COC => {
late_check("COC", warnings);
let parsed = parse_coc(segment_payload(r, "COC")?, csiz)?;
record_override(
&mut overrides.coc,
parsed,
|c| c.component,
csiz,
"COC",
&context,
warnings,
);
}
QCD => {
late_check("QCD", warnings);
let parsed = parse_qcd(segment_payload(r, "QCD")?)?;
if overrides.qcd.replace(parsed).is_some() {
warnings.push(JpxWarning::note(format!(
"{context}: duplicate QCD; the last wins (A.6.4)"
)));
}
}
QCC => {
late_check("QCC", warnings);
let parsed = parse_qcc(segment_payload(r, "QCC")?, csiz)?;
record_override(
&mut overrides.qcc,
parsed,
|q| q.component,
csiz,
"QCC",
&context,
warnings,
);
}
RGN => {
late_check("RGN", warnings);
if let Some(parsed) = parse_rgn(segment_payload(r, "RGN")?, csiz, warnings)? {
record_override(
&mut overrides.rgn,
parsed,
|x| x.component,
csiz,
"RGN",
&context,
warnings,
);
}
}
POC => overrides
.poc
.extend(parse_poc(segment_payload(r, "POC")?, csiz, csiz)?),
PPT => overrides.ppt.push(parse_ppt(segment_payload(r, "PPT")?)?),
PLT => {
segment_payload(r, "PLT")?;
}
COM => {
segment_payload(r, "COM")?;
}
TLM | PLM | CRG | PPM | SOP => {
warnings.push(JpxWarning::note(format!(
"{context}: {} not allowed in a tile-part header skipped (Table A.2)",
marker_name(marker)
)));
segment_payload(r, marker_name(marker))?;
}
EPH => {
warnings.push(JpxWarning::note(format!(
"{context}: stray EPH skipped (A.8.2)"
)));
}
RESERVED_NO_SEGMENT_FIRST..=RESERVED_NO_SEGMENT_LAST => {
}
SOT | SOC | EOC | SIZ => {
return Err(malformed(format!(
"{context}: {} before SOD (A.4.3)",
marker_name(marker)
)));
}
other => {
if other >> 8 != 255 {
return Err(malformed(format!(
"{context}: byte-aligned garbage {other} where a marker was expected (A.1.2)"
)));
}
warnings.push(JpxWarning::note(format!(
"{context}: unknown marker {other} skipped"
)));
segment_payload(r, "unknown segment")?;
}
}
}
}
pub(crate) fn parse_codestream<'a>(
data: &'a [u8],
limits: &DecodeLimits,
) -> Result<Codestream<'a>> {
let mut warnings = Vec::new();
let mut r = Reader::new(data);
if r.u16("codestream")? != SOC {
return Err(malformed("codestream: missing SOC (A.4.1)".into()));
}
if r.u16("main header")? != SIZ {
return Err(malformed(
"main header: SIZ must immediately follow SOC (A.5.1)".into(),
));
}
let siz = parse_siz(segment_payload(&mut r, "SIZ")?, limits, &mut warnings)?;
let csiz = siz.components.len() as u16;
let mut cod = None;
let mut qcd = None;
let mut coc: Vec<Coc> = Vec::new();
let mut qcc: Vec<Qcc> = Vec::new();
let mut rgn: Vec<Rgn> = Vec::new();
let mut poc: Vec<PocSegment> = Vec::new();
let mut ppm: Vec<Ppm> = Vec::new();
let first_sot;
let first_sot_start;
loop {
let marker_start = r.pos;
let marker = r.u16("main header")?;
match marker {
SOT => {
first_sot_start = marker_start;
first_sot = parse_sot(segment_payload(&mut r, "SOT")?)?;
break;
}
SIZ | SOC => {
return Err(malformed(format!(
"main header: duplicate {} (A.5.1)",
marker_name(marker)
)));
}
COD => {
let parsed = parse_cod(segment_payload(&mut r, "COD")?)?;
if cod.replace(parsed).is_some() {
return Err(malformed("main header: duplicate COD (A.6.1)".into()));
}
}
COC => {
let parsed = parse_coc(segment_payload(&mut r, "COC")?, csiz)?;
record_override(
&mut coc,
parsed,
|c| c.component,
csiz,
"COC",
"main header",
&mut warnings,
);
}
QCD => {
let parsed = parse_qcd(segment_payload(&mut r, "QCD")?)?;
if qcd.replace(parsed).is_some() {
return Err(malformed("main header: duplicate QCD (A.6.4)".into()));
}
}
QCC => {
let parsed = parse_qcc(segment_payload(&mut r, "QCC")?, csiz)?;
record_override(
&mut qcc,
parsed,
|q| q.component,
csiz,
"QCC",
"main header",
&mut warnings,
);
}
RGN => {
if let Some(parsed) =
parse_rgn(segment_payload(&mut r, "RGN")?, csiz, &mut warnings)?
{
record_override(
&mut rgn,
parsed,
|x| x.component,
csiz,
"RGN",
"main header",
&mut warnings,
);
}
}
POC => {
if !poc.is_empty() {
warnings.push(JpxWarning::note("main header: more than one POC (A.6.6)"));
}
poc.extend(parse_poc(segment_payload(&mut r, "POC")?, csiz, csiz)?);
}
PPM => ppm.push(parse_ppm(segment_payload(&mut r, "PPM")?)?),
TLM | PLM | CRG | COM => {
segment_payload(&mut r, marker_name(marker))?;
}
PLT | PPT | SOP => {
warnings.push(JpxWarning::note(format!(
"main header: {} not allowed here skipped (Table A.2)",
marker_name(marker)
)));
segment_payload(&mut r, marker_name(marker))?;
}
EPH => {
warnings.push(JpxWarning::note("main header: stray EPH skipped (A.8.2)"));
}
SOD | EOC => {
return Err(malformed(format!(
"main header: {} before any tile-part (A.4)",
marker_name(marker)
)));
}
RESERVED_NO_SEGMENT_FIRST..=RESERVED_NO_SEGMENT_LAST => {
}
other => {
if other >> 8 != 255 {
return Err(malformed(format!(
"main header: byte-aligned garbage {other} where a marker was expected (A.1.2)"
)));
}
warnings.push(JpxWarning::note(format!(
"main header: unknown marker {other} skipped"
)));
segment_payload(&mut r, "unknown segment")?;
}
}
}
let cod = cod.ok_or_else(|| malformed("main header: missing COD (A.6.1)".into()))?;
let qcd = qcd.ok_or_else(|| malformed("main header: missing QCD (A.6.4)".into()))?;
let main = MainHeader {
siz,
cod,
coc,
qcd,
qcc,
rgn,
poc,
ppm,
};
let mut tile_parts: Vec<TilePart<'a>> = Vec::new();
let mut sot = first_sot;
let mut sot_start = first_sot_start;
loop {
let overrides = match scan_tile_part_header(&mut r, csiz, sot, &mut warnings) {
Ok(overrides) => overrides,
Err(e) => {
warnings.push(JpxWarning::loss(format!(
"tile-part header abandoned, tail truncated: {e}"
)));
break;
}
};
let body_start = r.pos;
let psot = sot.tile_part_length;
let body_end = if psot == 0 {
if data.len() >= body_start + 2 && data[data.len() - 2..] == EOC.to_be_bytes() {
data.len() - 2
} else {
warnings.push(JpxWarning::note(
"codestream: missing EOC after the final tile-part (A.4.4)",
));
data.len()
}
} else {
match sot_start.checked_add(psot as usize) {
Some(end) if end >= body_start && end <= data.len() => end,
Some(end) if end > data.len() => {
warnings.push(JpxWarning::loss(format!(
"tile {}: Psot {psot} overruns the codestream; body truncated",
sot.tile_index
)));
data.len()
}
_ => {
warnings.push(JpxWarning::loss(format!(
"tile {}: Psot {psot} ends before its own SOD; tail kept as body",
sot.tile_index
)));
data.len()
}
}
};
tile_parts.push(TilePart {
sot,
overrides,
body: &data[body_start..body_end],
});
if psot == 0 {
break;
}
r.pos = body_end;
if r.at_end() {
warnings.push(JpxWarning::note("codestream: missing EOC (A.4.4)"));
break;
}
let marker_start = r.pos;
let marker = match r.u16("codestream") {
Ok(marker) => marker,
Err(_) => {
warnings.push(JpxWarning::note(
"codestream: lone trailing byte after a tile-part",
));
break;
}
};
match marker {
EOC => {
if !r.at_end() {
warnings.push(JpxWarning::note(format!(
"codestream: {} bytes after EOC ignored (A.4.4)",
r.remaining()
)));
}
break;
}
SOT => match segment_payload(&mut r, "SOT").and_then(parse_sot) {
Ok(next) => {
sot = next;
sot_start = marker_start;
}
Err(e) => {
warnings.push(JpxWarning::loss(format!(
"tile-part abandoned, tail truncated: {e}"
)));
break;
}
},
other => {
warnings.push(JpxWarning::loss(format!(
"codestream: expected SOT or EOC, found {other}; tail truncated"
)));
break;
}
}
}
Ok(Codestream {
main,
tile_parts,
warnings,
})
}
pub(crate) fn split_packed_headers(
segments: &[Ppm],
tile_part_count: usize,
) -> Result<Vec<Vec<u8>>> {
let mut ordered: Vec<&Ppm> = segments.iter().collect();
ordered.sort_by_key(|segment| segment.index);
let total = ordered.iter().map(|segment| segment.data.len()).sum();
let mut series = Vec::with_capacity(total);
for segment in &ordered {
series.extend_from_slice(&segment.data);
}
let mut r = Reader::new(&series);
let mut blobs = Vec::with_capacity(tile_part_count);
for _ in 0..tile_part_count {
let n = r.u32("PPM series")? as usize;
blobs.push(r.take(n, "PPM series")?.to_vec());
}
Ok(blobs)
}
pub(crate) fn merge_tile_overrides(parts: &[&TilePart<'_>]) -> Result<TileOverrides> {
let mut merged = TileOverrides::default();
for part in parts {
let overrides = &part.overrides;
if merged.cod.is_none() {
merged.cod = overrides.cod.clone();
}
if merged.qcd.is_none() {
merged.qcd = overrides.qcd.clone();
}
for coc in &overrides.coc {
if merged.coc.iter().all(|c| c.component != coc.component) {
merged.coc.push(coc.clone());
}
}
for qcc in &overrides.qcc {
if merged.qcc.iter().all(|q| q.component != qcc.component) {
merged.qcc.push(qcc.clone());
}
}
for rgn in &overrides.rgn {
if merged.rgn.iter().all(|x| x.component != rgn.component) {
merged.rgn.push(*rgn);
}
}
merged.poc.extend(overrides.poc.iter().copied());
let mut ppt: Vec<&Ppt> = overrides.ppt.iter().collect();
ppt.sort_by_key(|segment| segment.index);
merged.ppt.extend(ppt.into_iter().cloned());
}
Ok(merged)
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct TileCoding {
pub progression: ProgressionOrder,
pub layers: u16,
pub mct: u8,
pub sop_markers: bool,
pub eph_markers: bool,
pub poc: Vec<PocSegment>,
}
pub(crate) fn resolve_tile_coding(main: &MainHeader, tile: &TileOverrides) -> Result<TileCoding> {
let cod = tile.cod.as_ref().unwrap_or(&main.cod);
let poc = if tile.poc.is_empty() {
main.poc.clone()
} else {
tile.poc.clone()
};
Ok(TileCoding {
progression: cod.progression,
layers: cod.layers,
mct: cod.mct,
sop_markers: cod.sop_markers,
eph_markers: cod.eph_markers,
poc,
})
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct ComponentCoding {
pub style: CodingStyle,
pub quant: Quantization,
pub roi_shift: Option<u8>,
}
pub(crate) fn resolve_component_coding(
main: &MainHeader,
tile: &TileOverrides,
component: u16,
) -> Result<ComponentCoding> {
fn coc_style(list: &[Coc], component: u16) -> Option<&CodingStyle> {
list.iter()
.find(|c| c.component == component)
.map(|c| &c.style)
}
fn qcc_quant(list: &[Qcc], component: u16) -> Option<&Quantization> {
list.iter()
.find(|q| q.component == component)
.map(|q| &q.quant)
}
fn rgn_shift(list: &[Rgn], component: u16) -> Option<u8> {
list.iter()
.find(|r| r.component == component)
.map(|r| r.shift)
}
let style = coc_style(&tile.coc, component)
.or_else(|| tile.cod.as_ref().map(|cod| &cod.style))
.or_else(|| coc_style(&main.coc, component))
.unwrap_or(&main.cod.style)
.clone();
let quant = qcc_quant(&tile.qcc, component)
.or(tile.qcd.as_ref())
.or_else(|| qcc_quant(&main.qcc, component))
.unwrap_or(&main.qcd)
.clone();
let roi_shift = rgn_shift(&tile.rgn, component).or_else(|| rgn_shift(&main.rgn, component));
Ok(ComponentCoding {
style,
quant,
roi_shift,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn seg(marker: u16, payload: &[u8]) -> Vec<u8> {
let mut v = marker.to_be_bytes().to_vec();
let lmar = u16::try_from(payload.len() + 2).unwrap();
v.extend(lmar.to_be_bytes());
v.extend_from_slice(payload);
v
}
fn tiny_siz_payload(csiz: u16) -> Vec<u8> {
let mut v = 0u16.to_be_bytes().to_vec();
for value in [16u32, 16, 0, 0, 16, 16, 0, 0] {
v.extend(value.to_be_bytes());
}
v.extend(csiz.to_be_bytes());
for _ in 0..csiz {
v.extend([7, 1, 1]);
}
v
}
fn tiny_cod_payload() -> Vec<u8> {
vec![0, 0, 0, 1, 0, 1, 2, 2, 0, 1]
}
fn derived_qcd_payload() -> Vec<u8> {
vec![33, 40, 100]
}
fn tile_part(isot: u16, psot: u32, tpsot: u8, tnsot: u8, body: &[u8]) -> Vec<u8> {
let mut payload = isot.to_be_bytes().to_vec();
payload.extend(psot.to_be_bytes());
payload.extend([tpsot, tnsot]);
let mut v = seg(SOT, &payload);
v.extend(SOD.to_be_bytes());
v.extend_from_slice(body);
v
}
fn tiny_main_header() -> Vec<u8> {
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &derived_qcd_payload()));
v
}
fn parse(data: &[u8]) -> Result<Codestream<'_>> {
parse_codestream(data, &DecodeLimits::default())
}
fn fixture(name: &str) -> Vec<u8> {
let path = format!("{}/tests/fixtures/{name}", env!("CARGO_MANIFEST_DIR"));
std::fs::read(&path).unwrap_or_else(|e| panic!("{path}: {e}"))
}
fn codestream_of(bytes: &[u8]) -> Vec<u8> {
if bytes.starts_with(&[255, 79]) {
return bytes.to_vec();
}
let mut pos = 0usize;
while pos + 8 <= bytes.len() {
let lbox = u32::from_be_bytes(bytes[pos..pos + 4].try_into().unwrap());
let tbox = &bytes[pos + 4..pos + 8];
let (header, end) = match lbox {
0 => (8, bytes.len()),
1 => {
let xlbox = u64::from_be_bytes(bytes[pos + 8..pos + 16].try_into().unwrap());
(16, pos + usize::try_from(xlbox).unwrap())
}
n => (8, pos + n as usize),
};
if tbox == b"jp2c" {
return bytes[pos + header..end].to_vec();
}
pos = end;
}
panic!("fixture has no contiguous codestream box");
}
enum ZooQuant {
Reversible { depth: u8 },
Irreversible97,
}
fn reversible_exponents(depth: u8, levels: u8) -> Vec<u8> {
let mut v = vec![depth];
for _ in 0..levels {
v.extend([depth + 1, depth + 1, depth + 2]);
}
v
}
fn irreversible_97_steps() -> Vec<(u8, u16)> {
vec![
(14, 1824),
(14, 1776),
(14, 1776),
(14, 1728),
(13, 1792),
(13, 1792),
(13, 1760),
(12, 1872),
(12, 1872),
(12, 1896),
(10, 5),
(10, 5),
(10, 71),
(10, 2003),
(10, 2003),
(10, 1890),
]
}
struct Zoo {
file: &'static str,
width: u32,
height: u32,
tile: (u32, u32),
components: usize,
depth: u8,
progression: ProgressionOrder,
layers: u16,
levels: u8,
cb_exp: (u8, u8),
wavelet: WaveletKind,
precincts: &'static [(u8, u8)],
quant: ZooQuant,
tile_parts: usize,
}
fn zoo_base(
file: &'static str,
width: u32,
height: u32,
components: usize,
wavelet: WaveletKind,
quant: ZooQuant,
) -> Zoo {
Zoo {
file,
width,
height,
tile: (width, height),
components,
depth: 8,
progression: ProgressionOrder::Lrcp,
layers: 1,
levels: 5,
cb_exp: (6, 6),
wavelet,
precincts: &[],
quant,
tile_parts: 1,
}
}
fn zoo() -> Vec<Zoo> {
use super::ProgressionOrder as Po;
use super::WaveletKind::{Irreversible97, Reversible53};
let rev = || ZooQuant::Reversible { depth: 8 };
vec![
zoo_base("gray-53-jp2.jp2", 97, 61, 1, Reversible53, rev()),
zoo_base(
"gray-97-jp2.jp2",
97,
61,
1,
Irreversible97,
ZooQuant::Irreversible97,
),
zoo_base("rgb-53-jp2.jp2", 130, 83, 3, Reversible53, rev()),
zoo_base(
"rgb-97-jp2.jp2",
130,
83,
3,
Irreversible97,
ZooQuant::Irreversible97,
),
zoo_base("gray-53-raw.j2k", 97, 61, 1, Reversible53, rev()),
zoo_base(
"rgb-97-raw.j2k",
130,
83,
3,
Irreversible97,
ZooQuant::Irreversible97,
),
zoo_base("rgba-53-jp2.jp2", 64, 64, 4, Reversible53, rev()),
Zoo {
tile: (128, 128),
tile_parts: 15,
..zoo_base("rgb-tiled.jp2", 523, 311, 3, Reversible53, rev())
},
Zoo {
layers: 3,
..zoo_base(
"rgb-layers.jp2",
523,
311,
3,
Irreversible97,
ZooQuant::Irreversible97,
)
},
Zoo {
levels: 2,
..zoo_base("rgb-res3.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
cb_exp: (4, 4),
..zoo_base("rgb-cb16.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
precincts: &[(2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)],
..zoo_base("rgb-precinct.jp2", 523, 311, 3, Reversible53, rev())
},
zoo_base("rgb-prog-lrcp.jp2", 130, 83, 3, Reversible53, rev()),
Zoo {
progression: Po::Rlcp,
..zoo_base("rgb-prog-rlcp.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
progression: Po::Rpcl,
..zoo_base("rgb-prog-rpcl.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
progression: Po::Pcrl,
..zoo_base("rgb-prog-pcrl.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
progression: Po::Cprl,
..zoo_base("rgb-prog-cprl.jp2", 130, 83, 3, Reversible53, rev())
},
Zoo {
depth: 16,
..zoo_base(
"gray16-53-jp2.jp2",
80,
50,
1,
Reversible53,
ZooQuant::Reversible { depth: 16 },
)
},
]
}
#[test]
fn zoo_main_headers_match_the_manifest() {
for z in zoo() {
let raw = fixture(z.file);
let bytes = codestream_of(&raw);
let cs = parse(&bytes).unwrap_or_else(|e| panic!("{}: {e}", z.file));
assert!(cs.warnings.is_empty(), "{}: {:?}", z.file, cs.warnings);
let siz = &cs.main.siz;
assert_eq!(siz.rsiz, 0, "{}", z.file);
assert_eq!((siz.xsiz, siz.ysiz), (z.width, z.height), "{}", z.file);
assert_eq!((siz.xosiz, siz.yosiz), (0, 0), "{}", z.file);
assert_eq!((siz.xtsiz, siz.ytsiz), z.tile, "{}", z.file);
assert_eq!((siz.xtosiz, siz.ytosiz), (0, 0), "{}", z.file);
assert_eq!(siz.components.len(), z.components, "{}", z.file);
for c in &siz.components {
assert_eq!(c.depth, z.depth, "{}", z.file);
assert!(!c.signed, "{}", z.file);
assert_eq!((c.xrsiz, c.yrsiz), (1, 1), "{}", z.file);
}
let cod = &cs.main.cod;
assert!(!cod.sop_markers && !cod.eph_markers, "{}", z.file);
assert_eq!(cod.progression, z.progression, "{}", z.file);
assert_eq!(cod.layers, z.layers, "{}", z.file);
assert_eq!(cod.mct, 0, "{}", z.file);
assert_eq!(cod.style.decomposition_levels, z.levels, "{}", z.file);
assert_eq!(
(
cod.style.code_block_width_exp,
cod.style.code_block_height_exp
),
z.cb_exp,
"{}",
z.file
);
assert_eq!(cod.style.code_block_style, 0, "{}", z.file);
assert_eq!(cod.style.wavelet, z.wavelet, "{}", z.file);
let precincts: Vec<(u8, u8)> =
cod.style.precincts.iter().map(|p| (p.ppx, p.ppy)).collect();
assert_eq!(precincts, z.precincts, "{}", z.file);
assert_eq!(cs.main.qcd.guard_bits, 2, "{}", z.file);
match (&z.quant, &cs.main.qcd.style) {
(ZooQuant::Reversible { depth }, QuantizationStyle::None { exponents }) => {
assert_eq!(
exponents,
&reversible_exponents(*depth, z.levels),
"{}",
z.file
);
}
(ZooQuant::Irreversible97, QuantizationStyle::ScalarExpounded { steps }) => {
let pairs: Vec<(u8, u16)> =
steps.iter().map(|s| (s.exponent, s.mantissa)).collect();
assert_eq!(pairs, irreversible_97_steps(), "{}", z.file);
}
(_, other) => panic!("{}: unexpected quantization {other:?}", z.file),
}
assert!(cs.main.coc.is_empty(), "{}", z.file);
assert!(cs.main.qcc.is_empty(), "{}", z.file);
assert!(cs.main.rgn.is_empty(), "{}", z.file);
assert!(cs.main.poc.is_empty(), "{}", z.file);
assert!(cs.main.ppm.is_empty(), "{}", z.file);
assert_eq!(cs.tile_parts.len(), z.tile_parts, "{}", z.file);
for (i, part) in cs.tile_parts.iter().enumerate() {
assert_eq!(part.sot.tile_index as usize, i, "{}", z.file);
assert_eq!(part.sot.tile_part_index, 0, "{}", z.file);
assert_eq!(part.sot.tile_part_count, 1, "{}", z.file);
assert_eq!(
part.body.len() as u32,
part.sot.tile_part_length - 14,
"{}",
z.file
);
}
}
}
#[test]
fn psot_zero_final_tile_part_runs_to_eoc() {
let mut s = tiny_main_header();
s.extend(tile_part(0, 17, 0, 2, &[1, 2, 3]));
s.extend(tile_part(0, 0, 1, 2, &[4, 5, 6, 7]));
s.extend(EOC.to_be_bytes());
let cs = parse(&s).unwrap();
assert!(cs.warnings.is_empty(), "{:?}", cs.warnings);
assert_eq!(cs.tile_parts.len(), 2);
assert_eq!(cs.tile_parts[0].body, [1, 2, 3]);
assert_eq!(cs.tile_parts[0].sot.tile_part_index, 0);
assert_eq!(cs.tile_parts[1].body, [4, 5, 6, 7]);
assert_eq!(cs.tile_parts[1].sot.tile_part_index, 1);
assert_eq!(cs.tile_parts[1].sot.tile_part_length, 0);
}
#[test]
fn tnsot_undercount_is_tolerated_for_compatibility() {
let mut s = tiny_main_header();
for tpsot in 0..6u8 {
s.extend(tile_part(0, 15, tpsot, 5, &[tpsot]));
}
s.extend(EOC.to_be_bytes());
let cs = parse(&s).unwrap();
assert_eq!(cs.tile_parts.len(), 6);
for (i, part) in cs.tile_parts.iter().enumerate() {
assert_eq!(part.sot.tile_part_index as usize, i);
assert_eq!(part.sot.tile_part_count, 5);
assert_eq!(part.body, [i as u8]);
}
assert!(cs.warnings.is_empty(), "{:?}", cs.warnings);
}
#[test]
fn truncated_main_headers_error_never_panic() {
let mut s = tiny_main_header();
let sot_at = s.len();
s.extend(tile_part(0, 17, 0, 1, &[1, 2, 3]));
s.extend(EOC.to_be_bytes());
parse(&s).unwrap();
for cut in 0..sot_at {
assert!(parse(&s[..cut]).is_err(), "prefix of {cut} bytes");
}
}
#[test]
fn malformed_main_headers_error_never_panic() {
let tail = {
let mut v = tile_part(0, 15, 0, 1, &[9]);
v.extend(EOC.to_be_bytes());
v
};
let mut cases: Vec<(&str, Vec<u8>)> = Vec::new();
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(COD, &tiny_cod_payload()));
cases.push(("missing SIZ", v));
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(&tail);
cases.push(("duplicate SIZ", v));
let mut v = tiny_main_header();
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(&tail);
cases.push(("duplicate COD", v));
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(&tail);
cases.push(("missing QCD", v));
let mut v = tiny_main_header();
v.extend(COM.to_be_bytes());
v.extend(500u16.to_be_bytes());
v.extend([1, 104, 105]);
cases.push(("segment length overrun", v));
let mut v = tiny_main_header();
v.extend([0, 5]);
cases.push(("not a marker", v));
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(0)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(&tail);
cases.push(("zero components", v));
let mut cod = tiny_cod_payload();
cod[2] = 0;
cod[3] = 0;
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &cod));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(&tail);
cases.push(("zero layers", v));
let mut cod = tiny_cod_payload();
cod[6] = 9;
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &cod));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(&tail);
cases.push(("code-block exponent 9", v));
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(1)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &[31, 40, 100]));
v.extend(&tail);
cases.push(("reserved quantization style", v));
let mut siz = tiny_siz_payload(1);
let ssiz_at = siz.len() - 3;
siz[ssiz_at] = 40;
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &siz));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(&tail);
cases.push(("depth beyond 38 bits", v));
let mut v = tiny_main_header();
v.extend(seg(POC, &[0, 0, 0, 1, 1]));
v.extend(&tail);
cases.push(("ragged POC", v));
let mut v = tiny_main_header();
v.extend(EOC.to_be_bytes());
cases.push(("no tile-part", v));
for (name, bytes) in cases {
assert!(parse(&bytes).is_err(), "{name}");
}
}
#[test]
fn component_count_limit_trips_before_allocation() {
let mut v = SOC.to_be_bytes().to_vec();
v.extend(seg(SIZ, &tiny_siz_payload(17)));
v.extend(seg(COD, &tiny_cod_payload()));
v.extend(seg(QCD, &derived_qcd_payload()));
v.extend(tile_part(0, 15, 0, 1, &[9]));
v.extend(EOC.to_be_bytes());
match parse(&v) {
Err(JpxError::LimitExceeded { what, actual, .. }) => {
assert_eq!(what, "max_components");
assert_eq!(actual, 17);
}
other => panic!("expected the component bound to trip, got {other:?}"),
}
}
#[test]
fn functional_segments_parse_in_the_main_header() {
let mut s = tiny_main_header();
s.extend(seg(COC, &[0, 1, 1, 1, 1, 0, 0, 18, 51]));
s.extend(seg(QCC, &[0, 64, 64, 72, 72, 80]));
s.extend(seg(RGN, &[0, 0, 37]));
s.extend(seg(RGN, &[0, 3, 99]));
s.extend(seg(POC, &[0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 6, 0, 4]));
s.extend(seg(PPM, &[0, 0, 0, 0, 1, 7]));
s.extend(seg(TLM, &[0, 0]));
s.extend(seg(CRG, &[0, 0, 0, 0]));
s.extend(seg(COM, &[0, 1, 104, 105]));
s.extend(tile_part(0, 15, 0, 1, &[9]));
s.extend(EOC.to_be_bytes());
let cs = parse(&s).unwrap();
assert_eq!(cs.main.coc.len(), 1);
let coc = &cs.main.coc[0];
assert_eq!(coc.component, 0);
assert_eq!(coc.style.decomposition_levels, 1);
assert_eq!(coc.style.code_block_width_exp, 3);
assert_eq!(coc.style.code_block_height_exp, 3);
assert_eq!(coc.style.wavelet, WaveletKind::Irreversible97);
let precincts: Vec<(u8, u8)> = coc.style.precincts.iter().map(|p| (p.ppx, p.ppy)).collect();
assert_eq!(precincts, [(2, 1), (3, 3)]);
assert_eq!(cs.main.qcc.len(), 1);
let qcc = &cs.main.qcc[0];
assert_eq!(qcc.component, 0);
assert_eq!(qcc.quant.guard_bits, 2);
match &qcc.quant.style {
QuantizationStyle::None { exponents } => {
assert_eq!(exponents, &[8, 9, 9, 10]);
}
other => panic!("unexpected QCC style {other:?}"),
}
assert_eq!(cs.main.rgn.len(), 1);
assert_eq!(cs.main.rgn[0].component, 0);
assert_eq!(cs.main.rgn[0].shift, 37);
assert_eq!(cs.main.poc.len(), 2);
assert_eq!(cs.main.poc[0].order, ProgressionOrder::Lrcp);
assert_eq!(cs.main.poc[0].layer_end, 1);
assert_eq!(cs.main.poc[0].res_end, 1);
assert_eq!(cs.main.poc[0].comp_end, 1);
assert_eq!(cs.main.poc[1].order, ProgressionOrder::Cprl);
assert_eq!(cs.main.poc[1].res_start, 1);
assert_eq!(cs.main.poc[1].res_end, 6);
assert_eq!(cs.main.poc[1].comp_end, 1);
assert_eq!(cs.main.ppm.len(), 1);
assert_eq!(cs.main.ppm[0].index, 0);
assert_eq!(cs.main.ppm[0].data, [0, 0, 0, 1, 7]);
assert_eq!(cs.warnings.len(), 1, "{:?}", cs.warnings);
assert!(cs.warnings[0].message.contains("RGN"), "{:?}", cs.warnings);
}
#[test]
fn tile_part_header_overrides_are_recorded() {
let mut cod2 = tiny_cod_payload();
cod2[5] = 2;
let mut header = seg(COD, &cod2);
header.extend(seg(QCC, &[0, 64, 64, 72, 72, 80, 72, 72, 80]));
header.extend(seg(PPT, &[0, 5, 6]));
header.extend(seg(PLT, &[0, 3]));
header.extend(seg(COM, &[0, 1, 104, 105]));
let body = [1, 2];
let psot = u32::try_from(12 + header.len() + 2 + body.len()).unwrap();
let mut payload = 0u16.to_be_bytes().to_vec();
payload.extend(psot.to_be_bytes());
payload.extend([0, 1]);
let mut s = tiny_main_header();
s.extend(seg(SOT, &payload));
s.extend(header);
s.extend(SOD.to_be_bytes());
s.extend(body);
s.extend(EOC.to_be_bytes());
let cs = parse(&s).unwrap();
assert!(cs.warnings.is_empty(), "{:?}", cs.warnings);
assert_eq!(cs.tile_parts.len(), 1);
let o = &cs.tile_parts[0].overrides;
assert_eq!(o.cod.as_ref().unwrap().style.decomposition_levels, 2);
assert_eq!(o.qcc.len(), 1);
assert_eq!(o.ppt.len(), 1);
assert_eq!(o.ppt[0].index, 0);
assert_eq!(o.ppt[0].data, [5, 6]);
assert_eq!(cs.tile_parts[0].body, [1, 2]);
}
#[test]
fn late_tile_part_coding_markers_warn_but_parse() {
let header = seg(COD, &tiny_cod_payload());
let psot = u32::try_from(12 + header.len() + 2).unwrap();
let mut payload = 0u16.to_be_bytes().to_vec();
payload.extend(psot.to_be_bytes());
payload.extend([1, 0]);
let mut s = tiny_main_header();
s.extend(tile_part(0, 15, 0, 0, &[9]));
s.extend(seg(SOT, &payload));
s.extend(header);
s.extend(SOD.to_be_bytes());
s.extend(EOC.to_be_bytes());
let cs = parse(&s).unwrap();
assert_eq!(cs.tile_parts.len(), 2);
assert!(cs.tile_parts[1].overrides.cod.is_some());
assert!(
cs.warnings
.iter()
.any(|warning| warning.message.contains("COD")),
"{:?}",
cs.warnings
);
}
#[test]
fn corrupt_tile_territory_degrades_to_warnings() {
let mut s = tiny_main_header();
s.extend(tile_part(0, 1000, 0, 1, &[1, 2, 3]));
let cs = parse(&s).unwrap();
assert_eq!(cs.tile_parts.len(), 1);
assert_eq!(cs.tile_parts[0].body, [1, 2, 3]);
assert!(!cs.warnings.is_empty());
let mut s = tiny_main_header();
s.extend(tile_part(0, 15, 0, 1, &[7]));
s.extend([0, 0, 0, 0]);
let cs = parse(&s).unwrap();
assert_eq!(cs.tile_parts.len(), 1);
assert_eq!(cs.tile_parts[0].body, [7]);
assert!(!cs.warnings.is_empty());
let mut s = tiny_main_header();
s.extend(tile_part(0, 0, 0, 1, &[9, 9]));
let cs = parse(&s).unwrap();
assert_eq!(cs.tile_parts.len(), 1);
assert_eq!(cs.tile_parts[0].body, [9, 9]);
assert!(
cs.warnings
.iter()
.any(|warning| warning.message.contains("EOC")),
"{:?}",
cs.warnings
);
}
#[test]
fn split_packed_headers_concatenates_in_zppm_order() {
let series = [0, 0, 0, 3, 97, 98, 99, 0, 0, 0, 2, 100, 101];
let segments = vec![
Ppm {
index: 1,
data: series[9..].to_vec(),
},
Ppm {
index: 0,
data: series[..9].to_vec(),
},
];
let blobs = split_packed_headers(&segments, 2).unwrap();
assert_eq!(blobs, [vec![97, 98, 99], vec![100, 101]]);
let blobs = split_packed_headers(&segments, 1).unwrap();
assert_eq!(blobs, [vec![97, 98, 99]]);
}
#[test]
fn split_packed_headers_rejects_truncated_series() {
let segments = vec![Ppm {
index: 0,
data: vec![0, 0, 0, 3, 97, 98],
}];
assert!(split_packed_headers(&segments, 1).is_err());
let segments = vec![Ppm {
index: 0,
data: vec![0, 0],
}];
assert!(split_packed_headers(&segments, 1).is_err());
}
fn style_with_levels(levels: u8) -> CodingStyle {
CodingStyle {
decomposition_levels: levels,
code_block_width_exp: 6,
code_block_height_exp: 6,
code_block_style: 0,
wavelet: WaveletKind::Reversible53,
precincts: Vec::new(),
}
}
fn cod_with(layers: u16, levels: u8) -> Cod {
Cod {
sop_markers: false,
eph_markers: false,
progression: ProgressionOrder::Lrcp,
layers,
mct: 0,
style: style_with_levels(levels),
}
}
fn quant_with_guard(guard: u8) -> Quantization {
Quantization {
guard_bits: guard,
style: QuantizationStyle::ScalarDerived {
exponent: 5,
mantissa: 100,
},
}
}
fn poc_with_order(order: ProgressionOrder) -> PocSegment {
PocSegment {
res_start: 0,
comp_start: 0,
layer_end: 1,
res_end: 1,
comp_end: 1,
order,
}
}
fn sot_with_part(tile_part_index: u8) -> Sot {
Sot {
tile_index: 0,
tile_part_length: 0,
tile_part_index,
tile_part_count: 0,
}
}
fn tiny_siz_struct() -> Siz {
Siz {
rsiz: 0,
xsiz: 16,
ysiz: 16,
xosiz: 0,
yosiz: 0,
xtsiz: 16,
ytsiz: 16,
xtosiz: 0,
ytosiz: 0,
components: vec![
SizComponent {
depth: 8,
signed: false,
xrsiz: 1,
yrsiz: 1,
},
SizComponent {
depth: 8,
signed: false,
xrsiz: 1,
yrsiz: 1,
},
],
}
}
#[test]
fn merge_tile_overrides_first_part_wins_and_orders_ppt() {
let part0 = TilePart {
sot: sot_with_part(0),
overrides: TileOverrides {
qcd: Some(quant_with_guard(1)),
coc: vec![Coc {
component: 0,
style: style_with_levels(1),
}],
ppt: vec![
Ppt {
index: 1,
data: vec![2],
},
Ppt {
index: 0,
data: vec![1],
},
],
..TileOverrides::default()
},
body: &[],
};
let part1 = TilePart {
sot: sot_with_part(1),
overrides: TileOverrides {
cod: Some(cod_with(3, 2)),
qcd: Some(quant_with_guard(2)),
coc: vec![
Coc {
component: 0,
style: style_with_levels(2),
},
Coc {
component: 1,
style: style_with_levels(3),
},
],
poc: vec![poc_with_order(ProgressionOrder::Rlcp)],
ppt: vec![Ppt {
index: 0,
data: vec![3],
}],
..TileOverrides::default()
},
body: &[],
};
let merged = merge_tile_overrides(&[&part0, &part1]).unwrap();
assert_eq!(merged.qcd.as_ref().unwrap().guard_bits, 1);
assert_eq!(merged.cod.as_ref().unwrap().layers, 3);
assert_eq!(merged.coc.len(), 2);
assert_eq!(merged.coc[0].component, 0);
assert_eq!(merged.coc[0].style.decomposition_levels, 1);
assert_eq!(merged.coc[1].component, 1);
assert_eq!(merged.coc[1].style.decomposition_levels, 3);
assert_eq!(merged.poc.len(), 1);
let ppt_data: Vec<u8> = merged.ppt.iter().flat_map(|p| p.data.clone()).collect();
assert_eq!(ppt_data, [1, 2, 3]);
}
#[test]
fn resolve_tile_coding_precedence() {
let main = MainHeader {
siz: tiny_siz_struct(),
cod: cod_with(10, 5),
coc: Vec::new(),
qcd: quant_with_guard(0),
qcc: Vec::new(),
rgn: Vec::new(),
poc: vec![poc_with_order(ProgressionOrder::Rlcp)],
ppm: Vec::new(),
};
let tile = TileOverrides {
cod: Some(Cod {
progression: ProgressionOrder::Rpcl,
sop_markers: true,
eph_markers: true,
..cod_with(20, 6)
}),
poc: vec![poc_with_order(ProgressionOrder::Cprl)],
..TileOverrides::default()
};
let coding = resolve_tile_coding(&main, &tile).unwrap();
assert_eq!(coding.progression, ProgressionOrder::Rpcl);
assert_eq!(coding.layers, 20);
assert!(coding.sop_markers);
assert!(coding.eph_markers);
assert_eq!(coding.poc.len(), 1);
assert_eq!(coding.poc[0].order, ProgressionOrder::Cprl);
let coding = resolve_tile_coding(&main, &TileOverrides::default()).unwrap();
assert_eq!(coding.progression, ProgressionOrder::Lrcp);
assert_eq!(coding.layers, 10);
assert!(!coding.sop_markers);
assert_eq!(coding.poc[0].order, ProgressionOrder::Rlcp);
}
#[test]
fn resolve_component_coding_precedence_ladder() {
let main = MainHeader {
siz: tiny_siz_struct(),
cod: cod_with(1, 5),
coc: vec![Coc {
component: 1,
style: style_with_levels(4),
}],
qcd: quant_with_guard(0),
qcc: vec![Qcc {
component: 1,
quant: quant_with_guard(3),
}],
rgn: vec![Rgn {
component: 0,
shift: 5,
}],
poc: Vec::new(),
ppm: Vec::new(),
};
let tile = TileOverrides {
cod: Some(cod_with(1, 6)),
coc: vec![Coc {
component: 0,
style: style_with_levels(7),
}],
qcd: Some(quant_with_guard(1)),
qcc: vec![Qcc {
component: 0,
quant: quant_with_guard(4),
}],
rgn: vec![Rgn {
component: 0,
shift: 9,
}],
..TileOverrides::default()
};
let c0 = resolve_component_coding(&main, &tile, 0).unwrap();
assert_eq!(c0.style.decomposition_levels, 7);
assert_eq!(c0.quant.guard_bits, 4);
assert_eq!(c0.roi_shift, Some(9));
let c1 = resolve_component_coding(&main, &tile, 1).unwrap();
assert_eq!(c1.style.decomposition_levels, 6);
assert_eq!(c1.quant.guard_bits, 1);
assert_eq!(c1.roi_shift, None);
let empty = TileOverrides::default();
let c0 = resolve_component_coding(&main, &empty, 0).unwrap();
assert_eq!(c0.style.decomposition_levels, 5);
assert_eq!(c0.quant.guard_bits, 0);
assert_eq!(c0.roi_shift, Some(5));
let c1 = resolve_component_coding(&main, &empty, 1).unwrap();
assert_eq!(c1.style.decomposition_levels, 4);
assert_eq!(c1.quant.guard_bits, 3);
}
fn quant(style: QuantizationStyle) -> Quantization {
Quantization {
guard_bits: 2,
style,
}
}
#[test]
fn band_quant_takes_listed_entries_and_derives_short_tails() {
let reversible = quant(QuantizationStyle::None {
exponents: vec![8, 9, 9],
});
for (flat, level, exponent) in [(0, 2, 8), (1, 2, 9), (2, 2, 9), (3, 2, 8), (4, 1, 7)] {
let resolved = reversible.band_quant(2, level, flat);
assert_eq!(
resolved,
BandQuant {
exponent,
mantissa: 0
},
"flat {flat}"
);
}
assert!(reversible.short_for(2));
assert!(!quant(QuantizationStyle::None {
exponents: vec![8; 7],
})
.short_for(2));
let derived = quant(QuantizationStyle::ScalarDerived {
exponent: 8,
mantissa: 100,
});
assert_eq!(
derived.band_quant(2, 1, 4),
BandQuant {
exponent: 7,
mantissa: 100
}
);
assert!(!derived.short_for(2));
let hostile = quant(QuantizationStyle::ScalarDerived {
exponent: 1,
mantissa: 0,
});
assert_eq!(hostile.band_quant(5, 1, 13).exponent, 0);
let expounded = quant(QuantizationStyle::ScalarExpounded {
steps: vec![QuantStep {
exponent: 8,
mantissa: 50,
}],
});
assert_eq!(
expounded.band_quant(1, 1, 3),
BandQuant {
exponent: 8,
mantissa: 50
}
);
assert!(expounded.short_for(1));
}
}