use alloc::vec::Vec;
use core::ops::Range;
use super::super::super::codestream::WaveletTransform;
use super::super::super::rect::IntRect;
use super::{
apply_vertical_step, horizontal_scratch_elements, packed_band_rects, parallel_strip_layout,
periodic_symmetric_extension_left, periodic_symmetric_extension_right,
split_horizontal_scratch, synthesize_level_row, three_rows, use_banded, vertical_steps,
Level2d, RING_ROWS,
};
use crate::error::{bail, DecodingError};
use crate::math::{dispatch, Level, Simd};
use crate::{try_resize_decode_elements, Result, ValidationError};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct LastLevel {
rect: IntRect,
offsets: [usize; 3],
transform: WaveletTransform,
}
impl LastLevel {
pub(crate) fn new(
rect: IntRect,
bands: [(IntRect, Range<usize>); 3],
transform: WaveletTransform,
) -> Option<Self> {
if !use_banded(rect.width() as usize, rect.height() as usize) {
return None;
}
let packed = packed_band_rects(rect);
let mut offsets = [0; 3];
for ((offset, packed), (band, range)) in offsets.iter_mut().zip(&packed[1..]).zip(bands) {
if band != *packed || range.len() != band_len(band)? {
return None;
}
*offset = range.start;
}
if band_len(packed[0])? > *offsets.iter().min()? {
return None;
}
Some(Self {
rect,
offsets,
transform,
})
}
pub(crate) fn ll_rect(&self) -> IntRect {
packed_band_rects(self.rect)[0]
}
fn level<'a>(&self, plane: &'a [f32], ll: &'a [f32]) -> Option<Level2d<'a>> {
let packed = packed_band_rects(self.rect);
let mut bands = [ll.get(..band_len(packed[0])?)?; 4];
for ((band, rect), offset) in bands[1..].iter_mut().zip(&packed[1..]).zip(self.offsets) {
*band = plane.get(offset..offset.checked_add(band_len(*rect)?)?)?;
}
Some(Level2d::new(
self.rect,
bands,
self.transform,
super::super::OPENJPEG_NORMALIZED_HIGH_PASS_F32,
))
}
}
const SERIAL_BATCH_ROWS: usize = 16;
#[cfg(feature = "parallel")]
const PARALLEL_BATCH_ROWS: usize = 8;
const RING_ROW_PAD: usize = 16;
fn band_len(rect: IntRect) -> Option<usize> {
(rect.width() as usize).checked_mul(rect.height() as usize)
}
const fn ring_rows(batch: usize) -> usize {
batch + RING_ROWS - 1
}
fn worker_scratch_elements(width: usize, batch: usize) -> Result<usize> {
(width + RING_ROW_PAD)
.checked_mul(3 * ring_rows(batch))
.and_then(|rings| rings.checked_add(horizontal_scratch_elements(width).ok()?))
.ok_or(ValidationError::ImageTooLarge.into())
}
pub(crate) fn pack_last_level(
level: LastLevel,
planes: [&[f32]; 3],
first_ll: &[f32],
scratch: [&mut [f32]; 2],
output: &mut [u8],
parallel: bool,
emit: impl Fn([&mut [f32]; 3], &mut [u8]) + Sync,
) -> Result<()> {
let lls = [first_ll, planes[0], planes[1]];
let levels = [0, 1, 2].map(|component| level.level(planes[component], lls[component]));
let [Some(r), Some(g), Some(b)] = levels else {
bail!(DecodingError::CodeBlockDecodeFailure);
};
let levels = [r, g, b];
let (width, height) = (r.width, r.height);
let row_bytes = 3 * width;
if output.len() != row_bytes * height {
bail!(DecodingError::OutputBufferTooSmall);
}
let Some(layout) = parallel_strip_layout(width, height, parallel)? else {
let batch = SERIAL_BATCH_ROWS;
let mut scratch = Vec::new();
try_resize_decode_elements(&mut scratch, worker_scratch_elements(width, batch)?, 0.0)?;
dispatch!(Level::new(), simd => stream_rows(
simd, &levels, 0, batch, output, &mut scratch, &emit,
));
return Ok(());
};
#[cfg(feature = "parallel")]
{
let strip_bytes = layout.strip_len / width * row_bytes;
let workers = layout.concurrent_strips;
let per_worker = worker_scratch_elements(width, PARALLEL_BATCH_ROWS)?;
let complete = dispatch!(Level::new(), simd => run_strips(
simd, &levels, output, strip_bytes, workers, per_worker, scratch, &emit,
));
if !complete {
bail!(DecodingError::HostAllocationFailed);
}
}
#[cfg(not(feature = "parallel"))]
let _ = (layout, scratch);
Ok(())
}
#[cfg(feature = "parallel")]
#[inline(always)]
#[expect(
clippy::too_many_arguments,
reason = "the strip driver keeps geometry and reused scratch explicit"
)]
fn run_strips<S: Simd>(
simd: S,
levels: &[Level2d<'_>; 3],
output: &mut [u8],
strip_bytes: usize,
workers: usize,
per_worker: usize,
scratch: [&mut [f32]; 2],
emit: &(impl Fn([&mut [f32]; 3], &mut [u8]) + Sync),
) -> bool {
use core::sync::atomic::{AtomicBool, Ordering};
use rayon::prelude::*;
use std::sync::Mutex;
let row_bytes = 3 * levels[0].width;
let strips = Mutex::new(output.chunks_mut(strip_bytes).enumerate());
let next = || strips.lock().ok().and_then(|mut strips| strips.next());
let [coarse, bands] = scratch;
let spare = Mutex::new(
coarse
.chunks_exact_mut(per_worker)
.chain(bands.chunks_exact_mut(per_worker)),
);
let failed = AtomicBool::new(false);
(0..workers).into_par_iter().with_max_len(1).for_each(|_| {
let mut owned = Vec::new();
let scratch = if let Some(scratch) = spare.lock().ok().and_then(|mut spare| spare.next()) {
scratch
} else {
if try_resize_decode_elements(&mut owned, per_worker, 0.0).is_err() {
failed.store(true, Ordering::Relaxed);
return;
}
&mut owned
};
#[cfg(feature = "simd")]
simd.vectorize(
#[inline(always)]
|| {
while let Some((strip, output)) = next() {
let first_row = strip * strip_bytes / row_bytes;
stream_rows(
simd,
levels,
first_row,
PARALLEL_BATCH_ROWS,
output,
scratch,
emit,
);
}
},
);
#[cfg(not(feature = "simd"))]
while let Some((strip, output)) = next() {
let first_row = strip * strip_bytes / row_bytes;
stream_rows(
simd,
levels,
first_row,
PARALLEL_BATCH_ROWS,
output,
scratch,
emit,
);
}
});
!failed.load(Ordering::Relaxed)
}
#[expect(
clippy::inline_always,
reason = "the strip kernel is specialized at the architecture dispatch boundary"
)]
#[inline(always)]
fn stream_rows<S: Simd>(
simd: S,
levels: &[Level2d<'_>; 3],
first_row: usize,
batch: usize,
output: &mut [u8],
scratch: &mut [f32],
emit: &impl Fn([&mut [f32]; 3], &mut [u8]),
) {
let width = levels[0].width;
let height = levels[0].height;
let row_bytes = 3 * width;
let end = first_row + output.len() / row_bytes;
let (steps, count) = vertical_steps(levels[0]);
let lo = first_row.saturating_sub(count);
let hi = (end + count).min(height);
let stride = width + RING_ROW_PAD;
let ring_rows = ring_rows(batch);
let ring_len = ring_rows * stride;
let (rings, horizontal_scratch) = scratch.split_at_mut(3 * ring_len);
let (low, high) = split_horizontal_scratch(levels[0], horizontal_scratch);
let (r, rest) = rings.split_at_mut(ring_len);
let (g, b) = rest.split_at_mut(ring_len);
let mut rings = [r, g, b];
let slot = |row: usize| row % ring_rows;
let mut emitted = first_row;
let mut emit_rows = |rings: &mut [&mut [f32]; 3], rows_end: usize| {
for row in emitted..rows_end {
let start = slot(row) * stride;
let samples = rings.each_mut().map(|ring| &mut ring[start..][..width]);
emit(
samples,
&mut output[(row - first_row) * row_bytes..][..row_bytes],
);
}
emitted = emitted.max(rows_end);
};
for first in (lo..hi + count).step_by(batch) {
let arrivals = first..(first + batch).min(hi + count);
for (level, ring) in levels.iter().zip(rings.iter_mut()) {
for arrival in arrivals.clone() {
if arrival < hi {
let row = &mut ring[slot(arrival) * stride..][..width];
synthesize_level_row(simd, *level, arrival, row, low, high);
}
for (index, &(parity, step)) in steps[..count].iter().enumerate() {
let Some(row) = arrival.checked_sub(index + 1) else {
break;
};
if row < lo || row >= hi || row % 2 != parity {
continue;
}
let above = periodic_symmetric_extension_left(row, 1);
let below = periodic_symmetric_extension_right(row, 1, height);
if above < lo || below >= hi {
continue;
}
let (current, above, below) =
three_rows(ring, stride, slot(row), slot(above), slot(below));
let (current, above, below) =
(&mut current[..width], &above[..width], &below[..width]);
apply_vertical_step(simd, step, current, above, below);
}
}
}
let unread = (arrivals.end - 1).saturating_sub(count);
emit_rows(&mut rings, unread.min(end));
}
emit_rows(&mut rings, end);
}