use alloc::vec::Vec;
use core::ops::Range;
use super::subband::decode_sub_band_bitplanes;
use super::{
decode_component_tile_bit_planes_budgeted, tier1, ComponentInfo, CpuDecodeParallelism,
DecodeAllocationBudget, DecodeProfileTimings, DecompositionStorage, Header, Result, Tile,
TileDecodeContext,
};
use super::{record_workspace, store};
use crate::error::{DecodeError, DecodingError, ValidationError};
use crate::j2c::build;
use crate::j2c::codestream::WaveletTransform;
use crate::j2c::idwt::{ComponentLines, LinePlan, LineReconstruction, SubBandRows};
use crate::j2c::rect::IntRect;
use crate::profile;
use crate::{try_resize_decode_elements, ComponentPlane, ComponentRowSink};
pub(super) fn applies(
header: &Header<'_>,
tile: &Tile<'_>,
parallelism: CpuDecodeParallelism,
clip_to_idwt_windows: bool,
) -> bool {
clip_to_idwt_windows
&& tier1_runs_serially(parallelism)
&& tile.component_infos.iter().all(|component| {
component.num_decomposition_levels() > header.skipped_resolution_levels
})
}
pub(super) fn tier1_runs_serially(parallelism: CpuDecodeParallelism) -> bool {
#[cfg(feature = "parallel")]
{
parallelism != CpuDecodeParallelism::Auto || rayon::current_num_threads() < 2
}
#[cfg(not(feature = "parallel"))]
{
let _ = parallelism;
true
}
}
#[expect(
clippy::too_many_arguments,
reason = "the banded stage shares decode_tile's state and profiling explicitly"
)]
pub(super) fn decode_component<'a>(
tile: &Tile<'a>,
component_idx: usize,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
transform: WaveletTransform,
profile_enabled: bool,
profile_timings: &mut DecodeProfileTimings,
) -> Result<()> {
let stage_start = profile::profile_now(profile_enabled);
let workspace = tier1::prepare_tier1_workspace(
tile,
component_idx..component_idx + 1,
header,
tile_ctx,
storage,
)?;
let mut tier1_us = 0;
let result = decode_bands(
tile,
component_idx,
header,
tile_ctx,
storage,
transform,
profile_enabled,
&mut tier1_us,
);
tier1::release_tier1_workspace(tile_ctx, storage, &workspace)?;
let elapsed = profile::elapsed_us(stage_start);
profile_timings.codeblock_us += tier1_us;
profile_timings.idwt_us += elapsed.saturating_sub(tier1_us);
result.map_err(|banded_error| {
whole_window_error(tile, component_idx, header, tile_ctx, storage, banded_error)
})
}
pub(super) fn decode_tile_rows<'a>(
tile: &Tile<'a>,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
sink: &mut dyn ComponentRowSink,
profile_enabled: bool,
profile_timings: &mut DecodeProfileTimings,
) -> Result<()> {
debug_assert!(storage.banded_tier1, "a streamed tile must band");
if !storage.banded_tier1 {
return Err(
DecodingError::UnsupportedFeature("row streaming for a tile without bands").into(),
);
}
let stage_start = profile::profile_now(profile_enabled);
let components = tile.component_infos.len();
let workspace = tier1::prepare_tier1_workspace(tile, 0..components, header, tile_ctx, storage)?;
let mut tier1_us = 0;
let result = stream_rows(
tile,
header,
tile_ctx,
storage,
sink,
profile_enabled,
&mut tier1_us,
);
tier1::release_tier1_workspace(tile_ctx, storage, &workspace)?;
let elapsed = profile::elapsed_us(stage_start);
profile_timings.codeblock_us += tier1_us;
profile_timings.idwt_us += elapsed.saturating_sub(tier1_us);
result.map_err(|banded_error| {
whole_window_tile_error(tile, header, tile_ctx, storage, banded_error)
})?;
record_workspace(tile_ctx, storage, 0)
}
fn stream_rows<'a>(
tile: &Tile<'a>,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
sink: &mut dyn ComponentRowSink,
profile_enabled: bool,
tier1_us: &mut u128,
) -> Result<()> {
let components = tile.component_infos.len();
let mut bands = Vec::new();
let line_plans = plan_tile_lines(tile, header, tile_ctx, storage, &mut bands)?;
let empty = IntRect::from_xywh(0, 0, 0, 0);
let (copy, region) = line_plans
.first()
.map_or((empty, empty), |plan| (plan.copy, plan.region));
let scratch_len = (copy.width() as usize)
.checked_mul(components)
.ok_or(ValidationError::ImageTooLarge)?;
let (precinct_rows, index_bytes) = index_bands(storage, tile_ctx, &bands)?;
let (mut buffer, ring_lens) =
match take_line_buffer(tile_ctx, storage, &line_plans, scratch_len) {
Ok(taken) => taken,
Err(error) => {
storage.structural_workspace_bytes -= index_bytes;
return Err(error);
}
};
let mut output = RowOutput {
tile,
sink,
round_irreversible_output: tile_ctx.round_irreversible_output,
depths: core::array::from_fn(|index| {
tile_ctx
.channel_data
.get(index)
.map_or((0, false), |component| {
(component.bit_depth, component.signed)
})
}),
copy,
region,
streamed: 0,
};
let mut output_samples = 0usize;
let result = (|| {
let rings_len = buffer.len() - scratch_len;
let (mut rings, scratch) = buffer.split_at_mut(rings_len);
let mut lines = Vec::new();
lines
.try_reserve_exact(line_plans.len())
.map_err(|_| DecodingError::HostAllocationFailed)?;
for (plan, len) in line_plans.iter().zip(&ring_lens) {
let (ring, tail) = core::mem::take(&mut rings).split_at_mut(*len);
rings = tail;
lines.push(plan.reconstruction(ring)?);
}
let mut rows = BandedRows {
header,
component_infos: &tile.component_infos,
tile_ctx: &mut *tile_ctx,
storage: &mut *storage,
bands,
precinct_rows,
profile_enabled,
tier1_us: 0,
};
let result = output
.write(&mut lines, &mut rows, scratch)
.and_then(|()| rows.decode_remaining_bands());
*tier1_us = rows.tier1_us;
output_samples = lines.iter().map(LineReconstruction::output_samples).sum();
result
})();
storage.structural_workspace_bytes -= index_bytes;
tile_ctx.idwt_line_rings = buffer;
tile_ctx.streamed_samples = tile_ctx.streamed_samples.saturating_add(output.streamed);
tile_ctx.debug_counters.idwt_output_samples = tile_ctx
.debug_counters
.idwt_output_samples
.saturating_add(output_samples);
result
}
fn plan_tile_lines(
tile: &Tile<'_>,
header: &Header<'_>,
tile_ctx: &TileDecodeContext,
storage: &DecompositionStorage<'_>,
bands: &mut Vec<SubBandBands>,
) -> Result<Vec<LinePlan>> {
let mut line_plans = Vec::new();
line_plans
.try_reserve_exact(tile.component_infos.len())
.map_err(|_| DecodingError::HostAllocationFailed)?;
let mut missing = 0;
for (component_idx, component_info) in tile.component_infos.iter().enumerate() {
let active_levels = push_component_bands(storage, header, component_idx, bands)?;
match LinePlan::new(
storage,
tile_ctx,
component_idx,
active_levels,
header,
component_info.wavelet_transform(),
)? {
Some(plan) => line_plans.push(plan),
None => missing += 1,
}
}
if missing == tile.component_infos.len() {
line_plans.clear();
return Ok(line_plans);
}
if missing > 0
|| line_plans
.iter()
.any(|plan| plan.copy != line_plans[0].copy || plan.region != line_plans[0].region)
{
return Err(DecodingError::CodeBlockDecodeFailure.into());
}
Ok(line_plans)
}
fn take_line_buffer(
tile_ctx: &mut TileDecodeContext,
storage: &DecompositionStorage<'_>,
line_plans: &[LinePlan],
scratch_len: usize,
) -> Result<(Vec<f32>, Vec<usize>)> {
let mut ring_lens = Vec::new();
ring_lens
.try_reserve_exact(line_plans.len())
.map_err(|_| DecodingError::HostAllocationFailed)?;
for plan in line_plans {
ring_lens.push(plan.ring_len()?);
}
let total = ring_lens
.iter()
.try_fold(scratch_len, |total, len| total.checked_add(*len))
.ok_or(ValidationError::ImageTooLarge)?;
debug_assert!(
total <= storage.roi_idwt_charge_elements,
"the ROI plan charged {} IDWT samples for {total}",
storage.roi_idwt_charge_elements
);
let mut buffer = core::mem::take(&mut tile_ctx.idwt_line_rings);
let resized = if buffer.len() < total {
try_resize_decode_elements(&mut buffer, total, 0.0)
} else {
Ok(())
};
tile_ctx.idwt_line_rings = buffer;
resized?;
record_workspace(tile_ctx, storage, 0)?;
let mut buffer = core::mem::take(&mut tile_ctx.idwt_line_rings);
buffer.truncate(total);
Ok((buffer, ring_lens))
}
struct RowOutput<'o, 't> {
tile: &'o Tile<'t>,
sink: &'o mut dyn ComponentRowSink,
round_irreversible_output: bool,
depths: [(u8, bool); 4],
copy: IntRect,
region: IntRect,
streamed: usize,
}
impl RowOutput<'_, '_> {
fn write(
&mut self,
lines: &mut [LineReconstruction<'_>],
rows: &mut BandedRows<'_, '_>,
scratch: &mut [f32],
) -> Result<()> {
let (copy, components) = (self.copy, lines.len());
let width = copy.width() as usize;
if lines.is_empty() || width == 0 {
return Ok(());
}
for y in copy.y0..copy.y1 {
let mut outputs: [&mut [f32]; 4] = Default::default();
for ((line, output), chunk) in lines
.iter_mut()
.zip(outputs.iter_mut())
.zip(scratch.chunks_exact_mut(width))
{
let (row_y, row) = line
.next_row(rows)?
.ok_or(DecodingError::CodeBlockDecodeFailure)?;
if row_y != y || row.len() != width {
return Err(DecodingError::CodeBlockDecodeFailure.into());
}
chunk.copy_from_slice(row);
*output = chunk;
}
let outputs = &mut outputs[..components];
store::transform_streamed_rows(self.tile, self.round_irreversible_output, outputs)?;
let component_rows: [ComponentPlane<'_>; 4] =
core::array::from_fn(|index| ComponentPlane {
samples: outputs.get(index).map_or(&[][..], |row| &**row),
dimensions: (copy.width(), 1),
bit_depth: self.depths[index].0,
signed: self.depths[index].1,
sampling: (1, 1),
});
self.sink.write_rows(
copy.x0 - self.region.x0,
y - self.region.y0,
&component_rows[..components],
);
self.streamed = self.streamed.saturating_add(width * components);
}
Ok(())
}
}
fn whole_window_tile_error<'a>(
tile: &Tile<'a>,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
banded_error: DecodeError,
) -> DecodeError {
storage.coefficient_layout = build::CoefficientLayout::Component;
if build::lay_out_whole_region_coefficients(storage).is_err() {
return banded_error;
}
for component_idx in 0..tile.component_infos.len() {
storage.stale_coefficients = true;
if let Err(error) = decode_component_tile_bit_planes_budgeted(
tile,
component_idx..component_idx + 1,
tile_ctx,
storage,
header,
&mut None,
CpuDecodeParallelism::Serial,
false,
) {
return error;
}
}
banded_error
}
#[expect(
clippy::too_many_arguments,
reason = "the banded stage shares decode_tile's state and profiling explicitly"
)]
fn decode_bands<'a>(
tile: &Tile<'a>,
component_idx: usize,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
transform: WaveletTransform,
profile_enabled: bool,
tier1_us: &mut u128,
) -> Result<()> {
let mut bands = Vec::new();
let active_levels = push_component_bands(storage, header, component_idx, &mut bands)?;
let (precinct_rows, index_bytes) = index_bands(storage, tile_ctx, &bands)?;
tile_ctx.idwt_output.placement = crate::j2c::idwt::IdwtPlacement::Coefficients;
tile_ctx.idwt_output.coefficients = Vec::new();
tile_ctx.idwt_output.rect = IntRect::from_xywh(0, 0, 0, 0);
let mut lines = match ComponentLines::prepare(
storage,
tile_ctx,
component_idx,
active_levels,
header,
transform,
) {
Ok(lines) => lines,
Err(error) => {
storage.structural_workspace_bytes -= index_bytes;
return Err(error);
}
};
let mut rows = BandedRows {
header,
component_infos: &tile.component_infos,
tile_ctx: &mut *tile_ctx,
storage: &mut *storage,
bands,
precinct_rows,
profile_enabled,
tier1_us: 0,
};
let result = lines.as_mut().map_or(Ok(()), |lines| lines.run(&mut rows));
let result = result.and_then(|()| rows.decode_remaining_bands());
*tier1_us = rows.tier1_us;
drop(rows);
let result = lines.map_or(result, |lines| lines.finish(tile_ctx, result));
storage.structural_workspace_bytes -= index_bytes;
result
}
fn push_component_bands(
storage: &DecompositionStorage<'_>,
header: &Header<'_>,
component_idx: usize,
bands: &mut Vec<SubBandBands>,
) -> Result<usize> {
let tile_decompositions = &storage.tile_decompositions[component_idx];
let active_levels = tile_decompositions
.decompositions
.len()
.checked_sub(usize::from(header.skipped_resolution_levels))
.filter(|levels| *levels > 0)
.ok_or(DecodingError::CodeBlockDecodeFailure)?;
bands
.try_reserve(1 + 3 * active_levels)
.map_err(|_| DecodingError::HostAllocationFailed)?;
for resolution in 0..=active_levels {
let resolution =
u8::try_from(resolution).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
for sub_band in tile_decompositions.sub_band_iter(resolution, &storage.decompositions) {
let stored = storage.sub_bands[sub_band].storage_rect;
bands.push(SubBandBands {
sub_band,
component: component_idx,
resolution,
stored,
slot: IntRect::from_ltrb(stored.x0, stored.y0, stored.x1, stored.y0),
last_band: None,
});
}
}
Ok(active_levels)
}
fn index_bands(
storage: &mut DecompositionStorage<'_>,
tile_ctx: &TileDecodeContext,
bands: &[SubBandBands],
) -> Result<(Option<Vec<Vec<PrecinctRow>>>, usize)> {
let mut index_budget = DecodeAllocationBudget::for_storage(storage)?;
let baseline_bytes = index_budget.live_bytes();
#[cfg(test)]
let precinct_rows = if tile_ctx.reject_band_index {
Err(ValidationError::ImageTooLarge.into())
} else {
build_precinct_rows(storage, bands, &mut index_budget)
}
.ok();
#[cfg(not(test))]
let precinct_rows = {
let _ = tile_ctx;
build_precinct_rows(storage, bands, &mut index_budget).ok()
};
let index_bytes = if precinct_rows.is_some() {
index_budget.live_bytes() - baseline_bytes
} else {
0
};
storage.structural_workspace_bytes = storage
.structural_workspace_bytes
.checked_add(index_bytes)
.ok_or(ValidationError::ImageTooLarge)?;
Ok((precinct_rows, index_bytes))
}
fn whole_window_error<'a>(
tile: &Tile<'a>,
component_idx: usize,
header: &Header<'_>,
tile_ctx: &mut TileDecodeContext,
storage: &mut DecompositionStorage<'a>,
banded_error: DecodeError,
) -> DecodeError {
if build::lay_out_whole_region_coefficients(storage).is_err() {
return banded_error;
}
storage.stale_coefficients = true;
match decode_component_tile_bit_planes_budgeted(
tile,
component_idx..component_idx + 1,
tile_ctx,
storage,
header,
&mut None,
CpuDecodeParallelism::Serial,
false,
) {
Err(error) => error,
Ok(()) => banded_error,
}
}
struct SubBandBands {
sub_band: usize,
component: usize,
resolution: u8,
stored: IntRect,
slot: IntRect,
last_band: Option<u32>,
}
struct PrecinctRow {
first_y: u32,
last_y: u32,
precincts: Range<usize>,
}
fn build_precinct_rows(
storage: &DecompositionStorage<'_>,
bands: &[SubBandBands],
budget: &mut DecodeAllocationBudget,
) -> Result<Vec<Vec<PrecinctRow>>> {
let mut indexed = Vec::new();
budget.reserve_new(&mut indexed, bands.len())?;
for band in bands {
indexed.push(collect_precinct_rows(storage, band.sub_band, budget)?);
}
Ok(indexed)
}
fn collect_precinct_rows(
storage: &DecompositionStorage<'_>,
sub_band: usize,
budget: &mut DecodeAllocationBudget,
) -> Result<Vec<PrecinctRow>> {
let mut rows: Vec<PrecinctRow> = Vec::new();
for index in storage.sub_bands[sub_band].precincts.clone() {
let precinct = &storage.precincts[index];
let blocks = &storage.code_blocks[precinct.code_blocks.clone()];
let Some((first, last)) = blocks.first().zip(blocks.last()) else {
continue;
};
if let Some(row) = rows.last_mut().filter(|row| row.first_y == first.rect.y0) {
row.last_y = row.last_y.max(last.rect.y0);
row.precincts.end = index + 1;
} else {
budget.reserve_additional(&mut rows, 1)?;
rows.push(PrecinctRow {
first_y: first.rect.y0,
last_y: last.rect.y0,
precincts: index..index + 1,
});
}
}
Ok(rows)
}
struct BandedRows<'r, 'a> {
header: &'r Header<'r>,
component_infos: &'r [ComponentInfo],
tile_ctx: &'r mut TileDecodeContext,
storage: &'r mut DecompositionStorage<'a>,
bands: Vec<SubBandBands>,
precinct_rows: Option<Vec<Vec<PrecinctRow>>>,
profile_enabled: bool,
tier1_us: u128,
}
impl BandedRows<'_, '_> {
fn position(&self, sub_band: usize) -> Result<usize> {
self.bands
.iter()
.position(|bands| bands.sub_band == sub_band)
.ok_or(DecodingError::CodeBlockDecodeFailure.into())
}
fn band_precincts(&self, index: usize, y: u32) -> Result<Range<usize>> {
let rows = self
.precinct_rows
.as_ref()
.and_then(|indexed| indexed.get(index))
.ok_or(DecodingError::CodeBlockDecodeFailure)?;
let at = rows.partition_point(|row| row.last_y < y);
rows.get(at)
.filter(|row| row.first_y <= y)
.map(|row| row.precincts.clone())
.ok_or(DecodingError::CodeBlockDecodeFailure.into())
}
fn next_band(&self, index: usize) -> Option<Range<u32>> {
let bands = &self.bands[index];
let Some(indexed) = self.precinct_rows.as_ref() else {
let sub_band = &self.storage.sub_bands[bands.sub_band];
return self.storage.precincts[sub_band.precincts.clone()]
.iter()
.flat_map(|precinct| &self.storage.code_blocks[precinct.code_blocks.clone()])
.map(|block| block.rect)
.filter(|rect| bands.last_band.is_none_or(|last| rect.y0 > last))
.min_by_key(|rect| rect.y0)
.map(|rect| rect.y0..rect.y1);
};
let rows = &indexed[index];
let start = bands
.last_band
.map_or(0, |last| rows.partition_point(|row| row.last_y <= last));
for row in &rows[start..] {
let mut next: Option<IntRect> = None;
for index in row.precincts.clone() {
let precinct = &self.storage.precincts[index];
let blocks = &self.storage.code_blocks[precinct.code_blocks.clone()];
let at = blocks.partition_point(|block| {
bands.last_band.is_some_and(|last| block.rect.y0 <= last)
});
if let Some(block) = blocks.get(at) {
if next.is_none_or(|rect| block.rect.y0 < rect.y0) {
next = Some(block.rect);
}
}
}
if let Some(rect) = next {
return Some(rect.y0..rect.y1);
}
}
None
}
fn decode_band(&mut self, index: usize, rows: Range<u32>) -> Result<()> {
let start = profile::profile_now(self.profile_enabled);
let bands = &self.bands[index];
let (sub_band, resolution, stored) = (bands.sub_band, bands.resolution, bands.stored);
let component_info = self
.component_infos
.get(bands.component)
.ok_or(DecodingError::CodeBlockDecodeFailure)?;
let top = rows.start.clamp(stored.y0, stored.y1);
let bottom = rows.end.clamp(top, stored.y1);
let slot = IntRect::from_ltrb(stored.x0, top, stored.x1, bottom);
let original_precincts = self.storage.sub_bands[sub_band].precincts.clone();
let band_precincts = self
.precinct_rows
.as_ref()
.map(|_| self.band_precincts(index, rows.start))
.transpose()?;
let coefficients = self.storage.sub_bands[sub_band].coefficients.clone();
self.storage
.coefficients
.get_mut(coefficients)
.ok_or(DecodingError::CodeBlockDecodeFailure)?
.fill(0.0);
self.storage.sub_bands[sub_band].storage_rect = slot;
if let Some(precincts) = band_precincts {
self.storage.sub_bands[sub_band].precincts = precincts;
}
self.storage.tier1_band = Some(rows.start);
let result = decode_sub_band_bitplanes(
sub_band,
resolution,
component_info,
self.tile_ctx,
self.storage,
self.header,
&mut None,
CpuDecodeParallelism::Serial,
self.profile_enabled,
);
self.storage.tier1_band = None;
self.storage.sub_bands[sub_band].storage_rect = stored;
self.storage.sub_bands[sub_band].precincts = original_precincts;
let bands = &mut self.bands[index];
bands.slot = slot;
bands.last_band = Some(rows.start);
self.tier1_us += profile::elapsed_us(start);
result
}
fn decode_remaining_bands(&mut self) -> Result<()> {
for index in 0..self.bands.len() {
while let Some(rows) = self.next_band(index) {
self.decode_band(index, rows)?;
}
}
Ok(())
}
}
impl SubBandRows for BandedRows<'_, '_> {
fn prepare_row(&mut self, sub_band: usize, y: u32) -> Result<()> {
let index = self.position(sub_band)?;
let stored = self.bands[index].stored;
if y < stored.y0 || y >= stored.y1 {
return Ok(());
}
while self.bands[index].slot.y1 <= y {
match self.next_band(index) {
Some(rows) if rows.start <= y => self.decode_band(index, rows)?,
_ => {
let bands = &mut self.bands[index];
bands.slot = IntRect::from_ltrb(stored.x0, y, stored.x1, y);
break;
}
}
}
if y < self.bands[index].slot.y0 {
return Err(DecodingError::CodeBlockDecodeFailure.into());
}
Ok(())
}
fn samples(&self, sub_band: usize) -> (&[f32], IntRect, u32) {
let Ok(index) = self.position(sub_band) else {
return (&[], IntRect::from_xywh(0, 0, 0, 0), 0);
};
let bands = &self.bands[index];
let coefficients = self.storage.sub_bands[sub_band].coefficients.clone();
(
&self.storage.coefficients[coefficients],
bands.slot,
bands.stored.width(),
)
}
}