Skip to main content

j2k_native/j2c/
decode.rs

1//! Decoding JPEG2000 code streams.
2//!
3//! This is the "core" module of the crate that orchestrates all
4//! stages in such a way that a given codestream is decoded into its
5//! component channels.
6
7use alloc::boxed::Box;
8use alloc::vec::Vec;
9
10use super::bitplane::{BitPlaneDecodeBuffers, BitPlaneDecodeContext};
11use super::build::{CodeBlock, Decomposition, Layer, Precinct, Segment, SubBand, SubBandType};
12use super::codestream::{ComponentInfo, Header, QuantizationStyle, WaveletTransform};
13use super::ht_block_decode::{self, HtBlockDecodeContext};
14use super::idwt::IDWTOutput;
15use super::progression::{progression_iterator, ProgressionData};
16use super::roi::RoiPlan;
17use super::tag_tree::TagNode;
18use super::tile::{ComponentTile, ResolutionTile, Tile};
19use super::{bitplane, build, idwt, mct, segment, tile, ComponentData};
20use crate::error::{
21    bail, ColorError, DecodeError, DecodingError, DirectPlanUnsupportedReason, Result, TileError,
22    ValidationError,
23};
24use crate::j2c::segment::MAX_BITPLANE_COUNT;
25use crate::profile;
26use crate::reader::BitReader;
27use crate::{
28    add_roi_shift_to_bitplanes, apply_roi_maxshift_inverse_i32, apply_roi_maxshift_inverse_i64,
29    decode_j2k_code_block_scalar_with_workspace, HtCodeBlockBatchJob, HtCodeBlockDecodeJob,
30    HtCodeBlockDecoder, HtOwnedCodeBlockBatchJob, HtOwnedSubBandPlan, HtSubBandDecodeJob,
31    J2kCodeBlockBatchJob, J2kCodeBlockDecodeJob, J2kCodeBlockDecodeWorkspace, J2kCodeBlockStyle,
32    J2kDirectBandId, J2kDirectColorPlan, J2kDirectGrayscalePlan, J2kDirectGrayscaleStep,
33    J2kDirectIdwtStep, J2kDirectStoreStep, J2kOwnedCodeBlockBatchJob, J2kOwnedSubBandPlan, J2kRect,
34    J2kStoreComponentJob, J2kSubBandDecodeJob, J2kSubBandType, J2kWaveletTransform,
35};
36use core::mem::size_of;
37use core::ops::Range;
38
39mod allocation;
40mod direct_plan;
41mod reuse;
42mod store;
43mod subband;
44mod subband_params;
45mod tier1;
46pub(crate) use self::allocation::DecodeAllocationBudget;
47use self::direct_plan::collect_classic_code_block_data;
48pub(crate) use self::direct_plan::{build_direct_color_plan, build_direct_grayscale_plan};
49use self::store::{apply_sign_shift_after_mct, component_unsigned_level_shift, store};
50use self::subband::{code_block_required_by_index, decode_component_tile_bit_planes};
51#[cfg(all(test, feature = "parallel"))]
52use self::subband::{
53    copy_decoded_classic_blocks_to_sub_band, copy_decoded_ht_blocks_to_sub_band,
54    DecodedClassicBlock, DecodedHtBlock,
55};
56#[cfg(test)]
57pub(crate) use self::subband::{
58    should_decode_classic_sub_band_in_parallel, should_decode_ht_sub_band_in_parallel,
59};
60use self::subband_params::{
61    classic_decode_job_parameters, ht_code_block_has_decodable_passes, sub_band_decode_parameters,
62    SubBandDecodeParameters,
63};
64
65pub(crate) fn decode<'a>(
66    data: &'a [u8],
67    header: &Header<'a>,
68    retained_image_bytes: usize,
69    ctx: &mut DecoderContext<'a>,
70    ht_decoder: &mut Option<&mut dyn HtCodeBlockDecoder>,
71) -> Result<()> {
72    let mut reused_baseline = ctx.prepare_reused_decode_baseline(retained_image_bytes)?;
73    let profile_enabled = profile::profile_stages_enabled();
74    let total_start = profile::profile_now(profile_enabled);
75    let mut profile_timings = DecodeProfileTimings::default();
76    let stage_start = profile::profile_now(profile_enabled);
77    let mut reader = BitReader::new(data);
78    let mut parsed_tiles = tile::parse(&mut reader, header, reused_baseline.parser_bytes);
79    if reused_baseline.retained_channel_bytes != 0
80        && matches!(
81            parsed_tiles.as_ref(),
82            Err(error) if reuse::is_capacity_error(error)
83        )
84    {
85        // Retained component buffers are an optimization, not a reason for an
86        // otherwise valid decode to fail its aggregate allocation cap.
87        ctx.discard_reused_channels();
88        reused_baseline = reuse::ReusedDecodeBaseline {
89            parser_bytes: retained_image_bytes,
90            retained_channel_bytes: 0,
91        };
92        reader = BitReader::new(data);
93        parsed_tiles = tile::parse(&mut reader, header, reused_baseline.parser_bytes);
94    }
95    let tiles = parsed_tiles?;
96    profile_timings.parse_tiles_us += profile::elapsed_us(stage_start);
97
98    if tiles.is_empty() {
99        bail!(TileError::Invalid);
100    }
101
102    let retained_decode_baseline = ctx.reset(
103        header,
104        &tiles[0],
105        tiles.structural_workspace_bytes(),
106        reused_baseline.retained_channel_bytes,
107    )?;
108    let cpu_decode_parallelism = ctx.cpu_decode_parallelism;
109    let (tile_ctx, storage) = (&mut ctx.tile_decode_context, &mut ctx.storage);
110
111    for tile in tiles.iter() {
112        ltrace!(
113            "tile {} rect [{},{} {}x{}]",
114            tile.idx,
115            tile.rect.x0,
116            tile.rect.y0,
117            tile.rect.width(),
118            tile.rect.height(),
119        );
120
121        decode_tile(
122            tile,
123            header,
124            progression_iterator(tile)?,
125            tile_ctx,
126            storage,
127            ht_decoder,
128            cpu_decode_parallelism,
129            profile_enabled,
130            &mut profile_timings,
131            retained_decode_baseline,
132        )?;
133    }
134
135    // Note that this assumes that either all tiles have MCT or none of them.
136    // In theory, only some could have it... But hopefully no such cursed
137    // images exist!
138    if tiles[0].mct {
139        let stage_start = profile::profile_now(profile_enabled);
140        mct::apply_inverse(tile_ctx, &tiles[0].component_infos, header, ht_decoder)?;
141        apply_sign_shift_after_mct(tile_ctx, &header.component_infos);
142        profile_timings.mct_us += profile::elapsed_us(stage_start);
143    }
144
145    if profile_enabled {
146        emit_decode_profile_row(tile_ctx, &profile_timings, total_start);
147    }
148
149    // The returned image only borrows channel data. Release tile graph,
150    // packet, Tier-1, and IDWT owners before callers allocate packed output so
151    // output conversion does not overlap a completed decode workspace.
152    storage.release_all_allocations();
153    tile_ctx.release_tile_scratch_allocations();
154
155    Ok(())
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub(crate) struct OutputRegion {
160    pub(crate) x: u32,
161    pub(crate) y: u32,
162    pub(crate) width: u32,
163    pub(crate) height: u32,
164}
165
166impl OutputRegion {
167    pub(crate) fn from_tuple(region: (u32, u32, u32, u32)) -> Self {
168        let (x, y, width, height) = region;
169        Self {
170            x,
171            y,
172            width,
173            height,
174        }
175    }
176
177    fn dimensions(self) -> (u32, u32) {
178        (self.width, self.height)
179    }
180}
181
182#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
183pub(crate) struct DecodeDebugCounters {
184    pub(crate) decoded_code_blocks: usize,
185    pub(crate) skipped_code_blocks: usize,
186    pub(crate) idwt_output_samples: usize,
187    pub(crate) ht_phase_stats: ht_block_decode::HtBlockDecodeStats,
188}
189
190/// CPU parallelism policy for native JPEG 2000 decode.
191#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
192pub enum CpuDecodeParallelism {
193    /// Allow a single tile decode to use internal code-block parallelism.
194    #[default]
195    Auto,
196    /// Keep code-block decode serial for callers that already parallelize tiles.
197    Serial,
198}
199
200/// A decoder context for decoding JPEG2000 images.
201pub struct DecoderContext<'a> {
202    pub(crate) tile_decode_context: TileDecodeContext,
203    pub(crate) storage: DecompositionStorage<'a>,
204    cpu_decode_parallelism: CpuDecodeParallelism,
205}
206
207impl Default for DecoderContext<'_> {
208    fn default() -> Self {
209        Self {
210            tile_decode_context: TileDecodeContext::default(),
211            storage: DecompositionStorage::default(),
212            cpu_decode_parallelism: CpuDecodeParallelism::Auto,
213        }
214    }
215}
216
217impl DecoderContext<'_> {
218    pub(crate) fn release_reusable_allocations(&mut self) {
219        self.tile_decode_context.release_all_allocations();
220        self.storage.release_all_allocations();
221    }
222
223    pub(crate) fn set_output_region(&mut self, output_region: Option<(u32, u32, u32, u32)>) {
224        self.tile_decode_context.output_region = output_region.map(OutputRegion::from_tuple);
225    }
226
227    /// Return the native CPU decode parallelism policy.
228    #[must_use]
229    pub fn cpu_decode_parallelism(&self) -> CpuDecodeParallelism {
230        self.cpu_decode_parallelism
231    }
232
233    /// Set the native CPU decode parallelism policy.
234    pub fn set_cpu_decode_parallelism(&mut self, parallelism: CpuDecodeParallelism) {
235        self.cpu_decode_parallelism = parallelism;
236    }
237}
238
239#[expect(
240    clippy::too_many_arguments,
241    reason = "this codec boundary keeps geometry, state buffers, and validated options explicit without allocation or indirection"
242)]
243fn decode_tile<'a, 'b>(
244    tile: &'b Tile<'a>,
245    header: &Header<'_>,
246    progression_iterator: Box<dyn Iterator<Item = ProgressionData> + '_>,
247    tile_ctx: &mut TileDecodeContext,
248    storage: &mut DecompositionStorage<'a>,
249    ht_decoder: &mut Option<&mut dyn HtCodeBlockDecoder>,
250    cpu_decode_parallelism: CpuDecodeParallelism,
251    profile_enabled: bool,
252    profile_timings: &mut DecodeProfileTimings,
253    retained_decode_baseline: usize,
254) -> Result<()> {
255    storage.reset_for_next_tile();
256    tile_ctx.release_tile_scratch_allocations();
257    storage.exact_integer_decode = tile_requires_exact_integer_decode(tile);
258    if storage.exact_integer_decode {
259        validate_exact_integer_decode_tile(tile)?;
260        if tile_ctx.output_region.is_some() {
261            bail!(DecodingError::UnsupportedFeature(
262                "25-38 bit region decode requires exact integer region IDWT support"
263            ));
264        }
265    }
266
267    // This is the method that orchestrates all steps.
268
269    // First, we build the decompositions, including their sub-bands, precincts
270    // and code blocks.
271    let stage_start = profile::profile_now(profile_enabled);
272    build::build(
273        tile,
274        storage,
275        retained_decode_baseline,
276        tile_ctx.output_region.is_some(),
277        build::BuildWorkspace::DecodePixels {
278            skipped_resolution_levels: header.skipped_resolution_levels,
279        },
280    )?;
281    if let Some(output_region) = tile_ctx.output_region {
282        storage.roi_plan = RoiPlan::build(tile, header, storage, output_region)?;
283        if storage.roi_plan.is_some() {
284            storage.coefficients.fill(0.0);
285            if storage.exact_integer_decode {
286                storage.coefficients_i64.fill(0);
287            }
288        } else {
289            build::release_unused_roi_workspace(storage, tile.component_infos.len())?;
290        }
291    }
292    profile_timings.build_us += profile::elapsed_us(stage_start);
293    // Next, we parse the layers/segments for each code block.
294    let stage_start = profile::profile_now(profile_enabled);
295    segment::parse(tile, progression_iterator, header, storage)?;
296    profile_timings.segment_us += profile::elapsed_us(stage_start);
297    // We then decode the bitplanes of each code block, yielding the
298    // (possibly dequantized) coefficients of each code block.
299    let stage_start = profile::profile_now(profile_enabled);
300    decode_component_tile_bit_planes_budgeted(
301        tile,
302        tile_ctx,
303        storage,
304        header,
305        ht_decoder,
306        cpu_decode_parallelism,
307        profile_enabled,
308    )?;
309    profile_timings.codeblock_us += profile::elapsed_us(stage_start);
310
311    // Unlike before, we interleave the apply_idwt and store stages
312    // for each component tile so we can reuse allocations better.
313    for (idx, component_info) in header.component_infos.iter().enumerate() {
314        // Next, we apply the inverse discrete wavelet transform.
315        let stage_start = profile::profile_now(profile_enabled);
316        tile_ctx.release_idwt_allocations();
317        idwt::apply(
318            storage,
319            tile_ctx,
320            idx,
321            header,
322            component_info.wavelet_transform(),
323            ht_decoder,
324        )?;
325        profile_timings.idwt_us += profile::elapsed_us(stage_start);
326        // Finally, we store the raw samples for the tile area in the correct
327        // location. Note that in case we have MCT, we are not applying it yet.
328        // It will be applied in the very end once all tiles have been processed.
329        // The reason we do this is that applying MCT requires access to the
330        // data from _all_ components. If we didn't defer this until the end
331        // we would have to collect the IDWT outputs of all components before
332        // applying it. By not applying MCT here, we can get away with doing
333        // IDWT and store on a per-component basis. Thus, we only need to
334        // store one IDWT output at a time, allowing for better reuse of
335        // allocations.
336        let stage_start = profile::profile_now(profile_enabled);
337        store(tile, header, tile_ctx, component_info, idx, ht_decoder)?;
338        profile_timings.store_us += profile::elapsed_us(stage_start);
339    }
340
341    Ok(())
342}
343
344pub(crate) fn decode_component_tile_bit_planes_budgeted<'a>(
345    tile: &Tile<'a>,
346    tile_ctx: &mut TileDecodeContext,
347    storage: &mut DecompositionStorage<'a>,
348    header: &Header<'_>,
349    ht_decoder: &mut Option<&mut dyn HtCodeBlockDecoder>,
350    cpu_decode_parallelism: CpuDecodeParallelism,
351    profile_enabled: bool,
352) -> Result<()> {
353    let tier1_workspace_bytes = tier1::prepare_tier1_workspace(tile, header, tile_ctx, storage)?;
354    let decode_result = decode_component_tile_bit_planes(
355        tile,
356        tile_ctx,
357        storage,
358        header,
359        ht_decoder,
360        cpu_decode_parallelism,
361        profile_enabled,
362    );
363    tier1::release_tier1_workspace(tile_ctx, storage, tier1_workspace_bytes)?;
364    decode_result
365}
366
367fn tile_requires_exact_integer_decode(tile: &Tile<'_>) -> bool {
368    tile.component_infos
369        .iter()
370        .any(ComponentInfo::requires_exact_integer_decode)
371}
372
373fn validate_exact_integer_decode_tile(tile: &Tile<'_>) -> Result<()> {
374    for component in &tile.component_infos {
375        if component.size_info.precision > 38 {
376            bail!(DecodingError::UnsupportedFeature(
377                "JPEG 2000 Part 1 component precision is limited to 38 bits"
378            ));
379        }
380        if component.wavelet_transform() != WaveletTransform::Reversible53 {
381            bail!(DecodingError::UnsupportedFeature(
382                "25-38 bit decode currently requires reversible 5/3 coding"
383            ));
384        }
385        if component.quantization_info.quantization_style != QuantizationStyle::NoQuantization {
386            bail!(DecodingError::UnsupportedFeature(
387                "25-38 bit decode currently requires reversible no-quantization coding"
388            ));
389        }
390    }
391    Ok(())
392}
393
394#[derive(Default)]
395#[expect(
396    clippy::struct_field_names,
397    reason = "the repeated _us suffix makes every profiling unit explicit"
398)]
399struct DecodeProfileTimings {
400    parse_tiles_us: u128,
401    build_us: u128,
402    segment_us: u128,
403    codeblock_us: u128,
404    idwt_us: u128,
405    store_us: u128,
406    mct_us: u128,
407}
408
409#[cold]
410#[inline(never)]
411fn emit_decode_profile_row(
412    tile_ctx: &TileDecodeContext,
413    profile_timings: &DecodeProfileTimings,
414    total_start: Option<profile::ProfileInstant>,
415) {
416    profile::emit_profile_row(
417        "decode",
418        "cpu",
419        &[
420            ("parse_tiles_us", profile_timings.parse_tiles_us),
421            ("build_us", profile_timings.build_us),
422            ("segment_us", profile_timings.segment_us),
423            ("codeblock_us", profile_timings.codeblock_us),
424            ("ht_blocks", tile_ctx.debug_counters.ht_phase_stats.blocks),
425            (
426                "ht_refinement_blocks",
427                tile_ctx.debug_counters.ht_phase_stats.refinement_blocks,
428            ),
429            (
430                "ht_cleanup_bytes",
431                tile_ctx.debug_counters.ht_phase_stats.cleanup_bytes,
432            ),
433            (
434                "ht_refinement_bytes",
435                tile_ctx.debug_counters.ht_phase_stats.refinement_bytes,
436            ),
437            (
438                "ht_cleanup_us",
439                tile_ctx.debug_counters.ht_phase_stats.ht_cleanup_us,
440            ),
441            (
442                "ht_mag_sgn_us",
443                tile_ctx.debug_counters.ht_phase_stats.ht_mag_sgn_us,
444            ),
445            (
446                "ht_sigma_us",
447                tile_ctx.debug_counters.ht_phase_stats.ht_sigma_us,
448            ),
449            (
450                "ht_sigprop_us",
451                tile_ctx.debug_counters.ht_phase_stats.ht_sigprop_us,
452            ),
453            (
454                "ht_magref_us",
455                tile_ctx.debug_counters.ht_phase_stats.ht_magref_us,
456            ),
457            ("idwt_us", profile_timings.idwt_us),
458            ("store_us", profile_timings.store_us),
459            ("mct_us", profile_timings.mct_us),
460            ("total_us", profile::elapsed_us(total_start)),
461        ],
462    );
463}
464
465/// All decompositions for a single tile.
466#[derive(Clone)]
467pub(crate) struct TileDecompositions {
468    pub(crate) first_ll_sub_band: usize,
469    pub(crate) decompositions: Range<usize>,
470}
471
472impl TileDecompositions {
473    pub(crate) fn sub_band_iter(
474        &self,
475        resolution: u8,
476        decompositions: &[Decomposition],
477    ) -> SubBandIter {
478        let indices = if resolution == 0 {
479            [
480                self.first_ll_sub_band,
481                self.first_ll_sub_band,
482                self.first_ll_sub_band,
483            ]
484        } else {
485            decompositions[self.decompositions.clone()][resolution as usize - 1].sub_bands
486        };
487
488        SubBandIter {
489            next_idx: 0,
490            indices,
491            resolution,
492        }
493    }
494}
495
496#[derive(Clone)]
497pub(crate) struct SubBandIter {
498    resolution: u8,
499    next_idx: usize,
500    indices: [usize; 3],
501}
502
503impl Iterator for SubBandIter {
504    type Item = usize;
505
506    fn next(&mut self) -> Option<Self::Item> {
507        let value = if self.resolution == 0 {
508            if self.next_idx > 0 {
509                None
510            } else {
511                Some(self.indices[0])
512            }
513        } else if self.next_idx >= self.indices.len() {
514            None
515        } else {
516            Some(self.indices[self.next_idx])
517        };
518
519        self.next_idx += 1;
520
521        value
522    }
523}
524
525/// Owned decomposition workspace for one active tile.
526///
527/// Within an image, reset retains capacities and the next build counts them in
528/// its live baseline. Crossing to a new image drops every retained allocation.
529#[derive(Default)]
530pub(crate) struct DecompositionStorage<'a> {
531    pub(crate) segments: Vec<Segment<'a>>,
532    pub(crate) layers: Vec<Layer>,
533    pub(crate) code_blocks: Vec<CodeBlock>,
534    pub(crate) precincts: Vec<Precinct>,
535    pub(crate) tag_tree_nodes: Vec<TagNode>,
536    pub(crate) coefficients: Vec<f32>,
537    pub(crate) coefficients_i64: Vec<i64>,
538    pub(crate) sub_bands: Vec<SubBand>,
539    pub(crate) decompositions: Vec<Decomposition>,
540    pub(crate) tile_decompositions: Vec<TileDecompositions>,
541    pub(crate) roi_plan: Option<RoiPlan>,
542    pub(crate) exact_integer_decode: bool,
543    /// Planned non-segment live bytes for the active tile and future decode scratch.
544    pub(crate) structural_workspace_bytes: usize,
545    /// Allocation/cap failure raised from the option-based packet parser.
546    pub(crate) packet_workspace_error: Option<DecodeError>,
547}
548
549impl DecompositionStorage<'_> {
550    pub(crate) fn reset_for_next_tile(&mut self) {
551        self.segments.clear();
552        self.layers.clear();
553        self.code_blocks.clear();
554        self.precincts.clear();
555        self.tag_tree_nodes.clear();
556        self.coefficients.clear();
557        self.coefficients_i64.clear();
558        self.sub_bands.clear();
559        self.decompositions.clear();
560        self.tile_decompositions.clear();
561        self.roi_plan = None;
562        self.exact_integer_decode = false;
563        self.structural_workspace_bytes = 0;
564        self.packet_workspace_error = None;
565    }
566
567    pub(crate) fn release_all_allocations(&mut self) {
568        *self = Self::default();
569    }
570
571    pub(crate) fn retained_capacity_bytes(&self) -> Result<usize> {
572        let mut bytes = 0_usize;
573        include_capacity::<Segment<'_>>(&mut bytes, self.segments.capacity())?;
574        include_capacity::<Layer>(&mut bytes, self.layers.capacity())?;
575        include_capacity::<CodeBlock>(&mut bytes, self.code_blocks.capacity())?;
576        include_capacity::<Precinct>(&mut bytes, self.precincts.capacity())?;
577        include_capacity::<TagNode>(&mut bytes, self.tag_tree_nodes.capacity())?;
578        include_capacity::<f32>(&mut bytes, self.coefficients.capacity())?;
579        include_capacity::<i64>(&mut bytes, self.coefficients_i64.capacity())?;
580        include_capacity::<SubBand>(&mut bytes, self.sub_bands.capacity())?;
581        include_capacity::<Decomposition>(&mut bytes, self.decompositions.capacity())?;
582        include_capacity::<TileDecompositions>(&mut bytes, self.tile_decompositions.capacity())?;
583        Ok(bytes)
584    }
585}
586
587fn include_capacity<T>(bytes: &mut usize, capacity: usize) -> Result<()> {
588    let additional = capacity
589        .checked_mul(size_of::<T>())
590        .ok_or(ValidationError::ImageTooLarge)?;
591    *bytes = bytes
592        .checked_add(additional)
593        .ok_or(ValidationError::ImageTooLarge)?;
594    if *bytes > crate::DEFAULT_MAX_DECODE_BYTES {
595        return Err(ValidationError::ImageTooLarge.into());
596    }
597    Ok(())
598}
599
600/// A reusable context used during the decoding of a single tile.
601///
602/// Some of the fields are temporary in nature and reset after moving on to the
603/// next tile, some contain global state.
604#[derive(Default)]
605pub(crate) struct TileDecodeContext {
606    /// A reusable buffer for the IDWT output.
607    pub(crate) idwt_output: IDWTOutput,
608    /// A scratch buffer used during IDWT.
609    pub(crate) idwt_scratch_buffer: Vec<f32>,
610    /// A scratch buffer used during exact reversible integer IDWT.
611    pub(crate) idwt_scratch_buffer_i64: Vec<i64>,
612    /// A reusable context for decoding code blocks.
613    pub(crate) bit_plane_decode_context: BitPlaneDecodeContext,
614    /// Reusable buffers for decoding bitplanes.
615    pub(crate) bit_plane_decode_buffers: BitPlaneDecodeBuffers,
616    /// A reusable context for decoding HTJ2K code blocks.
617    pub(crate) ht_block_decode_context: HtBlockDecodeContext,
618    /// The raw, decoded samples for each channel.
619    pub(crate) channel_data: Vec<ComponentData>,
620    /// Optional output window for region-local decode storage.
621    pub(crate) output_region: Option<OutputRegion>,
622    /// Debug counters for tests and ROI instrumentation.
623    pub(crate) debug_counters: DecodeDebugCounters,
624}
625
626impl TileDecodeContext {
627    fn release_all_allocations(&mut self) {
628        let output_region = self.output_region;
629        *self = Self::default();
630        self.output_region = output_region;
631    }
632
633    fn release_tile_scratch_allocations(&mut self) {
634        self.release_idwt_allocations();
635        self.release_tier1_allocations();
636    }
637
638    fn release_tier1_allocations(&mut self) {
639        self.bit_plane_decode_context = BitPlaneDecodeContext::default();
640        self.bit_plane_decode_buffers = BitPlaneDecodeBuffers::default();
641        self.ht_block_decode_context = HtBlockDecodeContext::default();
642    }
643
644    pub(crate) fn tier1_capacity_bytes(&self) -> Result<usize> {
645        let classic_bytes = self.bit_plane_decode_context.allocated_bytes()?;
646        let buffer_bytes = self.bit_plane_decode_buffers.allocated_bytes()?;
647        let ht_bytes = self.ht_block_decode_context.allocated_bytes()?;
648        classic_bytes
649            .checked_add(buffer_bytes)
650            .and_then(|bytes| bytes.checked_add(ht_bytes))
651            .ok_or(ValidationError::ImageTooLarge.into())
652    }
653
654    fn release_idwt_allocations(&mut self) {
655        self.idwt_output = IDWTOutput::default();
656        self.idwt_scratch_buffer = Vec::new();
657        self.idwt_scratch_buffer_i64 = Vec::new();
658    }
659}
660
661#[cfg(test)]
662mod tests {
663    use super::{
664        collect_classic_code_block_data, CodeBlock, DecodeAllocationBudget, DecoderContext,
665        DecompositionStorage, Layer, Segment,
666    };
667    use crate::error::DecodingError;
668    #[cfg(feature = "parallel")]
669    use crate::j2c::build::{SubBand, SubBandType};
670    use crate::j2c::codestream::CodeBlockStyle;
671    use crate::j2c::rect::IntRect;
672    use alloc::vec;
673
674    fn classic_test_style() -> CodeBlockStyle {
675        CodeBlockStyle {
676            selective_arithmetic_coding_bypass: false,
677            reset_context_probabilities: false,
678            termination_on_each_pass: true,
679            vertically_causal_context: false,
680            segmentation_symbols: false,
681            high_throughput_block_coding: false,
682        }
683    }
684
685    fn classic_test_code_block() -> CodeBlock {
686        CodeBlock {
687            rect: IntRect::from_xywh(0, 0, 1, 1),
688            x_idx: 0,
689            y_idx: 0,
690            layers: 0..1,
691            has_been_included: true,
692            missing_bit_planes: 0,
693            number_of_coding_passes: 3,
694            l_block: 3,
695            non_empty_layer_count: 1,
696        }
697    }
698
699    #[test]
700    fn cross_image_release_drops_reusable_decode_capacities() {
701        let mut ctx = DecoderContext::default();
702        ctx.storage.coefficients.reserve(64);
703        ctx.storage.segments.reserve(16);
704        ctx.tile_decode_context.idwt_scratch_buffer.reserve(64);
705        ctx.tile_decode_context
706            .bit_plane_decode_context
707            .reserve_coefficients_for_test(64);
708
709        ctx.release_reusable_allocations();
710
711        assert_eq!(ctx.storage.coefficients.capacity(), 0);
712        assert_eq!(ctx.storage.segments.capacity(), 0);
713        assert_eq!(ctx.tile_decode_context.idwt_scratch_buffer.capacity(), 0);
714        assert_eq!(
715            ctx.tile_decode_context
716                .bit_plane_decode_context
717                .coefficient_capacity_for_test(),
718            0
719        );
720    }
721
722    #[test]
723    fn next_tile_reset_preserves_storage_capacity_for_accounted_reuse() {
724        let mut storage = DecompositionStorage::default();
725        storage.coefficients.reserve(64);
726        storage.layers.reserve(16);
727        let coefficient_capacity = storage.coefficients.capacity();
728        let layer_capacity = storage.layers.capacity();
729
730        storage.reset_for_next_tile();
731
732        assert_eq!(storage.coefficients.capacity(), coefficient_capacity);
733        assert_eq!(storage.layers.capacity(), layer_capacity);
734    }
735
736    #[test]
737    fn collect_classic_code_block_data_preserves_zero_length_segments() {
738        let mut storage = DecompositionStorage::default();
739        storage.layers.push(Layer {
740            segments: Some(0..3),
741        });
742        storage.segments.push(Segment {
743            idx: 0,
744            coding_pases: 1,
745            data_length: 1,
746            data: &[0xAA],
747        });
748        storage.segments.push(Segment {
749            idx: 1,
750            coding_pases: 1,
751            data_length: 0,
752            data: &[],
753        });
754        storage.segments.push(Segment {
755            idx: 2,
756            coding_pases: 1,
757            data_length: 1,
758            data: &[0xBB],
759        });
760
761        let mut budget = DecodeAllocationBudget::for_storage(&storage).expect("budget baseline");
762        let (combined_data, segments) = collect_classic_code_block_data(
763            &classic_test_code_block(),
764            &classic_test_style(),
765            &storage,
766            &mut budget,
767        )
768        .expect("collect classic segments");
769
770        assert_eq!(combined_data, vec![0xAA, 0xBB]);
771        assert_eq!(segments.len(), 3);
772        assert_eq!(segments[0].data_offset, 0);
773        assert_eq!(segments[0].data_length, 1);
774        assert_eq!(segments[0].start_coding_pass, 0);
775        assert_eq!(segments[0].end_coding_pass, 1);
776        assert_eq!(segments[1].data_offset, 1);
777        assert_eq!(segments[1].data_length, 0);
778        assert_eq!(segments[1].start_coding_pass, 1);
779        assert_eq!(segments[1].end_coding_pass, 2);
780        assert_eq!(segments[2].data_offset, 1);
781        assert_eq!(segments[2].data_length, 1);
782        assert_eq!(segments[2].start_coding_pass, 2);
783        assert_eq!(segments[2].end_coding_pass, 3);
784    }
785
786    #[test]
787    fn collect_classic_code_block_data_rejects_non_contiguous_segment_indices() {
788        let mut storage = DecompositionStorage::default();
789        storage.layers.push(Layer {
790            segments: Some(0..2),
791        });
792        storage.segments.push(Segment {
793            idx: 0,
794            coding_pases: 1,
795            data_length: 1,
796            data: &[0xAA],
797        });
798        storage.segments.push(Segment {
799            idx: 2,
800            coding_pases: 2,
801            data_length: 1,
802            data: &[0xBB],
803        });
804
805        let mut budget = DecodeAllocationBudget::for_storage(&storage).expect("budget baseline");
806        let error = collect_classic_code_block_data(
807            &classic_test_code_block(),
808            &classic_test_style(),
809            &storage,
810            &mut budget,
811        )
812        .expect_err("non-contiguous segment indices must fail");
813
814        assert_eq!(error, DecodingError::CodeBlockDecodeFailure.into());
815    }
816
817    #[cfg(feature = "parallel")]
818    fn copyback_test_sub_band(width: u32, height: u32) -> (SubBand, DecompositionStorage<'static>) {
819        let len = (width * height) as usize;
820        let storage = DecompositionStorage {
821            coefficients: vec![-1.0; len],
822            ..DecompositionStorage::default()
823        };
824        let sub_band = SubBand {
825            sub_band_type: SubBandType::LowLow,
826            rect: IntRect::from_xywh(0, 0, width, height),
827            precincts: 0..0,
828            coefficients: 0..len,
829        };
830        (sub_band, storage)
831    }
832
833    #[cfg(feature = "parallel")]
834    #[test]
835    fn decoded_classic_block_copyback_covers_full_block() {
836        let (sub_band, mut storage) = copyback_test_sub_band(4, 3);
837        let block = super::DecodedClassicBlock {
838            output_x: 0,
839            output_y: 0,
840            width: 4,
841            height: 3,
842            coefficients: (0..12)
843                .map(|value| f32::from(u8::try_from(value).expect("test value fits u8")))
844                .collect(),
845        };
846
847        super::copy_decoded_classic_blocks_to_sub_band(&[block], &sub_band, &mut storage)
848            .expect("full classic block copyback");
849
850        assert_eq!(
851            storage.coefficients,
852            (0..12)
853                .map(|value| f32::from(u8::try_from(value).expect("test value fits u8")))
854                .collect::<Vec<_>>()
855        );
856    }
857
858    #[cfg(feature = "parallel")]
859    #[test]
860    fn decoded_ht_block_copyback_covers_partial_edge_block() {
861        let (sub_band, mut storage) = copyback_test_sub_band(5, 3);
862        let block = super::DecodedHtBlock {
863            output_x: 3,
864            output_y: 1,
865            width: 2,
866            height: 2,
867            coefficients: vec![1.0, 2.0, 3.0, 4.0],
868        };
869
870        super::copy_decoded_ht_blocks_to_sub_band(&[block], &sub_band, &mut storage)
871            .expect("partial HT block copyback");
872
873        assert_eq!(
874            storage.coefficients,
875            vec![
876                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 2.0, -1.0, -1.0, -1.0, 3.0,
877                4.0,
878            ]
879        );
880    }
881
882    #[cfg(feature = "parallel")]
883    #[test]
884    fn decoded_block_copyback_rejects_out_of_bounds_blocks() {
885        let (sub_band, mut storage) = copyback_test_sub_band(5, 3);
886        let block = super::DecodedClassicBlock {
887            output_x: 4,
888            output_y: 1,
889            width: 2,
890            height: 1,
891            coefficients: vec![1.0, 2.0],
892        };
893
894        let error =
895            super::copy_decoded_classic_blocks_to_sub_band(&[block], &sub_band, &mut storage)
896                .expect_err("out-of-bounds block must fail");
897
898        assert_eq!(error, DecodingError::CodeBlockDecodeFailure.into());
899    }
900
901    #[cfg(feature = "parallel")]
902    #[test]
903    fn auto_cpu_parallelism_enables_ht_sub_band_parallel_branch() {
904        assert!(super::should_decode_ht_sub_band_in_parallel(
905            super::CpuDecodeParallelism::Auto,
906            16
907        ));
908    }
909
910    #[test]
911    fn serial_cpu_parallelism_disables_ht_sub_band_parallel_branch() {
912        assert!(!super::should_decode_ht_sub_band_in_parallel(
913            super::CpuDecodeParallelism::Serial,
914            16
915        ));
916    }
917}