Skip to main content

j2k_native/
lib.rs

1/*!
2Internal pure-Rust JPEG 2000 codec engine for `j2k`.
3
4This module tree was imported from the `dicom-toolkit-jpeg2000` 0.5.0 crate
5and adapted in-repo so `j2k` no longer depends on an external
6production decoder crate.
7
8`dicom-toolkit-jpeg2000` is the JPEG 2000 engine used by `dicom-toolkit-rs`.
9It is a maintained fork of the original `hayro-jpeg2000` project with
10DICOM-focused extensions, including native-bit-depth decode for 8/12/16-bit
11images and pure-Rust JPEG 2000 encoding.
12
13The crate can decode raw JPEG 2000 codestreams (`.j2c`) and still-image JP2/JPH
14wrappers. It implements the JPEG 2000 core coding system (ISO/IEC 15444-1) and
15HTJ2K block coding (ISO/IEC 15444-15) through the support boundary documented in
16`docs/public-support.md`. The remaining declared gaps are tracked there instead
17of being hidden behind a broad "vast majority" claim.
18
19The crate offers both a high-level 8-bit decode path for general image use and
20a native-bit-depth decode path for integrations such as DICOM, plus encoder APIs
21for emitting raw JPEG 2000 and HTJ2K codestreams.
22
23# Example
24```rust,no_run
25use j2k_native::{DecodeSettings, Image};
26
27let data = std::fs::read("image.jp2").unwrap();
28let image = Image::new(&data, &DecodeSettings::default()).unwrap();
29
30println!(
31    "{}x{} image in {:?} with alpha={}",
32    image.width(),
33    image.height(),
34    image.color_space(),
35    image.has_alpha(),
36);
37
38let bitmap = image.decode().unwrap();
39```
40
41If you want to see a more comprehensive example, please take a look
42at the example in [GitHub](https://github.com/knopkem/dicom-toolkit-rs/blob/main/crates/dicom-toolkit-jpeg2000/examples/png.rs),
43which shows the main steps needed to convert a JPEG 2000 image into PNG.
44
45# Testing
46The decoder has been tested against 20.000+ images scraped from random PDFs
47on the internet and also passes a large part of the `OpenJPEG` test suite. So you
48can expect the crate to perform decently in terms of decoding correctness.
49
50# Performance
51A decent amount of effort has already been put into optimizing this crate
52(both in terms of raw performance but also memory allocations). However, there
53are some more important optimizations that have not been implemented yet, so
54there is definitely still room for improvement (and I am planning on implementing
55them eventually).
56
57Overall, you should expect this crate to have worse performance than `OpenJPEG`,
58but the difference gap should not be too large.
59
60# Safety
61By default, the crate has the `simd` feature enabled, which uses the
62[`fearless_simd`](https://github.com/linebender/fearless_simd) crate to accelerate
63important parts of the pipeline. If you want to eliminate any usage of unsafe
64in this crate as well as its dependencies, you can simply disable this
65feature, at the cost of worse decoding performance. Unsafe code is forbidden
66via a crate-level attribute.
67
68The crate is `no_std` compatible but requires an allocator to be available.
69*/
70
71#![cfg_attr(not(feature = "std"), no_std)]
72#![forbid(unsafe_code)]
73#![forbid(missing_docs)]
74#![allow(clippy::too_many_arguments)]
75
76extern crate alloc;
77
78use alloc::vec;
79use alloc::vec::Vec;
80
81use crate::error::{bail, err};
82use crate::j2c::{ComponentData, Header};
83use crate::jp2::cdef::{ChannelAssociation, ChannelType};
84use crate::jp2::cmap::ComponentMappingType;
85use crate::jp2::colr::{CieLab, EnumeratedColorspace};
86use crate::jp2::icc::ICCMetadata;
87use crate::jp2::{DecodedImage, ImageBoxes};
88
89pub mod error;
90mod inspect;
91#[macro_use]
92pub(crate) mod log;
93mod direct_cpu;
94mod direct_plan;
95pub(crate) mod math;
96#[doc(hidden)]
97pub mod packet_math;
98pub(crate) mod profile;
99pub(crate) mod writer;
100
101use crate::math::{dispatch, f32x8, Level, Simd, SIMD_WIDTH};
102pub use direct_cpu::{
103    execute_direct_color_plan_rgb8_into, execute_direct_color_plan_rgba8_into, J2kDirectCpuScratch,
104};
105pub use direct_plan::{
106    HtOwnedCodeBlockBatchJob, HtOwnedSubBandPlan, J2kDirectBandId, J2kDirectColorPlan,
107    J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kDirectIdwtStep, J2kDirectStoreStep,
108    J2kOwnedCodeBlockBatchJob, J2kOwnedSubBandPlan,
109};
110pub use inspect::{
111    inspect_j2k_codestream_header, looks_like_j2k_codestream, J2kCodestreamComponentHeader,
112    J2kCodestreamHeaderError, J2kCodestreamHeaderMetadata,
113};
114
115/// Maps an output coordinate within an IDWT step to the source sub-band index.
116///
117/// `origin` is the global coordinate of the IDWT output rectangle,
118/// `local_coord` is the coordinate within that output rectangle, and
119/// `low_pass` selects the low-pass (`LL`/`LH`) or high-pass (`HL`/`HH`) band
120/// along one axis. This helper is exposed so backend adapters can compute
121/// required input windows with the same odd-origin rounding as the native IDWT.
122#[must_use]
123pub fn idwt_band_index(origin: u32, local_coord: u32, low_pass: bool) -> u32 {
124    let global = u64::from(origin) + u64::from(local_coord);
125    let origin = u64::from(origin);
126    let index = if low_pass {
127        global.div_ceil(2).saturating_sub(origin.div_ceil(2))
128    } else {
129        (global / 2).saturating_sub(origin / 2)
130    };
131    u32::try_from(index).unwrap_or(u32::MAX)
132}
133
134pub use error::{
135    ColorError, DecodeError, DecodingError, FormatError, MarkerError, Result, TileError,
136    ValidationError,
137};
138pub use j2c::encode::{
139    encode, encode_component_planes_53, encode_htj2k, encode_precomputed_htj2k_53,
140    encode_precomputed_htj2k_53_with_accelerator, encode_precomputed_htj2k_53_with_mct,
141    encode_precomputed_htj2k_53_with_mct_and_accelerator, encode_precomputed_htj2k_97,
142    encode_precomputed_htj2k_97_batch_with_accelerator,
143    encode_precomputed_htj2k_97_with_accelerator, encode_precomputed_j2k_53,
144    encode_precomputed_j2k_53_with_accelerator, encode_precomputed_j2k_53_with_mct,
145    encode_precomputed_j2k_53_with_mct_and_accelerator, encode_preencoded_htj2k_97,
146    encode_preencoded_htj2k_97_compact_owned_with_accelerator,
147    encode_preencoded_htj2k_97_owned_with_accelerator, encode_preencoded_htj2k_97_with_accelerator,
148    encode_prequantized_htj2k_97, encode_prequantized_htj2k_97_with_accelerator,
149    encode_typed_component_planes_53, encode_with_accelerator,
150    encode_with_accelerator_and_roi_regions, encode_with_roi_regions,
151    irreversible_quantization_step_for_subband, EncodeComponentPlane, EncodeOptions,
152    EncodeProgressionOrder, EncodeRoiRegion, EncodeTypedComponentPlane,
153};
154pub use j2c::{CpuDecodeParallelism, DecoderContext, Reversible53CoefficientImage};
155pub use j2k_types::{
156    CpuOnlyJ2kEncodeStageAccelerator, EncodedHtJ2kCodeBlock, EncodedJ2kCodeBlock,
157    IrreversibleQuantizationStep, IrreversibleQuantizationSubbandScales, J2kCodeBlockSegment,
158    J2kCodeBlockStyle, J2kDeinterleaveToF32Job, J2kEncodeDispatchReport, J2kForwardDwt53Job,
159    J2kForwardDwt53Level, J2kForwardDwt53Output, J2kForwardDwt97Job, J2kForwardDwt97Level,
160    J2kForwardDwt97Output, J2kForwardIctJob, J2kForwardRctJob, J2kHtCodeBlockEncodeJob,
161    J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob, J2kPacketizationBlockCodingMode,
162    J2kPacketizationCodeBlock, J2kPacketizationEncodeJob, J2kPacketizationPacketDescriptor,
163    J2kPacketizationProgressionOrder, J2kPacketizationResolution, J2kPacketizationSubband,
164    J2kQuantizeSubbandJob, J2kSubBandType, J2kTier1CodeBlockEncodeJob, PrecomputedHtj2k53Component,
165    PrecomputedHtj2k53Image, PrecomputedHtj2k97Component, PrecomputedHtj2k97Image,
166    PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
167    PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
168    PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
169    PreencodedHtj2k97Component, PreencodedHtj2k97Image, PreencodedHtj2k97Resolution,
170    PreencodedHtj2k97Subband, PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component,
171    PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
172};
173
174mod j2c;
175mod jp2;
176pub(crate) mod reader;
177pub use j2c::ht_encode_tables::HtUvlcTableEntry;
178
179const MAX_CLASSIC_DECODE_BITPLANES: u8 = j2c::MAX_BITPLANE_COUNT;
180pub(crate) const MAX_J2K_SPEC_COMPONENTS: u16 = 16_384;
181pub(crate) const MAX_J2K_IMAGE_DIMENSION: u32 = 60_000;
182pub(crate) const MAX_J2K_TILE_COUNT: u64 = u16::MAX as u64 + 1;
183pub(crate) const DEFAULT_MAX_DECODE_BYTES: usize = 512 * 1024 * 1024;
184
185#[inline]
186pub(crate) fn checked_decode_usize_product2(left: usize, right: usize) -> Result<usize> {
187    left.checked_mul(right)
188        .ok_or(ValidationError::ImageTooLarge.into())
189}
190
191#[inline]
192fn checked_decode_byte_cap(len: usize) -> Result<usize> {
193    if len > DEFAULT_MAX_DECODE_BYTES {
194        bail!(ValidationError::ImageTooLarge);
195    }
196    Ok(len)
197}
198
199#[inline]
200pub(crate) fn checked_decode_byte_len2(left: usize, right: usize) -> Result<usize> {
201    checked_decode_byte_cap(checked_decode_usize_product2(left, right)?)
202}
203
204#[inline]
205pub(crate) fn checked_decode_byte_len3(first: usize, second: usize, third: usize) -> Result<usize> {
206    let partial = checked_decode_usize_product2(first, second)?;
207    checked_decode_byte_cap(checked_decode_usize_product2(partial, third)?)
208}
209
210#[inline]
211pub(crate) fn checked_decode_byte_len4(
212    first: usize,
213    second: usize,
214    third: usize,
215    fourth: usize,
216) -> Result<usize> {
217    let partial = checked_decode_usize_product2(first, second)?;
218    let partial = checked_decode_usize_product2(partial, third)?;
219    checked_decode_byte_cap(checked_decode_usize_product2(partial, fourth)?)
220}
221
222#[inline]
223pub(crate) fn checked_decode_sample_count(width: u32, height: u32) -> Result<usize> {
224    #[cfg(target_pointer_width = "64")]
225    {
226        Ok((u64::from(width) * u64::from(height)) as usize)
227    }
228
229    #[cfg(not(target_pointer_width = "64"))]
230    {
231        checked_decode_usize_product2(width as usize, height as usize)
232    }
233}
234
235#[inline]
236fn native_bytes_per_sample(bit_depth: u8) -> Result<usize> {
237    if bit_depth == 0 || bit_depth > 63 {
238        bail!(ValidationError::ImageTooLarge);
239    }
240    Ok(usize::from(bit_depth).div_ceil(8).max(1))
241}
242
243/// Adapter HTJ2K code-block job description for backend experimentation.
244#[derive(Debug, Clone, Copy)]
245pub struct HtCodeBlockDecodeJob<'a> {
246    /// Combined cleanup/refinement bytes for the code block.
247    pub data: &'a [u8],
248    /// Cleanup segment length in bytes.
249    pub cleanup_length: u32,
250    /// Refinement segment length in bytes.
251    pub refinement_length: u32,
252    /// Code-block width in samples.
253    pub width: u32,
254    /// Code-block height in samples.
255    pub height: u32,
256    /// Output row stride, in samples, for the target sub-band storage.
257    pub output_stride: usize,
258    /// Missing most-significant bit planes for this code block.
259    pub missing_bit_planes: u8,
260    /// Number of coding passes present for this code block.
261    pub number_of_coding_passes: u8,
262    /// Total coded bitplanes for the parent sub-band.
263    pub num_bitplanes: u8,
264    /// Region-of-interest maxshift value from RGN marker metadata.
265    pub roi_shift: u8,
266    /// Whether vertically causal context was enabled.
267    pub stripe_causal: bool,
268    /// Whether strict decode validation is enabled for the parent image.
269    pub strict: bool,
270    /// Dequantization step to apply to decoded coefficients.
271    pub dequantization_step: f32,
272}
273
274/// Adapter HTJ2K scalar decode phase limit for backend experimentation.
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum HtCodeBlockDecodePhaseLimit {
277    /// Stop after the cleanup pass has produced coefficient magnitudes/signs.
278    Cleanup,
279    /// Stop after the significance propagation refinement pass.
280    SignificancePropagation,
281    /// Decode through the magnitude refinement pass when present.
282    MagnitudeRefinement,
283}
284
285/// Adapter HTJ2K batched code-block decode job for one sub-band.
286#[derive(Debug, Clone, Copy)]
287pub struct HtCodeBlockBatchJob<'a> {
288    /// X offset within the target sub-band coefficient buffer.
289    pub output_x: u32,
290    /// Y offset within the target sub-band coefficient buffer.
291    pub output_y: u32,
292    /// The actual code-block decode parameters.
293    pub code_block: HtCodeBlockDecodeJob<'a>,
294}
295
296/// Adapter HTJ2K batched sub-band decode request for backend experimentation.
297#[derive(Debug, Clone, Copy)]
298pub struct HtSubBandDecodeJob<'a> {
299    /// Sub-band width in samples.
300    pub width: u32,
301    /// Sub-band height in samples.
302    pub height: u32,
303    /// Code blocks to decode into this sub-band.
304    pub jobs: &'a [HtCodeBlockBatchJob<'a>],
305}
306
307/// Adapter Classic Tier-1 compact token segment for backend experimentation.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct J2kTier1TokenSegment {
310    /// Bit offset of this segment within the compact token buffer.
311    pub token_bit_offset: u32,
312    /// Number of token bits in this segment.
313    ///
314    /// Arithmetic segments contain 6-bit MQ tokens. Raw bypass segments contain
315    /// one bit per raw bypass event.
316    pub token_bit_count: u32,
317    /// First coding pass covered by this segment.
318    pub start_coding_pass: u8,
319    /// One-past-last coding pass covered by this segment.
320    pub end_coding_pass: u8,
321    /// Whether this segment should be packed through the MQ arithmetic path.
322    pub use_arithmetic: bool,
323}
324
325/// Adapter classic J2K code-block job description for backend experimentation.
326#[derive(Debug, Clone, Copy)]
327pub struct J2kCodeBlockDecodeJob<'a> {
328    /// Combined payload bytes for all coded segments in this code block.
329    pub data: &'a [u8],
330    /// Coded segments for the code block.
331    pub segments: &'a [J2kCodeBlockSegment],
332    /// Code-block width in samples.
333    pub width: u32,
334    /// Code-block height in samples.
335    pub height: u32,
336    /// Output row stride, in samples, for the target sub-band storage.
337    pub output_stride: usize,
338    /// Missing most-significant bit planes for this code block.
339    pub missing_bit_planes: u8,
340    /// Number of coding passes present for this code block.
341    pub number_of_coding_passes: u8,
342    /// Total coded bitplanes for the parent sub-band.
343    pub total_bitplanes: u8,
344    /// Region-of-interest maxshift value from RGN marker metadata.
345    pub roi_shift: u8,
346    /// The sub-band type containing this code block.
347    pub sub_band_type: J2kSubBandType,
348    /// The code-block style flags.
349    pub style: J2kCodeBlockStyle,
350    /// Whether strict decode validation is enabled for the parent image.
351    pub strict: bool,
352    /// Dequantization step to apply to decoded coefficients.
353    pub dequantization_step: f32,
354}
355
356/// Adapter HTJ2K cleanup-encode shape counters for backend benchmarking.
357#[derive(Debug, Clone, Default, PartialEq, Eq)]
358pub struct HtCleanupEncodeDistribution {
359    /// Total 2x2 cleanup quads visited.
360    pub total_quads: u64,
361    /// Quads encoded in the first cleanup row pair.
362    pub initial_quads: u64,
363    /// Quads encoded after the first cleanup row pair.
364    pub non_initial_quads: u64,
365    /// All-quad `rho` histogram, indexed by the low four `rho` bits.
366    pub rho_counts: [u64; 16],
367    /// First-row-pair `rho` histogram, indexed by the low four `rho` bits.
368    pub initial_rho_counts: [u64; 16],
369    /// Non-initial-row `rho` histogram, indexed by the low four `rho` bits.
370    pub non_initial_rho_counts: [u64; 16],
371    /// Non-initial-row `u_q` histogram.
372    pub non_initial_u_q_counts: [u64; 32],
373    /// Non-initial-row `e_qmax` histogram.
374    pub non_initial_e_qmax_counts: [u64; 32],
375    /// Non-initial-row `kappa` histogram.
376    pub non_initial_kappa_counts: [u64; 32],
377    /// Non-initial-row joint `rho`/`u_q` histogram.
378    pub non_initial_rho_u_q_counts: [[u64; 32]; 16],
379    /// Calls that emitted at least one magnitude/sign sample.
380    pub mag_sign_calls: u64,
381    /// Magnitude/sign call histogram, indexed by the low four `rho` bits.
382    pub mag_sign_rho_counts: [u64; 16],
383    /// Encoded magnitude/sign sample payload bit-count histogram.
384    pub mag_sign_sample_bit_counts: [u64; 32],
385    /// Number of individual magnitude/sign samples emitted.
386    pub mag_sign_encoded_samples: u64,
387}
388
389/// Adapter JPEG 2000 encode-stage accelerator for backend experimentation.
390pub trait J2kEncodeStageAccelerator {
391    /// Report cumulative backend dispatches completed by this accelerator.
392    fn dispatch_report(&self) -> J2kEncodeDispatchReport {
393        J2kEncodeDispatchReport::default()
394    }
395
396    /// Optionally deinterleave interleaved pixel bytes into f32 component planes.
397    ///
398    /// Return `Ok(Some(components))` with one plane per component. Return
399    /// `Ok(None)` to use the CPU fallback.
400    fn encode_deinterleave(
401        &mut self,
402        _job: J2kDeinterleaveToF32Job<'_>,
403    ) -> core::result::Result<Option<Vec<Vec<f32>>>, &'static str> {
404        Ok(None)
405    }
406
407    /// Optionally apply forward RCT in place.
408    ///
409    /// Return `Ok(true)` after writing transformed planes. Return `Ok(false)`
410    /// to use the CPU fallback.
411    fn encode_forward_rct(
412        &mut self,
413        _job: J2kForwardRctJob<'_>,
414    ) -> core::result::Result<bool, &'static str> {
415        Ok(false)
416    }
417
418    /// Optionally apply forward ICT in place.
419    ///
420    /// Return `Ok(true)` after writing transformed planes. Return `Ok(false)`
421    /// to use the CPU fallback.
422    fn encode_forward_ict(
423        &mut self,
424        _job: J2kForwardIctJob<'_>,
425    ) -> core::result::Result<bool, &'static str> {
426        Ok(false)
427    }
428
429    /// Optionally run a forward reversible 5/3 DWT.
430    ///
431    /// Return `Ok(Some(output))` with all subbands populated. Return
432    /// `Ok(None)` to use the CPU fallback.
433    fn encode_forward_dwt53(
434        &mut self,
435        _job: J2kForwardDwt53Job<'_>,
436    ) -> core::result::Result<Option<J2kForwardDwt53Output>, &'static str> {
437        Ok(None)
438    }
439
440    /// Optionally run a forward irreversible 9/7 DWT.
441    ///
442    /// Return `Ok(Some(output))` with all subbands populated. Return
443    /// `Ok(None)` to use the CPU fallback.
444    fn encode_forward_dwt97(
445        &mut self,
446        _job: J2kForwardDwt97Job<'_>,
447    ) -> core::result::Result<Option<J2kForwardDwt97Output>, &'static str> {
448        Ok(None)
449    }
450
451    /// Optionally quantize one sub-band.
452    ///
453    /// Return `Ok(Some(coefficients))` with one quantized coefficient for each
454    /// input coefficient. Return `Ok(None)` to use the CPU fallback.
455    fn encode_quantize_subband(
456        &mut self,
457        _job: J2kQuantizeSubbandJob<'_>,
458    ) -> core::result::Result<Option<Vec<i32>>, &'static str> {
459        Ok(None)
460    }
461
462    /// Optionally encode one classic Tier-1 code-block.
463    ///
464    /// Return `Ok(Some(output))` with encoded bytes and pass metadata. Return
465    /// `Ok(None)` to use the CPU fallback.
466    fn encode_tier1_code_block(
467        &mut self,
468        _job: J2kTier1CodeBlockEncodeJob<'_>,
469    ) -> core::result::Result<Option<EncodedJ2kCodeBlock>, &'static str> {
470        Ok(None)
471    }
472
473    /// Optionally encode multiple classic Tier-1 code-blocks in one backend dispatch.
474    ///
475    /// Return `Ok(Some(outputs))` with one encoded output per input job. Return
476    /// `Ok(None)` to use the per-block hook or CPU fallback.
477    fn encode_tier1_code_blocks(
478        &mut self,
479        _jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
480    ) -> core::result::Result<Option<Vec<EncodedJ2kCodeBlock>>, &'static str> {
481        Ok(None)
482    }
483
484    /// Optionally encode one HTJ2K code-block.
485    ///
486    /// Return `Ok(Some(output))` with encoded bytes and pass metadata. Return
487    /// `Ok(None)` to use the CPU fallback.
488    fn encode_ht_code_block(
489        &mut self,
490        _job: J2kHtCodeBlockEncodeJob<'_>,
491    ) -> core::result::Result<Option<EncodedHtJ2kCodeBlock>, &'static str> {
492        Ok(None)
493    }
494
495    /// Optionally encode multiple HTJ2K code-blocks in one backend dispatch.
496    ///
497    /// Return `Ok(Some(outputs))` with one encoded output per input job. Return
498    /// `Ok(None)` to use the per-block hook or CPU fallback.
499    fn encode_ht_code_blocks(
500        &mut self,
501        _jobs: &[J2kHtCodeBlockEncodeJob<'_>],
502    ) -> core::result::Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
503        Ok(None)
504    }
505
506    /// Optionally quantize and encode one HTJ2K cleanup/refinement sub-band.
507    ///
508    /// Return `Ok(Some(outputs))` with one encoded output per code block in
509    /// raster code-block order. Return `Ok(None)` to use the separate
510    /// quantization and code-block hooks or CPU fallback.
511    fn encode_ht_subband(
512        &mut self,
513        _job: J2kHtSubbandEncodeJob<'_>,
514    ) -> core::result::Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
515        Ok(None)
516    }
517
518    /// Optionally encode the complete HTJ2K tile packet body.
519    ///
520    /// Return `Ok(Some(bytes))` with the complete tile bitstream body. CPU
521    /// marker/header writing remains outside this hook. Return `Ok(None)` to
522    /// use the normal staged encode pipeline.
523    fn encode_htj2k_tile(
524        &mut self,
525        _job: J2kHtj2kTileEncodeJob<'_>,
526    ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
527        Ok(None)
528    }
529
530    /// Return whether native CPU code-block fallback should use internal rayon parallelism.
531    ///
532    /// External accelerators keep serial per-block fallback so their hooks still
533    /// observe every fallback block after a declined batch hook.
534    fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
535        false
536    }
537
538    /// Return whether whole-tile CPU-only batch encode may be parallelized by callers.
539    ///
540    /// This is narrower than [`Self::prefer_parallel_cpu_code_block_fallback`]:
541    /// callers must only bypass the supplied accelerator when it is known to
542    /// have no observable hooks.
543    fn prefer_parallel_cpu_tile_encode(&self) -> bool {
544        false
545    }
546
547    /// Optionally packetize prepared packet contributions.
548    ///
549    /// Return `Ok(Some(bytes))` with the complete tile bitstream. Return
550    /// `Ok(None)` to use the CPU fallback.
551    fn encode_packetization(
552        &mut self,
553        _job: J2kPacketizationEncodeJob<'_>,
554    ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
555        Ok(None)
556    }
557}
558
559impl J2kEncodeStageAccelerator for CpuOnlyJ2kEncodeStageAccelerator {
560    fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
561        true
562    }
563
564    fn prefer_parallel_cpu_tile_encode(&self) -> bool {
565        true
566    }
567}
568
569/// Adapter classic J2K batched code-block decode job for one sub-band.
570#[derive(Debug, Clone, Copy)]
571pub struct J2kCodeBlockBatchJob<'a> {
572    /// X offset within the target sub-band coefficient buffer.
573    pub output_x: u32,
574    /// Y offset within the target sub-band coefficient buffer.
575    pub output_y: u32,
576    /// The actual code-block decode parameters.
577    pub code_block: J2kCodeBlockDecodeJob<'a>,
578}
579
580/// Adapter classic J2K batched sub-band decode request for backend experimentation.
581#[derive(Debug, Clone, Copy)]
582pub struct J2kSubBandDecodeJob<'a> {
583    /// Sub-band width in samples.
584    pub width: u32,
585    /// Sub-band height in samples.
586    pub height: u32,
587    /// Code blocks to decode into this sub-band.
588    pub jobs: &'a [J2kCodeBlockBatchJob<'a>],
589}
590
591/// Adapter integer rectangle for backend experimentation.
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub struct J2kRect {
594    /// Inclusive minimum x coordinate.
595    pub x0: u32,
596    /// Inclusive minimum y coordinate.
597    pub y0: u32,
598    /// Exclusive maximum x coordinate.
599    pub x1: u32,
600    /// Exclusive maximum y coordinate.
601    pub y1: u32,
602}
603
604impl J2kRect {
605    /// Rectangle width in samples.
606    pub fn width(self) -> u32 {
607        self.x1.saturating_sub(self.x0)
608    }
609
610    /// Rectangle height in samples.
611    pub fn height(self) -> u32 {
612        self.y1.saturating_sub(self.y0)
613    }
614}
615
616/// Adapter wavelet transform selector for backend experimentation.
617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
618pub enum J2kWaveletTransform {
619    /// Reversible 5/3 transform.
620    Reversible53,
621    /// Irreversible 9/7 transform.
622    Irreversible97,
623}
624
625/// Adapter single sub-band payload for backend experimentation.
626#[derive(Debug, Clone, Copy)]
627pub struct J2kIdwtBand<'a> {
628    /// Rect covered by this band.
629    pub rect: J2kRect,
630    /// Band coefficients in row-major order.
631    pub coefficients: &'a [f32],
632}
633
634/// Adapter single-decomposition IDWT job for backend experimentation.
635#[derive(Debug, Clone, Copy)]
636pub struct J2kSingleDecompositionIdwtJob<'a> {
637    /// Output rect of the decomposition level.
638    pub rect: J2kRect,
639    /// Transform to apply.
640    pub transform: J2kWaveletTransform,
641    /// LL band input.
642    pub ll: J2kIdwtBand<'a>,
643    /// HL band input.
644    pub hl: J2kIdwtBand<'a>,
645    /// LH band input.
646    pub lh: J2kIdwtBand<'a>,
647    /// HH band input.
648    pub hh: J2kIdwtBand<'a>,
649}
650
651/// Adapter inverse MCT job for backend experimentation.
652#[derive(Debug)]
653pub struct J2kInverseMctJob<'a> {
654    /// Transform to apply.
655    pub transform: J2kWaveletTransform,
656    /// First component plane, updated in place.
657    pub plane0: &'a mut [f32],
658    /// Second component plane, updated in place.
659    pub plane1: &'a mut [f32],
660    /// Third component plane, updated in place.
661    pub plane2: &'a mut [f32],
662    /// Constant sign-shift addend applied to the first plane after inverse MCT.
663    pub addend0: f32,
664    /// Constant sign-shift addend applied to the second plane after inverse MCT.
665    pub addend1: f32,
666    /// Constant sign-shift addend applied to the third plane after inverse MCT.
667    pub addend2: f32,
668}
669
670/// Adapter component-store job for backend experimentation.
671#[derive(Debug)]
672pub struct J2kStoreComponentJob<'a> {
673    /// Source IDWT coefficients in row-major order.
674    pub input: &'a [f32],
675    /// Source row width.
676    pub input_width: u32,
677    /// Source x offset to begin copying from.
678    pub source_x: u32,
679    /// Source y offset to begin copying from.
680    pub source_y: u32,
681    /// Number of samples to copy per row.
682    pub copy_width: u32,
683    /// Number of rows to copy.
684    pub copy_height: u32,
685    /// Destination component plane in row-major order.
686    pub output: &'a mut [f32],
687    /// Destination row width.
688    pub output_width: u32,
689    /// Destination x offset to begin writing at.
690    pub output_x: u32,
691    /// Destination y offset to begin writing at.
692    pub output_y: u32,
693    /// Constant value added to every copied sample.
694    pub addend: f32,
695}
696
697/// Adapter HTJ2K code-block decode hook for backend experimentation.
698pub trait HtCodeBlockDecoder {
699    /// Optionally decode a full classic J2K sub-band in one batch.
700    ///
701    /// Implementations should return `Ok(true)` if they handled the request and
702    /// wrote the decoded coefficients into `output`. Returning `Ok(false)`
703    /// falls back to per-code-block decode via `decode_j2k_code_block`.
704    fn decode_j2k_sub_band(
705        &mut self,
706        _job: J2kSubBandDecodeJob<'_>,
707        _output: &mut [f32],
708    ) -> Result<bool> {
709        Ok(false)
710    }
711
712    /// Optionally decode one classic J2K code block.
713    ///
714    /// Implementations should return `Ok(true)` if they handled the request
715    /// and wrote the decoded coefficients into `output`. Returning `Ok(false)`
716    /// falls back to the scalar bitplane decoder.
717    fn decode_j2k_code_block(
718        &mut self,
719        _job: J2kCodeBlockDecodeJob<'_>,
720        _output: &mut [f32],
721    ) -> Result<bool> {
722        Ok(false)
723    }
724
725    /// Optionally decode a full HTJ2K sub-band in one batch.
726    ///
727    /// Implementations should return `Ok(true)` if they handled the request and
728    /// wrote the decoded coefficients into `output`. Returning `Ok(false)`
729    /// falls back to per-code-block decode via `decode_code_block`.
730    fn decode_sub_band(
731        &mut self,
732        _job: HtSubBandDecodeJob<'_>,
733        _output: &mut [f32],
734    ) -> Result<bool> {
735        Ok(false)
736    }
737
738    /// Optionally decode one single-decomposition IDWT level on a backend.
739    ///
740    /// Implementations should return `Ok(true)` if they handled the request
741    /// and wrote the transformed coefficients into `output`. Returning
742    /// `Ok(false)` falls back to the scalar/SIMD CPU IDWT path.
743    fn decode_single_decomposition_idwt(
744        &mut self,
745        _job: J2kSingleDecompositionIdwtJob<'_>,
746        _output: &mut [f32],
747    ) -> Result<bool> {
748        Ok(false)
749    }
750
751    /// Optionally apply inverse MCT on a backend.
752    ///
753    /// Implementations should return `Ok(true)` if they handled the request
754    /// and updated the component planes in place. Returning `Ok(false)` falls
755    /// back to the scalar/SIMD CPU MCT path.
756    fn decode_inverse_mct(&mut self, _job: J2kInverseMctJob<'_>) -> Result<bool> {
757        Ok(false)
758    }
759
760    /// Optionally store one component plane on a backend.
761    ///
762    /// Implementations should return `Ok(true)` if they handled the request
763    /// and updated the destination plane in place. Returning `Ok(false)` falls
764    /// back to the CPU store path.
765    fn decode_store_component(&mut self, _job: J2kStoreComponentJob<'_>) -> Result<bool> {
766        Ok(false)
767    }
768
769    /// Decode one HTJ2K code block into `output`, writing `job.width` samples per row.
770    fn decode_code_block(
771        &mut self,
772        job: HtCodeBlockDecodeJob<'_>,
773        output: &mut [f32],
774    ) -> Result<()>;
775}
776
777fn internal_j2k_sub_band_type(sub_band_type: J2kSubBandType) -> j2c::build::SubBandType {
778    match sub_band_type {
779        J2kSubBandType::LowLow => j2c::build::SubBandType::LowLow,
780        J2kSubBandType::HighLow => j2c::build::SubBandType::HighLow,
781        J2kSubBandType::LowHigh => j2c::build::SubBandType::LowHigh,
782        J2kSubBandType::HighHigh => j2c::build::SubBandType::HighHigh,
783    }
784}
785
786fn internal_j2k_code_block_style(style: J2kCodeBlockStyle) -> j2c::codestream::CodeBlockStyle {
787    j2c::codestream::CodeBlockStyle {
788        selective_arithmetic_coding_bypass: style.selective_arithmetic_coding_bypass,
789        reset_context_probabilities: style.reset_context_probabilities,
790        termination_on_each_pass: style.termination_on_each_pass,
791        vertically_causal_context: style.vertically_causal_context,
792        segmentation_symbols: style.segmentation_symbols,
793        high_throughput_block_coding: false,
794    }
795}
796
797pub(crate) fn add_roi_shift_to_bitplanes(
798    bitplanes: u8,
799    roi_shift: u8,
800    max_bitplanes: u8,
801) -> Result<u8> {
802    let Some(coded_bitplanes) = bitplanes.checked_add(roi_shift) else {
803        bail!(DecodingError::TooManyBitplanes);
804    };
805    if coded_bitplanes > max_bitplanes {
806        bail!(DecodingError::TooManyBitplanes);
807    }
808    Ok(coded_bitplanes)
809}
810
811pub(crate) fn apply_roi_maxshift_inverse_i64(coefficient: i64, roi_shift: u8) -> i64 {
812    if roi_shift == 0 || coefficient == 0 {
813        return coefficient;
814    }
815
816    let magnitude = coefficient.unsigned_abs();
817    let threshold = 1_u64.checked_shl(roi_shift as u32).unwrap_or(u64::MAX);
818    if magnitude < threshold {
819        return coefficient;
820    }
821
822    let shifted = magnitude >> roi_shift;
823    let shifted = shifted.min(i64::MAX as u64) as i64;
824    if coefficient < 0 {
825        -shifted
826    } else {
827        shifted
828    }
829}
830
831pub(crate) fn apply_roi_maxshift_inverse_i32(coefficient: i32, roi_shift: u8) -> i32 {
832    apply_roi_maxshift_inverse_i64(i64::from(coefficient), roi_shift)
833        .clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
834}
835
836/// Adapter scalar classic J2K encoder helper for backend experimentation.
837pub fn encode_j2k_code_block_scalar_with_style(
838    coefficients: &[i32],
839    width: u32,
840    height: u32,
841    sub_band_type: J2kSubBandType,
842    total_bitplanes: u8,
843    style: J2kCodeBlockStyle,
844) -> core::result::Result<EncodedJ2kCodeBlock, &'static str> {
845    let encoded = j2c::bitplane_encode::encode_code_block_segments_with_style(
846        coefficients,
847        width,
848        height,
849        internal_j2k_sub_band_type(sub_band_type),
850        total_bitplanes,
851        &internal_j2k_code_block_style(style),
852    );
853    let segments = encoded
854        .segments
855        .into_iter()
856        .map(|segment| J2kCodeBlockSegment {
857            data_offset: segment.data_offset,
858            data_length: segment.data_length,
859            start_coding_pass: segment.start_coding_pass,
860            end_coding_pass: segment.end_coding_pass,
861            use_arithmetic: segment.use_arithmetic,
862        })
863        .collect();
864
865    Ok(EncodedJ2kCodeBlock {
866        data: encoded.data,
867        segments,
868        number_of_coding_passes: encoded.num_coding_passes,
869        missing_bit_planes: encoded.num_zero_bitplanes,
870    })
871}
872
873/// Adapter scalar Classic Tier-1 compact token packer for backend experimentation.
874///
875/// The token format matches the Metal Classic Tier-1 token-emitter contract:
876/// arithmetic segments are 6-bit `(context_label, bit)` MQ tokens, while raw
877/// bypass segments are one bit per raw bypass event.
878pub fn pack_j2k_code_block_scalar_from_tier1_tokens(
879    token_bytes: &[u8],
880    token_segments: &[J2kTier1TokenSegment],
881    number_of_coding_passes: u8,
882    missing_bit_planes: u8,
883) -> core::result::Result<EncodedJ2kCodeBlock, &'static str> {
884    let internal_segments = token_segments
885        .iter()
886        .map(|segment| j2c::bitplane_encode::ClassicTier1TokenSegment {
887            token_bit_offset: segment.token_bit_offset,
888            token_bit_count: segment.token_bit_count,
889            start_coding_pass: segment.start_coding_pass,
890            end_coding_pass: segment.end_coding_pass,
891            use_arithmetic: segment.use_arithmetic,
892        })
893        .collect::<Vec<_>>();
894    let encoded = j2c::bitplane_encode::pack_classic_selective_bypass_tier1_tokens(
895        token_bytes,
896        &internal_segments,
897        number_of_coding_passes,
898        missing_bit_planes,
899    )?;
900    let segments = encoded
901        .segments
902        .into_iter()
903        .map(|segment| J2kCodeBlockSegment {
904            data_offset: segment.data_offset,
905            data_length: segment.data_length,
906            start_coding_pass: segment.start_coding_pass,
907            end_coding_pass: segment.end_coding_pass,
908            use_arithmetic: segment.use_arithmetic,
909        })
910        .collect();
911
912    Ok(EncodedJ2kCodeBlock {
913        data: encoded.data,
914        segments,
915        number_of_coding_passes: encoded.num_coding_passes,
916        missing_bit_planes: encoded.num_zero_bitplanes,
917    })
918}
919
920/// Adapter scalar HTJ2K cleanup-only encoder helper for backend experimentation.
921pub fn encode_ht_code_block_scalar(
922    coefficients: &[i32],
923    width: u32,
924    height: u32,
925    total_bitplanes: u8,
926) -> core::result::Result<EncodedHtJ2kCodeBlock, &'static str> {
927    let encoded =
928        j2c::ht_block_encode::encode_code_block(coefficients, width, height, total_bitplanes)?;
929    Ok(EncodedHtJ2kCodeBlock {
930        data: encoded.data,
931        cleanup_length: encoded.ht_cleanup_length,
932        refinement_length: encoded.ht_refinement_length,
933        num_coding_passes: encoded.num_coding_passes,
934        num_zero_bitplanes: encoded.num_zero_bitplanes,
935    })
936}
937
938/// Adapter scalar HTJ2K encoder helper with an explicit coding-pass request.
939pub fn encode_ht_code_block_scalar_with_passes(
940    coefficients: &[i32],
941    width: u32,
942    height: u32,
943    total_bitplanes: u8,
944    target_coding_passes: u8,
945) -> core::result::Result<EncodedHtJ2kCodeBlock, &'static str> {
946    let encoded = j2c::ht_block_encode::encode_code_block_with_passes(
947        coefficients,
948        width,
949        height,
950        total_bitplanes,
951        target_coding_passes,
952    )?;
953    Ok(EncodedHtJ2kCodeBlock {
954        data: encoded.data,
955        cleanup_length: encoded.ht_cleanup_length,
956        refinement_length: encoded.ht_refinement_length,
957        num_coding_passes: encoded.num_coding_passes,
958        num_zero_bitplanes: encoded.num_zero_bitplanes,
959    })
960}
961
962/// Adapter HTJ2K cleanup-encode distribution helper for benchmark tuning.
963pub fn collect_ht_cleanup_encode_distribution(
964    coefficients: &[i32],
965    width: u32,
966    height: u32,
967    total_bitplanes: u8,
968) -> core::result::Result<HtCleanupEncodeDistribution, &'static str> {
969    j2c::ht_block_encode::collect_encode_distribution(coefficients, width, height, total_bitplanes)
970}
971
972/// Adapter scalar forward 5/3 DWT reference for CUDA stage parity.
973///
974/// Runs the native CPU reversible 5/3 forward DWT on `samples` and returns
975/// the decomposed subbands packed into the public `J2kForwardDwt53Output`
976/// type.  The returned layout matches what the encoder feeds to Tier-1.
977pub fn forward_dwt53_reference(
978    samples: &[f32],
979    width: u32,
980    height: u32,
981    num_levels: u8,
982) -> J2kForwardDwt53Output {
983    let decomp = j2c::fdwt::forward_dwt(samples, width, height, num_levels, true);
984    let levels = decomp
985        .levels
986        .into_iter()
987        .map(|lvl| J2kForwardDwt53Level {
988            hl: lvl.hl,
989            lh: lvl.lh,
990            hh: lvl.hh,
991            width: lvl.low_width + lvl.high_width,
992            height: lvl.low_height + lvl.high_height,
993            low_width: lvl.low_width,
994            low_height: lvl.low_height,
995            high_width: lvl.high_width,
996            high_height: lvl.high_height,
997        })
998        .collect();
999    J2kForwardDwt53Output {
1000        ll: decomp.ll,
1001        ll_width: decomp.ll_width,
1002        ll_height: decomp.ll_height,
1003        levels,
1004    }
1005}
1006
1007/// Adapter scalar forward 9/7 DWT reference for Metal/CUDA stage parity.
1008///
1009/// Runs the native CPU irreversible 9/7 forward DWT on `samples` and returns
1010/// the decomposed subbands packed into the public `J2kForwardDwt97Output`
1011/// type. The returned layout matches what the encoder feeds to Tier-1.
1012pub fn forward_dwt97_reference(
1013    samples: &[f32],
1014    width: u32,
1015    height: u32,
1016    num_levels: u8,
1017) -> J2kForwardDwt97Output {
1018    let decomp = j2c::fdwt::forward_dwt(samples, width, height, num_levels, false);
1019    let levels = decomp
1020        .levels
1021        .into_iter()
1022        .map(|lvl| J2kForwardDwt97Level {
1023            hl: lvl.hl,
1024            lh: lvl.lh,
1025            hh: lvl.hh,
1026            width: lvl.low_width + lvl.high_width,
1027            height: lvl.low_height + lvl.high_height,
1028            low_width: lvl.low_width,
1029            low_height: lvl.low_height,
1030            high_width: lvl.high_width,
1031            high_height: lvl.high_height,
1032        })
1033        .collect();
1034    J2kForwardDwt97Output {
1035        ll: decomp.ll,
1036        ll_width: decomp.ll_width,
1037        ll_height: decomp.ll_height,
1038        levels,
1039    }
1040}
1041
1042/// Adapter scalar forward RCT reference for CUDA stage parity.
1043///
1044/// Applies the native CPU forward Reversible Color Transform to three
1045/// component planes supplied as owned `Vec<f32>` arrays.  The transform is
1046/// applied in place and the mutated planes are returned, so callers do not
1047/// need to pass a mutable slice.
1048pub fn forward_rct_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1049    j2c::forward_mct::forward_rct(&mut planes);
1050    planes
1051}
1052
1053/// Adapter scalar forward ICT reference for Metal/CUDA stage parity.
1054///
1055/// Applies the native CPU forward Irreversible Color Transform to three
1056/// component planes supplied as owned `Vec<f32>` arrays. The transform is
1057/// applied in place and the mutated planes are returned.
1058pub fn forward_ict_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1059    j2c::forward_mct::forward_ict(&mut planes);
1060    planes
1061}
1062
1063/// Adapter scalar sub-band quantization reference for backend stage parity.
1064pub fn quantize_subband_reference(
1065    coefficients: &[f32],
1066    step_exponent: u16,
1067    step_mantissa: u16,
1068    range_bits: u8,
1069    reversible: bool,
1070) -> Vec<i32> {
1071    let step = j2c::quantize::QuantStepSize {
1072        exponent: step_exponent,
1073        mantissa: step_mantissa,
1074    };
1075    j2c::quantize::quantize_subband(coefficients, &step, range_bits, reversible)
1076}
1077
1078/// Adapter scalar reversible sub-band quantization reference for CUDA stage parity.
1079///
1080/// Quantizes `coefficients` using the reversible (lossless) integer path of
1081/// the native CPU quantizer.  `step_exponent` and `step_mantissa` encode the
1082/// JPEG 2000 `QuantStepSize` for the sub-band; `range_bits` is the nominal
1083/// bit depth for the sub-band.  When `reversible` is `true` the step-size
1084/// parameters are ignored and each coefficient is rounded to the nearest
1085/// integer.
1086pub fn quantize_reversible_reference(
1087    coefficients: &[f32],
1088    step_exponent: u16,
1089    step_mantissa: u16,
1090    range_bits: u8,
1091    reversible: bool,
1092) -> Vec<i32> {
1093    quantize_subband_reference(
1094        coefficients,
1095        step_exponent,
1096        step_mantissa,
1097        range_bits,
1098        reversible,
1099    )
1100}
1101
1102/// Adapter scalar pixel deinterleave/level-shift reference for CUDA stage parity.
1103///
1104/// Converts interleaved pixel bytes to per-component f32 planes with the
1105/// same level-shift logic as the native CPU encode path.  The result is one
1106/// `Vec<f32>` per component, each of length `num_pixels`.
1107pub fn deinterleave_reference(
1108    pixels: &[u8],
1109    num_pixels: usize,
1110    num_components: u16,
1111    bit_depth: u8,
1112    signed: bool,
1113) -> Vec<Vec<f32>> {
1114    j2c::encode::deinterleave_to_f32(pixels, num_pixels, num_components, bit_depth, signed)
1115}
1116
1117/// Adapter scalar Tier-2 packetization helper for backend experimentation.
1118pub fn encode_j2k_packetization_scalar(
1119    job: J2kPacketizationEncodeJob<'_>,
1120) -> core::result::Result<Vec<u8>, &'static str> {
1121    let mut resolutions = job
1122        .resolutions
1123        .iter()
1124        .map(|resolution| j2c::packet_encode::ResolutionPacket {
1125            subbands: resolution
1126                .subbands
1127                .iter()
1128                .map(|subband| j2c::packet_encode::SubbandPrecinct {
1129                    code_blocks: subband
1130                        .code_blocks
1131                        .iter()
1132                        .map(|code_block| j2c::packet_encode::CodeBlockPacketData {
1133                            data: code_block.data.to_vec(),
1134                            ht_cleanup_length: code_block.ht_cleanup_length,
1135                            ht_refinement_length: code_block.ht_refinement_length,
1136                            num_coding_passes: code_block.num_coding_passes,
1137                            classic_segment_lengths: Vec::new(),
1138                            num_zero_bitplanes: code_block.num_zero_bitplanes,
1139                            previously_included: code_block.previously_included,
1140                            l_block: code_block.l_block,
1141                            block_coding_mode: match code_block.block_coding_mode {
1142                                J2kPacketizationBlockCodingMode::Classic => {
1143                                    j2c::codestream_write::BlockCodingMode::Classic
1144                                }
1145                                J2kPacketizationBlockCodingMode::HighThroughput => {
1146                                    j2c::codestream_write::BlockCodingMode::HighThroughput
1147                                }
1148                            },
1149                        })
1150                        .collect(),
1151                    num_cbs_x: subband.num_cbs_x,
1152                    num_cbs_y: subband.num_cbs_y,
1153                })
1154                .collect(),
1155        })
1156        .collect::<Vec<_>>();
1157
1158    let descriptors = job
1159        .packet_descriptors
1160        .iter()
1161        .map(|descriptor| j2c::packet_encode::PacketDescriptor {
1162            packet_index: descriptor.packet_index,
1163            state_index: descriptor.state_index,
1164            layer: descriptor.layer,
1165            resolution: descriptor.resolution,
1166            component: descriptor.component,
1167            precinct: descriptor.precinct,
1168        })
1169        .collect::<Vec<_>>();
1170
1171    j2c::packet_encode::validate_ht_segment_lengths(&resolutions)?;
1172
1173    if descriptors.is_empty() {
1174        Ok(j2c::packet_encode::form_tile_bitstream_for_progression(
1175            &mut resolutions,
1176            job.num_layers,
1177            job.num_components,
1178            job.progression_order,
1179        ))
1180    } else {
1181        j2c::packet_encode::form_tile_bitstream_with_descriptors(&mut resolutions, &descriptors)
1182    }
1183}
1184
1185/// Adapter scalar classic J2K decoder helper for backend experimentation.
1186pub fn decode_j2k_code_block_scalar(
1187    job: J2kCodeBlockDecodeJob<'_>,
1188    output: &mut [f32],
1189) -> Result<()> {
1190    let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1191    decode_j2k_code_block_scalar_with_workspace(job, output, &mut workspace)
1192}
1193
1194/// Reusable scratch for scalar classic J2K code-block decoding.
1195#[derive(Default)]
1196pub struct J2kCodeBlockDecodeWorkspace {
1197    bit_plane_decode_context: j2c::bitplane::BitPlaneDecodeContext,
1198}
1199
1200/// Adapter scalar classic J2K decoder helper that reuses caller-provided scratch.
1201pub fn decode_j2k_code_block_scalar_with_workspace(
1202    job: J2kCodeBlockDecodeJob<'_>,
1203    output: &mut [f32],
1204    workspace: &mut J2kCodeBlockDecodeWorkspace,
1205) -> Result<()> {
1206    let required_len = if job.height == 0 {
1207        0
1208    } else {
1209        job.output_stride
1210            .checked_mul(job.height as usize - 1)
1211            .and_then(|prefix| prefix.checked_add(job.width as usize))
1212            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1213    };
1214    if output.len() < required_len {
1215        bail!(DecodingError::CodeBlockDecodeFailure);
1216    }
1217
1218    let style = internal_j2k_code_block_style(job.style);
1219    let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1220    let code_block_stride =
1221        usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1222    let coded_bitplanes = add_roi_shift_to_bitplanes(
1223        job.total_bitplanes,
1224        job.roi_shift,
1225        MAX_CLASSIC_DECODE_BITPLANES,
1226    )?;
1227
1228    j2c::bitplane::decode_code_block_segments_validated(
1229        job.data,
1230        job.segments,
1231        job.width,
1232        job.height,
1233        job.missing_bit_planes,
1234        job.number_of_coding_passes,
1235        coded_bitplanes,
1236        sub_band_type,
1237        &style,
1238        job.strict,
1239        &mut workspace.bit_plane_decode_context,
1240    )?;
1241
1242    for (row_idx, coeff_row) in workspace
1243        .bit_plane_decode_context
1244        .coefficient_rows()
1245        .enumerate()
1246        .take(job.height as usize)
1247    {
1248        let row_start = row_idx * job.output_stride;
1249        let output_row = &mut output[row_start..row_start + code_block_stride];
1250        for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1251            let coefficient = apply_roi_maxshift_inverse_i64(coefficient.get_i64(), job.roi_shift);
1252            *sample = coefficient as f32 * job.dequantization_step;
1253        }
1254    }
1255
1256    Ok(())
1257}
1258
1259/// Adapter scalar classic J2K pass timings for backend experimentation.
1260#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1261pub struct J2kCodeBlockDecodeProfile {
1262    /// Significance propagation pass elapsed time in microseconds.
1263    pub sigprop_us: u128,
1264    /// Magnitude refinement pass elapsed time in microseconds.
1265    pub magref_us: u128,
1266    /// Cleanup pass elapsed time in microseconds.
1267    pub cleanup_us: u128,
1268    /// Raw bypass pass elapsed time in microseconds.
1269    pub bypass_us: u128,
1270    /// Coefficient output conversion elapsed time in microseconds.
1271    pub output_convert_us: u128,
1272}
1273
1274impl J2kCodeBlockDecodeProfile {
1275    fn add_native_stats(&mut self, stats: j2c::bitplane::J2kBlockDecodeStats) {
1276        self.sigprop_us += stats.sigprop_us;
1277        self.magref_us += stats.magref_us;
1278        self.cleanup_us += stats.cleanup_us;
1279        self.bypass_us += stats.bypass_us;
1280    }
1281}
1282
1283/// Adapter scalar classic J2K decoder helper that records pass timings.
1284pub fn decode_j2k_code_block_scalar_profiled(
1285    job: J2kCodeBlockDecodeJob<'_>,
1286    output: &mut [f32],
1287    profile: &mut J2kCodeBlockDecodeProfile,
1288) -> Result<()> {
1289    let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1290    decode_j2k_code_block_scalar_with_workspace_profiled(job, output, &mut workspace, profile)
1291}
1292
1293/// Adapter scalar classic J2K decoder helper that records pass timings and reuses scratch.
1294pub fn decode_j2k_code_block_scalar_with_workspace_profiled(
1295    job: J2kCodeBlockDecodeJob<'_>,
1296    output: &mut [f32],
1297    workspace: &mut J2kCodeBlockDecodeWorkspace,
1298    profile: &mut J2kCodeBlockDecodeProfile,
1299) -> Result<()> {
1300    let required_len = if job.height == 0 {
1301        0
1302    } else {
1303        job.output_stride
1304            .checked_mul(job.height as usize - 1)
1305            .and_then(|prefix| prefix.checked_add(job.width as usize))
1306            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1307    };
1308    if output.len() < required_len {
1309        bail!(DecodingError::CodeBlockDecodeFailure);
1310    }
1311
1312    let style = internal_j2k_code_block_style(job.style);
1313    let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1314    let code_block_stride =
1315        usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1316    let coded_bitplanes = add_roi_shift_to_bitplanes(
1317        job.total_bitplanes,
1318        job.roi_shift,
1319        MAX_CLASSIC_DECODE_BITPLANES,
1320    )?;
1321    let mut stats = j2c::bitplane::J2kBlockDecodeStats::default();
1322
1323    j2c::bitplane::decode_code_block_segments_validated_profiled(
1324        job.data,
1325        job.segments,
1326        job.width,
1327        job.height,
1328        job.missing_bit_planes,
1329        job.number_of_coding_passes,
1330        coded_bitplanes,
1331        sub_band_type,
1332        &style,
1333        job.strict,
1334        &mut workspace.bit_plane_decode_context,
1335        &mut stats,
1336        true,
1337    )?;
1338    profile.add_native_stats(stats);
1339
1340    let output_convert_started = profile::profile_now(true);
1341    for (row_idx, coeff_row) in workspace
1342        .bit_plane_decode_context
1343        .coefficient_rows()
1344        .enumerate()
1345        .take(job.height as usize)
1346    {
1347        let row_start = row_idx * job.output_stride;
1348        let output_row = &mut output[row_start..row_start + code_block_stride];
1349        for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1350            let coefficient = apply_roi_maxshift_inverse_i64(coefficient.get_i64(), job.roi_shift);
1351            *sample = coefficient as f32 * job.dequantization_step;
1352        }
1353    }
1354    profile.output_convert_us += profile::elapsed_us(output_convert_started);
1355
1356    Ok(())
1357}
1358
1359/// Adapter scalar classic J2K batched decoder helper for backend experimentation.
1360pub fn decode_j2k_sub_band_scalar(job: J2kSubBandDecodeJob<'_>, output: &mut [f32]) -> Result<()> {
1361    let required_len = if job.height == 0 {
1362        0
1363    } else {
1364        usize::try_from(job.width)
1365            .ok()
1366            .and_then(|width| width.checked_mul(job.height as usize))
1367            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1368    };
1369    if output.len() < required_len {
1370        bail!(DecodingError::CodeBlockDecodeFailure);
1371    }
1372
1373    let sub_band_width =
1374        usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1375
1376    for batch_job in job.jobs {
1377        let code_block = batch_job.code_block;
1378        if code_block.output_stride != sub_band_width {
1379            bail!(DecodingError::CodeBlockDecodeFailure);
1380        }
1381        if batch_job
1382            .output_x
1383            .checked_add(code_block.width)
1384            .is_none_or(|x1| x1 > job.width)
1385            || batch_job
1386                .output_y
1387                .checked_add(code_block.height)
1388                .is_none_or(|y1| y1 > job.height)
1389        {
1390            bail!(DecodingError::CodeBlockDecodeFailure);
1391        }
1392
1393        let base_idx = usize::try_from(batch_job.output_y)
1394            .ok()
1395            .and_then(|y| y.checked_mul(sub_band_width))
1396            .and_then(|row| row.checked_add(batch_job.output_x as usize))
1397            .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1398        let block_output_len = if code_block.height == 0 {
1399            0
1400        } else {
1401            code_block
1402                .output_stride
1403                .checked_mul(code_block.height as usize - 1)
1404                .and_then(|prefix| prefix.checked_add(code_block.width as usize))
1405                .ok_or(DecodingError::CodeBlockDecodeFailure)?
1406        };
1407        let end_idx = base_idx
1408            .checked_add(block_output_len)
1409            .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1410        if end_idx > output.len() {
1411            bail!(DecodingError::CodeBlockDecodeFailure);
1412        }
1413
1414        decode_j2k_code_block_scalar(code_block, &mut output[base_idx..end_idx])?;
1415    }
1416
1417    Ok(())
1418}
1419
1420/// Adapter scalar HTJ2K decoder helper for backend experimentation.
1421pub fn decode_ht_code_block_scalar(
1422    job: HtCodeBlockDecodeJob<'_>,
1423    output: &mut [f32],
1424) -> Result<()> {
1425    decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1426        job, output,
1427    )
1428}
1429
1430/// Adapter scalar HTJ2K decoder helper that stops after the selected phase.
1431pub fn decode_ht_code_block_scalar_until_phase(
1432    job: HtCodeBlockDecodeJob<'_>,
1433    output: &mut [f32],
1434    phase_limit: HtCodeBlockDecodePhaseLimit,
1435) -> Result<()> {
1436    match phase_limit {
1437        HtCodeBlockDecodePhaseLimit::Cleanup => decode_ht_code_block_scalar_for_phase::<
1438            { j2c::ht_block_decode::PHASE_LIMIT_CLEANUP },
1439        >(job, output),
1440        HtCodeBlockDecodePhaseLimit::SignificancePropagation => {
1441            decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_SIGPROP }>(
1442                job, output,
1443            )
1444        }
1445        HtCodeBlockDecodePhaseLimit::MagnitudeRefinement => {
1446            decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1447                job, output,
1448            )
1449        }
1450    }
1451}
1452
1453/// Adapter reusable scalar HTJ2K decode workspace for backend experimentation.
1454#[derive(Default)]
1455pub struct HtCodeBlockDecodeWorkspace {
1456    coefficients: Vec<u32>,
1457    scratch: j2c::ht_block_decode::HtBlockDecodeScratch,
1458}
1459
1460impl HtCodeBlockDecodeWorkspace {
1461    /// Current coefficient buffer capacity retained by this workspace.
1462    pub fn coefficient_capacity(&self) -> usize {
1463        self.coefficients.capacity()
1464    }
1465}
1466
1467/// Adapter scalar HTJ2K phase timings for backend experimentation.
1468#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1469pub struct HtCodeBlockDecodeProfile {
1470    /// Number of decoded HT code blocks.
1471    pub blocks: u128,
1472    /// Number of decoded HT code blocks with refinement data.
1473    pub refinement_blocks: u128,
1474    /// Total cleanup segment bytes consumed by decoded HT code blocks.
1475    pub cleanup_bytes: u128,
1476    /// Total refinement segment bytes consumed by decoded HT code blocks.
1477    pub refinement_bytes: u128,
1478    /// Cleanup phase elapsed time in microseconds.
1479    pub cleanup_us: u128,
1480    /// Magnitude/sign phase elapsed time in microseconds.
1481    pub mag_sgn_us: u128,
1482    /// Sigma build phase elapsed time in microseconds.
1483    pub sigma_us: u128,
1484    /// Significance propagation phase elapsed time in microseconds.
1485    pub sigprop_us: u128,
1486    /// Magnitude refinement phase elapsed time in microseconds.
1487    pub magref_us: u128,
1488}
1489
1490impl HtCodeBlockDecodeProfile {
1491    fn add_native_stats(&mut self, stats: j2c::ht_block_decode::HtBlockDecodeStats) {
1492        self.blocks += stats.blocks;
1493        self.refinement_blocks += stats.refinement_blocks;
1494        self.cleanup_bytes += stats.cleanup_bytes;
1495        self.refinement_bytes += stats.refinement_bytes;
1496        self.cleanup_us += stats.ht_cleanup_us;
1497        self.mag_sgn_us += stats.ht_mag_sgn_us;
1498        self.sigma_us += stats.ht_sigma_us;
1499        self.sigprop_us += stats.ht_sigprop_us;
1500        self.magref_us += stats.ht_magref_us;
1501    }
1502}
1503
1504/// Adapter scalar HTJ2K decoder helper that reuses caller-owned scratch buffers.
1505pub fn decode_ht_code_block_scalar_with_workspace(
1506    job: HtCodeBlockDecodeJob<'_>,
1507    output: &mut [f32],
1508    workspace: &mut HtCodeBlockDecodeWorkspace,
1509) -> Result<()> {
1510    decode_ht_code_block_scalar_for_phase_with_workspace::<
1511        { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1512    >(job, output, workspace)
1513}
1514
1515/// Adapter scalar HTJ2K decoder helper that reuses scratch and records phase timings.
1516pub fn decode_ht_code_block_scalar_with_workspace_profiled(
1517    job: HtCodeBlockDecodeJob<'_>,
1518    output: &mut [f32],
1519    workspace: &mut HtCodeBlockDecodeWorkspace,
1520    profile: &mut HtCodeBlockDecodeProfile,
1521) -> Result<()> {
1522    decode_ht_code_block_scalar_for_phase_with_workspace_profiled::<
1523        { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1524    >(job, output, workspace, profile)
1525}
1526
1527fn decode_ht_code_block_scalar_for_phase<const PHASE_LIMIT: u8>(
1528    job: HtCodeBlockDecodeJob<'_>,
1529    output: &mut [f32],
1530) -> Result<()> {
1531    let mut workspace = HtCodeBlockDecodeWorkspace::default();
1532    decode_ht_code_block_scalar_for_phase_with_workspace::<PHASE_LIMIT>(job, output, &mut workspace)
1533}
1534
1535fn decode_ht_code_block_scalar_for_phase_with_workspace<const PHASE_LIMIT: u8>(
1536    job: HtCodeBlockDecodeJob<'_>,
1537    output: &mut [f32],
1538    workspace: &mut HtCodeBlockDecodeWorkspace,
1539) -> Result<()> {
1540    let required_len = if job.height == 0 {
1541        0
1542    } else {
1543        job.output_stride
1544            .checked_mul(job.height as usize - 1)
1545            .and_then(|prefix| prefix.checked_add(job.width as usize))
1546            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1547    };
1548    if output.len() < required_len {
1549        bail!(DecodingError::CodeBlockDecodeFailure);
1550    }
1551    let code_block_stride =
1552        usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1553    let code_block_len = code_block_stride
1554        .checked_mul(job.height as usize)
1555        .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1556
1557    let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1558        job.data,
1559        job.cleanup_length,
1560        job.refinement_length,
1561    )?;
1562    let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1563    workspace.coefficients.clear();
1564    workspace.coefficients.resize(code_block_len, 0);
1565    j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1566        &segments,
1567        job.missing_bit_planes,
1568        coded_bitplanes,
1569        job.number_of_coding_passes,
1570        job.stripe_causal,
1571        job.strict,
1572        &mut workspace.coefficients,
1573        job.width,
1574        job.height,
1575        job.width,
1576        &mut workspace.scratch,
1577        None,
1578        false,
1579    )?;
1580
1581    for (row_idx, coeff_row) in workspace
1582        .coefficients
1583        .chunks_exact(code_block_stride)
1584        .enumerate()
1585        .take(job.height as usize)
1586    {
1587        let row_start = row_idx * job.output_stride;
1588        let output_row = &mut output[row_start..row_start + code_block_stride];
1589        for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1590            let coefficient =
1591                j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1592            let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1593            *sample = coefficient as f32 * job.dequantization_step;
1594        }
1595    }
1596
1597    Ok(())
1598}
1599
1600fn decode_ht_code_block_scalar_for_phase_with_workspace_profiled<const PHASE_LIMIT: u8>(
1601    job: HtCodeBlockDecodeJob<'_>,
1602    output: &mut [f32],
1603    workspace: &mut HtCodeBlockDecodeWorkspace,
1604    profile: &mut HtCodeBlockDecodeProfile,
1605) -> Result<()> {
1606    let required_len = if job.height == 0 {
1607        0
1608    } else {
1609        job.output_stride
1610            .checked_mul(job.height as usize - 1)
1611            .and_then(|prefix| prefix.checked_add(job.width as usize))
1612            .ok_or(DecodingError::CodeBlockDecodeFailure)?
1613    };
1614    if output.len() < required_len {
1615        bail!(DecodingError::CodeBlockDecodeFailure);
1616    }
1617    let code_block_stride =
1618        usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1619    let code_block_len = code_block_stride
1620        .checked_mul(job.height as usize)
1621        .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1622
1623    let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1624        job.data,
1625        job.cleanup_length,
1626        job.refinement_length,
1627    )?;
1628    let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1629    workspace.coefficients.clear();
1630    workspace.coefficients.resize(code_block_len, 0);
1631    let mut stats = j2c::ht_block_decode::HtBlockDecodeStats::default();
1632    j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1633        &segments,
1634        job.missing_bit_planes,
1635        coded_bitplanes,
1636        job.number_of_coding_passes,
1637        job.stripe_causal,
1638        job.strict,
1639        &mut workspace.coefficients,
1640        job.width,
1641        job.height,
1642        job.width,
1643        &mut workspace.scratch,
1644        Some(&mut stats),
1645        true,
1646    )?;
1647    profile.add_native_stats(stats);
1648
1649    for (row_idx, coeff_row) in workspace
1650        .coefficients
1651        .chunks_exact(code_block_stride)
1652        .enumerate()
1653        .take(job.height as usize)
1654    {
1655        let row_start = row_idx * job.output_stride;
1656        let output_row = &mut output[row_start..row_start + code_block_stride];
1657        for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1658            let coefficient =
1659                j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1660            let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1661            *sample = coefficient as f32 * job.dequantization_step;
1662        }
1663    }
1664
1665    Ok(())
1666}
1667
1668/// Adapter HTJ2K SigProp benchmark state for backend experimentation.
1669pub struct HtSigPropBenchmarkState(j2c::ht_block_decode::HtSigPropBenchmarkState);
1670
1671impl HtSigPropBenchmarkState {
1672    /// Coefficient buffer length required by `decode_ht_sigprop_benchmark_state`.
1673    pub fn output_len(&self) -> usize {
1674        self.0.output_len()
1675    }
1676}
1677
1678/// Adapter helper that precomputes cleanup-derived SigProp inputs for benchmarks.
1679pub fn prepare_ht_sigprop_benchmark_state(
1680    job: HtCodeBlockDecodeJob<'_>,
1681) -> Result<HtSigPropBenchmarkState> {
1682    let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1683        job.data,
1684        job.cleanup_length,
1685        job.refinement_length,
1686    )?;
1687    let state = j2c::ht_block_decode::prepare_sigprop_benchmark_state(
1688        &segments,
1689        job.missing_bit_planes,
1690        job.num_bitplanes,
1691        job.number_of_coding_passes,
1692        job.stripe_causal,
1693        job.strict,
1694        job.width,
1695        job.height,
1696        job.width,
1697    )?;
1698    Ok(HtSigPropBenchmarkState(state))
1699}
1700
1701/// Adapter helper that runs only the HTJ2K significance-propagation phase.
1702pub fn decode_ht_sigprop_benchmark_state(
1703    state: &mut HtSigPropBenchmarkState,
1704    output: &mut [u32],
1705) -> Result<()> {
1706    j2c::ht_block_decode::decode_sigprop_benchmark_state(&mut state.0, output)
1707}
1708
1709/// Adapter HTJ2K VLC table 0 for backend experimentation.
1710pub fn ht_vlc_table0() -> &'static [u16; 1024] {
1711    &j2c::ht_tables::VLC_TABLE0
1712}
1713
1714/// Adapter HTJ2K VLC table 1 for backend experimentation.
1715pub fn ht_vlc_table1() -> &'static [u16; 1024] {
1716    &j2c::ht_tables::VLC_TABLE1
1717}
1718
1719/// Adapter HTJ2K UVLC table 0 for backend experimentation.
1720pub fn ht_uvlc_table0() -> &'static [u16; 320] {
1721    &j2c::ht_tables::UVLC_TABLE0
1722}
1723
1724/// Adapter HTJ2K UVLC table 1 for backend experimentation.
1725pub fn ht_uvlc_table1() -> &'static [u16; 256] {
1726    &j2c::ht_tables::UVLC_TABLE1
1727}
1728
1729/// Adapter HTJ2K cleanup encoder VLC table 0 for backend experimentation.
1730pub fn ht_vlc_encode_table0() -> &'static [u16; 2048] {
1731    &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE0
1732}
1733
1734/// Adapter HTJ2K cleanup encoder VLC table 1 for backend experimentation.
1735pub fn ht_vlc_encode_table1() -> &'static [u16; 2048] {
1736    &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE1
1737}
1738
1739/// Adapter HTJ2K cleanup encoder UVLC table for backend experimentation.
1740pub fn ht_uvlc_encode_table() -> &'static [HtUvlcTableEntry; 75] {
1741    &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE
1742}
1743
1744/// Adapter HTJ2K cleanup encoder UVLC table packed for byte-addressed backends.
1745pub fn ht_uvlc_encode_table_bytes() -> &'static [u8] {
1746    &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE_BYTES
1747}
1748
1749/// JP2 signature box: 00 00 00 0C 6A 50 20 20
1750pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
1751/// Codestream signature: FF 4F FF 51 (SOC + SIZ markers)
1752pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
1753
1754/// Settings to apply during decoding.
1755#[derive(Debug, Copy, Clone)]
1756pub struct DecodeSettings {
1757    /// Whether palette indices should be resolved.
1758    ///
1759    /// JPEG2000 images can be stored in two different ways. First, by storing
1760    /// RGB values (depending on the color space) for each pixel. Secondly, by
1761    /// only storing a single index for each channel, and then resolving the
1762    /// actual color using the index.
1763    ///
1764    /// If you disable this option, in case you have an image with palette
1765    /// indices, they will not be resolved, but instead a grayscale image
1766    /// will be returned, with each pixel value corresponding to the palette
1767    /// index of the location.
1768    pub resolve_palette_indices: bool,
1769    /// Whether strict mode should be enabled when decoding.
1770    ///
1771    /// It is recommended to leave this flag disabled, unless you have a
1772    /// specific reason not to.
1773    pub strict: bool,
1774    /// A hint for the target resolution that the image should be decoded at.
1775    pub target_resolution: Option<(u32, u32)>,
1776}
1777
1778impl Default for DecodeSettings {
1779    fn default() -> Self {
1780        Self {
1781            resolve_palette_indices: true,
1782            strict: false,
1783            target_resolution: None,
1784        }
1785    }
1786}
1787
1788/// A JPEG2000 image or codestream.
1789pub struct Image<'a> {
1790    /// The tile-part payload used by the legacy JPEG 2000 decoder.
1791    pub(crate) codestream: &'a [u8],
1792    /// The header of the J2C codestream.
1793    pub(crate) header: Header<'a>,
1794    /// The JP2 boxes of the image. In the case of a raw codestream, we
1795    /// will synthesize the necessary boxes.
1796    pub(crate) boxes: ImageBoxes,
1797    /// Settings that should be applied during decoding.
1798    pub(crate) settings: DecodeSettings,
1799    /// Whether the image has an alpha channel.
1800    pub(crate) has_alpha: bool,
1801    /// The color space of the image.
1802    pub(crate) color_space: ColorSpace,
1803}
1804
1805impl<'a> Image<'a> {
1806    /// Try to create a new JPEG2000 image from the given data.
1807    pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
1808        if data.starts_with(JP2_MAGIC) {
1809            jp2::parse(data, *settings)
1810        } else if data.starts_with(CODESTREAM_MAGIC) {
1811            j2c::parse(data, settings)
1812        } else {
1813            err!(FormatError::InvalidSignature)
1814        }
1815    }
1816
1817    /// Whether the image has an alpha channel.
1818    pub fn has_alpha(&self) -> bool {
1819        self.has_alpha
1820    }
1821
1822    /// The color space of the image.
1823    pub fn color_space(&self) -> &ColorSpace {
1824        &self.color_space
1825    }
1826
1827    /// The width of the image.
1828    pub fn width(&self) -> u32 {
1829        self.header.size_data.image_width()
1830    }
1831
1832    /// The height of the image.
1833    pub fn height(&self) -> u32 {
1834        self.header.size_data.image_height()
1835    }
1836
1837    /// The original bit depth of the image. You usually don't need to do anything
1838    /// with this parameter, it just exists for informational purposes.
1839    pub fn original_bit_depth(&self) -> u8 {
1840        // Note that this only works if all components have the same precision.
1841        self.header.component_infos[0].size_info.precision
1842    }
1843
1844    /// Whether decode finishes with additional host-side component mutation or reordering.
1845    pub fn supports_direct_device_plane_reuse(&self) -> bool {
1846        if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
1847            return false;
1848        }
1849        if self.boxes.channel_definition.is_some() {
1850            return false;
1851        }
1852        !matches!(
1853            self.boxes
1854                .color_specification
1855                .as_ref()
1856                .map(|spec| &spec.color_space),
1857            Some(jp2::colr::ColorSpace::Enumerated(
1858                EnumeratedColorspace::Sycc | EnumeratedColorspace::CieLab(_)
1859            ))
1860        )
1861    }
1862
1863    /// Decode the image and return its decoded result as a `Vec<u8>`, with each
1864    /// channel interleaved.
1865    pub fn decode(&self) -> Result<Vec<u8>> {
1866        let bitmap = self.decode_with_context(&mut DecoderContext::default())?;
1867        Ok(bitmap.data)
1868    }
1869
1870    /// Decode the image and return its decoded result using a caller-provided
1871    /// decoder context so allocations can be reused across repeated decodes.
1872    pub fn decode_with_context(&self, decoder_context: &mut DecoderContext<'a>) -> Result<Bitmap> {
1873        let buffer_size = checked_decode_byte_len3(
1874            self.width() as usize,
1875            self.height() as usize,
1876            self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 },
1877        )?;
1878        let mut buf = vec![0; buffer_size];
1879        self.decode_into(&mut buf, decoder_context)?;
1880
1881        Ok(Bitmap {
1882            color_space: self.color_space.clone(),
1883            data: buf,
1884            has_alpha: self.has_alpha,
1885            width: self.width(),
1886            height: self.height(),
1887            original_bit_depth: self.original_bit_depth(),
1888        })
1889    }
1890
1891    /// Decode the image into borrowed component planes using a caller-provided
1892    /// decoder context so allocations can be reused across repeated decodes.
1893    pub fn decode_components_with_context<'ctx>(
1894        &self,
1895        decoder_context: &'ctx mut DecoderContext<'a>,
1896    ) -> Result<DecodedComponents<'ctx>> {
1897        self.validate_component_plane_precision()?;
1898        let decoded_image = self.prepare_decoded_image(decoder_context)?;
1899        let sampling = self.component_plane_sampling(decoded_image.decoded_components.len());
1900        let planes = decoded_image
1901            .decoded_components
1902            .iter()
1903            .zip(sampling)
1904            .map(|(component, sampling)| ComponentPlane {
1905                samples: component.container.truncated(),
1906                dimensions: (self.width(), self.height()),
1907                bit_depth: component.bit_depth,
1908                signed: component.signed,
1909                sampling,
1910            })
1911            .collect();
1912
1913        Ok(DecodedComponents {
1914            dimensions: (self.width(), self.height()),
1915            color_space: self.color_space.clone(),
1916            has_alpha: self.has_alpha,
1917            planes,
1918        })
1919    }
1920
1921    /// Decode the image into owned native-bit-depth component planes.
1922    ///
1923    /// Unlike [`Self::decode_native`], this preserves per-component bit depth
1924    /// and signedness metadata and does not require all components to share a
1925    /// single packed interleaved representation.
1926    pub fn decode_native_components(&self) -> Result<DecodedNativeComponents> {
1927        let mut decoder_context = DecoderContext::default();
1928        self.decode_native_components_with_context(&mut decoder_context)
1929    }
1930
1931    /// Decode the image into owned native-bit-depth component planes using a
1932    /// caller-provided decoder context.
1933    pub fn decode_native_components_with_context(
1934        &self,
1935        decoder_context: &mut DecoderContext<'a>,
1936    ) -> Result<DecodedNativeComponents> {
1937        let decoded_image = self.prepare_decoded_image(decoder_context)?;
1938        self.pack_native_component_planes(
1939            decoded_image.decoded_components,
1940            (self.width(), self.height()),
1941        )
1942    }
1943
1944    /// Build a adapter grayscale direct device plan without materializing host component planes.
1945    pub fn build_direct_grayscale_plan_with_context(
1946        &self,
1947        decoder_context: &mut DecoderContext<'a>,
1948    ) -> Result<J2kDirectGrayscalePlan> {
1949        if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1950            bail!(DecodingError::UnsupportedFeature(
1951                "direct grayscale plan only supports grayscale images without alpha"
1952            ));
1953        }
1954
1955        j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context)
1956    }
1957
1958    /// Build a adapter grayscale direct device plan for an output-space region.
1959    pub fn build_direct_grayscale_plan_region_with_context(
1960        &self,
1961        decoder_context: &mut DecoderContext<'a>,
1962        output_region: (u32, u32, u32, u32),
1963    ) -> Result<J2kDirectGrayscalePlan> {
1964        if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1965            bail!(DecodingError::UnsupportedFeature(
1966                "direct grayscale plan only supports grayscale images without alpha"
1967            ));
1968        }
1969
1970        decoder_context.set_output_region(Some(output_region));
1971        let result =
1972            j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context);
1973        decoder_context.set_output_region(None);
1974        result
1975    }
1976
1977    /// Build a adapter RGB direct device plan without materializing host component planes.
1978    pub fn build_direct_color_plan_with_context(
1979        &self,
1980        decoder_context: &mut DecoderContext<'a>,
1981    ) -> Result<J2kDirectColorPlan> {
1982        if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1983            bail!(DecodingError::UnsupportedFeature(
1984                "direct color plan only supports RGB images without alpha"
1985            ));
1986        }
1987
1988        j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context)
1989    }
1990
1991    /// Build a adapter RGB direct device plan for an output-space region.
1992    pub fn build_direct_color_plan_region_with_context(
1993        &self,
1994        decoder_context: &mut DecoderContext<'a>,
1995        output_region: (u32, u32, u32, u32),
1996    ) -> Result<J2kDirectColorPlan> {
1997        if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1998            bail!(DecodingError::UnsupportedFeature(
1999                "direct color plan only supports RGB images without alpha"
2000            ));
2001        }
2002
2003        decoder_context.set_output_region(Some(output_region));
2004        let result = j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context);
2005        decoder_context.set_output_region(None);
2006        result
2007    }
2008
2009    /// Decode borrowed component planes while delegating HTJ2K code-block decode.
2010    pub fn decode_components_with_ht_decoder<'ctx>(
2011        &self,
2012        decoder_context: &'ctx mut DecoderContext<'a>,
2013        ht_decoder: &mut dyn HtCodeBlockDecoder,
2014    ) -> Result<DecodedComponents<'ctx>> {
2015        self.validate_component_plane_precision()?;
2016        let decoded_image =
2017            self.prepare_decoded_image_with_ht_decoder(decoder_context, ht_decoder)?;
2018        let sampling = self.component_plane_sampling(decoded_image.decoded_components.len());
2019        let planes = decoded_image
2020            .decoded_components
2021            .iter()
2022            .zip(sampling)
2023            .map(|(component, sampling)| ComponentPlane {
2024                samples: component.container.truncated(),
2025                dimensions: (self.width(), self.height()),
2026                bit_depth: component.bit_depth,
2027                signed: component.signed,
2028                sampling,
2029            })
2030            .collect();
2031
2032        Ok(DecodedComponents {
2033            dimensions: (self.width(), self.height()),
2034            color_space: self.color_space.clone(),
2035            has_alpha: self.has_alpha,
2036            planes,
2037        })
2038    }
2039
2040    /// Decode borrowed component planes for a requested region using a
2041    /// caller-provided decoder context.
2042    pub fn decode_region_components_with_context<'ctx>(
2043        &self,
2044        roi: (u32, u32, u32, u32),
2045        decoder_context: &'ctx mut DecoderContext<'a>,
2046    ) -> Result<DecodedComponents<'ctx>> {
2047        validate_roi((self.width(), self.height()), roi)?;
2048        self.validate_component_plane_precision()?;
2049        let (_x, _y, width, height) = roi;
2050        let decoded_image = self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
2051        let sampling = self.component_plane_sampling(decoded_image.decoded_components.len());
2052        let planes = decoded_image
2053            .decoded_components
2054            .iter()
2055            .zip(sampling)
2056            .map(|(component, sampling)| ComponentPlane {
2057                samples: component.container.truncated(),
2058                dimensions: (width, height),
2059                bit_depth: component.bit_depth,
2060                signed: component.signed,
2061                sampling,
2062            })
2063            .collect();
2064
2065        Ok(DecodedComponents {
2066            dimensions: (width, height),
2067            color_space: self.color_space.clone(),
2068            has_alpha: self.has_alpha,
2069            planes,
2070        })
2071    }
2072
2073    /// Decode a source-coordinate region into owned native-bit-depth component
2074    /// planes using a caller-provided decoder context.
2075    pub fn decode_native_region_components_with_context(
2076        &self,
2077        roi: (u32, u32, u32, u32),
2078        decoder_context: &mut DecoderContext<'a>,
2079    ) -> Result<DecodedNativeComponents> {
2080        validate_roi((self.width(), self.height()), roi)?;
2081        if self.requires_exact_integer_decode() {
2082            return self.decode_native_region_components_via_full_decode(roi, decoder_context);
2083        }
2084        let (_x, _y, width, height) = roi;
2085        let decoded_image = self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
2086        self.pack_native_component_planes(decoded_image.decoded_components, (width, height))
2087    }
2088
2089    /// Decode borrowed component planes for a requested region while
2090    /// delegating code-block/transform stages through the adapter backend hook.
2091    pub fn decode_region_components_with_ht_decoder<'ctx>(
2092        &self,
2093        decoder_context: &'ctx mut DecoderContext<'a>,
2094        roi: (u32, u32, u32, u32),
2095        ht_decoder: &mut dyn HtCodeBlockDecoder,
2096    ) -> Result<DecodedComponents<'ctx>> {
2097        validate_roi((self.width(), self.height()), roi)?;
2098        self.validate_component_plane_precision()?;
2099        let (_x, _y, width, height) = roi;
2100        let decoded_image = self.prepare_decoded_image_with_region_and_ht_decoder(
2101            decoder_context,
2102            Some(roi),
2103            Some(ht_decoder),
2104        )?;
2105        let sampling = self.component_plane_sampling(decoded_image.decoded_components.len());
2106        let planes = decoded_image
2107            .decoded_components
2108            .iter()
2109            .zip(sampling)
2110            .map(|(component, sampling)| ComponentPlane {
2111                samples: component.container.truncated(),
2112                dimensions: (width, height),
2113                bit_depth: component.bit_depth,
2114                signed: component.signed,
2115                sampling,
2116            })
2117            .collect();
2118
2119        Ok(DecodedComponents {
2120            dimensions: (width, height),
2121            color_space: self.color_space.clone(),
2122            has_alpha: self.has_alpha,
2123            planes,
2124        })
2125    }
2126
2127    /// Decode a region of the image and return it as an 8-bit interleaved bitmap.
2128    pub fn decode_region(&self, roi: (u32, u32, u32, u32)) -> Result<Bitmap> {
2129        self.decode_region_with_context(roi, &mut DecoderContext::default())
2130    }
2131
2132    /// Decode a region of the image and return it as an 8-bit interleaved bitmap
2133    /// using a caller-provided decoder context.
2134    pub fn decode_region_with_context(
2135        &self,
2136        roi: (u32, u32, u32, u32),
2137        decoder_context: &mut DecoderContext<'a>,
2138    ) -> Result<Bitmap> {
2139        validate_roi((self.width(), self.height()), roi)?;
2140        let mut decoded_image =
2141            self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
2142        let (_x, _y, width, height) = roi;
2143        let channels =
2144            self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 };
2145        let data_len = checked_decode_byte_len3(width as usize, height as usize, channels)?;
2146        let mut data = vec![0; data_len];
2147        interleave_and_convert_region(
2148            &mut decoded_image,
2149            width as usize,
2150            (0, 0, width, height),
2151            &mut data,
2152        );
2153        Ok(Bitmap {
2154            color_space: self.color_space.clone(),
2155            data,
2156            has_alpha: self.has_alpha,
2157            width,
2158            height,
2159            original_bit_depth: self.original_bit_depth(),
2160        })
2161    }
2162
2163    /// Decode the image at native bit depth without scaling to 8-bit.
2164    ///
2165    /// For images with bit depth ≤ 8, returns pixel data as `Vec<u8>`.
2166    /// For images with bit depth > 8 (e.g., 12-bit or 16-bit), returns
2167    /// pixel data as little-endian `u16` values packed into `Vec<u8>`.
2168    ///
2169    /// This is essential for medical imaging (DICOM) where 12-bit and 16-bit
2170    /// images must preserve their full dynamic range.
2171    pub fn decode_native(&self) -> Result<RawBitmap> {
2172        let mut decoder_context = DecoderContext::default();
2173        self.decode_native_with_context(&mut decoder_context)
2174    }
2175
2176    /// Extract reversible 5/3 wavelet coefficients for coefficient-domain
2177    /// classic JPEG 2000 to HTJ2K recoding.
2178    ///
2179    /// This decodes classic Tier-1 code-blocks into dequantized reversible
2180    /// wavelet coefficients, but does not run inverse DWT or color conversion.
2181    pub fn decode_reversible_53_coefficients(&self) -> Result<Reversible53CoefficientImage> {
2182        let mut decoder_context = DecoderContext::default();
2183        self.decode_reversible_53_coefficients_with_context(&mut decoder_context)
2184    }
2185
2186    /// Extract reversible 5/3 wavelet coefficients using a caller-provided
2187    /// decoder context.
2188    pub fn decode_reversible_53_coefficients_with_context(
2189        &self,
2190        decoder_context: &mut DecoderContext<'a>,
2191    ) -> Result<Reversible53CoefficientImage> {
2192        j2c::recode::extract_reversible_53_coefficients(
2193            self.codestream,
2194            &self.header,
2195            decoder_context,
2196        )
2197    }
2198
2199    /// Decode a region of the image at native bit depth.
2200    pub fn decode_native_region(&self, roi: (u32, u32, u32, u32)) -> Result<RawBitmap> {
2201        self.decode_native_region_with_context(roi, &mut DecoderContext::default())
2202    }
2203
2204    /// Decode a source-coordinate region into owned native-bit-depth component
2205    /// planes.
2206    pub fn decode_native_region_components(
2207        &self,
2208        roi: (u32, u32, u32, u32),
2209    ) -> Result<DecodedNativeComponents> {
2210        self.decode_native_region_components_with_context(roi, &mut DecoderContext::default())
2211    }
2212
2213    /// Decode the image at native bit depth using a caller-provided decoder
2214    /// context so allocations can be reused across repeated decodes.
2215    pub fn decode_native_with_context(
2216        &self,
2217        decoder_context: &mut DecoderContext<'a>,
2218    ) -> Result<RawBitmap> {
2219        let bit_depth = self.uniform_header_bit_depth()?;
2220        self.decode_with_output_region(decoder_context, None)?;
2221
2222        let components = &decoder_context.tile_decode_context.channel_data;
2223        let num_components =
2224            u16::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2225        let width = self.width();
2226        let height = self.height();
2227        let pixel_count = checked_decode_sample_count(width, height)?;
2228        let component_signed = Self::component_signedness(components);
2229        let signed = component_signed.iter().all(|signed| *signed);
2230
2231        let bytes_per_sample = native_bytes_per_sample(bit_depth)?;
2232        if bytes_per_sample == 1 {
2233            let capacity = checked_decode_byte_len2(pixel_count, usize::from(num_components))?;
2234            let mut data = Vec::with_capacity(capacity);
2235            for i in 0..pixel_count {
2236                for component in components.iter() {
2237                    Self::push_component_native_sample_bytes(&mut data, component, i, bit_depth);
2238                }
2239            }
2240            Ok(RawBitmap {
2241                data,
2242                width,
2243                height,
2244                bit_depth,
2245                signed,
2246                component_signed,
2247                num_components,
2248                bytes_per_sample: 1,
2249            })
2250        } else {
2251            let capacity = checked_decode_byte_len3(
2252                pixel_count,
2253                usize::from(num_components),
2254                bytes_per_sample,
2255            )?;
2256            let mut data = Vec::with_capacity(capacity);
2257            for i in 0..pixel_count {
2258                for component in components.iter() {
2259                    Self::push_component_native_sample_bytes(&mut data, component, i, bit_depth);
2260                }
2261            }
2262            Ok(RawBitmap {
2263                data,
2264                width,
2265                height,
2266                bit_depth,
2267                signed,
2268                component_signed,
2269                num_components,
2270                bytes_per_sample: u8::try_from(bytes_per_sample)
2271                    .map_err(|_| ValidationError::ImageTooLarge)?,
2272            })
2273        }
2274    }
2275
2276    /// Decode a region of the image at native bit depth using a caller-provided
2277    /// decoder context.
2278    pub fn decode_native_region_with_context(
2279        &self,
2280        roi: (u32, u32, u32, u32),
2281        decoder_context: &mut DecoderContext<'a>,
2282    ) -> Result<RawBitmap> {
2283        validate_roi((self.width(), self.height()), roi)?;
2284        if self.requires_exact_integer_decode() {
2285            return self.decode_native_region_via_full_decode(roi, decoder_context);
2286        }
2287        let bit_depth = self.uniform_header_bit_depth()?;
2288        self.decode_with_output_region(decoder_context, Some(roi))?;
2289
2290        let components = &decoder_context.tile_decode_context.channel_data;
2291        let num_components =
2292            u16::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2293        let bytes_per_sample = native_bytes_per_sample(bit_depth)?;
2294        let (_x, _y, width, height) = roi;
2295        let capacity = checked_decode_byte_len4(
2296            width as usize,
2297            height as usize,
2298            usize::from(num_components),
2299            bytes_per_sample,
2300        )?;
2301        let mut data = Vec::with_capacity(capacity);
2302        let component_signed = Self::component_signedness(components);
2303        let signed = component_signed.iter().all(|signed| *signed);
2304
2305        for row in 0..height as usize {
2306            for col in 0..width as usize {
2307                let idx = row * width as usize + col;
2308                for component in components {
2309                    Self::push_component_native_sample_bytes(&mut data, component, idx, bit_depth);
2310                }
2311            }
2312        }
2313
2314        Ok(RawBitmap {
2315            data,
2316            width,
2317            height,
2318            bit_depth,
2319            signed,
2320            component_signed,
2321            num_components,
2322            bytes_per_sample: u8::try_from(bytes_per_sample)
2323                .map_err(|_| ValidationError::ImageTooLarge)?,
2324        })
2325    }
2326
2327    fn component_signedness(components: &[ComponentData]) -> Vec<bool> {
2328        components
2329            .iter()
2330            .map(|component| component.signed)
2331            .collect()
2332    }
2333
2334    fn component_plane_sampling(&self, plane_count: usize) -> Vec<(u8, u8)> {
2335        if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
2336            return vec![(1, 1); plane_count];
2337        }
2338
2339        let mut sampling = self
2340            .header
2341            .component_infos
2342            .iter()
2343            .take(plane_count)
2344            .map(|component| {
2345                (
2346                    component.size_info.horizontal_resolution,
2347                    component.size_info.vertical_resolution,
2348                )
2349            })
2350            .collect::<Vec<_>>();
2351        sampling.resize(plane_count, (1, 1));
2352        sampling
2353    }
2354
2355    fn uniform_header_bit_depth(&self) -> Result<u8> {
2356        let Some(first) = self.header.component_infos.first() else {
2357            bail!(DecodingError::CodeBlockDecodeFailure);
2358        };
2359        if self
2360            .header
2361            .component_infos
2362            .iter()
2363            .any(|component| component.size_info.precision != first.size_info.precision)
2364        {
2365            bail!(DecodingError::UnsupportedFeature(
2366                "decode_native requires uniform component bit depths; use decode_components for mixed-depth images"
2367            ));
2368        }
2369        if first.size_info.precision > 38 {
2370            bail!(DecodingError::UnsupportedFeature(
2371                "decode_native supports JPEG 2000 Part 1 component precision up to 38 bits"
2372            ));
2373        }
2374        Ok(first.size_info.precision)
2375    }
2376
2377    fn validate_component_plane_precision(&self) -> Result<()> {
2378        if self
2379            .header
2380            .component_infos
2381            .iter()
2382            .any(|component| component.size_info.precision > 24)
2383        {
2384            bail!(DecodingError::UnsupportedFeature(
2385                "decode_components currently supports component planes up to 24 bits per component"
2386            ));
2387        }
2388        Ok(())
2389    }
2390
2391    fn pack_native_component_planes(
2392        &self,
2393        components: &[ComponentData],
2394        dimensions: (u32, u32),
2395    ) -> Result<DecodedNativeComponents> {
2396        let sampling = self.component_plane_sampling(components.len());
2397        let mut planes = Vec::with_capacity(components.len());
2398        for (component, sampling) in components.iter().zip(sampling) {
2399            let bytes_per_sample = native_bytes_per_sample(component.bit_depth)?;
2400            let sample_count = component
2401                .integer_container
2402                .as_ref()
2403                .map_or(component.container.truncated().len(), Vec::len);
2404            let plane_dimensions =
2405                native_component_plane_dimensions(dimensions, sampling, sample_count)?;
2406            let capacity = checked_decode_byte_len2(sample_count, bytes_per_sample)?;
2407            let mut data = Vec::with_capacity(capacity);
2408            for idx in 0..sample_count {
2409                Self::push_component_native_sample_bytes(
2410                    &mut data,
2411                    component,
2412                    idx,
2413                    component.bit_depth,
2414                );
2415            }
2416            planes.push(NativeComponentPlane {
2417                data,
2418                dimensions: plane_dimensions,
2419                bit_depth: component.bit_depth,
2420                signed: component.signed,
2421                sampling,
2422                bytes_per_sample: u8::try_from(bytes_per_sample)
2423                    .map_err(|_| ValidationError::ImageTooLarge)?,
2424            });
2425        }
2426
2427        Ok(DecodedNativeComponents {
2428            dimensions,
2429            color_space: self.color_space.clone(),
2430            has_alpha: self.has_alpha,
2431            planes,
2432        })
2433    }
2434
2435    fn requires_exact_integer_decode(&self) -> bool {
2436        self.header
2437            .component_infos
2438            .iter()
2439            .any(|component| component.requires_exact_integer_decode())
2440    }
2441
2442    fn decode_native_region_via_full_decode(
2443        &self,
2444        roi: (u32, u32, u32, u32),
2445        decoder_context: &mut DecoderContext<'a>,
2446    ) -> Result<RawBitmap> {
2447        let full = self.decode_native_with_context(decoder_context)?;
2448        let (x, y, width, height) = roi;
2449        let bytes_per_pixel = usize::from(full.num_components)
2450            .checked_mul(usize::from(full.bytes_per_sample))
2451            .ok_or(ValidationError::ImageTooLarge)?;
2452        let row_bytes = (width as usize)
2453            .checked_mul(bytes_per_pixel)
2454            .ok_or(ValidationError::ImageTooLarge)?;
2455        let capacity = checked_decode_byte_len3(height as usize, width as usize, bytes_per_pixel)?;
2456        let mut data = Vec::with_capacity(capacity);
2457        let full_width = full.width as usize;
2458        for row in y as usize..(y + height) as usize {
2459            let start = row
2460                .checked_mul(full_width)
2461                .and_then(|offset| offset.checked_add(x as usize))
2462                .and_then(|sample| sample.checked_mul(bytes_per_pixel))
2463                .ok_or(ValidationError::ImageTooLarge)?;
2464            data.extend_from_slice(&full.data[start..start + row_bytes]);
2465        }
2466
2467        Ok(RawBitmap {
2468            data,
2469            width,
2470            height,
2471            bit_depth: full.bit_depth,
2472            signed: full.signed,
2473            component_signed: full.component_signed,
2474            num_components: full.num_components,
2475            bytes_per_sample: full.bytes_per_sample,
2476        })
2477    }
2478
2479    fn decode_native_region_components_via_full_decode(
2480        &self,
2481        roi: (u32, u32, u32, u32),
2482        decoder_context: &mut DecoderContext<'a>,
2483    ) -> Result<DecodedNativeComponents> {
2484        let full = self.decode_native_components_with_context(decoder_context)?;
2485        let (x, y, width, height) = roi;
2486        let mut planes = Vec::with_capacity(full.planes.len());
2487        for plane in &full.planes {
2488            let bytes_per_sample = usize::from(plane.bytes_per_sample);
2489            let (crop_x, crop_y, crop_width, crop_height) = if plane.dimensions == full.dimensions {
2490                (x, y, width, height)
2491            } else {
2492                let x1 = x.checked_add(width).ok_or(ValidationError::ImageTooLarge)?;
2493                let y1 = y
2494                    .checked_add(height)
2495                    .ok_or(ValidationError::ImageTooLarge)?;
2496                let (x_rsiz, y_rsiz) = plane.sampling;
2497                let crop_x = x / u32::from(x_rsiz);
2498                let crop_y = y / u32::from(y_rsiz);
2499                let crop_x1 = x1.div_ceil(u32::from(x_rsiz)).min(plane.dimensions.0);
2500                let crop_y1 = y1.div_ceil(u32::from(y_rsiz)).min(plane.dimensions.1);
2501                (
2502                    crop_x,
2503                    crop_y,
2504                    crop_x1.saturating_sub(crop_x),
2505                    crop_y1.saturating_sub(crop_y),
2506                )
2507            };
2508            let row_bytes = (crop_width as usize)
2509                .checked_mul(bytes_per_sample)
2510                .ok_or(ValidationError::ImageTooLarge)?;
2511            let capacity = checked_decode_byte_len3(
2512                crop_height as usize,
2513                crop_width as usize,
2514                bytes_per_sample,
2515            )?;
2516            let mut data = Vec::with_capacity(capacity);
2517            let full_width = plane.dimensions.0 as usize;
2518            for row in crop_y as usize..(crop_y + crop_height) as usize {
2519                let start = row
2520                    .checked_mul(full_width)
2521                    .and_then(|offset| offset.checked_add(crop_x as usize))
2522                    .and_then(|sample| sample.checked_mul(bytes_per_sample))
2523                    .ok_or(ValidationError::ImageTooLarge)?;
2524                data.extend_from_slice(&plane.data[start..start + row_bytes]);
2525            }
2526            planes.push(NativeComponentPlane {
2527                data,
2528                dimensions: (crop_width, crop_height),
2529                bit_depth: plane.bit_depth,
2530                signed: plane.signed,
2531                sampling: plane.sampling,
2532                bytes_per_sample: plane.bytes_per_sample,
2533            });
2534        }
2535
2536        Ok(DecodedNativeComponents {
2537            dimensions: (width, height),
2538            color_space: full.color_space,
2539            has_alpha: full.has_alpha,
2540            planes,
2541        })
2542    }
2543
2544    fn push_component_native_sample_bytes(
2545        out: &mut Vec<u8>,
2546        component: &ComponentData,
2547        index: usize,
2548        bit_depth: u8,
2549    ) {
2550        if let Some(samples) = component.integer_container.as_ref() {
2551            Self::push_native_i64_sample_bytes(out, samples[index], bit_depth, component.signed);
2552        } else {
2553            Self::push_native_sample_bytes(
2554                out,
2555                component.container.truncated()[index],
2556                bit_depth,
2557                component.signed,
2558            );
2559        }
2560    }
2561
2562    fn push_native_i64_sample_bytes(out: &mut Vec<u8>, sample: i64, bit_depth: u8, signed: bool) {
2563        if signed {
2564            let magnitude_bits = u32::from(bit_depth.saturating_sub(1));
2565            let min = -(1_i64 << magnitude_bits);
2566            let max = (1_i64 << magnitude_bits) - 1;
2567            let clamped = sample.clamp(min, max);
2568            if bit_depth <= 8 {
2569                out.push((clamped as i8) as u8);
2570            } else if bit_depth <= 16 {
2571                out.extend_from_slice(&(clamped as i16).to_le_bytes());
2572            } else {
2573                let bytes = clamped.to_le_bytes();
2574                let byte_count = native_bytes_per_sample(bit_depth).unwrap_or(8);
2575                out.extend_from_slice(&bytes[..byte_count]);
2576            }
2577        } else {
2578            let max = (1u64 << u32::from(bit_depth)) - 1;
2579            let clamped = if sample <= 0 {
2580                0
2581            } else {
2582                (sample as u64).min(max)
2583            };
2584            if bit_depth <= 8 {
2585                out.push(clamped as u8);
2586            } else if bit_depth <= 16 {
2587                out.extend_from_slice(&(clamped as u16).to_le_bytes());
2588            } else {
2589                let bytes = clamped.to_le_bytes();
2590                let byte_count = native_bytes_per_sample(bit_depth).unwrap_or(8);
2591                out.extend_from_slice(&bytes[..byte_count]);
2592            }
2593        }
2594    }
2595
2596    fn push_native_sample_bytes(out: &mut Vec<u8>, sample: f32, bit_depth: u8, signed: bool) {
2597        let rounded = math::round_f32(sample);
2598        if signed {
2599            let magnitude_bits = u32::from(bit_depth.saturating_sub(1));
2600            let min = -(1_i64 << magnitude_bits);
2601            let max = (1_i64 << magnitude_bits) - 1;
2602            let rounded = f64::from(rounded);
2603            let clamped = if rounded.is_nan() {
2604                0
2605            } else if rounded <= min as f64 {
2606                min
2607            } else if rounded >= max as f64 {
2608                max
2609            } else {
2610                rounded as i64
2611            };
2612            if bit_depth <= 8 {
2613                out.push((clamped as i8) as u8);
2614            } else if bit_depth <= 16 {
2615                out.extend_from_slice(&(clamped as i16).to_le_bytes());
2616            } else {
2617                let bytes = clamped.to_le_bytes();
2618                let byte_count = native_bytes_per_sample(bit_depth).unwrap_or(8);
2619                out.extend_from_slice(&bytes[..byte_count]);
2620            }
2621        } else {
2622            let max = (1u64 << u32::from(bit_depth)) - 1;
2623            let rounded = f64::from(rounded);
2624            let clamped = if rounded.is_nan() || rounded <= 0.0 {
2625                0
2626            } else if rounded >= max as f64 {
2627                max
2628            } else {
2629                rounded as u64
2630            };
2631            if bit_depth <= 8 {
2632                out.push(clamped as u8);
2633            } else if bit_depth <= 16 {
2634                out.extend_from_slice(&(clamped as u16).to_le_bytes());
2635            } else {
2636                let bytes = clamped.to_le_bytes();
2637                let byte_count = native_bytes_per_sample(bit_depth).unwrap_or(8);
2638                out.extend_from_slice(&bytes[..byte_count]);
2639            }
2640        }
2641    }
2642
2643    /// Decode the image into the given buffer.
2644    ///
2645    /// This method does the same as [`Image::decode`], but you can provide
2646    /// a custom buffer for the output, as well as a decoder context. Doing
2647    /// so allows the internal decode engine to reuse memory allocations, so
2648    /// this is especially recommended if you plan on converting multiple
2649    /// images in the same session.
2650    ///
2651    /// The buffer must have the correct size.
2652    pub fn decode_into(
2653        &self,
2654        buf: &mut [u8],
2655        decoder_context: &mut DecoderContext<'a>,
2656    ) -> Result<()> {
2657        let mut decoded_image = self.prepare_decoded_image(decoder_context)?;
2658        validate_interleaved_output_buffer(&decoded_image, buf)?;
2659        interleave_and_convert(&mut decoded_image, buf)?;
2660
2661        Ok(())
2662    }
2663
2664    fn prepare_decoded_image<'ctx>(
2665        &self,
2666        decoder_context: &'ctx mut DecoderContext<'a>,
2667    ) -> Result<DecodedImage<'ctx>> {
2668        self.prepare_decoded_image_with_region(decoder_context, None)
2669    }
2670
2671    fn prepare_decoded_image_with_ht_decoder<'ctx>(
2672        &self,
2673        decoder_context: &'ctx mut DecoderContext<'a>,
2674        ht_decoder: &mut dyn HtCodeBlockDecoder,
2675    ) -> Result<DecodedImage<'ctx>> {
2676        self.prepare_decoded_image_with_region_and_ht_decoder(
2677            decoder_context,
2678            None,
2679            Some(ht_decoder),
2680        )
2681    }
2682
2683    fn prepare_decoded_image_with_region<'ctx>(
2684        &self,
2685        decoder_context: &'ctx mut DecoderContext<'a>,
2686        output_region: Option<(u32, u32, u32, u32)>,
2687    ) -> Result<DecodedImage<'ctx>> {
2688        self.prepare_decoded_image_with_region_and_ht_decoder(decoder_context, output_region, None)
2689    }
2690
2691    fn prepare_decoded_image_with_region_and_ht_decoder<'ctx>(
2692        &self,
2693        decoder_context: &'ctx mut DecoderContext<'a>,
2694        output_region: Option<(u32, u32, u32, u32)>,
2695        ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2696    ) -> Result<DecodedImage<'ctx>> {
2697        let settings = &self.settings;
2698        self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, ht_decoder)?;
2699        let mut decoded_image = DecodedImage {
2700            decoded_components: &mut decoder_context.tile_decode_context.channel_data,
2701            boxes: self.boxes.clone(),
2702        };
2703
2704        if settings.resolve_palette_indices {
2705            let components = core::mem::take(decoded_image.decoded_components);
2706            *decoded_image.decoded_components =
2707                resolve_palette_indices(components, &decoded_image.boxes)?;
2708        }
2709
2710        if let Some(cdef) = &decoded_image.boxes.channel_definition {
2711            validate_channel_definition(cdef, decoded_image.decoded_components.len())?;
2712            let mut components = decoded_image
2713                .decoded_components
2714                .iter()
2715                .cloned()
2716                .zip(
2717                    cdef.channel_definitions
2718                        .iter()
2719                        .map(|c| match c._association {
2720                            ChannelAssociation::WholeImage => u16::MAX,
2721                            ChannelAssociation::Colour(c) => c,
2722                            ChannelAssociation::Unspecified => u16::MAX,
2723                        }),
2724                )
2725                .collect::<Vec<_>>();
2726            components.sort_by_key(|component| component.1);
2727            *decoded_image.decoded_components = components.into_iter().map(|c| c.0).collect();
2728        }
2729
2730        let bit_depth = decoded_image.decoded_components[0].bit_depth;
2731        convert_color_space(&mut decoded_image, bit_depth)?;
2732        Ok(decoded_image)
2733    }
2734
2735    fn decode_with_output_region(
2736        &self,
2737        decoder_context: &mut DecoderContext<'a>,
2738        output_region: Option<(u32, u32, u32, u32)>,
2739    ) -> Result<()> {
2740        self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, None)
2741    }
2742
2743    fn decode_with_output_region_and_ht_decoder(
2744        &self,
2745        decoder_context: &mut DecoderContext<'a>,
2746        output_region: Option<(u32, u32, u32, u32)>,
2747        mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2748    ) -> Result<()> {
2749        decoder_context.set_output_region(output_region);
2750        let decode_result = j2c::decode(
2751            self.codestream,
2752            &self.header,
2753            decoder_context,
2754            &mut ht_decoder,
2755        );
2756        decoder_context.set_output_region(None);
2757        decode_result
2758    }
2759}
2760
2761fn validate_channel_definition(
2762    cdef: &jp2::cdef::ChannelDefinitionBox,
2763    component_count: usize,
2764) -> Result<()> {
2765    if cdef.channel_definitions.len() != component_count {
2766        bail!(ValidationError::InvalidChannelDefinition);
2767    }
2768
2769    let mut seen_color_associations = vec![false; component_count];
2770    for definition in &cdef.channel_definitions {
2771        if let ChannelAssociation::Colour(association) = definition._association {
2772            let Some(index) = association.checked_sub(1).map(usize::from) else {
2773                bail!(ValidationError::InvalidChannelDefinition);
2774            };
2775            if index >= component_count || seen_color_associations[index] {
2776                bail!(ValidationError::InvalidChannelDefinition);
2777            }
2778            seen_color_associations[index] = true;
2779        }
2780    }
2781
2782    Ok(())
2783}
2784
2785pub(crate) fn resolve_alpha_and_color_space(
2786    boxes: &ImageBoxes,
2787    header: &Header<'_>,
2788    settings: &DecodeSettings,
2789) -> Result<(ColorSpace, bool)> {
2790    let mut num_components = header.component_infos.len();
2791
2792    // Override number of components with what is actually in the palette box
2793    // in case we resolve them.
2794    if settings.resolve_palette_indices {
2795        if let Some(palette_box) = &boxes.palette {
2796            num_components = palette_box.columns.len();
2797        }
2798    }
2799
2800    let mut has_alpha = false;
2801
2802    if let Some(cdef) = &boxes.channel_definition {
2803        has_alpha = cdef.channel_definitions.iter().any(|definition| {
2804            matches!(
2805                definition.channel_type,
2806                ChannelType::Opacity | ChannelType::PremultipliedOpacity
2807            )
2808        });
2809    }
2810
2811    let mut color_space = get_color_space(boxes, num_components)?;
2812
2813    // If we didn't resolve palette indices, we need to assume grayscale image.
2814    if !settings.resolve_palette_indices && boxes.palette.is_some() {
2815        has_alpha = false;
2816        color_space = ColorSpace::Gray;
2817    }
2818
2819    let actual_num_components = header.component_infos.len();
2820
2821    // Validate the number of channels.
2822    if boxes.palette.is_none()
2823        && actual_num_components != usize::from(color_space.num_channels() + u16::from(has_alpha))
2824    {
2825        if !settings.strict
2826            && actual_num_components == usize::from(color_space.num_channels()) + 1
2827            && !has_alpha
2828        {
2829            // See OPENJPEG test case orb-blue10-lin-j2k. Assume that we have an
2830            // alpha channel in this case.
2831            has_alpha = true;
2832        } else {
2833            // Color space is invalid, attempt to repair.
2834            if actual_num_components == 1 || (actual_num_components == 2 && has_alpha) {
2835                color_space = ColorSpace::Gray;
2836            } else if actual_num_components == 3 {
2837                color_space = ColorSpace::RGB;
2838            } else if actual_num_components == 4 {
2839                if has_alpha {
2840                    color_space = ColorSpace::RGB;
2841                } else {
2842                    color_space = ColorSpace::CMYK;
2843                }
2844            } else {
2845                color_space = ColorSpace::Unknown {
2846                    num_channels: u16::try_from(actual_num_components)
2847                        .map_err(|_| ValidationError::TooManyChannels)?,
2848                };
2849            }
2850        }
2851    }
2852
2853    Ok((color_space, has_alpha))
2854}
2855
2856/// The color space of the image.
2857#[derive(Debug, Clone)]
2858pub enum ColorSpace {
2859    /// A grayscale image.
2860    Gray,
2861    /// An RGB image.
2862    RGB,
2863    /// A CMYK image.
2864    CMYK,
2865    /// An unknown color space.
2866    Unknown {
2867        /// The number of channels of the color space.
2868        num_channels: u16,
2869    },
2870    /// An image based on an ICC profile.
2871    Icc {
2872        /// The raw data of the ICC profile.
2873        profile: Vec<u8>,
2874        /// The number of channels used by the ICC profile.
2875        num_channels: u16,
2876    },
2877}
2878
2879impl ColorSpace {
2880    /// Return the number of expected channels for the color space.
2881    pub fn num_channels(&self) -> u16 {
2882        match self {
2883            Self::Gray => 1,
2884            Self::RGB => 3,
2885            Self::CMYK => 4,
2886            Self::Unknown { num_channels } => *num_channels,
2887            Self::Icc {
2888                num_channels: num_components,
2889                ..
2890            } => *num_components,
2891        }
2892    }
2893}
2894
2895/// A bitmap storing the decoded result of the image.
2896pub struct Bitmap {
2897    /// The color space of the image.
2898    pub color_space: ColorSpace,
2899    /// The raw pixel data of the image. The result will always be in
2900    /// 8-bit (in case the original image had a different bit-depth, this
2901    /// decode path scales it to 8-bit).
2902    ///
2903    /// The size is guaranteed to equal
2904    /// `width * height * (num_channels + (if has_alpha { 1 } else { 0 })`.
2905    /// Pixels are interleaved on a per-channel basis, the alpha channel always
2906    /// appearing as the last channel, if available.
2907    pub data: Vec<u8>,
2908    /// Whether the image has an alpha channel.
2909    pub has_alpha: bool,
2910    /// The width of the image.
2911    pub width: u32,
2912    /// The height of the image.
2913    pub height: u32,
2914    /// The original bit depth of the image. You usually don't need to do anything
2915    /// with this parameter, it just exists for informational purposes.
2916    pub original_bit_depth: u8,
2917}
2918
2919/// Raw decoded pixel data at native bit depth (no 8-bit scaling).
2920///
2921/// For bit depths ≤ 8, `data` contains one byte per sample.
2922/// For bit depths > 8 (e.g., 12 or 16), `data` contains two bytes per sample
2923/// in little-endian byte order (`u16` LE).
2924///
2925/// Samples are interleaved: for a 3-component image, the layout is
2926/// `[R0, G0, B0, R1, G1, B1, ...]`.
2927pub struct RawBitmap {
2928    /// The raw pixel data at native bit depth.
2929    pub data: Vec<u8>,
2930    /// The width of the image in pixels.
2931    pub width: u32,
2932    /// The height of the image in pixels.
2933    pub height: u32,
2934    /// The original bit depth per sample (e.g., 8, 12, 16).
2935    pub bit_depth: u8,
2936    /// Whether every component in this packed bitmap is signed.
2937    ///
2938    /// Use [`Self::component_signed`] for per-component signedness when
2939    /// handling arbitrary JPEG 2000 component metadata.
2940    pub signed: bool,
2941    /// Per-component signedness in codestream/component order.
2942    pub component_signed: Vec<bool>,
2943    /// The number of components (e.g., 1 for grayscale, 3 for RGB).
2944    pub num_components: u16,
2945    /// Bytes per sample in the packed little-endian native representation.
2946    pub bytes_per_sample: u8,
2947}
2948
2949/// One owned decoded component plane at native bit depth.
2950pub struct NativeComponentPlane {
2951    data: Vec<u8>,
2952    dimensions: (u32, u32),
2953    bit_depth: u8,
2954    signed: bool,
2955    sampling: (u8, u8),
2956    bytes_per_sample: u8,
2957}
2958
2959impl NativeComponentPlane {
2960    /// Packed little-endian sample bytes for this component in row-major order.
2961    pub fn data(&self) -> &[u8] {
2962        &self.data
2963    }
2964
2965    /// Width and height of this decoded plane in output samples.
2966    pub fn dimensions(&self) -> (u32, u32) {
2967        self.dimensions
2968    }
2969
2970    /// Horizontal and vertical SIZ sampling factors (`XRsiz`, `YRsiz`) for
2971    /// the source component represented by this plane.
2972    pub fn sampling(&self) -> (u8, u8) {
2973        self.sampling
2974    }
2975
2976    /// Bit depth of this component plane.
2977    pub fn bit_depth(&self) -> u8 {
2978        self.bit_depth
2979    }
2980
2981    /// Whether this component plane stores signed sample values.
2982    pub fn signed(&self) -> bool {
2983        self.signed
2984    }
2985
2986    /// Bytes used for each packed little-endian sample in [`Self::data`].
2987    pub fn bytes_per_sample(&self) -> u8 {
2988        self.bytes_per_sample
2989    }
2990}
2991
2992/// Owned decoded native-bit-depth component planes for an image.
2993pub struct DecodedNativeComponents {
2994    dimensions: (u32, u32),
2995    color_space: ColorSpace,
2996    has_alpha: bool,
2997    planes: Vec<NativeComponentPlane>,
2998}
2999
3000impl DecodedNativeComponents {
3001    /// Dimensions of the decoded image represented by these planes.
3002    pub fn dimensions(&self) -> (u32, u32) {
3003        self.dimensions
3004    }
3005
3006    /// Color space after JPEG 2000 color conversion has been applied.
3007    pub fn color_space(&self) -> &ColorSpace {
3008        &self.color_space
3009    }
3010
3011    /// Whether the decoded image has an alpha channel.
3012    pub fn has_alpha(&self) -> bool {
3013        self.has_alpha
3014    }
3015
3016    /// Decoded component planes in display order.
3017    pub fn planes(&self) -> &[NativeComponentPlane] {
3018        &self.planes
3019    }
3020}
3021
3022/// A borrowed decoded component plane.
3023pub struct ComponentPlane<'a> {
3024    samples: &'a [f32],
3025    dimensions: (u32, u32),
3026    bit_depth: u8,
3027    signed: bool,
3028    sampling: (u8, u8),
3029}
3030
3031impl<'a> ComponentPlane<'a> {
3032    /// Component samples in row-major order.
3033    pub fn samples(&self) -> &'a [f32] {
3034        self.samples
3035    }
3036
3037    /// Width and height of this plane in decoded output samples.
3038    pub fn dimensions(&self) -> (u32, u32) {
3039        self.dimensions
3040    }
3041
3042    /// Horizontal and vertical SIZ sampling factors (`XRsiz`, `YRsiz`) for
3043    /// the source component represented by this plane.
3044    pub fn sampling(&self) -> (u8, u8) {
3045        self.sampling
3046    }
3047
3048    /// Bit depth of this component plane.
3049    pub fn bit_depth(&self) -> u8 {
3050        self.bit_depth
3051    }
3052
3053    /// Whether this component plane stores signed sample values.
3054    pub fn signed(&self) -> bool {
3055        self.signed
3056    }
3057}
3058
3059/// Borrowed decoded component planes for an image.
3060pub struct DecodedComponents<'a> {
3061    dimensions: (u32, u32),
3062    color_space: ColorSpace,
3063    has_alpha: bool,
3064    planes: Vec<ComponentPlane<'a>>,
3065}
3066
3067impl<'a> DecodedComponents<'a> {
3068    /// Dimensions of the decoded image represented by these planes.
3069    pub fn dimensions(&self) -> (u32, u32) {
3070        self.dimensions
3071    }
3072
3073    /// Color space after JPEG 2000 color conversion has been applied.
3074    pub fn color_space(&self) -> &ColorSpace {
3075        &self.color_space
3076    }
3077
3078    /// Whether the decoded image has an alpha channel.
3079    pub fn has_alpha(&self) -> bool {
3080        self.has_alpha
3081    }
3082
3083    /// Borrowed decoded component planes in display order.
3084    pub fn planes(&self) -> &[ComponentPlane<'a>] {
3085        &self.planes
3086    }
3087}
3088
3089fn validate_interleaved_output_buffer(image: &DecodedImage<'_>, buf: &[u8]) -> Result<()> {
3090    let required_len = interleaved_output_len(image)?;
3091    if buf.len() < required_len {
3092        bail!(DecodingError::OutputBufferTooSmall);
3093    }
3094    Ok(())
3095}
3096
3097fn interleaved_output_len(image: &DecodedImage<'_>) -> Result<usize> {
3098    let Some(first) = image.decoded_components.first() else {
3099        bail!(DecodingError::CodeBlockDecodeFailure);
3100    };
3101    first
3102        .container
3103        .truncated()
3104        .len()
3105        .checked_mul(image.decoded_components.len())
3106        .ok_or(ValidationError::ImageTooLarge.into())
3107}
3108
3109fn interleave_and_convert(image: &mut DecodedImage<'_>, buf: &mut [u8]) -> Result<()> {
3110    let components = &mut *image.decoded_components;
3111    let num_components = components.len();
3112
3113    let mut all_same_bit_depth = Some(components[0].bit_depth);
3114
3115    for component in components.iter().skip(1) {
3116        if Some(component.bit_depth) != all_same_bit_depth {
3117            all_same_bit_depth = None;
3118        }
3119    }
3120
3121    let max_len = components[0].container.truncated().len();
3122
3123    let mut output_iter = buf.iter_mut();
3124
3125    if all_same_bit_depth == Some(8) && num_components <= 4 {
3126        // Fast path for the common case.
3127        match num_components {
3128            // Gray-scale.
3129            1 => {
3130                for (output, input) in output_iter.zip(
3131                    components[0]
3132                        .container
3133                        .iter()
3134                        .map(|v| math::round_f32(*v) as u8),
3135                ) {
3136                    *output = input;
3137                }
3138            }
3139            // Gray-scale with alpha.
3140            2 => {
3141                let c0 = &components[0];
3142                let c1 = &components[1];
3143
3144                let c0 = &c0.container[..max_len];
3145                let c1 = &c1.container[..max_len];
3146
3147                for i in 0..max_len {
3148                    *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
3149                    *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
3150                }
3151            }
3152            // RGB
3153            3 => {
3154                let c0 = &components[0];
3155                let c1 = &components[1];
3156                let c2 = &components[2];
3157
3158                let c0 = &c0.container[..max_len];
3159                let c1 = &c1.container[..max_len];
3160                let c2 = &c2.container[..max_len];
3161
3162                for i in 0..max_len {
3163                    *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
3164                    *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
3165                    *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
3166                }
3167            }
3168            // RGBA or CMYK.
3169            4 => {
3170                let c0 = &components[0];
3171                let c1 = &components[1];
3172                let c2 = &components[2];
3173                let c3 = &components[3];
3174
3175                let c0 = &c0.container[..max_len];
3176                let c1 = &c1.container[..max_len];
3177                let c2 = &c2.container[..max_len];
3178                let c3 = &c3.container[..max_len];
3179
3180                for i in 0..max_len {
3181                    *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
3182                    *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
3183                    *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
3184                    *output_iter.next().unwrap() = math::round_f32(c3[i]) as u8;
3185                }
3186            }
3187            _ => bail!(ValidationError::TooManyChannels),
3188        }
3189    } else {
3190        // Slow path that also requires us to scale to 8 bit.
3191        let mul_factor = ((1 << 8) - 1) as f32;
3192
3193        for sample in 0..max_len {
3194            for channel in components.iter() {
3195                *output_iter.next().unwrap() = math::round_f32(
3196                    (channel.container[sample]
3197                        / ((1_u64 << u32::from(channel.bit_depth)) - 1) as f32)
3198                        * mul_factor,
3199                ) as u8;
3200            }
3201        }
3202    }
3203
3204    Ok(())
3205}
3206
3207fn interleave_and_convert_region(
3208    image: &mut DecodedImage<'_>,
3209    image_width: usize,
3210    roi: (u32, u32, u32, u32),
3211    buf: &mut [u8],
3212) {
3213    let components = &mut *image.decoded_components;
3214    let num_components = components.len();
3215    let (x, y, width, height) = roi;
3216    let mut output_iter = buf.iter_mut();
3217
3218    let mut all_same_bit_depth = Some(components[0].bit_depth);
3219    for component in components.iter().skip(1) {
3220        if Some(component.bit_depth) != all_same_bit_depth {
3221            all_same_bit_depth = None;
3222        }
3223    }
3224
3225    if all_same_bit_depth == Some(8) && num_components <= 4 {
3226        for row in y as usize..(y + height) as usize {
3227            let row_base = row * image_width;
3228            for col in x as usize..(x + width) as usize {
3229                let idx = row_base + col;
3230                for component in components.iter() {
3231                    *output_iter.next().unwrap() = math::round_f32(component.container[idx]) as u8;
3232                }
3233            }
3234        }
3235    } else {
3236        let mul_factor = ((1 << 8) - 1) as f32;
3237        for row in y as usize..(y + height) as usize {
3238            let row_base = row * image_width;
3239            for col in x as usize..(x + width) as usize {
3240                let idx = row_base + col;
3241                for component in components.iter() {
3242                    *output_iter.next().unwrap() = math::round_f32(
3243                        (component.container[idx]
3244                            / ((1_u64 << u32::from(component.bit_depth)) - 1) as f32)
3245                            * mul_factor,
3246                    ) as u8;
3247                }
3248            }
3249        }
3250    }
3251}
3252
3253fn validate_roi(dims: (u32, u32), roi: (u32, u32, u32, u32)) -> Result<()> {
3254    let (image_width, image_height) = dims;
3255    let (x, y, width, height) = roi;
3256    let x_end = x
3257        .checked_add(width)
3258        .ok_or(ValidationError::InvalidDimensions)?;
3259    let y_end = y
3260        .checked_add(height)
3261        .ok_or(ValidationError::InvalidDimensions)?;
3262    if x_end > image_width || y_end > image_height {
3263        return Err(ValidationError::InvalidDimensions.into());
3264    }
3265    Ok(())
3266}
3267
3268fn native_component_plane_dimensions(
3269    reference_dimensions: (u32, u32),
3270    sampling: (u8, u8),
3271    sample_count: usize,
3272) -> Result<(u32, u32)> {
3273    let reference_sample_count =
3274        checked_decode_sample_count(reference_dimensions.0, reference_dimensions.1)?;
3275    if sample_count == reference_sample_count {
3276        return Ok(reference_dimensions);
3277    }
3278
3279    let (x_rsiz, y_rsiz) = sampling;
3280    if x_rsiz == 0 || y_rsiz == 0 {
3281        bail!(DecodingError::CodeBlockDecodeFailure);
3282    }
3283    let sampled_dimensions = (
3284        reference_dimensions.0.div_ceil(u32::from(x_rsiz)),
3285        reference_dimensions.1.div_ceil(u32::from(y_rsiz)),
3286    );
3287    let sampled_sample_count =
3288        checked_decode_sample_count(sampled_dimensions.0, sampled_dimensions.1)?;
3289    if sample_count == sampled_sample_count {
3290        return Ok(sampled_dimensions);
3291    }
3292
3293    bail!(DecodingError::CodeBlockDecodeFailure)
3294}
3295
3296fn convert_color_space(image: &mut DecodedImage<'_>, bit_depth: u8) -> Result<()> {
3297    if let Some(jp2::colr::ColorSpace::Enumerated(e)) = &image
3298        .boxes
3299        .color_specification
3300        .as_ref()
3301        .map(|i| &i.color_space)
3302    {
3303        match e {
3304            EnumeratedColorspace::Sycc => {
3305                dispatch!(Level::new(), simd => {
3306                    sycc_to_rgb(simd, image.decoded_components, bit_depth)
3307                })?;
3308            }
3309            EnumeratedColorspace::CieLab(cielab) => {
3310                dispatch!(Level::new(), simd => {
3311                    cielab_to_rgb(simd, image.decoded_components, bit_depth, cielab)
3312                })?;
3313            }
3314            _ => {}
3315        }
3316    }
3317
3318    Ok(())
3319}
3320
3321fn get_color_space(boxes: &ImageBoxes, num_components: usize) -> Result<ColorSpace> {
3322    let cs = match boxes
3323        .color_specification
3324        .as_ref()
3325        .map(|c| &c.color_space)
3326        .unwrap_or(&jp2::colr::ColorSpace::Unknown)
3327    {
3328        jp2::colr::ColorSpace::Enumerated(e) => {
3329            match e {
3330                EnumeratedColorspace::Cmyk => ColorSpace::CMYK,
3331                EnumeratedColorspace::Srgb => ColorSpace::RGB,
3332                EnumeratedColorspace::RommRgb => {
3333                    // Use an ICC profile to process the RommRGB color space.
3334                    ColorSpace::Icc {
3335                        profile: include_bytes!("../assets/ProPhoto-v2-micro.icc").to_vec(),
3336                        num_channels: 3,
3337                    }
3338                }
3339                EnumeratedColorspace::EsRgb => ColorSpace::RGB,
3340                EnumeratedColorspace::Greyscale => ColorSpace::Gray,
3341                EnumeratedColorspace::Sycc => ColorSpace::RGB,
3342                EnumeratedColorspace::CieLab(_) => ColorSpace::Icc {
3343                    profile: include_bytes!("../assets/LAB.icc").to_vec(),
3344                    num_channels: 3,
3345                },
3346                _ => bail!(FormatError::Unsupported),
3347            }
3348        }
3349        jp2::colr::ColorSpace::Icc(icc) => {
3350            if let Some(metadata) = ICCMetadata::from_data(icc) {
3351                ColorSpace::Icc {
3352                    profile: icc.clone(),
3353                    num_channels: u16::from(metadata.color_space.num_components()),
3354                }
3355            } else {
3356                // See OPENJPEG test orb-blue10-lin-jp2.jp2. They seem to
3357                // assume RGB in this case (even though the image has 4
3358                // components with no opacity channel, they assume RGBA instead
3359                // of CMYK).
3360                ColorSpace::RGB
3361            }
3362        }
3363        jp2::colr::ColorSpace::Unknown => match num_components {
3364            1 => ColorSpace::Gray,
3365            3 => ColorSpace::RGB,
3366            4 => ColorSpace::CMYK,
3367            _ => ColorSpace::Unknown {
3368                num_channels: u16::try_from(num_components).unwrap_or(u16::MAX),
3369            },
3370        },
3371    };
3372
3373    Ok(cs)
3374}
3375
3376fn resolve_palette_indices(
3377    components: Vec<ComponentData>,
3378    boxes: &ImageBoxes,
3379) -> Result<Vec<ComponentData>> {
3380    let Some(palette) = boxes.palette.as_ref() else {
3381        // Nothing to resolve.
3382        return Ok(components);
3383    };
3384
3385    let Some(mapping) = boxes.component_mapping.as_ref() else {
3386        bail!(ColorError::PaletteResolutionFailed);
3387    };
3388    if mapping.entries.is_empty() {
3389        bail!(ColorError::PaletteResolutionFailed);
3390    }
3391
3392    let mut resolved = Vec::with_capacity(mapping.entries.len());
3393
3394    for entry in &mapping.entries {
3395        let component_idx = entry.component_index as usize;
3396        let component = components
3397            .get(component_idx)
3398            .ok_or(ColorError::PaletteResolutionFailed)?;
3399
3400        match entry.mapping_type {
3401            ComponentMappingType::Direct => resolved.push(component.clone()),
3402            ComponentMappingType::Palette { column } => {
3403                let column_idx = column as usize;
3404                let column_info = palette
3405                    .columns
3406                    .get(column_idx)
3407                    .ok_or(ColorError::PaletteResolutionFailed)?;
3408
3409                let mut mapped =
3410                    Vec::with_capacity(component.container.truncated().len() + SIMD_WIDTH);
3411
3412                for &sample in component.container.truncated() {
3413                    let index = math::round_f32(sample) as i64;
3414                    let raw = palette
3415                        .map(index as usize, column_idx)
3416                        .ok_or(ColorError::PaletteResolutionFailed)?;
3417                    let value = if column_info.signed {
3418                        sign_extend_palette_value(raw, column_info.bit_depth) as f32
3419                    } else {
3420                        raw as f32
3421                    };
3422                    mapped.push(value);
3423                }
3424
3425                resolved.push(ComponentData {
3426                    container: math::SimdBuffer::new(mapped),
3427                    integer_container: None,
3428                    bit_depth: column_info.bit_depth,
3429                    signed: column_info.signed,
3430                });
3431            }
3432        }
3433    }
3434
3435    Ok(resolved)
3436}
3437
3438fn sign_extend_palette_value(raw: u64, bit_depth: u8) -> i64 {
3439    if bit_depth == 0 {
3440        return raw as i64;
3441    }
3442    if bit_depth >= 64 {
3443        return raw as i64;
3444    }
3445
3446    let mask = (1_u64 << bit_depth) - 1;
3447    let value = raw & mask;
3448    let shift = 64 - u32::from(bit_depth);
3449    ((value << shift) as i64) >> shift
3450}
3451
3452#[inline(always)]
3453fn cielab_to_rgb<S: Simd>(
3454    simd: S,
3455    components: &mut [ComponentData],
3456    bit_depth: u8,
3457    lab: &CieLab,
3458) -> Result<()> {
3459    let (head, _) = components
3460        .split_at_mut_checked(3)
3461        .ok_or(ColorError::LabConversionFailed)?;
3462
3463    let [l, a, b] = head else {
3464        bail!(ColorError::LabConversionFailed);
3465    };
3466
3467    let prec0 = l.bit_depth;
3468    let prec1 = a.bit_depth;
3469    let prec2 = b.bit_depth;
3470
3471    // Prevent underflows/divisions by zero further below.
3472    if prec0 < 4 || prec1 < 4 || prec2 < 4 {
3473        bail!(ColorError::LabConversionFailed);
3474    }
3475
3476    let rl = lab.rl.unwrap_or(100);
3477    let ra = lab.ra.unwrap_or(170);
3478    let rb = lab.ra.unwrap_or(200);
3479    let ol = lab.ol.unwrap_or(0);
3480    let default_oa = (1_u64 << u32::from(bit_depth - 1)).min(u64::from(u32::MAX)) as u32;
3481    let default_ob = ((1_u64 << u32::from(bit_depth - 2)) + (1_u64 << u32::from(bit_depth - 3)))
3482        .min(u64::from(u32::MAX)) as u32;
3483    let oa = lab.oa.unwrap_or(default_oa);
3484    let ob = lab.ob.unwrap_or(default_ob);
3485
3486    // Copied from OpenJPEG.
3487    let min_l = -(rl as f32 * ol as f32) / ((1_u64 << u32::from(prec0)) - 1) as f32;
3488    let max_l = min_l + rl as f32;
3489    let min_a = -(ra as f32 * oa as f32) / ((1_u64 << u32::from(prec1)) - 1) as f32;
3490    let max_a = min_a + ra as f32;
3491    let min_b = -(rb as f32 * ob as f32) / ((1_u64 << u32::from(prec2)) - 1) as f32;
3492    let max_b = min_b + rb as f32;
3493
3494    let bit_max = ((1_u64 << u32::from(bit_depth)) - 1).min(u64::from(u32::MAX)) as u32;
3495
3496    // Note that we are not doing the actual conversion with the ICC profile yet,
3497    // just decoding the raw LAB values.
3498    // We leave applying the ICC profile to the user.
3499    let divisor_l = ((1_u64 << u32::from(prec0)) - 1) as f32;
3500    let divisor_a = ((1_u64 << u32::from(prec1)) - 1) as f32;
3501    let divisor_b = ((1_u64 << u32::from(prec2)) - 1) as f32;
3502
3503    let scale_l_final = bit_max as f32 / 100.0;
3504    let scale_ab_final = bit_max as f32 / 255.0;
3505
3506    let l_offset = min_l * scale_l_final;
3507    let l_scale = (max_l - min_l) / divisor_l * scale_l_final;
3508    let a_offset = (min_a + 128.0) * scale_ab_final;
3509    let a_scale = (max_a - min_a) / divisor_a * scale_ab_final;
3510    let b_offset = (min_b + 128.0) * scale_ab_final;
3511    let b_scale = (max_b - min_b) / divisor_b * scale_ab_final;
3512
3513    let l_offset_v = f32x8::splat(simd, l_offset);
3514    let l_scale_v = f32x8::splat(simd, l_scale);
3515    let a_offset_v = f32x8::splat(simd, a_offset);
3516    let a_scale_v = f32x8::splat(simd, a_scale);
3517    let b_offset_v = f32x8::splat(simd, b_offset);
3518    let b_scale_v = f32x8::splat(simd, b_scale);
3519
3520    // Note that we are not doing the actual conversion with the ICC profile yet,
3521    // just decoding the raw LAB values.
3522    // We leave applying the ICC profile to the user.
3523    for ((l_chunk, a_chunk), b_chunk) in l
3524        .container
3525        .chunks_exact_mut(SIMD_WIDTH)
3526        .zip(a.container.chunks_exact_mut(SIMD_WIDTH))
3527        .zip(b.container.chunks_exact_mut(SIMD_WIDTH))
3528    {
3529        let l_v = f32x8::from_slice(simd, l_chunk);
3530        let a_v = f32x8::from_slice(simd, a_chunk);
3531        let b_v = f32x8::from_slice(simd, b_chunk);
3532
3533        l_v.mul_add(l_scale_v, l_offset_v).store(l_chunk);
3534        a_v.mul_add(a_scale_v, a_offset_v).store(a_chunk);
3535        b_v.mul_add(b_scale_v, b_offset_v).store(b_chunk);
3536    }
3537
3538    Ok(())
3539}
3540
3541#[inline(always)]
3542fn sycc_to_rgb<S: Simd>(simd: S, components: &mut [ComponentData], bit_depth: u8) -> Result<()> {
3543    let offset = (1_u64 << (u32::from(bit_depth) - 1)) as f32;
3544    let max_value = ((1_u64 << u32::from(bit_depth)) - 1) as f32;
3545
3546    let (head, _) = components
3547        .split_at_mut_checked(3)
3548        .ok_or(ColorError::SyccConversionFailed)?;
3549
3550    let [y, cb, cr] = head else {
3551        bail!(ColorError::SyccConversionFailed);
3552    };
3553
3554    let offset_v = f32x8::splat(simd, offset);
3555    let max_v = f32x8::splat(simd, max_value);
3556    let zero_v = f32x8::splat(simd, 0.0);
3557    let cr_to_r = f32x8::splat(simd, 1.402);
3558    let cb_to_g = f32x8::splat(simd, -0.344136);
3559    let cr_to_g = f32x8::splat(simd, -0.714136);
3560    let cb_to_b = f32x8::splat(simd, 1.772);
3561
3562    for ((y_chunk, cb_chunk), cr_chunk) in y
3563        .container
3564        .chunks_exact_mut(SIMD_WIDTH)
3565        .zip(cb.container.chunks_exact_mut(SIMD_WIDTH))
3566        .zip(cr.container.chunks_exact_mut(SIMD_WIDTH))
3567    {
3568        let y_v = f32x8::from_slice(simd, y_chunk);
3569        let cb_v = f32x8::from_slice(simd, cb_chunk) - offset_v;
3570        let cr_v = f32x8::from_slice(simd, cr_chunk) - offset_v;
3571
3572        // r = y + 1.402 * cr
3573        let r = cr_v.mul_add(cr_to_r, y_v);
3574        // g = y - 0.344136 * cb - 0.714136 * cr
3575        let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
3576        // b = y + 1.772 * cb
3577        let b = cb_v.mul_add(cb_to_b, y_v);
3578
3579        r.min(max_v).max(zero_v).store(y_chunk);
3580        g.min(max_v).max(zero_v).store(cb_chunk);
3581        b.min(max_v).max(zero_v).store(cr_chunk);
3582    }
3583
3584    Ok(())
3585}
3586
3587#[cfg(test)]
3588mod tests {
3589    use super::*;
3590
3591    #[test]
3592    fn ht_uvlc_encode_table_bytes_match_entry_packing_order() {
3593        let entries = ht_uvlc_encode_table();
3594        let bytes = ht_uvlc_encode_table_bytes();
3595
3596        assert_eq!(bytes.len(), entries.len() * 6);
3597        for (index, entry) in entries.iter().enumerate() {
3598            let offset = index * 6;
3599            assert_eq!(
3600                &bytes[offset..offset + 6],
3601                &[
3602                    entry.pre,
3603                    entry.pre_len,
3604                    entry.suf,
3605                    entry.suf_len,
3606                    entry.ext,
3607                    entry.ext_len
3608                ],
3609            );
3610        }
3611    }
3612
3613    #[test]
3614    fn roi_maxshift_inverse_preserves_background_and_unshifts_roi_coefficients() {
3615        assert_eq!(apply_roi_maxshift_inverse_i32(127, 7), 127);
3616        assert_eq!(apply_roi_maxshift_inverse_i32(-127, 7), -127);
3617        assert_eq!(apply_roi_maxshift_inverse_i32(128, 7), 1);
3618        assert_eq!(apply_roi_maxshift_inverse_i32(-128, 7), -1);
3619        assert_eq!(apply_roi_maxshift_inverse_i32(255, 7), 1);
3620        assert_eq!(apply_roi_maxshift_inverse_i32(-255, 7), -1);
3621        assert_eq!(apply_roi_maxshift_inverse_i32(256, 7), 2);
3622        assert_eq!(apply_roi_maxshift_inverse_i32(-256, 7), -2);
3623        assert_eq!(apply_roi_maxshift_inverse_i32(42, 0), 42);
3624        assert_eq!(apply_roi_maxshift_inverse_i64(1_i64 << 38, 7), 1_i64 << 31);
3625        assert_eq!(
3626            apply_roi_maxshift_inverse_i64(-(1_i64 << 38), 7),
3627            -(1_i64 << 31)
3628        );
3629    }
3630
3631    #[test]
3632    fn classic_decode_adapter_accepts_legal_38_bit_roi_bitplane_count() {
3633        assert_eq!(
3634            add_roi_shift_to_bitplanes(38, 0, MAX_CLASSIC_DECODE_BITPLANES).unwrap(),
3635            38
3636        );
3637        assert_eq!(
3638            add_roi_shift_to_bitplanes(37, 1, MAX_CLASSIC_DECODE_BITPLANES).unwrap(),
3639            38
3640        );
3641    }
3642
3643    #[test]
3644    fn classic_scalar_decode_applies_nonzero_roi_maxshift() {
3645        let roi_shift = 3;
3646        let total_bitplanes = 3;
3647        let style = J2kCodeBlockStyle {
3648            selective_arithmetic_coding_bypass: false,
3649            reset_context_probabilities: false,
3650            termination_on_each_pass: false,
3651            vertically_causal_context: false,
3652            segmentation_symbols: false,
3653        };
3654        let coded_coefficients = [0, 5, 1 << roi_shift, -(2 << roi_shift)];
3655        let encoded = encode_j2k_code_block_scalar_with_style(
3656            &coded_coefficients,
3657            2,
3658            2,
3659            J2kSubBandType::LowLow,
3660            total_bitplanes + roi_shift,
3661            style,
3662        )
3663        .expect("encode ROI-shifted code block");
3664        let job = J2kCodeBlockDecodeJob {
3665            data: &encoded.data,
3666            segments: &encoded.segments,
3667            width: 2,
3668            height: 2,
3669            output_stride: 2,
3670            missing_bit_planes: encoded.missing_bit_planes,
3671            number_of_coding_passes: encoded.number_of_coding_passes,
3672            total_bitplanes,
3673            roi_shift,
3674            sub_band_type: J2kSubBandType::LowLow,
3675            style,
3676            strict: true,
3677            dequantization_step: 1.0,
3678        };
3679        let mut output = [0.0; 4];
3680
3681        decode_j2k_code_block_scalar(job, &mut output).expect("decode ROI-shifted code block");
3682
3683        assert_eq!(output, [0.0, 5.0, 1.0, -2.0]);
3684    }
3685
3686    #[test]
3687    fn classic_scalar_token_pack_matches_scalar_single_cleanup_block() {
3688        let style = J2kCodeBlockStyle {
3689            selective_arithmetic_coding_bypass: true,
3690            reset_context_probabilities: false,
3691            termination_on_each_pass: false,
3692            vertically_causal_context: false,
3693            segmentation_symbols: false,
3694        };
3695        let scalar =
3696            encode_j2k_code_block_scalar_with_style(&[1], 1, 1, J2kSubBandType::LowLow, 1, style)
3697                .expect("encode scalar");
3698        let token_bytes = pack_mq_test_tokens(&[(0, 1), (9, 0)]);
3699        let packed = pack_j2k_code_block_scalar_from_tier1_tokens(
3700            &token_bytes,
3701            &[J2kTier1TokenSegment {
3702                token_bit_offset: 0,
3703                token_bit_count: 12,
3704                start_coding_pass: 0,
3705                end_coding_pass: 1,
3706                use_arithmetic: true,
3707            }],
3708            scalar.number_of_coding_passes,
3709            scalar.missing_bit_planes,
3710        )
3711        .expect("pack tokens");
3712
3713        assert_eq!(packed.data, scalar.data);
3714        assert_eq!(packed.segments, scalar.segments);
3715        assert_eq!(
3716            packed.number_of_coding_passes,
3717            scalar.number_of_coding_passes
3718        );
3719        assert_eq!(packed.missing_bit_planes, scalar.missing_bit_planes);
3720    }
3721
3722    fn pack_mq_test_tokens(tokens: &[(u8, u8)]) -> Vec<u8> {
3723        let mut bytes = Vec::new();
3724        let mut current = 0u8;
3725        let mut bits = 0u8;
3726        for &(ctx, bit) in tokens {
3727            let value = (ctx & 0x1F) | ((bit & 1) << 5);
3728            for shift in (0..6).rev() {
3729                current = (current << 1) | ((value >> shift) & 1);
3730                bits += 1;
3731                if bits == 8 {
3732                    bytes.push(current);
3733                    current = 0;
3734                    bits = 0;
3735                }
3736            }
3737        }
3738        if bits != 0 {
3739            bytes.push(current << (8 - bits));
3740        }
3741        bytes
3742    }
3743
3744    #[test]
3745    fn classic_scalar_profiled_decode_matches_unprofiled_decode() {
3746        let width = 64u32;
3747        let height = 64u32;
3748        let sample_count = width as usize * height as usize;
3749        let total_bitplanes = 12;
3750        let style = J2kCodeBlockStyle {
3751            selective_arithmetic_coding_bypass: false,
3752            reset_context_probabilities: false,
3753            termination_on_each_pass: false,
3754            vertically_causal_context: false,
3755            segmentation_symbols: false,
3756        };
3757        let coefficients = (0..sample_count)
3758            .map(|idx| {
3759                let value = i32::try_from((idx * 37) % 4095).expect("sample value fits i32") - 2048;
3760                if idx % 17 == 0 {
3761                    0
3762                } else {
3763                    value
3764                }
3765            })
3766            .collect::<Vec<_>>();
3767        let encoded = encode_j2k_code_block_scalar_with_style(
3768            &coefficients,
3769            width,
3770            height,
3771            J2kSubBandType::LowLow,
3772            total_bitplanes,
3773            style,
3774        )
3775        .expect("encode classic block");
3776        let job = J2kCodeBlockDecodeJob {
3777            data: &encoded.data,
3778            segments: &encoded.segments,
3779            width,
3780            height,
3781            output_stride: width as usize,
3782            missing_bit_planes: encoded.missing_bit_planes,
3783            number_of_coding_passes: encoded.number_of_coding_passes,
3784            total_bitplanes,
3785            roi_shift: 0,
3786            sub_band_type: J2kSubBandType::LowLow,
3787            style,
3788            strict: true,
3789            dequantization_step: 1.0,
3790        };
3791        let mut expected = vec![0.0_f32; sample_count];
3792        let mut actual = vec![0.0_f32; sample_count];
3793        let mut profile = J2kCodeBlockDecodeProfile::default();
3794
3795        decode_j2k_code_block_scalar(job, &mut expected).expect("unprofiled classic decode");
3796        decode_j2k_code_block_scalar_profiled(job, &mut actual, &mut profile)
3797            .expect("profiled classic decode");
3798
3799        assert_eq!(actual, expected);
3800        assert!(profile.cleanup_us > 0);
3801    }
3802
3803    #[test]
3804    fn classic_scalar_workspace_reuse_matches_fresh_decode() {
3805        let total_bitplanes = 6;
3806        let style = J2kCodeBlockStyle {
3807            selective_arithmetic_coding_bypass: false,
3808            reset_context_probabilities: false,
3809            termination_on_each_pass: false,
3810            vertically_causal_context: false,
3811            segmentation_symbols: false,
3812        };
3813        let mut workspace = J2kCodeBlockDecodeWorkspace::default();
3814
3815        for (width, height, seed) in [(8, 8, 0x31), (4, 16, 0x47)] {
3816            let coefficients = (0..width * height)
3817                .map(|idx| {
3818                    let value = ((idx as i32 * seed) % 23) - 11;
3819                    if idx % 7 == 0 {
3820                        0
3821                    } else {
3822                        value
3823                    }
3824                })
3825                .collect::<Vec<_>>();
3826            let encoded = encode_j2k_code_block_scalar_with_style(
3827                &coefficients,
3828                width,
3829                height,
3830                J2kSubBandType::LowLow,
3831                total_bitplanes,
3832                style,
3833            )
3834            .expect("encode classic block");
3835            let job = J2kCodeBlockDecodeJob {
3836                data: &encoded.data,
3837                segments: &encoded.segments,
3838                width,
3839                height,
3840                output_stride: width as usize,
3841                missing_bit_planes: encoded.missing_bit_planes,
3842                number_of_coding_passes: encoded.number_of_coding_passes,
3843                total_bitplanes,
3844                roi_shift: 0,
3845                sub_band_type: J2kSubBandType::LowLow,
3846                style,
3847                strict: true,
3848                dequantization_step: 1.0,
3849            };
3850            let mut fresh = vec![0.0_f32; width as usize * height as usize];
3851            let mut reused = vec![0.0_f32; width as usize * height as usize];
3852
3853            decode_j2k_code_block_scalar(job, &mut fresh).expect("fresh classic decode");
3854            decode_j2k_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
3855                .expect("workspace classic decode");
3856
3857            assert_eq!(reused, fresh);
3858        }
3859    }
3860
3861    #[test]
3862    fn scalar_packetization_rejects_overflowing_ht_refinement_lengths_without_panic() {
3863        let payload = [0x12];
3864        let block = J2kPacketizationCodeBlock {
3865            data: &payload,
3866            ht_cleanup_length: u32::MAX,
3867            ht_refinement_length: 1,
3868            num_coding_passes: 3,
3869            num_zero_bitplanes: 2,
3870            previously_included: false,
3871            l_block: 3,
3872            block_coding_mode: J2kPacketizationBlockCodingMode::HighThroughput,
3873        };
3874        let subband = J2kPacketizationSubband {
3875            code_blocks: vec![block],
3876            num_cbs_x: 1,
3877            num_cbs_y: 1,
3878        };
3879        let resolution = J2kPacketizationResolution {
3880            subbands: vec![subband],
3881        };
3882        let resolutions = [resolution];
3883        let job = J2kPacketizationEncodeJob {
3884            resolution_count: 1,
3885            num_layers: 1,
3886            num_components: 1,
3887            code_block_count: 1,
3888            progression_order: J2kPacketizationProgressionOrder::Lrcp,
3889            packet_descriptors: &[],
3890            resolutions: &resolutions,
3891        };
3892
3893        let err = encode_j2k_packetization_scalar(job)
3894            .expect_err("overflowing HT packetization segment lengths rejected");
3895
3896        assert_eq!(err, "multi-pass HTJ2K packet contribution length overflow");
3897    }
3898
3899    #[derive(Default)]
3900    struct DecodeWorkCounter {
3901        classic_code_blocks: usize,
3902        ht_code_blocks: usize,
3903        idwt_output_samples: usize,
3904    }
3905
3906    impl DecodeWorkCounter {
3907        fn code_blocks(&self) -> usize {
3908            self.classic_code_blocks + self.ht_code_blocks
3909        }
3910    }
3911
3912    struct FailingHtDecoder {
3913        called: bool,
3914    }
3915
3916    impl HtCodeBlockDecoder for FailingHtDecoder {
3917        fn decode_code_block(
3918            &mut self,
3919            _job: HtCodeBlockDecodeJob<'_>,
3920            _output: &mut [f32],
3921        ) -> Result<()> {
3922            self.called = true;
3923            Err(DecodingError::CodeBlockDecodeFailure.into())
3924        }
3925    }
3926
3927    struct FailingClassicDecoder {
3928        called: bool,
3929    }
3930
3931    impl HtCodeBlockDecoder for FailingClassicDecoder {
3932        fn decode_code_block(
3933            &mut self,
3934            _job: HtCodeBlockDecodeJob<'_>,
3935            _output: &mut [f32],
3936        ) -> Result<()> {
3937            panic!("HT hook must not be used for classic J2K test")
3938        }
3939
3940        fn decode_j2k_code_block(
3941            &mut self,
3942            _job: J2kCodeBlockDecodeJob<'_>,
3943            _output: &mut [f32],
3944        ) -> Result<bool> {
3945            self.called = true;
3946            Err(DecodingError::CodeBlockDecodeFailure.into())
3947        }
3948    }
3949
3950    struct FailingClassicBatchDecoder {
3951        called: bool,
3952    }
3953
3954    #[derive(Default)]
3955    struct CapturingHtDecoder {
3956        called: bool,
3957        blocks: usize,
3958        refinement_jobs: usize,
3959        max_coding_passes: u8,
3960    }
3961
3962    impl HtCodeBlockDecoder for CapturingHtDecoder {
3963        fn decode_code_block(
3964            &mut self,
3965            job: HtCodeBlockDecodeJob<'_>,
3966            output: &mut [f32],
3967        ) -> Result<()> {
3968            self.called = true;
3969            self.blocks += 1;
3970            self.max_coding_passes = self.max_coding_passes.max(job.number_of_coding_passes);
3971            if job.refinement_length > 0 {
3972                self.refinement_jobs += 1;
3973                assert!(
3974                    job.number_of_coding_passes > 1,
3975                    "refinement bytes must correspond to refinement coding passes"
3976                );
3977            }
3978
3979            decode_ht_code_block_scalar(job, output)
3980        }
3981    }
3982
3983    #[derive(Clone)]
3984    struct CapturedHtDecodeJob {
3985        data: Vec<u8>,
3986        cleanup_length: u32,
3987        refinement_length: u32,
3988        width: u32,
3989        height: u32,
3990        output_stride: usize,
3991        missing_bit_planes: u8,
3992        number_of_coding_passes: u8,
3993        num_bitplanes: u8,
3994        roi_shift: u8,
3995        stripe_causal: bool,
3996        strict: bool,
3997        dequantization_step: f32,
3998    }
3999
4000    impl CapturedHtDecodeJob {
4001        fn from_job(job: HtCodeBlockDecodeJob<'_>) -> Self {
4002            Self {
4003                data: job.data.to_vec(),
4004                cleanup_length: job.cleanup_length,
4005                refinement_length: job.refinement_length,
4006                width: job.width,
4007                height: job.height,
4008                output_stride: job.output_stride,
4009                missing_bit_planes: job.missing_bit_planes,
4010                number_of_coding_passes: job.number_of_coding_passes,
4011                num_bitplanes: job.num_bitplanes,
4012                roi_shift: job.roi_shift,
4013                stripe_causal: job.stripe_causal,
4014                strict: job.strict,
4015                dequantization_step: job.dequantization_step,
4016            }
4017        }
4018
4019        fn borrowed(&self) -> HtCodeBlockDecodeJob<'_> {
4020            HtCodeBlockDecodeJob {
4021                data: &self.data,
4022                cleanup_length: self.cleanup_length,
4023                refinement_length: self.refinement_length,
4024                width: self.width,
4025                height: self.height,
4026                output_stride: self.output_stride,
4027                missing_bit_planes: self.missing_bit_planes,
4028                number_of_coding_passes: self.number_of_coding_passes,
4029                num_bitplanes: self.num_bitplanes,
4030                roi_shift: self.roi_shift,
4031                stripe_causal: self.stripe_causal,
4032                strict: self.strict,
4033                dequantization_step: self.dequantization_step,
4034            }
4035        }
4036    }
4037
4038    #[derive(Default)]
4039    struct FirstHtJobDecoder {
4040        job: Option<CapturedHtDecodeJob>,
4041    }
4042
4043    impl HtCodeBlockDecoder for FirstHtJobDecoder {
4044        fn decode_code_block(
4045            &mut self,
4046            job: HtCodeBlockDecodeJob<'_>,
4047            output: &mut [f32],
4048        ) -> Result<()> {
4049            if self.job.is_none() {
4050                self.job = Some(CapturedHtDecodeJob::from_job(job));
4051            }
4052            decode_ht_code_block_scalar(job, output)
4053        }
4054    }
4055
4056    struct ZeroRefinementHtDecoder;
4057
4058    impl HtCodeBlockDecoder for ZeroRefinementHtDecoder {
4059        fn decode_code_block(
4060            &mut self,
4061            job: HtCodeBlockDecodeJob<'_>,
4062            output: &mut [f32],
4063        ) -> Result<()> {
4064            let mut data = job.data.to_vec();
4065            let cleanup_len = job.cleanup_length as usize;
4066            let refinement_len = job.refinement_length as usize;
4067            data[cleanup_len..cleanup_len + refinement_len].fill(0);
4068            let zeroed = HtCodeBlockDecodeJob { data: &data, ..job };
4069
4070            decode_ht_code_block_scalar(zeroed, output)
4071        }
4072    }
4073
4074    #[derive(Default)]
4075    struct CleanupLimitedHtDecoder {
4076        blocks: usize,
4077        refinement_blocks: usize,
4078        cleanup_bytes: usize,
4079        refinement_bytes: usize,
4080    }
4081
4082    impl HtCodeBlockDecoder for CleanupLimitedHtDecoder {
4083        fn decode_code_block(
4084            &mut self,
4085            job: HtCodeBlockDecodeJob<'_>,
4086            output: &mut [f32],
4087        ) -> Result<()> {
4088            self.blocks += 1;
4089            self.cleanup_bytes += job.cleanup_length as usize;
4090            if job.refinement_length > 0 {
4091                self.refinement_blocks += 1;
4092                self.refinement_bytes += job.refinement_length as usize;
4093            }
4094
4095            decode_ht_code_block_scalar_until_phase(
4096                job,
4097                output,
4098                HtCodeBlockDecodePhaseLimit::Cleanup,
4099            )
4100        }
4101    }
4102
4103    impl HtCodeBlockDecoder for FailingClassicBatchDecoder {
4104        fn decode_code_block(
4105            &mut self,
4106            _job: HtCodeBlockDecodeJob<'_>,
4107            _output: &mut [f32],
4108        ) -> Result<()> {
4109            panic!("HT hook must not be used for classic J2K batch test")
4110        }
4111
4112        fn decode_j2k_code_block(
4113            &mut self,
4114            _job: J2kCodeBlockDecodeJob<'_>,
4115            _output: &mut [f32],
4116        ) -> Result<bool> {
4117            panic!(
4118                "per-block classic hook must not be used when the batch hook handles the sub-band"
4119            )
4120        }
4121
4122        fn decode_j2k_sub_band(
4123            &mut self,
4124            _job: J2kSubBandDecodeJob<'_>,
4125            _output: &mut [f32],
4126        ) -> Result<bool> {
4127            self.called = true;
4128            Err(DecodingError::CodeBlockDecodeFailure.into())
4129        }
4130    }
4131
4132    fn fixture() -> Vec<u8> {
4133        let pixels = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
4134        let options = EncodeOptions {
4135            reversible: true,
4136            num_decomposition_levels: 1,
4137            ..EncodeOptions::default()
4138        };
4139        encode(&pixels, 2, 2, 3, 8, false, &options).expect("encode")
4140    }
4141
4142    #[test]
4143    fn decode_into_rejects_short_output_buffer() {
4144        let bytes = fixture();
4145        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4146        let mut context = DecoderContext::default();
4147        let mut output = vec![0; 11];
4148
4149        let err = image
4150            .decode_into(&mut output, &mut context)
4151            .expect_err("short output buffer must be rejected");
4152
4153        assert_eq!(
4154            err,
4155            DecodeError::Decoding(DecodingError::OutputBufferTooSmall)
4156        );
4157    }
4158
4159    fn fixture_multi_block() -> Vec<u8> {
4160        let pixels: Vec<u8> = (0..64).collect();
4161        let options = EncodeOptions {
4162            reversible: true,
4163            num_decomposition_levels: 0,
4164            code_block_width_exp: 0,
4165            code_block_height_exp: 0,
4166            ..EncodeOptions::default()
4167        };
4168        encode(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block classic")
4169    }
4170
4171    fn fixture_gray() -> Vec<u8> {
4172        let pixels: Vec<u8> = (0..16).collect();
4173        let options = EncodeOptions {
4174            reversible: true,
4175            num_decomposition_levels: 1,
4176            ..EncodeOptions::default()
4177        };
4178        encode(&pixels, 4, 4, 1, 8, false, &options).expect("encode classic gray8")
4179    }
4180
4181    #[test]
4182    fn native_bytes_per_sample_tracks_high_bit_depths() {
4183        for (bit_depth, expected) in [
4184            (1_u8, 1_usize),
4185            (8, 1),
4186            (9, 2),
4187            (16, 2),
4188            (17, 3),
4189            (24, 3),
4190            (32, 4),
4191            (38, 5),
4192        ] {
4193            assert_eq!(native_bytes_per_sample(bit_depth).unwrap(), expected);
4194        }
4195    }
4196
4197    #[test]
4198    fn native_sample_packing_writes_high_bit_unsigned_little_endian_bytes() {
4199        let mut out = Vec::new();
4200        Image::push_native_sample_bytes(&mut out, 0x12_34_56 as f32, 24, false);
4201        assert_eq!(out, [0x56, 0x34, 0x12]);
4202
4203        out.clear();
4204        Image::push_native_sample_bytes(&mut out, f32::MAX, 24, false);
4205        assert_eq!(out, [0xff, 0xff, 0xff]);
4206    }
4207
4208    #[test]
4209    fn native_sample_packing_writes_high_bit_signed_little_endian_bytes() {
4210        let mut out = Vec::new();
4211        Image::push_native_sample_bytes(&mut out, -1.0, 38, true);
4212        assert_eq!(out, [0xff, 0xff, 0xff, 0xff, 0xff]);
4213
4214        out.clear();
4215        Image::push_native_sample_bytes(&mut out, -((1_i64 << 37) as f32), 38, true);
4216        assert_eq!(out, [0x00, 0x00, 0x00, 0x00, 0xe0]);
4217    }
4218
4219    fn rewrite_siz_to_single_large_tile(codestream: &mut [u8], dimensions: u32) {
4220        let siz = codestream
4221            .windows(2)
4222            .position(|w| w == [0xFF, 0x51])
4223            .expect("SIZ marker");
4224        codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.to_be_bytes());
4225        codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.to_be_bytes());
4226        codestream[siz + 22..siz + 26].copy_from_slice(&dimensions.to_be_bytes());
4227        codestream[siz + 26..siz + 30].copy_from_slice(&dimensions.to_be_bytes());
4228    }
4229
4230    fn rewrite_siz_tile_grid(codestream: &mut [u8], dimensions: (u32, u32), tile_size: (u32, u32)) {
4231        let siz = codestream
4232            .windows(2)
4233            .position(|w| w == [0xFF, 0x51])
4234            .expect("SIZ marker");
4235        codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.0.to_be_bytes());
4236        codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.1.to_be_bytes());
4237        codestream[siz + 22..siz + 26].copy_from_slice(&tile_size.0.to_be_bytes());
4238        codestream[siz + 26..siz + 30].copy_from_slice(&tile_size.1.to_be_bytes());
4239    }
4240
4241    fn rewrite_siz_component_count(codestream: &mut Vec<u8>, component_count: u16) {
4242        let siz = codestream
4243            .windows(2)
4244            .position(|w| w == [0xFF, 0x51])
4245            .expect("SIZ marker");
4246        let old_component_count =
4247            u16::from_be_bytes([codestream[siz + 38], codestream[siz + 39]]) as usize;
4248        let component_start = siz + 40;
4249        let component_end = component_start + old_component_count * 3;
4250        let descriptor = codestream[component_start..component_start + 3].to_vec();
4251        let mut descriptors = Vec::with_capacity(usize::from(component_count) * 3);
4252        for _ in 0..component_count {
4253            descriptors.extend_from_slice(&descriptor);
4254        }
4255
4256        let siz_len = 38_u16
4257            .checked_add(
4258                component_count
4259                    .checked_mul(3)
4260                    .expect("SIZ component bytes fit"),
4261            )
4262            .expect("SIZ length fits");
4263        codestream[siz + 2..siz + 4].copy_from_slice(&siz_len.to_be_bytes());
4264        codestream[siz + 38..siz + 40].copy_from_slice(&component_count.to_be_bytes());
4265        codestream.splice(component_start..component_end, descriptors);
4266    }
4267
4268    #[test]
4269    fn inspect_rejects_component_count_above_j2k_spec_cap() {
4270        let mut bytes = fixture_gray();
4271        rewrite_siz_component_count(&mut bytes, MAX_J2K_SPEC_COMPONENTS + 1);
4272
4273        let err = inspect_j2k_codestream_header(&bytes)
4274            .expect_err("SIZ component count above spec cap must be rejected");
4275
4276        assert_eq!(
4277            err,
4278            J2kCodestreamHeaderError::InvalidSiz {
4279                what: "component count exceeds JPEG 2000 limit"
4280            }
4281        );
4282    }
4283
4284    #[test]
4285    fn native_parse_accepts_spec_component_count_above_u8() {
4286        let mut bytes = fixture_gray();
4287        rewrite_siz_component_count(&mut bytes, MAX_J2K_SPEC_COMPONENTS + 1);
4288
4289        let err = match Image::new(&bytes, &DecodeSettings::default()) {
4290            Err(err) => err,
4291            Ok(_) => panic!("component count above the JPEG 2000 spec cap must still reject"),
4292        };
4293        assert_eq!(
4294            err,
4295            DecodeError::Validation(ValidationError::TooManyChannels)
4296        );
4297
4298        let mut bytes = fixture_gray();
4299        rewrite_siz_component_count(&mut bytes, 256);
4300        Image::new(&bytes, &DecodeSettings::default())
4301            .expect("component count above u8 should parse within the JPEG 2000 spec cap");
4302    }
4303
4304    #[test]
4305    fn tile_parse_rejects_component_tile_structural_bomb_before_allocation() {
4306        let mut bytes = fixture_gray();
4307        rewrite_siz_component_count(&mut bytes, MAX_J2K_SPEC_COMPONENTS);
4308        rewrite_siz_tile_grid(&mut bytes, (256, 256), (1, 1));
4309        let parsed = j2c::parse_raw(&bytes, &DecodeSettings::default()).expect("raw header parses");
4310        let mut context = j2c::DecoderContext::default();
4311        let mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder> = None;
4312
4313        let err = j2c::decode(parsed.data, &parsed.header, &mut context, &mut ht_decoder)
4314            .expect_err("tile structural budget must reject before tile allocation");
4315
4316        assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
4317    }
4318
4319    #[test]
4320    fn owned_decode_rejects_large_siz_before_allocating_output() {
4321        let mut bytes = fixture_gray();
4322        rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
4323        let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
4324
4325        let err = match image.decode() {
4326            Err(err) => err,
4327            Ok(_) => panic!("large owned decode must be capped"),
4328        };
4329
4330        assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
4331    }
4332
4333    #[test]
4334    fn decode_into_rejects_large_siz_before_allocating_component_storage() {
4335        let mut bytes = fixture_gray();
4336        rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
4337        let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
4338        let mut context = DecoderContext::default();
4339        let mut out = [];
4340
4341        let err = match image.decode_into(&mut out, &mut context) {
4342            Err(err) => err,
4343            Ok(_) => panic!("component storage must be capped before allocation"),
4344        };
4345
4346        assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
4347    }
4348
4349    fn fixture_ht_gray() -> Vec<u8> {
4350        let pixels: Vec<u8> = (0..16).collect();
4351        let options = EncodeOptions {
4352            reversible: true,
4353            num_decomposition_levels: 1,
4354            ..EncodeOptions::default()
4355        };
4356        encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht gray8")
4357    }
4358
4359    fn fixture_ht_multi_block() -> Vec<u8> {
4360        let pixels: Vec<u8> = (0..64).collect();
4361        let options = EncodeOptions {
4362            reversible: true,
4363            num_decomposition_levels: 0,
4364            code_block_width_exp: 0,
4365            code_block_height_exp: 0,
4366            ..EncodeOptions::default()
4367        };
4368        encode_htj2k(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block HT gray8")
4369    }
4370
4371    fn fixture_ht_rgb_multi_block() -> Vec<u8> {
4372        let pixels = gradient_pixels(8, 8, 3);
4373        let options = EncodeOptions {
4374            reversible: true,
4375            num_decomposition_levels: 0,
4376            code_block_width_exp: 0,
4377            code_block_height_exp: 0,
4378            ..EncodeOptions::default()
4379        };
4380        encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode multi-block HT RGB8")
4381    }
4382
4383    fn direct_ht_job_count(plan: &J2kDirectGrayscalePlan) -> usize {
4384        plan.steps
4385            .iter()
4386            .map(|step| match step {
4387                J2kDirectGrayscaleStep::HtSubBand(sub_band) => sub_band.jobs.len(),
4388                _ => 0,
4389            })
4390            .sum()
4391    }
4392
4393    fn direct_color_ht_job_count(plan: &J2kDirectColorPlan) -> usize {
4394        plan.component_plans.iter().map(direct_ht_job_count).sum()
4395    }
4396
4397    fn fixture_openhtj2k_ht_refinement() -> &'static [u8] {
4398        include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.j2k")
4399    }
4400
4401    fn fixture_openhtj2k_ht_refinement_pixels() -> &'static [u8] {
4402        include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.gray")
4403    }
4404
4405    fn fixture_openhtj2k_ht_refinement_odd() -> &'static [u8] {
4406        include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.j2k")
4407    }
4408
4409    fn fixture_openhtj2k_ht_refinement_odd_pixels() -> &'static [u8] {
4410        include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.gray")
4411    }
4412
4413    fn gradient_pixels(width: u32, height: u32, components: u8) -> Vec<u8> {
4414        let mut pixels = Vec::with_capacity(width as usize * height as usize * components as usize);
4415        for y in 0..height {
4416            for x in 0..width {
4417                for component in 0..components {
4418                    pixels.push(((x * 3 + y * 5 + u32::from(component) * 41) & 0xff) as u8);
4419                }
4420            }
4421        }
4422        pixels
4423    }
4424
4425    fn roi_fixture(classic: bool, components: u8) -> Vec<u8> {
4426        let width = 64;
4427        let height = 64;
4428        let pixels = gradient_pixels(width, height, components);
4429        let options = EncodeOptions {
4430            reversible: true,
4431            num_decomposition_levels: 2,
4432            code_block_width_exp: 0,
4433            code_block_height_exp: 0,
4434            ..EncodeOptions::default()
4435        };
4436        if classic {
4437            encode(
4438                &pixels,
4439                width,
4440                height,
4441                components.into(),
4442                8,
4443                false,
4444                &options,
4445            )
4446            .expect("encode ROI classic fixture")
4447        } else {
4448            encode_htj2k(
4449                &pixels,
4450                width,
4451                height,
4452                components.into(),
4453                8,
4454                false,
4455                &options,
4456            )
4457            .expect("encode ROI HT fixture")
4458        }
4459    }
4460
4461    fn crop_interleaved(
4462        full: &[u8],
4463        full_width: u32,
4464        channels: usize,
4465        roi: (u32, u32, u32, u32),
4466    ) -> Vec<u8> {
4467        let (x, y, width, height) = roi;
4468        let mut out = Vec::with_capacity(width as usize * height as usize * channels);
4469        let row_bytes = full_width as usize * channels;
4470        let roi_row_bytes = width as usize * channels;
4471        for row in y as usize..(y + height) as usize {
4472            let start = row * row_bytes + x as usize * channels;
4473            out.extend_from_slice(&full[start..start + roi_row_bytes]);
4474        }
4475        out
4476    }
4477
4478    fn count_decode_work(bytes: &[u8], roi: Option<(u32, u32, u32, u32)>) -> DecodeWorkCounter {
4479        let image = Image::new(bytes, &DecodeSettings::default()).expect("image");
4480        let mut context = DecoderContext::default();
4481        match roi {
4482            Some(roi) => {
4483                image
4484                    .decode_region_with_context(roi, &mut context)
4485                    .expect("region decode with counter");
4486            }
4487            None => {
4488                image
4489                    .decode_with_context(&mut context)
4490                    .expect("full decode with counter");
4491            }
4492        }
4493        let counters = context.tile_decode_context.debug_counters;
4494        DecodeWorkCounter {
4495            classic_code_blocks: counters.decoded_code_blocks,
4496            ht_code_blocks: 0,
4497            idwt_output_samples: counters.idwt_output_samples,
4498        }
4499    }
4500
4501    #[test]
4502    fn roi_decode_matches_full_crop_for_classic_and_htj2k_gray_and_rgb() {
4503        let cases = [
4504            (true, 1_u8, true, false),
4505            (true, 3_u8, false, false),
4506            (false, 1_u8, true, false),
4507            (false, 3_u8, false, false),
4508        ];
4509        let rois = [
4510            (20, 18, 17, 19),
4511            (0, 0, 9, 11),
4512            (63, 63, 1, 1),
4513            (7, 5, 13, 9),
4514            (0, 0, 64, 64),
4515        ];
4516
4517        for (classic, components, expect_gray, has_alpha) in cases {
4518            let bytes = roi_fixture(classic, components);
4519            let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4520            let full = image.decode().expect("full decode");
4521            let channels = components as usize;
4522            for roi in rois {
4523                let region = image.decode_region(roi).expect("region decode");
4524                assert_eq!(matches!(region.color_space, ColorSpace::Gray), expect_gray);
4525                assert_eq!(region.has_alpha, has_alpha);
4526                assert_eq!(
4527                    region.data,
4528                    crop_interleaved(&full, 64, channels, roi),
4529                    "classic={classic} components={components} roi={roi:?}"
4530                );
4531            }
4532        }
4533    }
4534
4535    #[test]
4536    fn roi_decode_prunes_code_blocks_and_idwt_work_for_classic_and_htj2k() {
4537        let roi = (48, 48, 16, 16);
4538        for classic in [true, false] {
4539            let bytes = {
4540                let pixels = gradient_pixels(128, 128, 1);
4541                let options = EncodeOptions {
4542                    reversible: true,
4543                    num_decomposition_levels: 3,
4544                    code_block_width_exp: 0,
4545                    code_block_height_exp: 0,
4546                    ..EncodeOptions::default()
4547                };
4548                if classic {
4549                    encode(&pixels, 128, 128, 1, 8, false, &options)
4550                        .expect("encode classic work fixture")
4551                } else {
4552                    encode_htj2k(&pixels, 128, 128, 1, 8, false, &options)
4553                        .expect("encode ht work fixture")
4554                }
4555            };
4556            let full = count_decode_work(&bytes, None);
4557            let region = count_decode_work(&bytes, Some(roi));
4558
4559            assert!(
4560                region.code_blocks() > 0 && region.code_blocks() < full.code_blocks(),
4561                "ROI should decode fewer code-blocks for classic={classic}; full={}, region={}",
4562                full.code_blocks(),
4563                region.code_blocks()
4564            );
4565            assert!(
4566                region.idwt_output_samples > 0
4567                    && region.idwt_output_samples < full.idwt_output_samples,
4568                "ROI should produce fewer IDWT output samples for classic={classic}; full={}, region={}",
4569                full.idwt_output_samples,
4570                region.idwt_output_samples
4571            );
4572        }
4573    }
4574
4575    #[test]
4576    fn region_decode_reuses_region_sized_component_storage() {
4577        let bytes = fixture();
4578        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4579        let mut context = DecoderContext::default();
4580
4581        let bitmap = image
4582            .decode_region_with_context((1, 0, 1, 2), &mut context)
4583            .expect("region decode");
4584
4585        assert_eq!((bitmap.width, bitmap.height), (1, 2));
4586        assert!(context
4587            .tile_decode_context
4588            .channel_data
4589            .iter()
4590            .all(|component| component.container.truncated().len() == 2));
4591    }
4592
4593    #[test]
4594    fn native_region_decode_reuses_region_sized_component_storage() {
4595        let bytes = fixture();
4596        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4597        let mut context = DecoderContext::default();
4598
4599        let bitmap = image
4600            .decode_native_region_with_context((1, 0, 1, 2), &mut context)
4601            .expect("native region decode");
4602
4603        assert_eq!((bitmap.width, bitmap.height), (1, 2));
4604        assert!(context
4605            .tile_decode_context
4606            .channel_data
4607            .iter()
4608            .all(|component| component.container.truncated().len() == 2));
4609    }
4610
4611    #[test]
4612    fn decoder_context_defaults_to_auto_cpu_parallelism() {
4613        let context = DecoderContext::default();
4614
4615        assert_eq!(context.cpu_decode_parallelism(), CpuDecodeParallelism::Auto);
4616    }
4617
4618    #[test]
4619    fn classic_j2k_auto_and_serial_cpu_parallelism_match_pixels() {
4620        let bytes = fixture_multi_block();
4621        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4622        let mut auto_context = DecoderContext::default();
4623        let mut serial_context = DecoderContext::default();
4624        serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
4625
4626        let auto = image
4627            .decode_with_context(&mut auto_context)
4628            .expect("auto decode");
4629        let serial = image
4630            .decode_with_context(&mut serial_context)
4631            .expect("serial decode");
4632
4633        assert_eq!(auto.data, serial.data);
4634    }
4635
4636    #[test]
4637    fn htj2k_97_auto_and_serial_cpu_parallelism_match_pixels() {
4638        let width = 128_u32;
4639        let height = 128_u32;
4640        let pixels = (0..width * height)
4641            .map(|idx| ((idx * 17 + idx / width * 31) & 0xff) as u8)
4642            .collect::<Vec<_>>();
4643        let bytes = encode_htj2k(
4644            &pixels,
4645            width,
4646            height,
4647            1,
4648            8,
4649            false,
4650            &EncodeOptions {
4651                reversible: false,
4652                guard_bits: 2,
4653                num_decomposition_levels: 5,
4654                ..EncodeOptions::default()
4655            },
4656        )
4657        .expect("encode HTJ2K 9/7");
4658        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4659        let mut auto_context = DecoderContext::default();
4660        let mut serial_context = DecoderContext::default();
4661        serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
4662
4663        let auto = image
4664            .decode_with_context(&mut auto_context)
4665            .expect("auto decode");
4666        let serial = image
4667            .decode_with_context(&mut serial_context)
4668            .expect("serial decode");
4669
4670        assert_eq!(auto.data, serial.data);
4671    }
4672
4673    #[test]
4674    fn serial_cpu_parallelism_disables_classic_sub_band_parallel_branch() {
4675        assert!(!j2c::should_decode_classic_sub_band_in_parallel(
4676            CpuDecodeParallelism::Serial,
4677            16
4678        ));
4679    }
4680
4681    #[test]
4682    fn grayscale_direct_plan_is_built_without_materializing_channel_data() {
4683        let bytes = fixture_gray();
4684        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4685        let mut context = DecoderContext::default();
4686
4687        let plan = image
4688            .build_direct_grayscale_plan_with_context(&mut context)
4689            .expect("build direct plan");
4690
4691        assert_eq!(plan.dimensions, (4, 4));
4692        assert_eq!(plan.bit_depth, 8);
4693        assert!(
4694            !plan.steps.is_empty(),
4695            "direct plan must contain executable steps"
4696        );
4697        assert!(
4698            plan.steps.iter().any(|step| matches!(
4699                step,
4700                J2kDirectGrayscaleStep::ClassicSubBand(plan) if !plan.jobs.is_empty()
4701            )),
4702            "classic J2K direct plan must contain at least one non-empty classic sub-band job"
4703        );
4704        assert!(
4705            context.tile_decode_context.channel_data.is_empty(),
4706            "building a direct plan must not materialize host component planes"
4707        );
4708    }
4709
4710    #[test]
4711    fn grayscale_direct_plan_honors_target_resolution() {
4712        let bytes = fixture_ht_gray();
4713        let image = Image::new(
4714            &bytes,
4715            &DecodeSettings {
4716                target_resolution: Some((2, 2)),
4717                ..DecodeSettings::default()
4718            },
4719        )
4720        .expect("scaled image");
4721        let mut context = DecoderContext::default();
4722
4723        let plan = image
4724            .build_direct_grayscale_plan_with_context(&mut context)
4725            .expect("build scaled direct plan");
4726
4727        assert_eq!(plan.dimensions, (2, 2));
4728        assert!(plan.steps.iter().any(|step| matches!(
4729            step,
4730            J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4731        )));
4732        assert!(plan.steps.iter().any(|step| matches!(
4733            step,
4734            J2kDirectGrayscaleStep::Store(store)
4735                if store.output_width == 2 && store.output_height == 2
4736        )));
4737        assert!(
4738            context.tile_decode_context.channel_data.is_empty(),
4739            "building a scaled direct plan must not materialize host component planes"
4740        );
4741    }
4742
4743    #[test]
4744    fn odd_dimensions_honor_covering_target_resolution() {
4745        let pixels = gradient_pixels(9, 7, 1);
4746        let options = EncodeOptions {
4747            reversible: true,
4748            num_decomposition_levels: 2,
4749            ..EncodeOptions::default()
4750        };
4751        let bytes =
4752            encode_htj2k(&pixels, 9, 7, 1, 8, false, &options).expect("encode odd HT gray8");
4753
4754        for (target, expected) in [((5, 4), (5, 4)), ((3, 2), (3, 2))] {
4755            let image = Image::new(
4756                &bytes,
4757                &DecodeSettings {
4758                    target_resolution: Some(target),
4759                    ..DecodeSettings::default()
4760                },
4761            )
4762            .expect("scaled odd image");
4763
4764            assert_eq!(
4765                (image.width(), image.height()),
4766                expected,
4767                "target {target:?}"
4768            );
4769        }
4770    }
4771
4772    #[test]
4773    fn grayscale_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4774        let bytes = fixture_ht_multi_block();
4775        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4776        let mut full_context = DecoderContext::default();
4777        let mut roi_context = DecoderContext::default();
4778
4779        let full = image
4780            .build_direct_grayscale_plan_with_context(&mut full_context)
4781            .expect("build full direct plan");
4782        let roi = image
4783            .build_direct_grayscale_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4784            .expect("build ROI direct plan");
4785
4786        let full_jobs = direct_ht_job_count(&full);
4787        let roi_jobs = direct_ht_job_count(&roi);
4788        assert!(full_jobs > 1, "fixture must expose multiple HT jobs");
4789        assert!(
4790            roi_jobs < full_jobs,
4791            "ROI direct plan must prune HT jobs before device preparation"
4792        );
4793    }
4794
4795    #[test]
4796    fn color_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4797        let bytes = fixture_ht_rgb_multi_block();
4798        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4799        let mut full_context = DecoderContext::default();
4800        let mut roi_context = DecoderContext::default();
4801
4802        let full = image
4803            .build_direct_color_plan_with_context(&mut full_context)
4804            .expect("build full RGB direct plan");
4805        let roi = image
4806            .build_direct_color_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4807            .expect("build ROI RGB direct plan");
4808
4809        let full_jobs = direct_color_ht_job_count(&full);
4810        let roi_jobs = direct_color_ht_job_count(&roi);
4811        assert!(full_jobs > 3, "fixture must expose multiple RGB HT jobs");
4812        assert!(
4813            roi_jobs < full_jobs,
4814            "RGB ROI direct plan must prune HT jobs before device preparation"
4815        );
4816    }
4817
4818    #[test]
4819    fn color_direct_plan_honors_target_resolution() {
4820        for (name, bytes) in [
4821            ("classic", {
4822                let pixels = gradient_pixels(8, 8, 3);
4823                let options = EncodeOptions {
4824                    reversible: true,
4825                    num_decomposition_levels: 2,
4826                    ..EncodeOptions::default()
4827                };
4828                encode(&pixels, 8, 8, 3, 8, false, &options).expect("encode classic rgb8")
4829            }),
4830            ("htj2k", {
4831                let pixels = gradient_pixels(8, 8, 3);
4832                let options = EncodeOptions {
4833                    reversible: true,
4834                    num_decomposition_levels: 2,
4835                    ..EncodeOptions::default()
4836                };
4837                encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode ht rgb8")
4838            }),
4839        ] {
4840            let image = Image::new(
4841                &bytes,
4842                &DecodeSettings {
4843                    target_resolution: Some((4, 4)),
4844                    ..DecodeSettings::default()
4845                },
4846            )
4847            .expect("scaled RGB image");
4848            let mut context = DecoderContext::default();
4849
4850            let plan = image
4851                .build_direct_color_plan_with_context(&mut context)
4852                .expect("build scaled direct color plan");
4853
4854            assert_eq!(plan.dimensions, (4, 4), "{name}: output dimensions");
4855            assert_eq!(plan.component_plans.len(), 3, "{name}: component count");
4856            for component_plan in &plan.component_plans {
4857                assert_eq!(component_plan.dimensions, (4, 4), "{name}: component dims");
4858                assert!(component_plan.steps.iter().any(|step| matches!(
4859                    step,
4860                    J2kDirectGrayscaleStep::Store(store)
4861                        if store.output_width == 4 && store.output_height == 4
4862                )));
4863            }
4864            assert!(
4865                context.tile_decode_context.channel_data.is_empty(),
4866                "{name}: building a scaled color direct plan must not materialize host component planes"
4867            );
4868        }
4869    }
4870
4871    #[test]
4872    fn direct_color_cpu_rgb8_executor_matches_scaled_region_decode() {
4873        for (name, bytes) in [
4874            ("classic", {
4875                let pixels = gradient_pixels(16, 16, 3);
4876                let options = EncodeOptions {
4877                    reversible: true,
4878                    num_decomposition_levels: 2,
4879                    ..EncodeOptions::default()
4880                };
4881                encode(&pixels, 16, 16, 3, 8, false, &options).expect("encode classic rgb8")
4882            }),
4883            ("htj2k", {
4884                let pixels = gradient_pixels(16, 16, 3);
4885                let options = EncodeOptions {
4886                    reversible: true,
4887                    num_decomposition_levels: 2,
4888                    ..EncodeOptions::default()
4889                };
4890                encode_htj2k(&pixels, 16, 16, 3, 8, false, &options).expect("encode ht rgb8")
4891            }),
4892        ] {
4893            let image = Image::new(
4894                &bytes,
4895                &DecodeSettings {
4896                    target_resolution: Some((4, 4)),
4897                    ..DecodeSettings::default()
4898                },
4899            )
4900            .expect("scaled RGB image");
4901            let mut expected_context = DecoderContext::default();
4902            let expected_full = image
4903                .decode_with_context(&mut expected_context)
4904                .expect("decode scaled reference");
4905            let output_region = J2kRect {
4906                x0: 1,
4907                y0: 1,
4908                x1: 3,
4909                y1: 3,
4910            };
4911            let mut direct_context = DecoderContext::default();
4912            let plan = image
4913                .build_direct_color_plan_region_with_context(
4914                    &mut direct_context,
4915                    (
4916                        output_region.x0,
4917                        output_region.y0,
4918                        output_region.width(),
4919                        output_region.height(),
4920                    ),
4921                )
4922                .expect("build direct RGB region plan");
4923
4924            let stride = output_region.width() as usize * 3;
4925            let mut direct = vec![0_u8; stride * output_region.height() as usize];
4926            let mut scratch = J2kDirectCpuScratch::new();
4927            execute_direct_color_plan_rgb8_into(
4928                &plan,
4929                output_region,
4930                &mut scratch,
4931                &mut direct,
4932                stride,
4933            )
4934            .expect("execute direct RGB plan");
4935
4936            let mut expected = Vec::with_capacity(direct.len());
4937            let full_stride = image.width() as usize * 3;
4938            for y in output_region.y0..output_region.y1 {
4939                let start = y as usize * full_stride + output_region.x0 as usize * 3;
4940                expected.extend_from_slice(&expected_full.data[start..start + stride]);
4941            }
4942
4943            assert_eq!(direct, expected, "{name}: direct RGB output");
4944
4945            let rgba_stride = output_region.width() as usize * 4;
4946            let mut direct_rgba = vec![0_u8; rgba_stride * output_region.height() as usize];
4947            execute_direct_color_plan_rgba8_into(
4948                &plan,
4949                output_region,
4950                &mut scratch,
4951                &mut direct_rgba,
4952                rgba_stride,
4953            )
4954            .expect("execute direct RGBA plan");
4955
4956            let mut expected_rgba = Vec::with_capacity(direct_rgba.len());
4957            for rgb in expected.chunks_exact(3) {
4958                expected_rgba.extend_from_slice(rgb);
4959                expected_rgba.push(255);
4960            }
4961            assert_eq!(direct_rgba, expected_rgba, "{name}: direct RGBA output");
4962        }
4963    }
4964
4965    #[test]
4966    fn htj2k_grayscale_direct_plan_contains_ht_sub_band_steps() {
4967        let bytes = fixture_ht_gray();
4968        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4969        let mut context = DecoderContext::default();
4970
4971        let plan = image
4972            .build_direct_grayscale_plan_with_context(&mut context)
4973            .expect("build direct plan");
4974
4975        assert!(
4976            plan.steps.iter().any(|step| matches!(
4977                step,
4978                J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4979            )),
4980            "HTJ2K direct plan must contain at least one non-empty HT sub-band decode step"
4981        );
4982    }
4983
4984    #[test]
4985    fn ht_decoder_hook_is_used_for_htj2k_codeblocks() {
4986        let pixels: Vec<u8> = (0..16).collect();
4987        let options = EncodeOptions {
4988            reversible: true,
4989            num_decomposition_levels: 1,
4990            ..EncodeOptions::default()
4991        };
4992        let bytes = encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht");
4993        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4994        let mut hooked_context = DecoderContext::default();
4995        let mut hook = FailingHtDecoder { called: false };
4996        let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4997            Ok(_) => panic!("hooked decode must use external HT decoder"),
4998            Err(error) => error,
4999        };
5000
5001        assert!(hook.called, "HT decoder hook must be invoked");
5002        assert_eq!(
5003            error,
5004            DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
5005        );
5006    }
5007
5008    #[test]
5009    fn openhtj2k_conformance_fixture_exercises_refinement_passes() {
5010        for fixture in [
5011            (
5012                "ds0_ht_12_b11",
5013                fixture_openhtj2k_ht_refinement(),
5014                fixture_openhtj2k_ht_refinement_pixels(),
5015                (3, 5),
5016                8,
5017                2,
5018                4,
5019            ),
5020            (
5021                "ds0_ht_09_b11",
5022                fixture_openhtj2k_ht_refinement_odd(),
5023                fixture_openhtj2k_ht_refinement_odd_pixels(),
5024                (17, 37),
5025                14,
5026                14,
5027                629,
5028            ),
5029        ] {
5030            let (
5031                name,
5032                codestream,
5033                expected_pixels,
5034                dimensions,
5035                blocks,
5036                refinement_jobs,
5037                zero_diffs,
5038            ) = fixture;
5039            let image = Image::new(codestream, &DecodeSettings::default()).expect("image");
5040            let mut context = DecoderContext::default();
5041            let mut hook = CapturingHtDecoder::default();
5042
5043            let components = image
5044                .decode_components_with_ht_decoder(&mut context, &mut hook)
5045                .expect("decode OpenHTJ2K HTJ2K fixture");
5046
5047            assert!(
5048                hook.called,
5049                "{name}: HTJ2K fixture must use HT code-block decode"
5050            );
5051            assert!(
5052                hook.refinement_jobs > 0,
5053                "{name}: OpenHTJ2K fixture must contain non-empty refinement segments"
5054            );
5055            assert!(
5056                hook.max_coding_passes > 1,
5057                "{name}: OpenHTJ2K fixture must exercise more than the cleanup pass"
5058            );
5059            assert_eq!(hook.blocks, blocks, "{name}: HT code-block count");
5060            assert_eq!(
5061                hook.refinement_jobs, refinement_jobs,
5062                "{name}: refinement job count"
5063            );
5064            assert_eq!(hook.max_coding_passes, 3, "{name}: max HT coding passes");
5065            assert_eq!(components.dimensions(), dimensions, "{name}: dimensions");
5066            assert_eq!(components.planes().len(), 1, "{name}: component planes");
5067
5068            let decoded: Vec<u8> = components.planes()[0]
5069                .samples()
5070                .iter()
5071                .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
5072                .collect();
5073            assert_eq!(decoded, expected_pixels, "{name}: decoded pixels");
5074
5075            let mut zero_context = DecoderContext::default();
5076            let mut zero_hook = ZeroRefinementHtDecoder;
5077            let zeroed_components = image
5078                .decode_components_with_ht_decoder(&mut zero_context, &mut zero_hook)
5079                .expect("decode OpenHTJ2K fixture with zeroed refinement bytes");
5080            let actual_zero_diffs = components.planes()[0]
5081                .samples()
5082                .iter()
5083                .zip(zeroed_components.planes()[0].samples())
5084                .filter(|(actual, zeroed)| (*actual - *zeroed).abs() > f32::EPSILON)
5085                .count();
5086            assert_eq!(
5087                actual_zero_diffs, zero_diffs,
5088                "{name}: zeroing refinement bytes must change decoded samples"
5089            );
5090        }
5091    }
5092
5093    #[test]
5094    fn openhtj2k_refinement_phase_limited_decode_differs_and_records_ht_stats() {
5095        let image = Image::new(
5096            fixture_openhtj2k_ht_refinement_odd(),
5097            &DecodeSettings::default(),
5098        )
5099        .expect("image");
5100        let mut full_context = DecoderContext::default();
5101
5102        let (full_samples, full_decoded) = {
5103            let full_components = image
5104                .decode_components_with_context(&mut full_context)
5105                .expect("full native decode of OpenHTJ2K refinement fixture");
5106            let full_samples = full_components.planes()[0].samples().to_vec();
5107            let full_decoded: Vec<u8> = full_samples
5108                .iter()
5109                .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
5110                .collect();
5111            (full_samples, full_decoded)
5112        };
5113        assert_eq!(
5114            full_decoded,
5115            fixture_openhtj2k_ht_refinement_odd_pixels(),
5116            "full decode must match the checked-in OpenHTJ2K oracle"
5117        );
5118
5119        let stats = full_context
5120            .tile_decode_context
5121            .debug_counters
5122            .ht_phase_stats;
5123        assert_eq!(stats.blocks, 14, "HT block count");
5124        assert_eq!(stats.refinement_blocks, 14, "HT refinement block count");
5125        assert!(stats.cleanup_bytes > 0, "cleanup byte total");
5126        assert!(stats.refinement_bytes > 0, "refinement byte total");
5127
5128        let mut cleanup_context = DecoderContext::default();
5129        let mut cleanup_hook = CleanupLimitedHtDecoder::default();
5130        let cleanup_components = image
5131            .decode_components_with_ht_decoder(&mut cleanup_context, &mut cleanup_hook)
5132            .expect("cleanup-limited decode of OpenHTJ2K refinement fixture");
5133        let cleanup_decoded: Vec<u8> = cleanup_components.planes()[0]
5134            .samples()
5135            .iter()
5136            .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
5137            .collect();
5138        let cleanup_sample_diffs = full_samples
5139            .iter()
5140            .zip(cleanup_components.planes()[0].samples())
5141            .filter(|(full, cleanup)| (*full - *cleanup).abs() > f32::EPSILON)
5142            .count();
5143
5144        assert!(
5145            cleanup_sample_diffs > 0,
5146            "cleanup-limited decode must omit refinement effects"
5147        );
5148        assert_eq!(
5149            cleanup_decoded, full_decoded,
5150            "fixture refinement differences are below final u8 clamping"
5151        );
5152        assert_eq!(cleanup_hook.blocks, 14, "hook HT block count");
5153        assert_eq!(
5154            cleanup_hook.refinement_blocks, 14,
5155            "hook HT refinement block count"
5156        );
5157        assert!(cleanup_hook.cleanup_bytes > 0, "hook cleanup byte total");
5158        assert!(
5159            cleanup_hook.refinement_bytes > 0,
5160            "hook refinement byte total"
5161        );
5162    }
5163
5164    #[test]
5165    fn scalar_htj2k_encoder_contract_is_cleanup_only() {
5166        let coefficients = (0..64)
5167            .map(|index| {
5168                let magnitude = (index % 7) + 1;
5169                if index % 2 == 0 {
5170                    magnitude
5171                } else {
5172                    -magnitude
5173                }
5174            })
5175            .collect::<Vec<_>>();
5176
5177        let encoded =
5178            encode_ht_code_block_scalar(&coefficients, 8, 8, 8).expect("encode HT code block");
5179
5180        assert_eq!(
5181            encoded.num_coding_passes, 1,
5182            "current scalar HTJ2K encoder emits only the cleanup pass"
5183        );
5184        assert_eq!(
5185            encoded.num_zero_bitplanes, 7,
5186            "current cleanup-only HTJ2K encoder includes one bitplane"
5187        );
5188        assert!(
5189            !encoded.data.is_empty(),
5190            "non-zero cleanup-only block must still produce payload bytes"
5191        );
5192    }
5193
5194    #[test]
5195    fn scalar_htj2k_decode_workspace_matches_fresh_decode_and_reuses_capacity() {
5196        let image = Image::new(
5197            fixture_openhtj2k_ht_refinement_odd(),
5198            &DecodeSettings::default(),
5199        )
5200        .expect("image");
5201        let mut context = DecoderContext::default();
5202        let mut hook = FirstHtJobDecoder::default();
5203        image
5204            .decode_components_with_ht_decoder(&mut context, &mut hook)
5205            .expect("decode fixture while collecting HT jobs");
5206        let job = hook
5207            .job
5208            .as_ref()
5209            .expect("fixture must expose an HT decode job")
5210            .borrowed();
5211        let mut fresh = vec![0.0_f32; job.width as usize * job.height as usize];
5212        let mut reused = vec![0.0_f32; fresh.len()];
5213        let mut profiled = vec![0.0_f32; fresh.len()];
5214        let mut workspace = HtCodeBlockDecodeWorkspace::default();
5215        let mut profile = HtCodeBlockDecodeProfile::default();
5216
5217        decode_ht_code_block_scalar(job, &mut fresh).expect("fresh HT decode");
5218        decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
5219            .expect("workspace HT decode");
5220        let first_capacity = workspace.coefficient_capacity();
5221        decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
5222            .expect("second workspace HT decode");
5223        decode_ht_code_block_scalar_with_workspace_profiled(
5224            job,
5225            &mut profiled,
5226            &mut workspace,
5227            &mut profile,
5228        )
5229        .expect("profiled workspace HT decode");
5230
5231        assert_eq!(reused, fresh);
5232        assert_eq!(profiled, fresh);
5233        assert!(first_capacity >= fresh.len());
5234        assert_eq!(workspace.coefficient_capacity(), first_capacity);
5235        assert_eq!(profile.blocks, 1);
5236        assert!(profile.cleanup_bytes > 0);
5237    }
5238
5239    #[test]
5240    fn classic_decoder_hook_is_used_for_j2k_codeblocks() {
5241        let bytes = fixture();
5242        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
5243        let mut hooked_context = DecoderContext::default();
5244        let mut hook = FailingClassicDecoder { called: false };
5245        let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
5246            Ok(_) => panic!("hooked decode must use external classic decoder"),
5247            Err(error) => error,
5248        };
5249
5250        assert!(hook.called, "classic decoder hook must be invoked");
5251        assert_eq!(
5252            error,
5253            DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
5254        );
5255    }
5256
5257    #[test]
5258    fn classic_sub_band_decoder_hook_is_used_for_j2k_codeblocks() {
5259        let bytes = fixture_multi_block();
5260        let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
5261        let mut hooked_context = DecoderContext::default();
5262        let mut hook = FailingClassicBatchDecoder { called: false };
5263        let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
5264            Ok(_) => panic!("hooked decode must use external classic batch decoder"),
5265            Err(error) => error,
5266        };
5267
5268        assert!(hook.called, "classic sub-band decoder hook must be invoked");
5269        assert_eq!(
5270            error,
5271            DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
5272        );
5273    }
5274
5275    // -----------------------------------------------------------------------
5276    // Sanity tests for the four scalar-reference exports
5277    // -----------------------------------------------------------------------
5278
5279    #[test]
5280    fn forward_dwt53_reference_matches_internal_path() {
5281        // 4×4 constant-ramp input; 1 decomposition level.
5282        let samples: Vec<f32> = (0..16).map(|i| i as f32).collect();
5283        let out = forward_dwt53_reference(&samples, 4, 4, 1);
5284
5285        // Internal path
5286        let internal = j2c::fdwt::forward_dwt(&samples, 4, 4, 1, true);
5287
5288        assert_eq!(out.ll, internal.ll, "LL subband mismatch");
5289        assert_eq!(out.ll_width, internal.ll_width, "LL width mismatch");
5290        assert_eq!(out.ll_height, internal.ll_height, "LL height mismatch");
5291        assert_eq!(out.levels.len(), internal.levels.len(), "level count");
5292        for (pub_lvl, int_lvl) in out.levels.iter().zip(internal.levels.iter()) {
5293            assert_eq!(pub_lvl.hl, int_lvl.hl, "HL mismatch");
5294            assert_eq!(pub_lvl.lh, int_lvl.lh, "LH mismatch");
5295            assert_eq!(pub_lvl.hh, int_lvl.hh, "HH mismatch");
5296        }
5297    }
5298
5299    #[test]
5300    fn forward_rct_reference_matches_internal_path() {
5301        // Single pixel: R=100, G=150, B=200
5302        let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
5303        let result = forward_rct_reference(planes.clone());
5304
5305        // Internal path
5306        let mut internal = planes;
5307        j2c::forward_mct::forward_rct(&mut internal);
5308
5309        assert_eq!(result, internal, "RCT output mismatch");
5310        // Y = floor((100 + 300 + 200) / 4) = 150
5311        assert_eq!(result[0][0], 150.0, "Y component");
5312        assert_eq!(result[1][0], 50.0, "Cb component");
5313        assert_eq!(result[2][0], -50.0, "Cr component");
5314    }
5315
5316    #[test]
5317    fn forward_ict_reference_matches_internal_path() {
5318        let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
5319        let result = forward_ict_reference(planes.clone());
5320
5321        let mut internal = planes;
5322        j2c::forward_mct::forward_ict(&mut internal);
5323
5324        assert_eq!(result, internal, "ICT output mismatch");
5325    }
5326
5327    #[test]
5328    fn forward_dwt97_reference_matches_internal_path() {
5329        let samples = (0..64)
5330            .map(|idx| {
5331                f32::from(u8::try_from((idx * 19 + idx / 3) & 0xff).expect("masked sample fits u8"))
5332                    - 128.0
5333            })
5334            .collect::<Vec<_>>();
5335        let result = forward_dwt97_reference(&samples, 8, 8, 2);
5336        let internal = j2c::fdwt::forward_dwt(&samples, 8, 8, 2, false);
5337
5338        assert_eq!(result.ll, internal.ll, "DWT 9/7 LL mismatch");
5339        assert_eq!(result.ll_width, internal.ll_width);
5340        assert_eq!(result.ll_height, internal.ll_height);
5341        assert_eq!(result.levels.len(), internal.levels.len());
5342        for (actual, expected) in result.levels.iter().zip(internal.levels.iter()) {
5343            assert_eq!(actual.hl, expected.hl, "DWT 9/7 HL mismatch");
5344            assert_eq!(actual.lh, expected.lh, "DWT 9/7 LH mismatch");
5345            assert_eq!(actual.hh, expected.hh, "DWT 9/7 HH mismatch");
5346        }
5347    }
5348
5349    #[test]
5350    fn quantize_reversible_reference_matches_internal_path() {
5351        let coefficients = vec![3.7f32, -8.2, 0.5, -0.5, 10.0];
5352        let exponent = 8u16;
5353        let mantissa = 0u16;
5354        let range_bits = 8u8;
5355
5356        let result =
5357            quantize_reversible_reference(&coefficients, exponent, mantissa, range_bits, true);
5358
5359        // Internal path
5360        let step = j2c::quantize::QuantStepSize { exponent, mantissa };
5361        let internal = j2c::quantize::quantize_subband(&coefficients, &step, range_bits, true);
5362
5363        assert_eq!(result, internal, "quantize output mismatch");
5364        // reversible: round to nearest
5365        assert_eq!(result[0], 4, "3.7 rounds to 4");
5366        assert_eq!(result[1], -8, "-8.2 rounds to -8");
5367    }
5368
5369    #[test]
5370    fn quantize_subband_reference_matches_irreversible_internal_path() {
5371        let coefficients = vec![3.7f32, -8.2, 0.5, -0.5, 10.0];
5372        let exponent = 8u16;
5373        let mantissa = 256u16;
5374        let range_bits = 8u8;
5375
5376        let result =
5377            quantize_subband_reference(&coefficients, exponent, mantissa, range_bits, false);
5378
5379        let step = j2c::quantize::QuantStepSize { exponent, mantissa };
5380        let internal = j2c::quantize::quantize_subband(&coefficients, &step, range_bits, false);
5381
5382        assert_eq!(result, internal, "irreversible quantize output mismatch");
5383    }
5384
5385    #[test]
5386    fn deinterleave_reference_matches_internal_path() {
5387        // 2-pixel RGB8 unsigned: [R0,G0,B0, R1,G1,B1]
5388        let pixels: Vec<u8> = vec![128, 64, 200, 10, 20, 30];
5389        let result = deinterleave_reference(&pixels, 2, 3, 8, false);
5390
5391        let internal = j2c::encode::deinterleave_to_f32(&pixels, 2, 3, 8, false);
5392
5393        assert_eq!(result, internal, "deinterleave output mismatch");
5394        assert_eq!(result.len(), 3, "three component planes");
5395        assert_eq!(result[0].len(), 2, "two pixels per plane");
5396        // unsigned 8-bit with level shift: val - 128
5397        assert!((result[0][0] - 0.0f32).abs() < 1e-6, "R0 level-shifted");
5398        assert!((result[1][0] - (-64.0f32)).abs() < 1e-6, "G0 level-shifted");
5399        assert!((result[2][0] - 72.0f32).abs() < 1e-6, "B0 level-shifted");
5400    }
5401}