1use 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 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 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 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
192pub enum CpuDecodeParallelism {
193 #[default]
195 Auto,
196 Serial,
198}
199
200pub 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 #[must_use]
229 pub fn cpu_decode_parallelism(&self) -> CpuDecodeParallelism {
230 self.cpu_decode_parallelism
231 }
232
233 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 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 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 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 for (idx, component_info) in header.component_infos.iter().enumerate() {
314 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 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#[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#[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 pub(crate) structural_workspace_bytes: usize,
545 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#[derive(Default)]
605pub(crate) struct TileDecodeContext {
606 pub(crate) idwt_output: IDWTOutput,
608 pub(crate) idwt_scratch_buffer: Vec<f32>,
610 pub(crate) idwt_scratch_buffer_i64: Vec<i64>,
612 pub(crate) bit_plane_decode_context: BitPlaneDecodeContext,
614 pub(crate) bit_plane_decode_buffers: BitPlaneDecodeBuffers,
616 pub(crate) ht_block_decode_context: HtBlockDecodeContext,
618 pub(crate) channel_data: Vec<ComponentData>,
620 pub(crate) output_region: Option<OutputRegion>,
622 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}