1#![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#[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#[derive(Debug, Clone, Copy)]
245pub struct HtCodeBlockDecodeJob<'a> {
246 pub data: &'a [u8],
248 pub cleanup_length: u32,
250 pub refinement_length: u32,
252 pub width: u32,
254 pub height: u32,
256 pub output_stride: usize,
258 pub missing_bit_planes: u8,
260 pub number_of_coding_passes: u8,
262 pub num_bitplanes: u8,
264 pub roi_shift: u8,
266 pub stripe_causal: bool,
268 pub strict: bool,
270 pub dequantization_step: f32,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum HtCodeBlockDecodePhaseLimit {
277 Cleanup,
279 SignificancePropagation,
281 MagnitudeRefinement,
283}
284
285#[derive(Debug, Clone, Copy)]
287pub struct HtCodeBlockBatchJob<'a> {
288 pub output_x: u32,
290 pub output_y: u32,
292 pub code_block: HtCodeBlockDecodeJob<'a>,
294}
295
296#[derive(Debug, Clone, Copy)]
298pub struct HtSubBandDecodeJob<'a> {
299 pub width: u32,
301 pub height: u32,
303 pub jobs: &'a [HtCodeBlockBatchJob<'a>],
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub struct J2kTier1TokenSegment {
310 pub token_bit_offset: u32,
312 pub token_bit_count: u32,
317 pub start_coding_pass: u8,
319 pub end_coding_pass: u8,
321 pub use_arithmetic: bool,
323}
324
325#[derive(Debug, Clone, Copy)]
327pub struct J2kCodeBlockDecodeJob<'a> {
328 pub data: &'a [u8],
330 pub segments: &'a [J2kCodeBlockSegment],
332 pub width: u32,
334 pub height: u32,
336 pub output_stride: usize,
338 pub missing_bit_planes: u8,
340 pub number_of_coding_passes: u8,
342 pub total_bitplanes: u8,
344 pub roi_shift: u8,
346 pub sub_band_type: J2kSubBandType,
348 pub style: J2kCodeBlockStyle,
350 pub strict: bool,
352 pub dequantization_step: f32,
354}
355
356#[derive(Debug, Clone, Default, PartialEq, Eq)]
358pub struct HtCleanupEncodeDistribution {
359 pub total_quads: u64,
361 pub initial_quads: u64,
363 pub non_initial_quads: u64,
365 pub rho_counts: [u64; 16],
367 pub initial_rho_counts: [u64; 16],
369 pub non_initial_rho_counts: [u64; 16],
371 pub non_initial_u_q_counts: [u64; 32],
373 pub non_initial_e_qmax_counts: [u64; 32],
375 pub non_initial_kappa_counts: [u64; 32],
377 pub non_initial_rho_u_q_counts: [[u64; 32]; 16],
379 pub mag_sign_calls: u64,
381 pub mag_sign_rho_counts: [u64; 16],
383 pub mag_sign_sample_bit_counts: [u64; 32],
385 pub mag_sign_encoded_samples: u64,
387}
388
389pub trait J2kEncodeStageAccelerator {
391 fn dispatch_report(&self) -> J2kEncodeDispatchReport {
393 J2kEncodeDispatchReport::default()
394 }
395
396 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 fn encode_forward_rct(
412 &mut self,
413 _job: J2kForwardRctJob<'_>,
414 ) -> core::result::Result<bool, &'static str> {
415 Ok(false)
416 }
417
418 fn encode_forward_ict(
423 &mut self,
424 _job: J2kForwardIctJob<'_>,
425 ) -> core::result::Result<bool, &'static str> {
426 Ok(false)
427 }
428
429 fn encode_forward_dwt53(
434 &mut self,
435 _job: J2kForwardDwt53Job<'_>,
436 ) -> core::result::Result<Option<J2kForwardDwt53Output>, &'static str> {
437 Ok(None)
438 }
439
440 fn encode_forward_dwt97(
445 &mut self,
446 _job: J2kForwardDwt97Job<'_>,
447 ) -> core::result::Result<Option<J2kForwardDwt97Output>, &'static str> {
448 Ok(None)
449 }
450
451 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 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 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 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 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 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 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 fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
535 false
536 }
537
538 fn prefer_parallel_cpu_tile_encode(&self) -> bool {
544 false
545 }
546
547 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#[derive(Debug, Clone, Copy)]
571pub struct J2kCodeBlockBatchJob<'a> {
572 pub output_x: u32,
574 pub output_y: u32,
576 pub code_block: J2kCodeBlockDecodeJob<'a>,
578}
579
580#[derive(Debug, Clone, Copy)]
582pub struct J2kSubBandDecodeJob<'a> {
583 pub width: u32,
585 pub height: u32,
587 pub jobs: &'a [J2kCodeBlockBatchJob<'a>],
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub struct J2kRect {
594 pub x0: u32,
596 pub y0: u32,
598 pub x1: u32,
600 pub y1: u32,
602}
603
604impl J2kRect {
605 pub fn width(self) -> u32 {
607 self.x1.saturating_sub(self.x0)
608 }
609
610 pub fn height(self) -> u32 {
612 self.y1.saturating_sub(self.y0)
613 }
614}
615
616#[derive(Debug, Clone, Copy, PartialEq, Eq)]
618pub enum J2kWaveletTransform {
619 Reversible53,
621 Irreversible97,
623}
624
625#[derive(Debug, Clone, Copy)]
627pub struct J2kIdwtBand<'a> {
628 pub rect: J2kRect,
630 pub coefficients: &'a [f32],
632}
633
634#[derive(Debug, Clone, Copy)]
636pub struct J2kSingleDecompositionIdwtJob<'a> {
637 pub rect: J2kRect,
639 pub transform: J2kWaveletTransform,
641 pub ll: J2kIdwtBand<'a>,
643 pub hl: J2kIdwtBand<'a>,
645 pub lh: J2kIdwtBand<'a>,
647 pub hh: J2kIdwtBand<'a>,
649}
650
651#[derive(Debug)]
653pub struct J2kInverseMctJob<'a> {
654 pub transform: J2kWaveletTransform,
656 pub plane0: &'a mut [f32],
658 pub plane1: &'a mut [f32],
660 pub plane2: &'a mut [f32],
662 pub addend0: f32,
664 pub addend1: f32,
666 pub addend2: f32,
668}
669
670#[derive(Debug)]
672pub struct J2kStoreComponentJob<'a> {
673 pub input: &'a [f32],
675 pub input_width: u32,
677 pub source_x: u32,
679 pub source_y: u32,
681 pub copy_width: u32,
683 pub copy_height: u32,
685 pub output: &'a mut [f32],
687 pub output_width: u32,
689 pub output_x: u32,
691 pub output_y: u32,
693 pub addend: f32,
695}
696
697pub trait HtCodeBlockDecoder {
699 fn decode_j2k_sub_band(
705 &mut self,
706 _job: J2kSubBandDecodeJob<'_>,
707 _output: &mut [f32],
708 ) -> Result<bool> {
709 Ok(false)
710 }
711
712 fn decode_j2k_code_block(
718 &mut self,
719 _job: J2kCodeBlockDecodeJob<'_>,
720 _output: &mut [f32],
721 ) -> Result<bool> {
722 Ok(false)
723 }
724
725 fn decode_sub_band(
731 &mut self,
732 _job: HtSubBandDecodeJob<'_>,
733 _output: &mut [f32],
734 ) -> Result<bool> {
735 Ok(false)
736 }
737
738 fn decode_single_decomposition_idwt(
744 &mut self,
745 _job: J2kSingleDecompositionIdwtJob<'_>,
746 _output: &mut [f32],
747 ) -> Result<bool> {
748 Ok(false)
749 }
750
751 fn decode_inverse_mct(&mut self, _job: J2kInverseMctJob<'_>) -> Result<bool> {
757 Ok(false)
758 }
759
760 fn decode_store_component(&mut self, _job: J2kStoreComponentJob<'_>) -> Result<bool> {
766 Ok(false)
767 }
768
769 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
836pub 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
873pub 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
920pub 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
938pub 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
962pub 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
972pub 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
1007pub 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
1042pub fn forward_rct_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1049 j2c::forward_mct::forward_rct(&mut planes);
1050 planes
1051}
1052
1053pub fn forward_ict_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1059 j2c::forward_mct::forward_ict(&mut planes);
1060 planes
1061}
1062
1063pub 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
1078pub 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
1102pub 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
1117pub 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
1185pub 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#[derive(Default)]
1196pub struct J2kCodeBlockDecodeWorkspace {
1197 bit_plane_decode_context: j2c::bitplane::BitPlaneDecodeContext,
1198}
1199
1200pub 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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1261pub struct J2kCodeBlockDecodeProfile {
1262 pub sigprop_us: u128,
1264 pub magref_us: u128,
1266 pub cleanup_us: u128,
1268 pub bypass_us: u128,
1270 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
1283pub 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
1293pub 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
1359pub 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
1420pub 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
1430pub 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#[derive(Default)]
1455pub struct HtCodeBlockDecodeWorkspace {
1456 coefficients: Vec<u32>,
1457 scratch: j2c::ht_block_decode::HtBlockDecodeScratch,
1458}
1459
1460impl HtCodeBlockDecodeWorkspace {
1461 pub fn coefficient_capacity(&self) -> usize {
1463 self.coefficients.capacity()
1464 }
1465}
1466
1467#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1469pub struct HtCodeBlockDecodeProfile {
1470 pub blocks: u128,
1472 pub refinement_blocks: u128,
1474 pub cleanup_bytes: u128,
1476 pub refinement_bytes: u128,
1478 pub cleanup_us: u128,
1480 pub mag_sgn_us: u128,
1482 pub sigma_us: u128,
1484 pub sigprop_us: u128,
1486 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
1504pub 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
1515pub 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
1668pub struct HtSigPropBenchmarkState(j2c::ht_block_decode::HtSigPropBenchmarkState);
1670
1671impl HtSigPropBenchmarkState {
1672 pub fn output_len(&self) -> usize {
1674 self.0.output_len()
1675 }
1676}
1677
1678pub 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
1701pub 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
1709pub fn ht_vlc_table0() -> &'static [u16; 1024] {
1711 &j2c::ht_tables::VLC_TABLE0
1712}
1713
1714pub fn ht_vlc_table1() -> &'static [u16; 1024] {
1716 &j2c::ht_tables::VLC_TABLE1
1717}
1718
1719pub fn ht_uvlc_table0() -> &'static [u16; 320] {
1721 &j2c::ht_tables::UVLC_TABLE0
1722}
1723
1724pub fn ht_uvlc_table1() -> &'static [u16; 256] {
1726 &j2c::ht_tables::UVLC_TABLE1
1727}
1728
1729pub fn ht_vlc_encode_table0() -> &'static [u16; 2048] {
1731 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE0
1732}
1733
1734pub fn ht_vlc_encode_table1() -> &'static [u16; 2048] {
1736 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE1
1737}
1738
1739pub fn ht_uvlc_encode_table() -> &'static [HtUvlcTableEntry; 75] {
1741 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE
1742}
1743
1744pub fn ht_uvlc_encode_table_bytes() -> &'static [u8] {
1746 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE_BYTES
1747}
1748
1749pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
1751pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
1753
1754#[derive(Debug, Copy, Clone)]
1756pub struct DecodeSettings {
1757 pub resolve_palette_indices: bool,
1769 pub strict: bool,
1774 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
1788pub struct Image<'a> {
1790 pub(crate) codestream: &'a [u8],
1792 pub(crate) header: Header<'a>,
1794 pub(crate) boxes: ImageBoxes,
1797 pub(crate) settings: DecodeSettings,
1799 pub(crate) has_alpha: bool,
1801 pub(crate) color_space: ColorSpace,
1803}
1804
1805impl<'a> Image<'a> {
1806 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 pub fn has_alpha(&self) -> bool {
1819 self.has_alpha
1820 }
1821
1822 pub fn color_space(&self) -> &ColorSpace {
1824 &self.color_space
1825 }
1826
1827 pub fn width(&self) -> u32 {
1829 self.header.size_data.image_width()
1830 }
1831
1832 pub fn height(&self) -> u32 {
1834 self.header.size_data.image_height()
1835 }
1836
1837 pub fn original_bit_depth(&self) -> u8 {
1840 self.header.component_infos[0].size_info.precision
1842 }
1843
1844 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 pub fn decode(&self) -> Result<Vec<u8>> {
1866 let bitmap = self.decode_with_context(&mut DecoderContext::default())?;
1867 Ok(bitmap.data)
1868 }
1869
1870 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 !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 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 has_alpha = true;
2832 } else {
2833 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#[derive(Debug, Clone)]
2858pub enum ColorSpace {
2859 Gray,
2861 RGB,
2863 CMYK,
2865 Unknown {
2867 num_channels: u16,
2869 },
2870 Icc {
2872 profile: Vec<u8>,
2874 num_channels: u16,
2876 },
2877}
2878
2879impl ColorSpace {
2880 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
2895pub struct Bitmap {
2897 pub color_space: ColorSpace,
2899 pub data: Vec<u8>,
2908 pub has_alpha: bool,
2910 pub width: u32,
2912 pub height: u32,
2914 pub original_bit_depth: u8,
2917}
2918
2919pub struct RawBitmap {
2928 pub data: Vec<u8>,
2930 pub width: u32,
2932 pub height: u32,
2934 pub bit_depth: u8,
2936 pub signed: bool,
2941 pub component_signed: Vec<bool>,
2943 pub num_components: u16,
2945 pub bytes_per_sample: u8,
2947}
2948
2949pub 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 pub fn data(&self) -> &[u8] {
2962 &self.data
2963 }
2964
2965 pub fn dimensions(&self) -> (u32, u32) {
2967 self.dimensions
2968 }
2969
2970 pub fn sampling(&self) -> (u8, u8) {
2973 self.sampling
2974 }
2975
2976 pub fn bit_depth(&self) -> u8 {
2978 self.bit_depth
2979 }
2980
2981 pub fn signed(&self) -> bool {
2983 self.signed
2984 }
2985
2986 pub fn bytes_per_sample(&self) -> u8 {
2988 self.bytes_per_sample
2989 }
2990}
2991
2992pub struct DecodedNativeComponents {
2994 dimensions: (u32, u32),
2995 color_space: ColorSpace,
2996 has_alpha: bool,
2997 planes: Vec<NativeComponentPlane>,
2998}
2999
3000impl DecodedNativeComponents {
3001 pub fn dimensions(&self) -> (u32, u32) {
3003 self.dimensions
3004 }
3005
3006 pub fn color_space(&self) -> &ColorSpace {
3008 &self.color_space
3009 }
3010
3011 pub fn has_alpha(&self) -> bool {
3013 self.has_alpha
3014 }
3015
3016 pub fn planes(&self) -> &[NativeComponentPlane] {
3018 &self.planes
3019 }
3020}
3021
3022pub 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 pub fn samples(&self) -> &'a [f32] {
3034 self.samples
3035 }
3036
3037 pub fn dimensions(&self) -> (u32, u32) {
3039 self.dimensions
3040 }
3041
3042 pub fn sampling(&self) -> (u8, u8) {
3045 self.sampling
3046 }
3047
3048 pub fn bit_depth(&self) -> u8 {
3050 self.bit_depth
3051 }
3052
3053 pub fn signed(&self) -> bool {
3055 self.signed
3056 }
3057}
3058
3059pub 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 pub fn dimensions(&self) -> (u32, u32) {
3070 self.dimensions
3071 }
3072
3073 pub fn color_space(&self) -> &ColorSpace {
3075 &self.color_space
3076 }
3077
3078 pub fn has_alpha(&self) -> bool {
3080 self.has_alpha
3081 }
3082
3083 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 match num_components {
3128 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 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 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 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 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 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 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 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 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 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 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 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 let r = cr_v.mul_add(cr_to_r, y_v);
3574 let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
3576 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 #[test]
5280 fn forward_dwt53_reference_matches_internal_path() {
5281 let samples: Vec<f32> = (0..16).map(|i| i as f32).collect();
5283 let out = forward_dwt53_reference(&samples, 4, 4, 1);
5284
5285 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 let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
5303 let result = forward_rct_reference(planes.clone());
5304
5305 let mut internal = planes;
5307 j2c::forward_mct::forward_rct(&mut internal);
5308
5309 assert_eq!(result, internal, "RCT output mismatch");
5310 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 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 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 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 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}