use crate::error::{JpxError, Result};
use crate::geometry::{BandKind, Rect, TileComponentGeometry};
use crate::markers::{ComponentCoding, PocSegment, ProgressionOrder};
use crate::tagtree::{BitReader, TagTree};
use crate::DecodeLimits;
use crate::JpxWarning;
pub(crate) struct ComponentContext {
pub geometry: TileComponentGeometry,
pub coding: ComponentCoding,
pub xrsiz: u8,
pub yrsiz: u8,
}
pub(crate) struct TileDecodeContext<'a> {
pub components: Vec<ComponentContext>,
pub tile_rect: Rect,
pub progression: ProgressionOrder,
pub layers: u16,
pub poc: Vec<PocSegment>,
pub sop_markers: bool,
pub eph_markers: bool,
pub bitstream: &'a [u8],
pub packed_headers: Option<&'a [u8]>,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct CodeBlockSegment {
pub start: usize,
pub len: usize,
pub passes: u32,
pub terminated: bool,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub(crate) struct CodeBlockInput {
pub rect: Rect,
pub band: BandKind,
pub missing_msbs: u32,
pub magnitude_bits: u8,
pub style: u8,
pub segments: Vec<CodeBlockSegment>,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct BandBlocks {
pub kind: BandKind,
pub level: u8,
pub rect: Rect,
pub blocks: Vec<CodeBlockInput>,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct ComponentPackets {
pub bands: Vec<BandBlocks>,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct TilePackets {
pub components: Vec<ComponentPackets>,
pub warnings: Vec<JpxWarning>,
pub packets_decoded: u64,
}
pub(crate) fn read_tile_packets(
ctx: &TileDecodeContext<'_>,
limits: &DecodeLimits,
prior_packets: bool,
) -> Result<TilePackets> {
decode_tile_packets(ctx, limits, prior_packets).map(|outcome| outcome.packets)
}
#[derive(Debug)]
struct Tier2Outcome {
packets: TilePackets,
#[allow(dead_code)]
header_end: usize,
#[allow(dead_code)]
body_end: usize,
}
fn decode_tile_packets(
ctx: &TileDecodeContext<'_>,
limits: &DecodeLimits,
prior_packets: bool,
) -> Result<Tier2Outcome> {
let headers = ctx.packed_headers.unwrap_or(ctx.bitstream);
let (components, state, slot_total) = build_state(ctx);
let budget = 4_194_304u64
.saturating_add(slot_total.saturating_mul(64))
.saturating_add((headers.len() as u64).saturating_mul(8 * 64));
let mut tier2 = Tier2 {
ctx,
limits,
headers,
packed: ctx.packed_headers.is_some(),
header_pos: 0,
body_pos: 0,
components,
state,
warnings: Vec::new(),
nsop_expected: 0,
sop_warned: false,
eph_warned: false,
prior_packets,
packets_done: 0,
segment_count: 0,
};
let volumes = plan_volumes(&ctx.components, ctx.progression, ctx.layers, &ctx.poc);
let mut sequencer = PacketSequencer::new(&ctx.components, ctx.tile_rect, volumes, budget);
let mut aborted = false;
loop {
let step = match sequencer.next_packet() {
Ok(step) => step,
Err(error) => {
tier2.soften(error, None)?;
aborted = true;
break;
}
};
let Some((layer, slot)) = step else { break };
if let Err(error) = tier2.parse_packet(slot, layer) {
tier2.soften(error, Some((layer, slot)))?;
aborted = true;
break;
}
}
let leftover = ctx.bitstream.len().saturating_sub(tier2.body_pos);
if !aborted && leftover > 0 {
tier2.warnings.push(JpxWarning::note(format!(
"{leftover} tile-body byte(s) after the last expected packet left unread \
(the progression description ends early; A.6.6/B.12)"
)));
}
Ok(Tier2Outcome {
packets: TilePackets {
components: tier2.components,
warnings: tier2.warnings,
packets_decoded: tier2.packets_done,
},
header_end: tier2.header_pos,
body_end: tier2.body_pos,
})
}
#[derive(Clone, Copy)]
struct BlockParseState {
included: bool,
lblock: u32,
passes: u32,
missing: u32,
}
struct PrecinctState {
inclusion: TagTree,
zero_planes: TagTree,
base: usize,
}
struct BandParseState {
precincts: Vec<PrecinctState>,
blocks: Vec<BlockParseState>,
magnitude_bits: u8,
}
fn flat_band_index(res: usize, band: usize) -> usize {
if res == 0 {
0
} else {
1 + 3 * (res - 1) + band
}
}
pub(crate) fn band_magnitude_bits(
coding: &ComponentCoding,
levels: u8,
level: u8,
flat: usize,
) -> u8 {
let exponent = coding.quant.band_quant(levels, level, flat).exponent;
let mb = (u32::from(coding.quant.guard_bits) + exponent).saturating_sub(1)
+ u32::from(coding.roi_shift.unwrap_or(0));
mb.min(255) as u8
}
fn build_state(
ctx: &TileDecodeContext<'_>,
) -> (Vec<ComponentPackets>, Vec<Vec<BandParseState>>, u64) {
let mut components = Vec::with_capacity(ctx.components.len());
let mut state = Vec::with_capacity(ctx.components.len());
let mut slot_total = 0u64;
for component in &ctx.components {
let geometry = &component.geometry;
let coding = &component.coding;
let style = coding.style.code_block_style;
let band_count = 1 + 3 * usize::from(geometry.levels);
let mut bands = Vec::with_capacity(band_count);
let mut band_states = Vec::with_capacity(band_count);
for (res, resolution) in geometry.resolutions.iter().enumerate() {
slot_total +=
u64::from(resolution.precincts_wide) * u64::from(resolution.precincts_high);
for (band_index, band) in resolution.bands.iter().enumerate() {
let flat = flat_band_index(res, band_index);
let magnitude_bits = band_magnitude_bits(coding, geometry.levels, band.level, flat);
let mut blocks = Vec::new();
let mut parse_blocks = Vec::new();
let mut precincts = Vec::with_capacity(band.precincts.len());
for grid in &band.precincts {
precincts.push(PrecinctState {
inclusion: TagTree::new(grid.blocks_wide, grid.blocks_high),
zero_planes: TagTree::new(grid.blocks_wide, grid.blocks_high),
base: blocks.len(),
});
for rect in &grid.blocks {
blocks.push(CodeBlockInput {
rect: *rect,
band: band.kind,
missing_msbs: 0,
magnitude_bits,
style,
segments: Vec::new(),
});
parse_blocks.push(BlockParseState {
included: false,
lblock: 3,
passes: 0,
missing: 0,
});
}
}
bands.push(BandBlocks {
kind: band.kind,
level: band.level,
rect: band.rect,
blocks,
});
band_states.push(BandParseState {
precincts,
blocks: parse_blocks,
magnitude_bits,
});
}
}
components.push(ComponentPackets { bands });
state.push(band_states);
}
(components, state, slot_total)
}
struct SegmentPlan {
passes: u32,
length: usize,
terminated: bool,
}
struct PlanEntry {
flat: usize,
index: usize,
first_missing: Option<u32>,
added_passes: u32,
lblock: u32,
segments: Vec<SegmentPlan>,
}
struct Tier2<'a, 'b> {
ctx: &'b TileDecodeContext<'a>,
limits: &'b DecodeLimits,
headers: &'b [u8],
packed: bool,
header_pos: usize,
body_pos: usize,
components: Vec<ComponentPackets>,
state: Vec<Vec<BandParseState>>,
warnings: Vec<JpxWarning>,
nsop_expected: u32,
sop_warned: bool,
eph_warned: bool,
prior_packets: bool,
packets_done: u64,
segment_count: u64,
}
impl Tier2<'_, '_> {
fn soften(&mut self, error: JpxError, at: Option<(u32, Slot)>) -> Result<()> {
if (self.packets_done == 0 && !self.prior_packets)
|| matches!(error, JpxError::LimitExceeded { .. })
{
return Err(error);
}
let scope = match at {
Some((layer, slot)) => format!(
"packet (layer {layer}, component {}, resolution {}, precinct {})",
slot.comp, slot.res, slot.precinct
),
None => "packet progression".to_string(),
};
self.warnings.push(JpxWarning::loss(format!(
"{scope}: {error}; the remaining packets of this tile are treated as empty"
)));
Ok(())
}
fn check_sop(&mut self) -> Result<()> {
let expected = self.nsop_expected % 65536;
self.nsop_expected = self.nsop_expected.wrapping_add(1);
if !self.ctx.sop_markers {
return Ok(());
}
let stream = self.ctx.bitstream;
let at = self.body_pos;
let Some(window) = stream.get(at..at.saturating_add(6)) else {
return Ok(());
};
if window[0] != 255 || window[1] != 145 {
return Ok(());
}
let length = u32::from(window[2]) * 256 + u32::from(window[3]);
if length != 4 {
return Err(JpxError::Malformed(
"SOP marker segment with Lsop != 4 (A.8.1)".into(),
));
}
let number = u32::from(window[4]) * 256 + u32::from(window[5]);
if number != expected {
if !self.sop_warned {
self.warnings.push(JpxWarning::note(format!(
"SOP sequence number {number} where {expected} was expected; \
resynchronizing (A.8.1)"
)));
self.sop_warned = true;
}
self.nsop_expected = number.wrapping_add(1);
}
self.body_pos = at + 6;
if !self.packed {
self.header_pos = self.body_pos;
}
Ok(())
}
fn consume_eph(&mut self) {
if !self.ctx.eph_markers {
return;
}
let at = self.header_pos;
if self.headers.get(at..at.saturating_add(2)) == Some([255u8, 146].as_slice()) {
self.header_pos = at + 2;
} else if !self.eph_warned {
self.warnings.push(JpxWarning::note(
"EPH marker signalled but missing after a packet header (A.8.2)",
));
self.eph_warned = true;
}
}
fn parse_packet(&mut self, slot: Slot, layer: u32) -> Result<()> {
self.check_sop()?;
let ctx = self.ctx;
let start = self.header_pos;
let mut reader = BitReader::new(self.headers.get(start..).unwrap_or(&[]));
let mut plan: Vec<PlanEntry> = Vec::new();
if reader.read_bit()? == 1 {
let resolution = &ctx.components[slot.comp].geometry.resolutions[slot.res];
let style = ctx.components[slot.comp].coding.style.code_block_style;
for (band_index, band) in resolution.bands.iter().enumerate() {
let flat = flat_band_index(slot.res, band_index);
let grid = &band.precincts[slot.precinct];
let entries = self.parse_band_blocks(
&mut reader,
slot,
layer,
flat,
style,
grid.blocks_wide,
grid.blocks_high,
)?;
plan.extend(entries);
}
}
reader.align()?;
self.header_pos = start + reader.byte_position();
self.consume_eph();
self.commit(slot, plan)?;
self.packets_done += 1;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn parse_band_blocks(
&mut self,
reader: &mut BitReader<'_>,
slot: Slot,
layer: u32,
flat: usize,
style: u8,
blocks_wide: u32,
blocks_high: u32,
) -> Result<Vec<PlanEntry>> {
let mut entries = Vec::new();
let band_state = &mut self.state[slot.comp][flat];
let magnitude_bits = band_state.magnitude_bits;
let BandParseState {
precincts, blocks, ..
} = band_state;
let precinct = &mut precincts[slot.precinct];
for row in 0..blocks_high {
for column in 0..blocks_wide {
let index = precinct.base + (row * blocks_wide + column) as usize;
let parse = blocks[index];
let mut first_missing = None;
let included = if parse.included {
reader.read_bit()? == 1
} else {
let now = precinct.inclusion.decode(reader, column, row, layer + 1)?;
if now {
let mut missing = None;
for threshold in 1..=MAX_MISSING_MSBS + 1 {
if precinct
.zero_planes
.decode(reader, column, row, threshold)?
{
missing = Some(threshold - 1);
break;
}
}
let Some(value) = missing else {
return Err(JpxError::Malformed(
"zero bit-plane count exceeds the (E-2) plane budget \
(B.10.5)"
.into(),
));
};
first_missing = Some(value);
}
now
};
if !included {
continue;
}
let added = read_pass_count(reader)?;
if u64::from(parse.passes) + u64::from(added) > MAX_CUMULATIVE_PASSES {
return Err(JpxError::Malformed(
"cumulative coding passes exceed 3 * Mb - 2 (B.10.6)".into(),
));
}
let mut lblock = parse.lblock;
while reader.read_bit()? == 1 {
lblock += 1;
if lblock > MAX_LBLOCK {
return Err(JpxError::Malformed(
"Lblock grew beyond any 32-bit length (B.10.7.1)".into(),
));
}
}
let missing = first_missing.unwrap_or(parse.missing);
let block_total = total_pass_count(magnitude_bits, missing);
let mut segments = Vec::new();
let mut run = 0u32;
for pass in parse.passes..parse.passes + added {
run += 1;
let boundary = pass_is_terminated(style, pass);
if boundary || pass + 1 == parse.passes + added {
let bits = lblock + floor_log2(run);
let length = read_length(reader, bits)?;
segments.push(SegmentPlan {
passes: run,
length,
terminated: boundary || (block_total > 0 && pass + 1 == block_total),
});
run = 0;
}
}
entries.push(PlanEntry {
flat,
index,
first_missing,
added_passes: added,
lblock,
segments,
});
}
}
Ok(entries)
}
fn commit(&mut self, slot: Slot, plan: Vec<PlanEntry>) -> Result<()> {
let base = if self.packed {
self.body_pos
} else {
self.header_pos
};
let mut total = 0usize;
let mut added_segments = 0u64;
for entry in &plan {
for segment in &entry.segments {
total = total
.checked_add(segment.length)
.ok_or_else(|| JpxError::Malformed("packet body length overflows".into()))?;
added_segments += 1;
}
}
let end = base
.checked_add(total)
.ok_or_else(|| JpxError::Malformed("packet body length overflows".into()))?;
if end > self.ctx.bitstream.len() {
return Err(JpxError::Malformed(
"packet body overruns the tile bit stream (B.9/B.11)".into(),
));
}
self.segment_count += added_segments;
let bytes = self
.segment_count
.saturating_mul(std::mem::size_of::<CodeBlockSegment>() as u64);
if bytes > self.limits.max_decoded_bytes {
return Err(JpxError::LimitExceeded {
what: "max_decoded_bytes",
actual: bytes,
limit: self.limits.max_decoded_bytes,
});
}
let mut cursor = base;
for entry in plan {
let output = &mut self.components[slot.comp].bands[entry.flat].blocks[entry.index];
let state = &mut self.state[slot.comp][entry.flat].blocks[entry.index];
if let Some(missing) = entry.first_missing {
output.missing_msbs = missing;
state.missing = missing;
state.included = true;
}
state.lblock = entry.lblock;
state.passes += entry.added_passes;
for segment in entry.segments {
output.segments.push(CodeBlockSegment {
start: cursor,
len: segment.length,
passes: segment.passes,
terminated: segment.terminated,
});
cursor += segment.length;
}
}
self.body_pos = cursor;
if !self.packed {
self.header_pos = cursor;
}
Ok(())
}
}
const MAX_MISSING_MSBS: u32 = 291;
const MAX_CUMULATIVE_PASSES: u64 = 874;
const MAX_LBLOCK: u32 = 35;
fn read_pass_count(reader: &mut BitReader<'_>) -> Result<u32> {
if reader.read_bit()? == 0 {
return Ok(1);
}
if reader.read_bit()? == 0 {
return Ok(2);
}
let two = reader.read_bits(2)?;
if two < 3 {
return Ok(3 + two);
}
let five = reader.read_bits(5)?;
if five < 31 {
return Ok(6 + five);
}
Ok(37 + reader.read_bits(7)?)
}
fn pass_is_terminated(style: u8, pass: u32) -> bool {
if style & 4 != 0 {
return true;
}
if style & 1 == 0 {
return false;
}
pass >= 9 && (pass.is_multiple_of(3) || pass % 3 == 2)
}
fn read_length(reader: &mut BitReader<'_>, bits: u32) -> Result<usize> {
let mut value = 0u64;
for _ in 0..bits {
value = (value << 1) | u64::from(reader.read_bit()?);
if value > u64::from(u32::MAX) {
return Err(JpxError::Malformed(
"codeword segment length exceeds 32 bits (B-19)".into(),
));
}
}
Ok(value as usize)
}
fn floor_log2(n: u32) -> u32 {
31 - n.leading_zeros()
}
fn total_pass_count(magnitude_bits: u8, missing: u32) -> u32 {
let planes = u32::from(magnitude_bits).saturating_sub(missing);
(3 * planes).saturating_sub(2)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Slot {
comp: usize,
res: usize,
precinct: usize,
}
struct Volume {
comp_start: usize,
comp_end: usize,
res_start: usize,
res_end: usize,
layer_end: u32,
order: ProgressionOrder,
}
fn plan_volumes(
components: &[ComponentContext],
progression: ProgressionOrder,
layers: u16,
poc: &[PocSegment],
) -> Vec<Volume> {
let nmax = components
.iter()
.map(|component| usize::from(component.geometry.levels))
.max()
.unwrap_or(0);
let res_cap = nmax + 1;
let comp_cap = components.len();
if poc.is_empty() {
return vec![Volume {
comp_start: 0,
comp_end: comp_cap,
res_start: 0,
res_end: res_cap,
layer_end: u32::from(layers),
order: progression,
}];
}
poc.iter()
.map(|segment| Volume {
comp_start: usize::from(segment.comp_start),
comp_end: usize::from(segment.comp_end).min(comp_cap),
res_start: usize::from(segment.res_start),
res_end: usize::from(segment.res_end).min(res_cap),
layer_end: u32::from(segment.layer_end).min(u32::from(layers)),
order: segment.order,
})
.collect()
}
fn charge(budget: &mut u64, cost: u64) -> Result<()> {
if *budget < cost {
return Err(JpxError::Malformed(
"progression order volume exceeds the iteration budget (B.12.2)".into(),
));
}
*budget -= cost;
Ok(())
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct PositionKey {
major: u64,
y: u128,
x: u128,
minor: u64,
}
fn axis_position(tile_edge: u32, tr0: u32, pp: u8, sub: u8, up: u32, index: u32) -> u128 {
if index == 0 && tr0 & ((1u32 << pp) - 1) != 0 {
return u128::from(tile_edge);
}
((u128::from(tr0 >> pp) + u128::from(index)) * u128::from(sub)) << (u32::from(pp) + up)
}
fn resolution_slot_count(components: &[ComponentContext], comp: usize, res: usize) -> usize {
let geometry = &components[comp].geometry;
if res > usize::from(geometry.levels) {
return 0;
}
let resolution = &geometry.resolutions[res];
resolution.precincts_wide as usize * resolution.precincts_high as usize
}
fn push_resolution_slots(
components: &[ComponentContext],
comp: usize,
res: usize,
slots: &mut Vec<Slot>,
budget: &mut u64,
) -> Result<()> {
let count = resolution_slot_count(components, comp, res);
charge(budget, count as u64)?;
for precinct in 0..count {
slots.push(Slot {
comp,
res,
precinct,
});
}
Ok(())
}
fn positional_groups(
components: &[ComponentContext],
tile_rect: Rect,
volume: &Volume,
budget: &mut u64,
) -> Result<Vec<Group>> {
let mut events: Vec<(PositionKey, Slot)> = Vec::new();
for (comp, component) in components
.iter()
.enumerate()
.take(volume.comp_end)
.skip(volume.comp_start)
{
let geometry = &component.geometry;
let res_end = volume.res_end.min(usize::from(geometry.levels) + 1);
for res in volume.res_start..res_end {
let resolution = &geometry.resolutions[res];
if resolution.precincts_wide == 0 || resolution.precincts_high == 0 {
continue;
}
let up = u32::from(geometry.levels) - res as u32;
for row in 0..resolution.precincts_high {
let y = axis_position(
tile_rect.y0,
resolution.rect.y0,
resolution.ppy,
component.yrsiz,
up,
row,
);
for column in 0..resolution.precincts_wide {
charge(budget, 1)?;
let x = axis_position(
tile_rect.x0,
resolution.rect.x0,
resolution.ppx,
component.xrsiz,
up,
column,
);
let precinct =
row as usize * resolution.precincts_wide as usize + column as usize;
let key = match volume.order {
ProgressionOrder::Rpcl => PositionKey {
major: res as u64,
y,
x,
minor: comp as u64,
},
ProgressionOrder::Pcrl => PositionKey {
major: 0,
y,
x,
minor: ((comp as u64) << 8) | res as u64,
},
_ => PositionKey {
major: comp as u64,
y,
x,
minor: res as u64,
},
};
events.push((
key,
Slot {
comp,
res,
precinct,
},
));
}
}
}
}
events.sort_unstable_by(|a, b| a.0.cmp(&b.0));
Ok(events
.into_iter()
.map(|(_, slot)| Group {
layer_end: volume.layer_end,
slots: vec![slot],
})
.collect())
}
fn volume_groups(
components: &[ComponentContext],
tile_rect: Rect,
volume: &Volume,
budget: &mut u64,
) -> Result<Vec<Group>> {
charge(budget, 1)?;
match volume.order {
ProgressionOrder::Lrcp => {
let mut slots = Vec::new();
for res in volume.res_start..volume.res_end {
for comp in volume.comp_start..volume.comp_end {
push_resolution_slots(components, comp, res, &mut slots, budget)?;
}
}
Ok(vec![Group {
layer_end: volume.layer_end,
slots,
}])
}
ProgressionOrder::Rlcp => {
let mut groups = Vec::new();
for res in volume.res_start..volume.res_end {
let mut slots = Vec::new();
for comp in volume.comp_start..volume.comp_end {
push_resolution_slots(components, comp, res, &mut slots, budget)?;
}
groups.push(Group {
layer_end: volume.layer_end,
slots,
});
}
Ok(groups)
}
_ => positional_groups(components, tile_rect, volume, budget),
}
}
struct PacketSequencer<'a> {
components: &'a [ComponentContext],
tile_rect: Rect,
volumes: Vec<Volume>,
volume_index: usize,
groups_built: bool,
groups: Vec<Group>,
group_index: usize,
layer: u32,
slot_index: usize,
next_layer: Vec<Vec<Vec<u32>>>,
budget: u64,
}
struct Group {
layer_end: u32,
slots: Vec<Slot>,
}
impl<'a> PacketSequencer<'a> {
fn new(
components: &'a [ComponentContext],
tile_rect: Rect,
volumes: Vec<Volume>,
budget: u64,
) -> Self {
let next_layer = components
.iter()
.map(|component| {
component
.geometry
.resolutions
.iter()
.map(|resolution| {
let count =
resolution.precincts_wide as usize * resolution.precincts_high as usize;
vec![0u32; count]
})
.collect()
})
.collect();
PacketSequencer {
components,
tile_rect,
volumes,
volume_index: 0,
groups_built: false,
groups: Vec::new(),
group_index: 0,
layer: 0,
slot_index: 0,
next_layer,
budget,
}
}
fn next_packet(&mut self) -> Result<Option<(u32, Slot)>> {
loop {
charge(&mut self.budget, 1)?;
if self.volume_index >= self.volumes.len() {
return Ok(None);
}
if !self.groups_built {
self.groups = volume_groups(
self.components,
self.tile_rect,
&self.volumes[self.volume_index],
&mut self.budget,
)?;
self.groups_built = true;
self.group_index = 0;
self.layer = 0;
self.slot_index = 0;
}
let Some(group) = self.groups.get(self.group_index) else {
self.volume_index += 1;
self.groups_built = false;
continue;
};
if group.slots.is_empty() || self.layer >= group.layer_end {
self.group_index += 1;
self.layer = 0;
self.slot_index = 0;
continue;
}
let Some(&slot) = group.slots.get(self.slot_index) else {
self.layer += 1;
self.slot_index = 0;
continue;
};
self.slot_index += 1;
let next = &mut self.next_layer[slot.comp][slot.res][slot.precinct];
if *next == self.layer {
*next += 1;
return Ok(Some((self.layer, slot)));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::markers::{
CodingStyle, PrecinctExponents, QuantStep, Quantization, QuantizationStyle, SizComponent,
WaveletKind,
};
fn rect(x0: u32, y0: u32, x1: u32, y1: u32) -> Rect {
Rect { x0, y0, x1, y1 }
}
fn coding_style(
levels: u8,
xcb: u8,
ycb: u8,
precincts: &[(u8, u8)],
block_style: u8,
) -> CodingStyle {
CodingStyle {
decomposition_levels: levels,
code_block_width_exp: xcb,
code_block_height_exp: ycb,
code_block_style: block_style,
wavelet: WaveletKind::Reversible53,
precincts: precincts
.iter()
.map(|&(ppx, ppy)| PrecinctExponents { ppx, ppy })
.collect(),
}
}
fn quant_none(guard_bits: u8, exponents: Vec<u8>) -> Quantization {
Quantization {
guard_bits,
style: QuantizationStyle::None { exponents },
}
}
fn component_context(
tile: Rect,
style: CodingStyle,
quant: Quantization,
roi_shift: Option<u8>,
) -> ComponentContext {
subsampled_component_context(tile, 1, 1, style, quant, roi_shift)
}
fn subsampled_component_context(
tile: Rect,
xrsiz: u8,
yrsiz: u8,
style: CodingStyle,
quant: Quantization,
roi_shift: Option<u8>,
) -> ComponentContext {
let component = SizComponent {
depth: 8,
signed: false,
xrsiz,
yrsiz,
};
let geometry = crate::geometry::tile_component_geometry(tile, &component, &style).unwrap();
ComponentContext {
geometry,
coding: ComponentCoding {
style,
quant,
roi_shift,
},
xrsiz,
yrsiz,
}
}
fn bit_bytes(spec: &str) -> Vec<u8> {
let mut bytes: Vec<u8> = Vec::new();
let mut current = 0u8;
let mut used = 0u8;
for ch in spec.chars() {
let bit = match ch {
'0' => 0,
'1' => 1,
_ => continue,
};
if used == 0 && bytes.last() == Some(&255) {
used = 1; }
current = (current << 1) | bit;
used += 1;
if used == 8 {
bytes.push(current);
current = 0;
used = 0;
}
}
if used > 0 {
bytes.push(current << (8 - used));
}
bytes
}
fn segment_tuples(block: &CodeBlockInput) -> Vec<(usize, usize, u32, bool)> {
block
.segments
.iter()
.map(|segment| {
(
segment.start,
segment.len,
segment.passes,
segment.terminated,
)
})
.collect()
}
fn collect_sequence(
components: &[ComponentContext],
tile_rect: Rect,
order: ProgressionOrder,
layers: u16,
poc: Vec<PocSegment>,
) -> Vec<(u32, usize, usize, usize)> {
let volumes = plan_volumes(components, order, layers, &poc);
let mut sequencer = PacketSequencer::new(components, tile_rect, volumes, 1 << 24);
let mut sequence = Vec::new();
while let Some((layer, slot)) = sequencer.next_packet().unwrap() {
sequence.push((layer, slot.comp, slot.res, slot.precinct));
}
sequence
}
#[test]
fn pass_count_codewords_match_table_b4() {
let data =
bit_bytes("0 10 1100 1101 1110 111100000 111111110 1111111110000000 1111111111111111");
let mut reader = BitReader::new(&data);
for expected in [1u32, 2, 3, 4, 5, 6, 36, 37, 164] {
assert_eq!(read_pass_count(&mut reader).unwrap(), expected);
}
}
#[test]
fn termination_boundaries_follow_tables_d8_and_d9() {
for pass in 0..24 {
assert!(!pass_is_terminated(0, pass), "pass {pass}");
}
for pass in 0..24 {
assert!(pass_is_terminated(4, pass), "pass {pass}");
}
let terminated = [9u32, 11, 12, 14, 15, 17, 18];
for pass in 0..20 {
assert_eq!(
pass_is_terminated(1, pass),
terminated.contains(&pass),
"pass {pass}"
);
}
}
fn figure_b13_component() -> ComponentContext {
component_context(
rect(0, 0, 48, 32),
coding_style(0, 4, 4, &[], 0),
quant_none(2, vec![8]),
None,
)
}
#[test]
fn header_walkthrough_reproduces_figure_b13_and_table_b5() {
let layer0 = bit_bytes("1 111 000111 1100 0 0100 1 01 10 10 00100 0 0 0");
assert_eq!(layer0, vec![241, 240, 150, 136, 0]);
let layer1 = bit_bytes("1 1 1100 0 1010 0 10 0 1 1 0 0 001 1 00011 0 0 010");
assert_eq!(layer1, vec![241, 73, 134, 49, 0]);
let mut stream = layer0;
stream.extend_from_slice(&[101, 102, 103, 104, 111, 112, 113, 114]);
stream.extend_from_slice(&layer1);
stream.extend_from_slice(&[0u8; 13]);
assert_eq!(stream.len(), 31);
let ctx = TileDecodeContext {
components: vec![figure_b13_component()],
tile_rect: rect(0, 0, 48, 32),
progression: ProgressionOrder::Lrcp,
layers: 2,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(outcome.body_end, 31);
assert_eq!(outcome.header_end, 31);
let band = &outcome.packets.components[0].bands[0];
assert_eq!(band.kind, BandKind::Ll);
assert_eq!(band.blocks.len(), 6);
assert_eq!(band.blocks[0].missing_msbs, 3);
assert_eq!(
segment_tuples(&band.blocks[0]),
vec![(5, 4, 3, false), (18, 10, 3, false)]
);
assert_eq!(band.blocks[1].missing_msbs, 4);
assert_eq!(segment_tuples(&band.blocks[1]), vec![(9, 4, 2, false)]);
assert!(band.blocks[2].segments.is_empty());
assert!(band.blocks[3].segments.is_empty());
assert_eq!(band.blocks[4].missing_msbs, 3);
assert_eq!(segment_tuples(&band.blocks[4]), vec![(28, 1, 1, false)]);
assert_eq!(band.blocks[5].missing_msbs, 6);
assert_eq!(segment_tuples(&band.blocks[5]), vec![(29, 2, 1, false)]);
for block in &band.blocks {
assert_eq!(block.magnitude_bits, 9);
assert_eq!(block.style, 0);
}
}
#[test]
fn packed_headers_split_bits_from_bodies() {
let headers = bit_bytes("1 111 000111 1100 0 0100 1 01 10 10 00100 0 0 0");
let bodies = [1u8, 2, 3, 4, 5, 6, 7, 8];
let ctx = TileDecodeContext {
components: vec![figure_b13_component()],
tile_rect: rect(0, 0, 48, 32),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &bodies,
packed_headers: Some(&headers),
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(outcome.header_end, 5);
assert_eq!(outcome.body_end, 8);
let band = &outcome.packets.components[0].bands[0];
assert_eq!(segment_tuples(&band.blocks[0]), vec![(0, 4, 3, false)]);
assert_eq!(segment_tuples(&band.blocks[1]), vec![(4, 4, 2, false)]);
}
#[test]
fn bypass_mode_splits_codeword_segments_per_the_b_10_7_2_note() {
let layer0 = bit_bytes("1 1 1 111100011 0 010100");
assert_eq!(layer0, vec![254, 50, 128]);
let layer1 = bit_bytes("1 1 1110 111110 00000110 001001011 10000110 11000000");
assert_eq!(layer1, vec![251, 224, 98, 92, 54, 0]);
let mut stream = layer0;
stream.extend_from_slice(&[0u8; 20]);
stream.extend_from_slice(&layer1);
stream.extend_from_slice(&[0u8; 407]);
assert_eq!(stream.len(), 436);
let component = component_context(
rect(0, 0, 16, 16),
coding_style(0, 4, 4, &[], 1),
quant_none(2, vec![9]),
None,
);
let ctx = TileDecodeContext {
components: vec![component],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 2,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(outcome.body_end, 436);
let block = &outcome.packets.components[0].bands[0].blocks[0];
assert_eq!(
segment_tuples(block),
vec![
(3, 20, 9, false),
(29, 6, 1, true),
(35, 75, 2, true),
(110, 134, 1, true),
(244, 192, 1, false),
]
);
}
#[test]
fn zero_length_packets_defer_inclusion_to_later_layers() {
let layer1 = bit_bytes("1 01 001 0 0 101");
assert_eq!(layer1, vec![164, 160]);
let mut stream = vec![0u8];
stream.extend_from_slice(&layer1);
stream.extend_from_slice(&[9, 9, 9, 9, 9]);
let component = component_context(
rect(0, 0, 16, 16),
coding_style(0, 4, 4, &[], 0),
quant_none(2, vec![8]),
None,
);
let ctx = TileDecodeContext {
components: vec![component],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 2,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(outcome.body_end, 8);
let block = &outcome.packets.components[0].bands[0].blocks[0];
assert_eq!(block.missing_msbs, 2);
assert_eq!(segment_tuples(block), vec![(3, 5, 1, false)]);
}
fn sop_eph_component() -> ComponentContext {
component_context(
rect(0, 0, 16, 16),
coding_style(0, 4, 4, &[], 0),
quant_none(2, vec![8]),
None,
)
}
#[test]
fn sop_and_eph_markers_wrap_the_packet() {
let header = bit_bytes("1 1 1 0 0 010");
assert_eq!(header, vec![226]);
let mut stream = vec![255, 145, 0, 4, 0, 0];
stream.extend_from_slice(&header);
stream.extend_from_slice(&[255, 146]);
stream.extend_from_slice(&[7, 7]);
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: true,
eph_markers: true,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(outcome.body_end, 11);
assert_eq!(outcome.header_end, 11);
let block = &outcome.packets.components[0].bands[0].blocks[0];
assert_eq!(segment_tuples(block), vec![(9, 2, 1, false)]);
}
#[test]
fn sop_sequence_mismatch_warns_and_resynchronizes() {
let header = bit_bytes("1 1 1 0 0 010");
let mut stream = vec![255, 145, 0, 4, 0, 5];
stream.extend_from_slice(&header);
stream.extend_from_slice(&[7, 7]);
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: true,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings.len(), 1);
assert!(outcome.packets.warnings[0].message.contains("SOP"));
let block = &outcome.packets.components[0].bands[0].blocks[0];
assert_eq!(segment_tuples(block), vec![(7, 2, 1, false)]);
}
#[test]
fn corruption_in_the_first_packet_is_a_hard_error() {
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &[],
packed_headers: None,
};
assert!(matches!(
decode_tile_packets(&ctx, &DecodeLimits::default(), false),
Err(JpxError::Malformed(_))
));
}
#[test]
fn corruption_in_a_later_tiles_first_packet_softens() {
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &[],
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), true).unwrap();
assert_eq!(outcome.packets.packets_decoded, 0);
assert_eq!(outcome.packets.warnings.len(), 1);
assert!(outcome.packets.components[0]
.bands
.iter()
.all(|band| band.blocks.iter().all(|block| block.segments.is_empty())));
}
#[test]
fn first_packet_body_overrun_is_a_hard_error() {
let header = bit_bytes("1 1 1 0 111110 11001000");
assert_eq!(header, vec![239, 178, 0]);
let mut stream = header;
stream.extend_from_slice(&[0u8; 5]);
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
match decode_tile_packets(&ctx, &DecodeLimits::default(), false) {
Err(JpxError::Malformed(message)) => assert!(message.contains("overrun")),
other => panic!("expected a hard body-overrun error, got {other:?}"),
}
}
#[test]
fn corruption_after_the_first_packet_degrades_to_a_warning() {
let mut stream = bit_bytes("1 111 000111 1100 0 0100 1 01 10 10 00100 0 0 0");
stream.extend_from_slice(&[101, 102, 103, 104, 111, 112, 113, 114]);
stream.push(241);
let ctx = TileDecodeContext {
components: vec![figure_b13_component()],
tile_rect: rect(0, 0, 48, 32),
progression: ProgressionOrder::Lrcp,
layers: 2,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.packets.warnings.len(), 1);
assert!(outcome.packets.warnings[0].message.contains("layer 1"));
let band = &outcome.packets.components[0].bands[0];
assert_eq!(segment_tuples(&band.blocks[0]), vec![(5, 4, 3, false)]);
assert_eq!(segment_tuples(&band.blocks[1]), vec![(9, 4, 2, false)]);
assert_eq!(outcome.body_end, 13);
}
#[test]
fn unconsumed_tail_bytes_get_a_diagnostic_note() {
let mut stream = bit_bytes("1 111 000111 1100 0 0100 1 01 10 10 00100 0 0 0");
stream.extend_from_slice(&[101, 102, 103, 104, 111, 112, 113, 114]);
stream.extend_from_slice(&[9, 9, 9]);
let ctx = TileDecodeContext {
components: vec![figure_b13_component()],
tile_rect: rect(0, 0, 48, 32),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
assert_eq!(outcome.body_end, 13);
assert_eq!(outcome.packets.warnings.len(), 1, "{:?}", outcome.packets);
let warning = &outcome.packets.warnings[0];
assert!(!warning.data_loss, "{warning:?}");
assert!(
warning.message.contains("3 tile-body byte(s)"),
"{warning:?}"
);
}
#[test]
fn segment_allocation_is_bounded_by_decode_limits() {
let header = bit_bytes("1 1 1 0 0 010");
let mut stream = header;
stream.extend_from_slice(&[7, 7]);
let limits = DecodeLimits {
max_decoded_bytes: std::mem::size_of::<CodeBlockSegment>() as u64 - 1,
..DecodeLimits::default()
};
let ctx = TileDecodeContext {
components: vec![sop_eph_component()],
tile_rect: rect(0, 0, 16, 16),
progression: ProgressionOrder::Lrcp,
layers: 1,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &stream,
packed_headers: None,
};
assert!(matches!(
decode_tile_packets(&ctx, &limits, false),
Err(JpxError::LimitExceeded {
what: "max_decoded_bytes",
..
})
));
}
#[test]
fn magnitude_bits_follow_e2_and_e5() {
let style = coding_style(2, 6, 6, &[], 0);
let quant = Quantization {
guard_bits: 2,
style: QuantizationStyle::ScalarDerived {
exponent: 8,
mantissa: 0,
},
};
let component = component_context(rect(0, 0, 64, 64), style, quant, None);
let ctx = TileDecodeContext {
components: vec![component],
tile_rect: rect(0, 0, 64, 64),
progression: ProgressionOrder::Lrcp,
layers: 0,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &[],
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
let bands = &outcome.packets.components[0].bands;
assert_eq!(bands.len(), 7);
let bits: Vec<u8> = bands
.iter()
.map(|band| band.blocks[0].magnitude_bits)
.collect();
assert_eq!(bits, vec![9, 9, 9, 9, 8, 8, 8]);
let kinds: Vec<BandKind> = bands.iter().map(|band| band.kind).collect();
assert_eq!(
kinds,
vec![
BandKind::Ll,
BandKind::Hl,
BandKind::Lh,
BandKind::Hh,
BandKind::Hl,
BandKind::Lh,
BandKind::Hh,
]
);
let levels: Vec<u8> = bands.iter().map(|band| band.level).collect();
assert_eq!(levels, vec![2, 2, 2, 2, 1, 1, 1]);
}
#[test]
fn magnitude_bits_add_the_rgn_maxshift_and_read_expounded_steps() {
let exponents = [9u8, 8, 7, 6, 5, 4, 3];
let style = coding_style(2, 6, 6, &[], 0);
let quant = Quantization {
guard_bits: 1,
style: QuantizationStyle::ScalarExpounded {
steps: exponents
.iter()
.map(|&exponent| QuantStep {
exponent,
mantissa: 0,
})
.collect(),
},
};
let component = component_context(rect(0, 0, 64, 64), style, quant, Some(4));
let ctx = TileDecodeContext {
components: vec![component],
tile_rect: rect(0, 0, 64, 64),
progression: ProgressionOrder::Lrcp,
layers: 0,
poc: Vec::new(),
sop_markers: false,
eph_markers: false,
bitstream: &[],
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &DecodeLimits::default(), false).unwrap();
let bits: Vec<u8> = outcome.packets.components[0]
.bands
.iter()
.map(|band| band.blocks[0].magnitude_bits)
.collect();
assert_eq!(bits, vec![13, 12, 11, 10, 9, 8, 7]);
}
fn progression_components() -> Vec<ComponentContext> {
vec![
component_context(
rect(0, 0, 32, 32),
coding_style(1, 3, 3, &[(3, 3), (4, 4)], 0),
quant_none(2, vec![8]),
None,
),
subsampled_component_context(
rect(0, 0, 32, 32),
2,
2,
coding_style(2, 3, 3, &[(3, 3), (3, 3), (4, 4)], 0),
quant_none(2, vec![8]),
None,
),
]
}
#[test]
fn lrcp_and_rlcp_orders_follow_the_b_12_1_loops() {
let components = progression_components();
assert_eq!(components[0].geometry.resolutions[0].precincts_wide, 2);
assert_eq!(components[0].geometry.resolutions[1].precincts_wide, 2);
assert_eq!(components[1].geometry.resolutions[0].precincts_wide, 1);
let lrcp = collect_sequence(
&components,
rect(0, 0, 32, 32),
ProgressionOrder::Lrcp,
2,
Vec::new(),
);
let expected_layer0 = [
(0, 0, 0, 0),
(0, 0, 0, 1),
(0, 0, 0, 2),
(0, 0, 0, 3),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 1, 1),
(0, 0, 1, 2),
(0, 0, 1, 3),
(0, 1, 1, 0),
(0, 1, 2, 0),
];
assert_eq!(&lrcp[..11], &expected_layer0);
let expected_layer1: Vec<(u32, usize, usize, usize)> = expected_layer0
.iter()
.map(|&(_, c, r, k)| (1, c, r, k))
.collect();
assert_eq!(&lrcp[11..], &expected_layer1[..]);
let rlcp = collect_sequence(
&components,
rect(0, 0, 32, 32),
ProgressionOrder::Rlcp,
2,
Vec::new(),
);
let expected = [
(0, 0, 0, 0),
(0, 0, 0, 1),
(0, 0, 0, 2),
(0, 0, 0, 3),
(0, 1, 0, 0),
(1, 0, 0, 0),
(1, 0, 0, 1),
(1, 0, 0, 2),
(1, 0, 0, 3),
(1, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 1, 1),
(0, 0, 1, 2),
(0, 0, 1, 3),
(0, 1, 1, 0),
(1, 0, 1, 0),
(1, 0, 1, 1),
(1, 0, 1, 2),
(1, 0, 1, 3),
(1, 1, 1, 0),
(0, 1, 2, 0),
(1, 1, 2, 0),
];
assert_eq!(rlcp, expected);
}
#[test]
fn positional_orders_follow_b_12_1_3_to_b_12_1_5() {
let components = progression_components();
let rpcl = collect_sequence(
&components,
rect(0, 0, 32, 32),
ProgressionOrder::Rpcl,
2,
Vec::new(),
);
let expected_rpcl = [
(0, 0, 0, 0),
(1, 0, 0, 0),
(0, 1, 0, 0),
(1, 1, 0, 0),
(0, 0, 0, 1),
(1, 0, 0, 1),
(0, 0, 0, 2),
(1, 0, 0, 2),
(0, 0, 0, 3),
(1, 0, 0, 3),
(0, 0, 1, 0),
(1, 0, 1, 0),
(0, 1, 1, 0),
(1, 1, 1, 0),
(0, 0, 1, 1),
(1, 0, 1, 1),
(0, 0, 1, 2),
(1, 0, 1, 2),
(0, 0, 1, 3),
(1, 0, 1, 3),
(0, 1, 2, 0),
(1, 1, 2, 0),
];
assert_eq!(rpcl, expected_rpcl);
let pcrl = collect_sequence(
&components,
rect(0, 0, 32, 32),
ProgressionOrder::Pcrl,
2,
Vec::new(),
);
let expected_pcrl = [
(0, 0, 0, 0),
(1, 0, 0, 0),
(0, 0, 1, 0),
(1, 0, 1, 0),
(0, 1, 0, 0),
(1, 1, 0, 0),
(0, 1, 1, 0),
(1, 1, 1, 0),
(0, 1, 2, 0),
(1, 1, 2, 0),
(0, 0, 0, 1),
(1, 0, 0, 1),
(0, 0, 1, 1),
(1, 0, 1, 1),
(0, 0, 0, 2),
(1, 0, 0, 2),
(0, 0, 1, 2),
(1, 0, 1, 2),
(0, 0, 0, 3),
(1, 0, 0, 3),
(0, 0, 1, 3),
(1, 0, 1, 3),
];
assert_eq!(pcrl, expected_pcrl);
let cprl = collect_sequence(
&components,
rect(0, 0, 32, 32),
ProgressionOrder::Cprl,
2,
Vec::new(),
);
let expected_cprl = [
(0, 0, 0, 0),
(1, 0, 0, 0),
(0, 0, 1, 0),
(1, 0, 1, 0),
(0, 0, 0, 1),
(1, 0, 0, 1),
(0, 0, 1, 1),
(1, 0, 1, 1),
(0, 0, 0, 2),
(1, 0, 0, 2),
(0, 0, 1, 2),
(1, 0, 1, 2),
(0, 0, 0, 3),
(1, 0, 0, 3),
(0, 0, 1, 3),
(1, 0, 1, 3),
(0, 1, 0, 0),
(1, 1, 0, 0),
(0, 1, 1, 0),
(1, 1, 1, 0),
(0, 1, 2, 0),
(1, 1, 2, 0),
];
assert_eq!(cprl, expected_cprl);
}
#[test]
fn unaligned_tile_origins_fire_their_first_precinct_at_the_tile_edge() {
let components = vec![
component_context(
rect(5, 5, 21, 21),
coding_style(0, 2, 2, &[(3, 3)], 0),
quant_none(2, vec![8]),
None,
),
subsampled_component_context(
rect(5, 5, 21, 21),
2,
2,
coding_style(0, 2, 2, &[(3, 3)], 0),
quant_none(2, vec![8]),
None,
),
];
assert_eq!(components[0].geometry.resolutions[0].precincts_wide, 3);
assert_eq!(components[1].geometry.rect, rect(3, 3, 11, 11));
assert_eq!(components[1].geometry.resolutions[0].precincts_wide, 2);
let pcrl = collect_sequence(
&components,
rect(5, 5, 21, 21),
ProgressionOrder::Pcrl,
1,
Vec::new(),
);
let expected = [
(0, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 0, 1),
(0, 0, 0, 2),
(0, 1, 0, 1),
(0, 0, 0, 3),
(0, 0, 0, 4),
(0, 0, 0, 5),
(0, 0, 0, 6),
(0, 1, 0, 2),
(0, 0, 0, 7),
(0, 0, 0, 8),
(0, 1, 0, 3),
];
assert_eq!(pcrl, expected);
}
#[test]
fn poc_volumes_chain_without_repeating_packets() {
let component = component_context(
rect(0, 0, 16, 16),
coding_style(1, 3, 3, &[], 0),
quant_none(2, vec![8]),
None,
);
let poc = vec![
PocSegment {
res_start: 0,
comp_start: 0,
layer_end: 2,
res_end: 1,
comp_end: 1,
order: ProgressionOrder::Lrcp,
},
PocSegment {
res_start: 0,
comp_start: 0,
layer_end: 3,
res_end: 2,
comp_end: 1,
order: ProgressionOrder::Rlcp,
},
];
let sequence = collect_sequence(
&[component],
rect(0, 0, 16, 16),
ProgressionOrder::Lrcp,
3,
poc,
);
let expected = [
(0, 0, 0, 0),
(1, 0, 0, 0),
(2, 0, 0, 0),
(0, 0, 1, 0),
(1, 0, 1, 0),
(2, 0, 1, 0),
];
assert_eq!(sequence, expected);
}
fn zoo_stream(name: &str) -> Vec<u8> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name);
std::fs::read(path).unwrap()
}
fn parse_zoo_tiles(name: &str) -> Vec<(TilePackets, usize, usize, usize)> {
let data = zoo_stream(name);
let limits = DecodeLimits::default();
let container = crate::boxes::scan(&data, &limits).unwrap();
let cs = crate::markers::parse_codestream(container.codestream, &limits).unwrap();
assert!(
cs.main.ppm.is_empty(),
"{name}: fixtures use in-stream headers"
);
let siz = &cs.main.siz;
let (tiles_wide, tiles_high) = crate::geometry::tile_grid(siz).unwrap();
let tile_total = tiles_wide as usize * tiles_high as usize;
let mut tiles: Vec<Vec<&crate::markers::TilePart<'_>>> =
(0..tile_total).map(|_| Vec::new()).collect();
for part in &cs.tile_parts {
tiles[usize::from(part.sot.tile_index)].push(part);
}
let mut outcomes = Vec::new();
for (tile_index, parts) in tiles.iter().enumerate() {
assert!(!parts.is_empty(), "{name}: tile {tile_index} has no parts");
let overrides = crate::markers::merge_tile_overrides(parts).unwrap();
assert!(overrides.ppt.is_empty());
let tile_coding = crate::markers::resolve_tile_coding(&cs.main, &overrides).unwrap();
let p = (tile_index % tiles_wide as usize) as u32;
let q = (tile_index / tiles_wide as usize) as u32;
let tile_rect = crate::geometry::tile_rect(siz, p, q);
let mut components = Vec::new();
for (index, component) in siz.components.iter().enumerate() {
let coding =
crate::markers::resolve_component_coding(&cs.main, &overrides, index as u16)
.unwrap();
let geometry =
crate::geometry::tile_component_geometry(tile_rect, component, &coding.style)
.unwrap();
components.push(ComponentContext {
geometry,
coding,
xrsiz: component.xrsiz,
yrsiz: component.yrsiz,
});
}
let bitstream: Vec<u8> = parts
.iter()
.flat_map(|part| part.body.iter().copied())
.collect();
let ctx = TileDecodeContext {
components,
tile_rect,
progression: tile_coding.progression,
layers: tile_coding.layers,
poc: tile_coding.poc.clone(),
sop_markers: tile_coding.sop_markers,
eph_markers: tile_coding.eph_markers,
bitstream: &bitstream,
packed_headers: None,
};
let outcome = decode_tile_packets(&ctx, &limits, false).unwrap();
outcomes.push((
outcome.packets,
outcome.header_end,
outcome.body_end,
bitstream.len(),
));
}
outcomes
}
fn assert_segments_within(name: &str, packets: &TilePackets, len: usize) -> u64 {
let mut count = 0u64;
for component in &packets.components {
for band in &component.bands {
for block in &band.blocks {
for segment in &block.segments {
assert!(
segment.start + segment.len <= len,
"{name}: segment {segment:?} beyond the {len}-byte body"
);
count += 1;
}
}
}
}
count
}
#[test]
fn zoo_progression_fixtures_parse_to_the_exact_stream_end() {
for name in [
"rgb-prog-lrcp.jp2",
"rgb-prog-rlcp.jp2",
"rgb-prog-rpcl.jp2",
"rgb-prog-pcrl.jp2",
"rgb-prog-cprl.jp2",
] {
for (packets, header_end, body_end, len) in parse_zoo_tiles(name) {
assert_eq!(packets.warnings, Vec::<JpxWarning>::new(), "{name}");
assert_eq!(body_end, len, "{name}");
assert_eq!(header_end, len, "{name}");
assert!(assert_segments_within(name, &packets, len) > 0, "{name}");
}
}
}
#[test]
fn zoo_layers_fixture_accumulates_segments_across_three_layers() {
let outcomes = parse_zoo_tiles("rgb-layers.jp2");
for (packets, header_end, body_end, len) in &outcomes {
assert_eq!(packets.warnings, Vec::<JpxWarning>::new());
assert_eq!(*body_end, *len);
assert_eq!(*header_end, *len);
}
let mut max_segments = 0usize;
for (packets, ..) in &outcomes {
for component in &packets.components {
for band in &component.bands {
for block in &band.blocks {
assert!(block.segments.len() <= 3);
max_segments = max_segments.max(block.segments.len());
}
}
}
}
assert!(max_segments >= 2, "layers never split: {max_segments}");
}
#[test]
fn zoo_structured_fixtures_parse_every_tile() {
for name in [
"rgb-precinct.jp2",
"rgb-tiled.jp2",
"rgb-res3.jp2",
"rgb-cb16.jp2",
] {
for (packets, header_end, body_end, len) in parse_zoo_tiles(name) {
assert_eq!(packets.warnings, Vec::<JpxWarning>::new(), "{name}");
assert_eq!(body_end, len, "{name}");
assert_eq!(header_end, len, "{name}");
assert_segments_within(name, &packets, len);
}
}
}
}