1#![cfg_attr(not(feature = "std"), no_std)]
74#![forbid(unsafe_code)]
75#![forbid(missing_docs)]
76#![allow(clippy::too_many_arguments)]
77
78extern crate alloc;
79
80use alloc::vec;
81use alloc::vec::Vec;
82
83use crate::error::{bail, err};
84use crate::j2c::{ComponentData, Header};
85use crate::jp2::cdef::{ChannelAssociation, ChannelType};
86use crate::jp2::cmap::ComponentMappingType;
87use crate::jp2::colr::{CieLab, EnumeratedColorspace};
88use crate::jp2::icc::ICCMetadata;
89use crate::jp2::{DecodedImage, ImageBoxes};
90
91pub mod error;
92mod inspect;
93#[macro_use]
94pub(crate) mod log;
95mod direct_cpu;
96mod direct_plan;
97pub(crate) mod math;
98#[doc(hidden)]
99pub mod packet_math;
100pub(crate) mod profile;
101pub(crate) mod writer;
102
103use crate::math::{dispatch, f32x8, Level, Simd, SIMD_WIDTH};
104pub use direct_cpu::{
105 execute_direct_color_plan_rgb8_into, execute_direct_color_plan_rgba8_into, J2kDirectCpuScratch,
106};
107pub use direct_plan::{
108 HtOwnedCodeBlockBatchJob, HtOwnedSubBandPlan, J2kDirectBandId, J2kDirectColorPlan,
109 J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kDirectIdwtStep, J2kDirectStoreStep,
110 J2kOwnedCodeBlockBatchJob, J2kOwnedSubBandPlan,
111};
112pub use inspect::{
113 inspect_j2k_codestream_header, looks_like_j2k_codestream, J2kCodestreamComponentHeader,
114 J2kCodestreamHeaderError, J2kCodestreamHeaderMetadata,
115};
116
117#[must_use]
125pub fn idwt_band_index(origin: u32, local_coord: u32, low_pass: bool) -> u32 {
126 let global = u64::from(origin) + u64::from(local_coord);
127 let origin = u64::from(origin);
128 let index = if low_pass {
129 global.div_ceil(2).saturating_sub(origin.div_ceil(2))
130 } else {
131 (global / 2).saturating_sub(origin / 2)
132 };
133 u32::try_from(index).unwrap_or(u32::MAX)
134}
135
136pub use error::{
137 ColorError, DecodeError, DecodingError, FormatError, MarkerError, Result, TileError,
138 ValidationError,
139};
140pub use j2c::encode::{
141 encode, encode_htj2k, encode_precomputed_htj2k_53,
142 encode_precomputed_htj2k_53_with_accelerator, encode_precomputed_htj2k_53_with_mct,
143 encode_precomputed_htj2k_53_with_mct_and_accelerator, encode_precomputed_htj2k_97,
144 encode_precomputed_htj2k_97_batch_with_accelerator,
145 encode_precomputed_htj2k_97_with_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_with_accelerator, irreversible_quantization_step_for_subband, EncodeOptions,
150 EncodeProgressionOrder,
151};
152pub use j2c::{CpuDecodeParallelism, DecoderContext, Reversible53CoefficientImage};
153pub use j2k_types::{
154 CpuOnlyJ2kEncodeStageAccelerator, EncodedHtJ2kCodeBlock, EncodedJ2kCodeBlock,
155 IrreversibleQuantizationStep, IrreversibleQuantizationSubbandScales, J2kCodeBlockSegment,
156 J2kCodeBlockStyle, J2kDeinterleaveToF32Job, J2kEncodeDispatchReport, J2kForwardDwt53Job,
157 J2kForwardDwt53Level, J2kForwardDwt53Output, J2kForwardDwt97Job, J2kForwardDwt97Level,
158 J2kForwardDwt97Output, J2kForwardIctJob, J2kForwardRctJob, J2kHtCodeBlockEncodeJob,
159 J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob, J2kPacketizationBlockCodingMode,
160 J2kPacketizationCodeBlock, J2kPacketizationEncodeJob, J2kPacketizationPacketDescriptor,
161 J2kPacketizationProgressionOrder, J2kPacketizationResolution, J2kPacketizationSubband,
162 J2kQuantizeSubbandJob, J2kSubBandType, J2kTier1CodeBlockEncodeJob, PrecomputedHtj2k53Component,
163 PrecomputedHtj2k53Image, PrecomputedHtj2k97Component, PrecomputedHtj2k97Image,
164 PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
165 PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
166 PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
167 PreencodedHtj2k97Component, PreencodedHtj2k97Image, PreencodedHtj2k97Resolution,
168 PreencodedHtj2k97Subband, PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component,
169 PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
170};
171
172mod j2c;
173mod jp2;
174pub(crate) mod reader;
175pub use j2c::ht_encode_tables::HtUvlcTableEntry;
176
177const MAX_CLASSIC_DECODE_BITPLANES: u8 = 32;
178pub(crate) const MAX_J2K_SPEC_COMPONENTS: u16 = 16_384;
179pub(crate) const MAX_NATIVE_DECODE_COMPONENTS: u16 = u8::MAX as u16;
180pub(crate) const MAX_J2K_IMAGE_DIMENSION: u32 = 60_000;
181pub(crate) const MAX_J2K_TILE_COUNT: u64 = u16::MAX as u64 + 1;
182pub(crate) const DEFAULT_MAX_DECODE_BYTES: usize = 512 * 1024 * 1024;
183
184#[inline]
185pub(crate) fn checked_decode_usize_product2(left: usize, right: usize) -> Result<usize> {
186 left.checked_mul(right)
187 .ok_or(ValidationError::ImageTooLarge.into())
188}
189
190#[inline]
191fn checked_decode_byte_cap(len: usize) -> Result<usize> {
192 if len > DEFAULT_MAX_DECODE_BYTES {
193 bail!(ValidationError::ImageTooLarge);
194 }
195 Ok(len)
196}
197
198#[inline]
199pub(crate) fn checked_decode_byte_len2(left: usize, right: usize) -> Result<usize> {
200 checked_decode_byte_cap(checked_decode_usize_product2(left, right)?)
201}
202
203#[inline]
204pub(crate) fn checked_decode_byte_len3(first: usize, second: usize, third: usize) -> Result<usize> {
205 let partial = checked_decode_usize_product2(first, second)?;
206 checked_decode_byte_cap(checked_decode_usize_product2(partial, third)?)
207}
208
209#[inline]
210pub(crate) fn checked_decode_byte_len4(
211 first: usize,
212 second: usize,
213 third: usize,
214 fourth: usize,
215) -> Result<usize> {
216 let partial = checked_decode_usize_product2(first, second)?;
217 let partial = checked_decode_usize_product2(partial, third)?;
218 checked_decode_byte_cap(checked_decode_usize_product2(partial, fourth)?)
219}
220
221#[inline]
222pub(crate) fn checked_decode_sample_count(width: u32, height: u32) -> Result<usize> {
223 #[cfg(target_pointer_width = "64")]
224 {
225 Ok((u64::from(width) * u64::from(height)) as usize)
226 }
227
228 #[cfg(not(target_pointer_width = "64"))]
229 {
230 checked_decode_usize_product2(width as usize, height as usize)
231 }
232}
233
234#[derive(Debug, Clone, Copy)]
236pub struct HtCodeBlockDecodeJob<'a> {
237 pub data: &'a [u8],
239 pub cleanup_length: u32,
241 pub refinement_length: u32,
243 pub width: u32,
245 pub height: u32,
247 pub output_stride: usize,
249 pub missing_bit_planes: u8,
251 pub number_of_coding_passes: u8,
253 pub num_bitplanes: u8,
255 pub roi_shift: u8,
257 pub stripe_causal: bool,
259 pub strict: bool,
261 pub dequantization_step: f32,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum HtCodeBlockDecodePhaseLimit {
268 Cleanup,
270 SignificancePropagation,
272 MagnitudeRefinement,
274}
275
276#[derive(Debug, Clone, Copy)]
278pub struct HtCodeBlockBatchJob<'a> {
279 pub output_x: u32,
281 pub output_y: u32,
283 pub code_block: HtCodeBlockDecodeJob<'a>,
285}
286
287#[derive(Debug, Clone, Copy)]
289pub struct HtSubBandDecodeJob<'a> {
290 pub width: u32,
292 pub height: u32,
294 pub jobs: &'a [HtCodeBlockBatchJob<'a>],
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub struct J2kTier1TokenSegment {
301 pub token_bit_offset: u32,
303 pub token_bit_count: u32,
308 pub start_coding_pass: u8,
310 pub end_coding_pass: u8,
312 pub use_arithmetic: bool,
314}
315
316#[derive(Debug, Clone, Copy)]
318pub struct J2kCodeBlockDecodeJob<'a> {
319 pub data: &'a [u8],
321 pub segments: &'a [J2kCodeBlockSegment],
323 pub width: u32,
325 pub height: u32,
327 pub output_stride: usize,
329 pub missing_bit_planes: u8,
331 pub number_of_coding_passes: u8,
333 pub total_bitplanes: u8,
335 pub roi_shift: u8,
337 pub sub_band_type: J2kSubBandType,
339 pub style: J2kCodeBlockStyle,
341 pub strict: bool,
343 pub dequantization_step: f32,
345}
346
347#[derive(Debug, Clone, Default, PartialEq, Eq)]
349pub struct HtCleanupEncodeDistribution {
350 pub total_quads: u64,
352 pub initial_quads: u64,
354 pub non_initial_quads: u64,
356 pub rho_counts: [u64; 16],
358 pub initial_rho_counts: [u64; 16],
360 pub non_initial_rho_counts: [u64; 16],
362 pub non_initial_u_q_counts: [u64; 32],
364 pub non_initial_e_qmax_counts: [u64; 32],
366 pub non_initial_kappa_counts: [u64; 32],
368 pub non_initial_rho_u_q_counts: [[u64; 32]; 16],
370 pub mag_sign_calls: u64,
372 pub mag_sign_rho_counts: [u64; 16],
374 pub mag_sign_sample_bit_counts: [u64; 32],
376 pub mag_sign_encoded_samples: u64,
378}
379
380pub trait J2kEncodeStageAccelerator {
382 fn dispatch_report(&self) -> J2kEncodeDispatchReport {
384 J2kEncodeDispatchReport::default()
385 }
386
387 fn encode_deinterleave(
392 &mut self,
393 _job: J2kDeinterleaveToF32Job<'_>,
394 ) -> core::result::Result<Option<Vec<Vec<f32>>>, &'static str> {
395 Ok(None)
396 }
397
398 fn encode_forward_rct(
403 &mut self,
404 _job: J2kForwardRctJob<'_>,
405 ) -> core::result::Result<bool, &'static str> {
406 Ok(false)
407 }
408
409 fn encode_forward_ict(
414 &mut self,
415 _job: J2kForwardIctJob<'_>,
416 ) -> core::result::Result<bool, &'static str> {
417 Ok(false)
418 }
419
420 fn encode_forward_dwt53(
425 &mut self,
426 _job: J2kForwardDwt53Job<'_>,
427 ) -> core::result::Result<Option<J2kForwardDwt53Output>, &'static str> {
428 Ok(None)
429 }
430
431 fn encode_forward_dwt97(
436 &mut self,
437 _job: J2kForwardDwt97Job<'_>,
438 ) -> core::result::Result<Option<J2kForwardDwt97Output>, &'static str> {
439 Ok(None)
440 }
441
442 fn encode_quantize_subband(
447 &mut self,
448 _job: J2kQuantizeSubbandJob<'_>,
449 ) -> core::result::Result<Option<Vec<i32>>, &'static str> {
450 Ok(None)
451 }
452
453 fn encode_tier1_code_block(
458 &mut self,
459 _job: J2kTier1CodeBlockEncodeJob<'_>,
460 ) -> core::result::Result<Option<EncodedJ2kCodeBlock>, &'static str> {
461 Ok(None)
462 }
463
464 fn encode_tier1_code_blocks(
469 &mut self,
470 _jobs: &[J2kTier1CodeBlockEncodeJob<'_>],
471 ) -> core::result::Result<Option<Vec<EncodedJ2kCodeBlock>>, &'static str> {
472 Ok(None)
473 }
474
475 fn encode_ht_code_block(
480 &mut self,
481 _job: J2kHtCodeBlockEncodeJob<'_>,
482 ) -> core::result::Result<Option<EncodedHtJ2kCodeBlock>, &'static str> {
483 Ok(None)
484 }
485
486 fn encode_ht_code_blocks(
491 &mut self,
492 _jobs: &[J2kHtCodeBlockEncodeJob<'_>],
493 ) -> core::result::Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
494 Ok(None)
495 }
496
497 fn encode_ht_subband(
503 &mut self,
504 _job: J2kHtSubbandEncodeJob<'_>,
505 ) -> core::result::Result<Option<Vec<EncodedHtJ2kCodeBlock>>, &'static str> {
506 Ok(None)
507 }
508
509 fn encode_htj2k_tile(
515 &mut self,
516 _job: J2kHtj2kTileEncodeJob<'_>,
517 ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
518 Ok(None)
519 }
520
521 fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
526 false
527 }
528
529 fn prefer_parallel_cpu_tile_encode(&self) -> bool {
535 false
536 }
537
538 fn encode_packetization(
543 &mut self,
544 _job: J2kPacketizationEncodeJob<'_>,
545 ) -> core::result::Result<Option<Vec<u8>>, &'static str> {
546 Ok(None)
547 }
548}
549
550impl J2kEncodeStageAccelerator for CpuOnlyJ2kEncodeStageAccelerator {
551 fn prefer_parallel_cpu_code_block_fallback(&self) -> bool {
552 true
553 }
554
555 fn prefer_parallel_cpu_tile_encode(&self) -> bool {
556 true
557 }
558}
559
560#[derive(Debug, Clone, Copy)]
562pub struct J2kCodeBlockBatchJob<'a> {
563 pub output_x: u32,
565 pub output_y: u32,
567 pub code_block: J2kCodeBlockDecodeJob<'a>,
569}
570
571#[derive(Debug, Clone, Copy)]
573pub struct J2kSubBandDecodeJob<'a> {
574 pub width: u32,
576 pub height: u32,
578 pub jobs: &'a [J2kCodeBlockBatchJob<'a>],
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub struct J2kRect {
585 pub x0: u32,
587 pub y0: u32,
589 pub x1: u32,
591 pub y1: u32,
593}
594
595impl J2kRect {
596 pub fn width(self) -> u32 {
598 self.x1.saturating_sub(self.x0)
599 }
600
601 pub fn height(self) -> u32 {
603 self.y1.saturating_sub(self.y0)
604 }
605}
606
607#[derive(Debug, Clone, Copy, PartialEq, Eq)]
609pub enum J2kWaveletTransform {
610 Reversible53,
612 Irreversible97,
614}
615
616#[derive(Debug, Clone, Copy)]
618pub struct J2kIdwtBand<'a> {
619 pub rect: J2kRect,
621 pub coefficients: &'a [f32],
623}
624
625#[derive(Debug, Clone, Copy)]
627pub struct J2kSingleDecompositionIdwtJob<'a> {
628 pub rect: J2kRect,
630 pub transform: J2kWaveletTransform,
632 pub ll: J2kIdwtBand<'a>,
634 pub hl: J2kIdwtBand<'a>,
636 pub lh: J2kIdwtBand<'a>,
638 pub hh: J2kIdwtBand<'a>,
640}
641
642#[derive(Debug)]
644pub struct J2kInverseMctJob<'a> {
645 pub transform: J2kWaveletTransform,
647 pub plane0: &'a mut [f32],
649 pub plane1: &'a mut [f32],
651 pub plane2: &'a mut [f32],
653 pub addend0: f32,
655 pub addend1: f32,
657 pub addend2: f32,
659}
660
661#[derive(Debug)]
663pub struct J2kStoreComponentJob<'a> {
664 pub input: &'a [f32],
666 pub input_width: u32,
668 pub source_x: u32,
670 pub source_y: u32,
672 pub copy_width: u32,
674 pub copy_height: u32,
676 pub output: &'a mut [f32],
678 pub output_width: u32,
680 pub output_x: u32,
682 pub output_y: u32,
684 pub addend: f32,
686}
687
688pub trait HtCodeBlockDecoder {
690 fn decode_j2k_sub_band(
696 &mut self,
697 _job: J2kSubBandDecodeJob<'_>,
698 _output: &mut [f32],
699 ) -> Result<bool> {
700 Ok(false)
701 }
702
703 fn decode_j2k_code_block(
709 &mut self,
710 _job: J2kCodeBlockDecodeJob<'_>,
711 _output: &mut [f32],
712 ) -> Result<bool> {
713 Ok(false)
714 }
715
716 fn decode_sub_band(
722 &mut self,
723 _job: HtSubBandDecodeJob<'_>,
724 _output: &mut [f32],
725 ) -> Result<bool> {
726 Ok(false)
727 }
728
729 fn decode_single_decomposition_idwt(
735 &mut self,
736 _job: J2kSingleDecompositionIdwtJob<'_>,
737 _output: &mut [f32],
738 ) -> Result<bool> {
739 Ok(false)
740 }
741
742 fn decode_inverse_mct(&mut self, _job: J2kInverseMctJob<'_>) -> Result<bool> {
748 Ok(false)
749 }
750
751 fn decode_store_component(&mut self, _job: J2kStoreComponentJob<'_>) -> Result<bool> {
757 Ok(false)
758 }
759
760 fn decode_code_block(
762 &mut self,
763 job: HtCodeBlockDecodeJob<'_>,
764 output: &mut [f32],
765 ) -> Result<()>;
766}
767
768fn internal_j2k_sub_band_type(sub_band_type: J2kSubBandType) -> j2c::build::SubBandType {
769 match sub_band_type {
770 J2kSubBandType::LowLow => j2c::build::SubBandType::LowLow,
771 J2kSubBandType::HighLow => j2c::build::SubBandType::HighLow,
772 J2kSubBandType::LowHigh => j2c::build::SubBandType::LowHigh,
773 J2kSubBandType::HighHigh => j2c::build::SubBandType::HighHigh,
774 }
775}
776
777fn internal_j2k_code_block_style(style: J2kCodeBlockStyle) -> j2c::codestream::CodeBlockStyle {
778 j2c::codestream::CodeBlockStyle {
779 selective_arithmetic_coding_bypass: style.selective_arithmetic_coding_bypass,
780 reset_context_probabilities: style.reset_context_probabilities,
781 termination_on_each_pass: style.termination_on_each_pass,
782 vertically_causal_context: style.vertically_causal_context,
783 segmentation_symbols: style.segmentation_symbols,
784 high_throughput_block_coding: false,
785 }
786}
787
788pub(crate) fn add_roi_shift_to_bitplanes(
789 bitplanes: u8,
790 roi_shift: u8,
791 max_bitplanes: u8,
792) -> Result<u8> {
793 let Some(coded_bitplanes) = bitplanes.checked_add(roi_shift) else {
794 bail!(DecodingError::TooManyBitplanes);
795 };
796 if coded_bitplanes > max_bitplanes {
797 bail!(DecodingError::TooManyBitplanes);
798 }
799 Ok(coded_bitplanes)
800}
801
802pub(crate) fn apply_roi_maxshift_inverse_i32(coefficient: i32, roi_shift: u8) -> i32 {
803 if roi_shift == 0 || coefficient == 0 {
804 return coefficient;
805 }
806
807 let magnitude = i64::from(coefficient).abs();
808 let threshold = 1_i64.checked_shl(roi_shift as u32).unwrap_or(i64::MAX);
809 if magnitude < threshold {
810 return coefficient;
811 }
812
813 let shifted = magnitude >> roi_shift;
814 let shifted = shifted.min(i64::from(i32::MAX)) as i32;
815 if coefficient < 0 {
816 -shifted
817 } else {
818 shifted
819 }
820}
821
822pub fn encode_j2k_code_block_scalar_with_style(
824 coefficients: &[i32],
825 width: u32,
826 height: u32,
827 sub_band_type: J2kSubBandType,
828 total_bitplanes: u8,
829 style: J2kCodeBlockStyle,
830) -> core::result::Result<EncodedJ2kCodeBlock, &'static str> {
831 let encoded = j2c::bitplane_encode::encode_code_block_segments_with_style(
832 coefficients,
833 width,
834 height,
835 internal_j2k_sub_band_type(sub_band_type),
836 total_bitplanes,
837 &internal_j2k_code_block_style(style),
838 );
839 let segments = encoded
840 .segments
841 .into_iter()
842 .map(|segment| J2kCodeBlockSegment {
843 data_offset: segment.data_offset,
844 data_length: segment.data_length,
845 start_coding_pass: segment.start_coding_pass,
846 end_coding_pass: segment.end_coding_pass,
847 use_arithmetic: segment.use_arithmetic,
848 })
849 .collect();
850
851 Ok(EncodedJ2kCodeBlock {
852 data: encoded.data,
853 segments,
854 number_of_coding_passes: encoded.num_coding_passes,
855 missing_bit_planes: encoded.num_zero_bitplanes,
856 })
857}
858
859pub fn pack_j2k_code_block_scalar_from_tier1_tokens(
865 token_bytes: &[u8],
866 token_segments: &[J2kTier1TokenSegment],
867 number_of_coding_passes: u8,
868 missing_bit_planes: u8,
869) -> core::result::Result<EncodedJ2kCodeBlock, &'static str> {
870 let internal_segments = token_segments
871 .iter()
872 .map(|segment| j2c::bitplane_encode::ClassicTier1TokenSegment {
873 token_bit_offset: segment.token_bit_offset,
874 token_bit_count: segment.token_bit_count,
875 start_coding_pass: segment.start_coding_pass,
876 end_coding_pass: segment.end_coding_pass,
877 use_arithmetic: segment.use_arithmetic,
878 })
879 .collect::<Vec<_>>();
880 let encoded = j2c::bitplane_encode::pack_classic_selective_bypass_tier1_tokens(
881 token_bytes,
882 &internal_segments,
883 number_of_coding_passes,
884 missing_bit_planes,
885 )?;
886 let segments = encoded
887 .segments
888 .into_iter()
889 .map(|segment| J2kCodeBlockSegment {
890 data_offset: segment.data_offset,
891 data_length: segment.data_length,
892 start_coding_pass: segment.start_coding_pass,
893 end_coding_pass: segment.end_coding_pass,
894 use_arithmetic: segment.use_arithmetic,
895 })
896 .collect();
897
898 Ok(EncodedJ2kCodeBlock {
899 data: encoded.data,
900 segments,
901 number_of_coding_passes: encoded.num_coding_passes,
902 missing_bit_planes: encoded.num_zero_bitplanes,
903 })
904}
905
906pub fn encode_ht_code_block_scalar(
908 coefficients: &[i32],
909 width: u32,
910 height: u32,
911 total_bitplanes: u8,
912) -> core::result::Result<EncodedHtJ2kCodeBlock, &'static str> {
913 let encoded =
914 j2c::ht_block_encode::encode_code_block(coefficients, width, height, total_bitplanes)?;
915 Ok(EncodedHtJ2kCodeBlock {
916 data: encoded.data,
917 cleanup_length: encoded.ht_cleanup_length,
918 refinement_length: encoded.ht_refinement_length,
919 num_coding_passes: encoded.num_coding_passes,
920 num_zero_bitplanes: encoded.num_zero_bitplanes,
921 })
922}
923
924pub fn collect_ht_cleanup_encode_distribution(
926 coefficients: &[i32],
927 width: u32,
928 height: u32,
929 total_bitplanes: u8,
930) -> core::result::Result<HtCleanupEncodeDistribution, &'static str> {
931 j2c::ht_block_encode::collect_encode_distribution(coefficients, width, height, total_bitplanes)
932}
933
934pub fn forward_dwt53_reference(
940 samples: &[f32],
941 width: u32,
942 height: u32,
943 num_levels: u8,
944) -> J2kForwardDwt53Output {
945 let decomp = j2c::fdwt::forward_dwt(samples, width, height, num_levels, true);
946 let levels = decomp
947 .levels
948 .into_iter()
949 .map(|lvl| J2kForwardDwt53Level {
950 hl: lvl.hl,
951 lh: lvl.lh,
952 hh: lvl.hh,
953 width: lvl.low_width + lvl.high_width,
954 height: lvl.low_height + lvl.high_height,
955 low_width: lvl.low_width,
956 low_height: lvl.low_height,
957 high_width: lvl.high_width,
958 high_height: lvl.high_height,
959 })
960 .collect();
961 J2kForwardDwt53Output {
962 ll: decomp.ll,
963 ll_width: decomp.ll_width,
964 ll_height: decomp.ll_height,
965 levels,
966 }
967}
968
969pub fn forward_dwt97_reference(
975 samples: &[f32],
976 width: u32,
977 height: u32,
978 num_levels: u8,
979) -> J2kForwardDwt97Output {
980 let decomp = j2c::fdwt::forward_dwt(samples, width, height, num_levels, false);
981 let levels = decomp
982 .levels
983 .into_iter()
984 .map(|lvl| J2kForwardDwt97Level {
985 hl: lvl.hl,
986 lh: lvl.lh,
987 hh: lvl.hh,
988 width: lvl.low_width + lvl.high_width,
989 height: lvl.low_height + lvl.high_height,
990 low_width: lvl.low_width,
991 low_height: lvl.low_height,
992 high_width: lvl.high_width,
993 high_height: lvl.high_height,
994 })
995 .collect();
996 J2kForwardDwt97Output {
997 ll: decomp.ll,
998 ll_width: decomp.ll_width,
999 ll_height: decomp.ll_height,
1000 levels,
1001 }
1002}
1003
1004pub fn forward_rct_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1011 j2c::forward_mct::forward_rct(&mut planes);
1012 planes
1013}
1014
1015pub fn forward_ict_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
1021 j2c::forward_mct::forward_ict(&mut planes);
1022 planes
1023}
1024
1025pub fn quantize_subband_reference(
1027 coefficients: &[f32],
1028 step_exponent: u16,
1029 step_mantissa: u16,
1030 range_bits: u8,
1031 reversible: bool,
1032) -> Vec<i32> {
1033 let step = j2c::quantize::QuantStepSize {
1034 exponent: step_exponent,
1035 mantissa: step_mantissa,
1036 };
1037 j2c::quantize::quantize_subband(coefficients, &step, range_bits, reversible)
1038}
1039
1040pub fn quantize_reversible_reference(
1049 coefficients: &[f32],
1050 step_exponent: u16,
1051 step_mantissa: u16,
1052 range_bits: u8,
1053 reversible: bool,
1054) -> Vec<i32> {
1055 quantize_subband_reference(
1056 coefficients,
1057 step_exponent,
1058 step_mantissa,
1059 range_bits,
1060 reversible,
1061 )
1062}
1063
1064pub fn deinterleave_reference(
1070 pixels: &[u8],
1071 num_pixels: usize,
1072 num_components: u8,
1073 bit_depth: u8,
1074 signed: bool,
1075) -> Vec<Vec<f32>> {
1076 j2c::encode::deinterleave_to_f32(pixels, num_pixels, num_components, bit_depth, signed)
1077}
1078
1079pub fn encode_j2k_packetization_scalar(
1081 job: J2kPacketizationEncodeJob<'_>,
1082) -> core::result::Result<Vec<u8>, &'static str> {
1083 let mut resolutions = job
1084 .resolutions
1085 .iter()
1086 .map(|resolution| j2c::packet_encode::ResolutionPacket {
1087 subbands: resolution
1088 .subbands
1089 .iter()
1090 .map(|subband| j2c::packet_encode::SubbandPrecinct {
1091 code_blocks: subband
1092 .code_blocks
1093 .iter()
1094 .map(|code_block| j2c::packet_encode::CodeBlockPacketData {
1095 data: code_block.data.to_vec(),
1096 ht_cleanup_length: code_block.ht_cleanup_length,
1097 ht_refinement_length: code_block.ht_refinement_length,
1098 num_coding_passes: code_block.num_coding_passes,
1099 classic_segment_lengths: Vec::new(),
1100 num_zero_bitplanes: code_block.num_zero_bitplanes,
1101 previously_included: code_block.previously_included,
1102 l_block: code_block.l_block,
1103 block_coding_mode: match code_block.block_coding_mode {
1104 J2kPacketizationBlockCodingMode::Classic => {
1105 j2c::codestream_write::BlockCodingMode::Classic
1106 }
1107 J2kPacketizationBlockCodingMode::HighThroughput => {
1108 j2c::codestream_write::BlockCodingMode::HighThroughput
1109 }
1110 },
1111 })
1112 .collect(),
1113 num_cbs_x: subband.num_cbs_x,
1114 num_cbs_y: subband.num_cbs_y,
1115 })
1116 .collect(),
1117 })
1118 .collect::<Vec<_>>();
1119
1120 let descriptors = job
1121 .packet_descriptors
1122 .iter()
1123 .map(|descriptor| j2c::packet_encode::PacketDescriptor {
1124 packet_index: descriptor.packet_index,
1125 state_index: descriptor.state_index,
1126 layer: descriptor.layer,
1127 resolution: descriptor.resolution,
1128 component: descriptor.component,
1129 precinct: descriptor.precinct,
1130 })
1131 .collect::<Vec<_>>();
1132
1133 j2c::packet_encode::validate_ht_segment_lengths(&resolutions)?;
1134
1135 if descriptors.is_empty() {
1136 Ok(j2c::packet_encode::form_tile_bitstream_for_progression(
1137 &mut resolutions,
1138 job.num_layers,
1139 job.num_components,
1140 job.progression_order,
1141 ))
1142 } else {
1143 j2c::packet_encode::form_tile_bitstream_with_descriptors(&mut resolutions, &descriptors)
1144 }
1145}
1146
1147pub fn decode_j2k_code_block_scalar(
1149 job: J2kCodeBlockDecodeJob<'_>,
1150 output: &mut [f32],
1151) -> Result<()> {
1152 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1153 decode_j2k_code_block_scalar_with_workspace(job, output, &mut workspace)
1154}
1155
1156#[derive(Default)]
1158pub struct J2kCodeBlockDecodeWorkspace {
1159 bit_plane_decode_context: j2c::bitplane::BitPlaneDecodeContext,
1160}
1161
1162pub fn decode_j2k_code_block_scalar_with_workspace(
1164 job: J2kCodeBlockDecodeJob<'_>,
1165 output: &mut [f32],
1166 workspace: &mut J2kCodeBlockDecodeWorkspace,
1167) -> Result<()> {
1168 let required_len = if job.height == 0 {
1169 0
1170 } else {
1171 job.output_stride
1172 .checked_mul(job.height as usize - 1)
1173 .and_then(|prefix| prefix.checked_add(job.width as usize))
1174 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1175 };
1176 if output.len() < required_len {
1177 bail!(DecodingError::CodeBlockDecodeFailure);
1178 }
1179
1180 let style = internal_j2k_code_block_style(job.style);
1181 let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1182 let code_block_stride =
1183 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1184 let coded_bitplanes = add_roi_shift_to_bitplanes(
1185 job.total_bitplanes,
1186 job.roi_shift,
1187 MAX_CLASSIC_DECODE_BITPLANES,
1188 )?;
1189
1190 j2c::bitplane::decode_code_block_segments_validated(
1191 job.data,
1192 job.segments,
1193 job.width,
1194 job.height,
1195 job.missing_bit_planes,
1196 job.number_of_coding_passes,
1197 coded_bitplanes,
1198 sub_band_type,
1199 &style,
1200 job.strict,
1201 &mut workspace.bit_plane_decode_context,
1202 )?;
1203
1204 for (row_idx, coeff_row) in workspace
1205 .bit_plane_decode_context
1206 .coefficient_rows()
1207 .enumerate()
1208 .take(job.height as usize)
1209 {
1210 let row_start = row_idx * job.output_stride;
1211 let output_row = &mut output[row_start..row_start + code_block_stride];
1212 for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1213 let coefficient = apply_roi_maxshift_inverse_i32(coefficient.get(), job.roi_shift);
1214 *sample = coefficient as f32 * job.dequantization_step;
1215 }
1216 }
1217
1218 Ok(())
1219}
1220
1221#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1223pub struct J2kCodeBlockDecodeProfile {
1224 pub sigprop_us: u128,
1226 pub magref_us: u128,
1228 pub cleanup_us: u128,
1230 pub bypass_us: u128,
1232 pub output_convert_us: u128,
1234}
1235
1236impl J2kCodeBlockDecodeProfile {
1237 fn add_native_stats(&mut self, stats: j2c::bitplane::J2kBlockDecodeStats) {
1238 self.sigprop_us += stats.sigprop_us;
1239 self.magref_us += stats.magref_us;
1240 self.cleanup_us += stats.cleanup_us;
1241 self.bypass_us += stats.bypass_us;
1242 }
1243}
1244
1245pub fn decode_j2k_code_block_scalar_profiled(
1247 job: J2kCodeBlockDecodeJob<'_>,
1248 output: &mut [f32],
1249 profile: &mut J2kCodeBlockDecodeProfile,
1250) -> Result<()> {
1251 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1252 decode_j2k_code_block_scalar_with_workspace_profiled(job, output, &mut workspace, profile)
1253}
1254
1255pub fn decode_j2k_code_block_scalar_with_workspace_profiled(
1257 job: J2kCodeBlockDecodeJob<'_>,
1258 output: &mut [f32],
1259 workspace: &mut J2kCodeBlockDecodeWorkspace,
1260 profile: &mut J2kCodeBlockDecodeProfile,
1261) -> Result<()> {
1262 let required_len = if job.height == 0 {
1263 0
1264 } else {
1265 job.output_stride
1266 .checked_mul(job.height as usize - 1)
1267 .and_then(|prefix| prefix.checked_add(job.width as usize))
1268 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1269 };
1270 if output.len() < required_len {
1271 bail!(DecodingError::CodeBlockDecodeFailure);
1272 }
1273
1274 let style = internal_j2k_code_block_style(job.style);
1275 let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1276 let code_block_stride =
1277 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1278 let coded_bitplanes = add_roi_shift_to_bitplanes(
1279 job.total_bitplanes,
1280 job.roi_shift,
1281 MAX_CLASSIC_DECODE_BITPLANES,
1282 )?;
1283 let mut stats = j2c::bitplane::J2kBlockDecodeStats::default();
1284
1285 j2c::bitplane::decode_code_block_segments_validated_profiled(
1286 job.data,
1287 job.segments,
1288 job.width,
1289 job.height,
1290 job.missing_bit_planes,
1291 job.number_of_coding_passes,
1292 coded_bitplanes,
1293 sub_band_type,
1294 &style,
1295 job.strict,
1296 &mut workspace.bit_plane_decode_context,
1297 &mut stats,
1298 true,
1299 )?;
1300 profile.add_native_stats(stats);
1301
1302 let output_convert_started = profile::profile_now(true);
1303 for (row_idx, coeff_row) in workspace
1304 .bit_plane_decode_context
1305 .coefficient_rows()
1306 .enumerate()
1307 .take(job.height as usize)
1308 {
1309 let row_start = row_idx * job.output_stride;
1310 let output_row = &mut output[row_start..row_start + code_block_stride];
1311 for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1312 let coefficient = apply_roi_maxshift_inverse_i32(coefficient.get(), job.roi_shift);
1313 *sample = coefficient as f32 * job.dequantization_step;
1314 }
1315 }
1316 profile.output_convert_us += profile::elapsed_us(output_convert_started);
1317
1318 Ok(())
1319}
1320
1321pub fn decode_j2k_sub_band_scalar(job: J2kSubBandDecodeJob<'_>, output: &mut [f32]) -> Result<()> {
1323 let required_len = if job.height == 0 {
1324 0
1325 } else {
1326 usize::try_from(job.width)
1327 .ok()
1328 .and_then(|width| width.checked_mul(job.height as usize))
1329 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1330 };
1331 if output.len() < required_len {
1332 bail!(DecodingError::CodeBlockDecodeFailure);
1333 }
1334
1335 let sub_band_width =
1336 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1337
1338 for batch_job in job.jobs {
1339 let code_block = batch_job.code_block;
1340 if code_block.output_stride != sub_band_width {
1341 bail!(DecodingError::CodeBlockDecodeFailure);
1342 }
1343 if batch_job
1344 .output_x
1345 .checked_add(code_block.width)
1346 .is_none_or(|x1| x1 > job.width)
1347 || batch_job
1348 .output_y
1349 .checked_add(code_block.height)
1350 .is_none_or(|y1| y1 > job.height)
1351 {
1352 bail!(DecodingError::CodeBlockDecodeFailure);
1353 }
1354
1355 let base_idx = usize::try_from(batch_job.output_y)
1356 .ok()
1357 .and_then(|y| y.checked_mul(sub_band_width))
1358 .and_then(|row| row.checked_add(batch_job.output_x as usize))
1359 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1360 let block_output_len = if code_block.height == 0 {
1361 0
1362 } else {
1363 code_block
1364 .output_stride
1365 .checked_mul(code_block.height as usize - 1)
1366 .and_then(|prefix| prefix.checked_add(code_block.width as usize))
1367 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1368 };
1369 let end_idx = base_idx
1370 .checked_add(block_output_len)
1371 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1372 if end_idx > output.len() {
1373 bail!(DecodingError::CodeBlockDecodeFailure);
1374 }
1375
1376 decode_j2k_code_block_scalar(code_block, &mut output[base_idx..end_idx])?;
1377 }
1378
1379 Ok(())
1380}
1381
1382pub fn decode_ht_code_block_scalar(
1384 job: HtCodeBlockDecodeJob<'_>,
1385 output: &mut [f32],
1386) -> Result<()> {
1387 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1388 job, output,
1389 )
1390}
1391
1392pub fn decode_ht_code_block_scalar_until_phase(
1394 job: HtCodeBlockDecodeJob<'_>,
1395 output: &mut [f32],
1396 phase_limit: HtCodeBlockDecodePhaseLimit,
1397) -> Result<()> {
1398 match phase_limit {
1399 HtCodeBlockDecodePhaseLimit::Cleanup => decode_ht_code_block_scalar_for_phase::<
1400 { j2c::ht_block_decode::PHASE_LIMIT_CLEANUP },
1401 >(job, output),
1402 HtCodeBlockDecodePhaseLimit::SignificancePropagation => {
1403 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_SIGPROP }>(
1404 job, output,
1405 )
1406 }
1407 HtCodeBlockDecodePhaseLimit::MagnitudeRefinement => {
1408 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1409 job, output,
1410 )
1411 }
1412 }
1413}
1414
1415#[derive(Default)]
1417pub struct HtCodeBlockDecodeWorkspace {
1418 coefficients: Vec<u32>,
1419 scratch: j2c::ht_block_decode::HtBlockDecodeScratch,
1420}
1421
1422impl HtCodeBlockDecodeWorkspace {
1423 pub fn coefficient_capacity(&self) -> usize {
1425 self.coefficients.capacity()
1426 }
1427}
1428
1429#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1431pub struct HtCodeBlockDecodeProfile {
1432 pub blocks: u128,
1434 pub refinement_blocks: u128,
1436 pub cleanup_bytes: u128,
1438 pub refinement_bytes: u128,
1440 pub cleanup_us: u128,
1442 pub mag_sgn_us: u128,
1444 pub sigma_us: u128,
1446 pub sigprop_us: u128,
1448 pub magref_us: u128,
1450}
1451
1452impl HtCodeBlockDecodeProfile {
1453 fn add_native_stats(&mut self, stats: j2c::ht_block_decode::HtBlockDecodeStats) {
1454 self.blocks += stats.blocks;
1455 self.refinement_blocks += stats.refinement_blocks;
1456 self.cleanup_bytes += stats.cleanup_bytes;
1457 self.refinement_bytes += stats.refinement_bytes;
1458 self.cleanup_us += stats.ht_cleanup_us;
1459 self.mag_sgn_us += stats.ht_mag_sgn_us;
1460 self.sigma_us += stats.ht_sigma_us;
1461 self.sigprop_us += stats.ht_sigprop_us;
1462 self.magref_us += stats.ht_magref_us;
1463 }
1464}
1465
1466pub fn decode_ht_code_block_scalar_with_workspace(
1468 job: HtCodeBlockDecodeJob<'_>,
1469 output: &mut [f32],
1470 workspace: &mut HtCodeBlockDecodeWorkspace,
1471) -> Result<()> {
1472 decode_ht_code_block_scalar_for_phase_with_workspace::<
1473 { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1474 >(job, output, workspace)
1475}
1476
1477pub fn decode_ht_code_block_scalar_with_workspace_profiled(
1479 job: HtCodeBlockDecodeJob<'_>,
1480 output: &mut [f32],
1481 workspace: &mut HtCodeBlockDecodeWorkspace,
1482 profile: &mut HtCodeBlockDecodeProfile,
1483) -> Result<()> {
1484 decode_ht_code_block_scalar_for_phase_with_workspace_profiled::<
1485 { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1486 >(job, output, workspace, profile)
1487}
1488
1489fn decode_ht_code_block_scalar_for_phase<const PHASE_LIMIT: u8>(
1490 job: HtCodeBlockDecodeJob<'_>,
1491 output: &mut [f32],
1492) -> Result<()> {
1493 let mut workspace = HtCodeBlockDecodeWorkspace::default();
1494 decode_ht_code_block_scalar_for_phase_with_workspace::<PHASE_LIMIT>(job, output, &mut workspace)
1495}
1496
1497fn decode_ht_code_block_scalar_for_phase_with_workspace<const PHASE_LIMIT: u8>(
1498 job: HtCodeBlockDecodeJob<'_>,
1499 output: &mut [f32],
1500 workspace: &mut HtCodeBlockDecodeWorkspace,
1501) -> Result<()> {
1502 let required_len = if job.height == 0 {
1503 0
1504 } else {
1505 job.output_stride
1506 .checked_mul(job.height as usize - 1)
1507 .and_then(|prefix| prefix.checked_add(job.width as usize))
1508 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1509 };
1510 if output.len() < required_len {
1511 bail!(DecodingError::CodeBlockDecodeFailure);
1512 }
1513 let code_block_stride =
1514 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1515 let code_block_len = code_block_stride
1516 .checked_mul(job.height as usize)
1517 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1518
1519 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1520 job.data,
1521 job.cleanup_length,
1522 job.refinement_length,
1523 )?;
1524 let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1525 workspace.coefficients.clear();
1526 workspace.coefficients.resize(code_block_len, 0);
1527 j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1528 &segments,
1529 job.missing_bit_planes,
1530 coded_bitplanes,
1531 job.number_of_coding_passes,
1532 job.stripe_causal,
1533 job.strict,
1534 &mut workspace.coefficients,
1535 job.width,
1536 job.height,
1537 job.width,
1538 &mut workspace.scratch,
1539 None,
1540 false,
1541 )?;
1542
1543 for (row_idx, coeff_row) in workspace
1544 .coefficients
1545 .chunks_exact(code_block_stride)
1546 .enumerate()
1547 .take(job.height as usize)
1548 {
1549 let row_start = row_idx * job.output_stride;
1550 let output_row = &mut output[row_start..row_start + code_block_stride];
1551 for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1552 let coefficient =
1553 j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1554 let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1555 *sample = coefficient as f32 * job.dequantization_step;
1556 }
1557 }
1558
1559 Ok(())
1560}
1561
1562fn decode_ht_code_block_scalar_for_phase_with_workspace_profiled<const PHASE_LIMIT: u8>(
1563 job: HtCodeBlockDecodeJob<'_>,
1564 output: &mut [f32],
1565 workspace: &mut HtCodeBlockDecodeWorkspace,
1566 profile: &mut HtCodeBlockDecodeProfile,
1567) -> Result<()> {
1568 let required_len = if job.height == 0 {
1569 0
1570 } else {
1571 job.output_stride
1572 .checked_mul(job.height as usize - 1)
1573 .and_then(|prefix| prefix.checked_add(job.width as usize))
1574 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1575 };
1576 if output.len() < required_len {
1577 bail!(DecodingError::CodeBlockDecodeFailure);
1578 }
1579 let code_block_stride =
1580 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1581 let code_block_len = code_block_stride
1582 .checked_mul(job.height as usize)
1583 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1584
1585 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1586 job.data,
1587 job.cleanup_length,
1588 job.refinement_length,
1589 )?;
1590 let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1591 workspace.coefficients.clear();
1592 workspace.coefficients.resize(code_block_len, 0);
1593 let mut stats = j2c::ht_block_decode::HtBlockDecodeStats::default();
1594 j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1595 &segments,
1596 job.missing_bit_planes,
1597 coded_bitplanes,
1598 job.number_of_coding_passes,
1599 job.stripe_causal,
1600 job.strict,
1601 &mut workspace.coefficients,
1602 job.width,
1603 job.height,
1604 job.width,
1605 &mut workspace.scratch,
1606 Some(&mut stats),
1607 true,
1608 )?;
1609 profile.add_native_stats(stats);
1610
1611 for (row_idx, coeff_row) in workspace
1612 .coefficients
1613 .chunks_exact(code_block_stride)
1614 .enumerate()
1615 .take(job.height as usize)
1616 {
1617 let row_start = row_idx * job.output_stride;
1618 let output_row = &mut output[row_start..row_start + code_block_stride];
1619 for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1620 let coefficient =
1621 j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1622 let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1623 *sample = coefficient as f32 * job.dequantization_step;
1624 }
1625 }
1626
1627 Ok(())
1628}
1629
1630pub struct HtSigPropBenchmarkState(j2c::ht_block_decode::HtSigPropBenchmarkState);
1632
1633impl HtSigPropBenchmarkState {
1634 pub fn output_len(&self) -> usize {
1636 self.0.output_len()
1637 }
1638}
1639
1640pub fn prepare_ht_sigprop_benchmark_state(
1642 job: HtCodeBlockDecodeJob<'_>,
1643) -> Result<HtSigPropBenchmarkState> {
1644 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1645 job.data,
1646 job.cleanup_length,
1647 job.refinement_length,
1648 )?;
1649 let state = j2c::ht_block_decode::prepare_sigprop_benchmark_state(
1650 &segments,
1651 job.missing_bit_planes,
1652 job.num_bitplanes,
1653 job.number_of_coding_passes,
1654 job.stripe_causal,
1655 job.strict,
1656 job.width,
1657 job.height,
1658 job.width,
1659 )?;
1660 Ok(HtSigPropBenchmarkState(state))
1661}
1662
1663pub fn decode_ht_sigprop_benchmark_state(
1665 state: &mut HtSigPropBenchmarkState,
1666 output: &mut [u32],
1667) -> Result<()> {
1668 j2c::ht_block_decode::decode_sigprop_benchmark_state(&mut state.0, output)
1669}
1670
1671pub fn ht_vlc_table0() -> &'static [u16; 1024] {
1673 &j2c::ht_tables::VLC_TABLE0
1674}
1675
1676pub fn ht_vlc_table1() -> &'static [u16; 1024] {
1678 &j2c::ht_tables::VLC_TABLE1
1679}
1680
1681pub fn ht_uvlc_table0() -> &'static [u16; 320] {
1683 &j2c::ht_tables::UVLC_TABLE0
1684}
1685
1686pub fn ht_uvlc_table1() -> &'static [u16; 256] {
1688 &j2c::ht_tables::UVLC_TABLE1
1689}
1690
1691pub fn ht_vlc_encode_table0() -> &'static [u16; 2048] {
1693 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE0
1694}
1695
1696pub fn ht_vlc_encode_table1() -> &'static [u16; 2048] {
1698 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE1
1699}
1700
1701pub fn ht_uvlc_encode_table() -> &'static [HtUvlcTableEntry; 75] {
1703 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE
1704}
1705
1706pub fn ht_uvlc_encode_table_bytes() -> &'static [u8] {
1708 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE_BYTES
1709}
1710
1711pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
1713pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
1715
1716#[derive(Debug, Copy, Clone)]
1718pub struct DecodeSettings {
1719 pub resolve_palette_indices: bool,
1731 pub strict: bool,
1736 pub target_resolution: Option<(u32, u32)>,
1738}
1739
1740impl Default for DecodeSettings {
1741 fn default() -> Self {
1742 Self {
1743 resolve_palette_indices: true,
1744 strict: false,
1745 target_resolution: None,
1746 }
1747 }
1748}
1749
1750pub struct Image<'a> {
1752 pub(crate) codestream: &'a [u8],
1754 pub(crate) header: Header<'a>,
1756 pub(crate) boxes: ImageBoxes,
1759 pub(crate) settings: DecodeSettings,
1761 pub(crate) has_alpha: bool,
1763 pub(crate) color_space: ColorSpace,
1765}
1766
1767impl<'a> Image<'a> {
1768 pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
1770 if data.starts_with(JP2_MAGIC) {
1771 jp2::parse(data, *settings)
1772 } else if data.starts_with(CODESTREAM_MAGIC) {
1773 j2c::parse(data, settings)
1774 } else {
1775 err!(FormatError::InvalidSignature)
1776 }
1777 }
1778
1779 pub fn has_alpha(&self) -> bool {
1781 self.has_alpha
1782 }
1783
1784 pub fn color_space(&self) -> &ColorSpace {
1786 &self.color_space
1787 }
1788
1789 pub fn width(&self) -> u32 {
1791 self.header.size_data.image_width()
1792 }
1793
1794 pub fn height(&self) -> u32 {
1796 self.header.size_data.image_height()
1797 }
1798
1799 pub fn original_bit_depth(&self) -> u8 {
1802 self.header.component_infos[0].size_info.precision
1804 }
1805
1806 pub fn supports_direct_device_plane_reuse(&self) -> bool {
1808 if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
1809 return false;
1810 }
1811 if self.boxes.channel_definition.is_some() {
1812 return false;
1813 }
1814 !matches!(
1815 self.boxes
1816 .color_specification
1817 .as_ref()
1818 .map(|spec| &spec.color_space),
1819 Some(jp2::colr::ColorSpace::Enumerated(
1820 EnumeratedColorspace::Sycc | EnumeratedColorspace::CieLab(_)
1821 ))
1822 )
1823 }
1824
1825 pub fn decode(&self) -> Result<Vec<u8>> {
1828 let bitmap = self.decode_with_context(&mut DecoderContext::default())?;
1829 Ok(bitmap.data)
1830 }
1831
1832 pub fn decode_with_context(&self, decoder_context: &mut DecoderContext<'a>) -> Result<Bitmap> {
1835 let buffer_size = checked_decode_byte_len3(
1836 self.width() as usize,
1837 self.height() as usize,
1838 self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 },
1839 )?;
1840 let mut buf = vec![0; buffer_size];
1841 self.decode_into(&mut buf, decoder_context)?;
1842
1843 Ok(Bitmap {
1844 color_space: self.color_space.clone(),
1845 data: buf,
1846 has_alpha: self.has_alpha,
1847 width: self.width(),
1848 height: self.height(),
1849 original_bit_depth: self.original_bit_depth(),
1850 })
1851 }
1852
1853 pub fn decode_components_with_context<'ctx>(
1856 &self,
1857 decoder_context: &'ctx mut DecoderContext<'a>,
1858 ) -> Result<DecodedComponents<'ctx>> {
1859 let decoded_image = self.prepare_decoded_image(decoder_context)?;
1860 let planes = decoded_image
1861 .decoded_components
1862 .iter()
1863 .map(|component| ComponentPlane {
1864 samples: component.container.truncated(),
1865 bit_depth: component.bit_depth,
1866 })
1867 .collect();
1868
1869 Ok(DecodedComponents {
1870 dimensions: (self.width(), self.height()),
1871 color_space: self.color_space.clone(),
1872 has_alpha: self.has_alpha,
1873 planes,
1874 })
1875 }
1876
1877 pub fn build_direct_grayscale_plan_with_context(
1879 &self,
1880 decoder_context: &mut DecoderContext<'a>,
1881 ) -> Result<J2kDirectGrayscalePlan> {
1882 if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1883 bail!(DecodingError::UnsupportedFeature(
1884 "direct grayscale plan only supports grayscale images without alpha"
1885 ));
1886 }
1887
1888 j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context)
1889 }
1890
1891 pub fn build_direct_grayscale_plan_region_with_context(
1893 &self,
1894 decoder_context: &mut DecoderContext<'a>,
1895 output_region: (u32, u32, u32, u32),
1896 ) -> Result<J2kDirectGrayscalePlan> {
1897 if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1898 bail!(DecodingError::UnsupportedFeature(
1899 "direct grayscale plan only supports grayscale images without alpha"
1900 ));
1901 }
1902
1903 decoder_context.set_output_region(Some(output_region));
1904 let result =
1905 j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context);
1906 decoder_context.set_output_region(None);
1907 result
1908 }
1909
1910 pub fn build_direct_color_plan_with_context(
1912 &self,
1913 decoder_context: &mut DecoderContext<'a>,
1914 ) -> Result<J2kDirectColorPlan> {
1915 if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1916 bail!(DecodingError::UnsupportedFeature(
1917 "direct color plan only supports RGB images without alpha"
1918 ));
1919 }
1920
1921 j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context)
1922 }
1923
1924 pub fn build_direct_color_plan_region_with_context(
1926 &self,
1927 decoder_context: &mut DecoderContext<'a>,
1928 output_region: (u32, u32, u32, u32),
1929 ) -> Result<J2kDirectColorPlan> {
1930 if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1931 bail!(DecodingError::UnsupportedFeature(
1932 "direct color plan only supports RGB images without alpha"
1933 ));
1934 }
1935
1936 decoder_context.set_output_region(Some(output_region));
1937 let result = j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context);
1938 decoder_context.set_output_region(None);
1939 result
1940 }
1941
1942 pub fn decode_components_with_ht_decoder<'ctx>(
1944 &self,
1945 decoder_context: &'ctx mut DecoderContext<'a>,
1946 ht_decoder: &mut dyn HtCodeBlockDecoder,
1947 ) -> Result<DecodedComponents<'ctx>> {
1948 let decoded_image =
1949 self.prepare_decoded_image_with_ht_decoder(decoder_context, ht_decoder)?;
1950 let planes = decoded_image
1951 .decoded_components
1952 .iter()
1953 .map(|component| ComponentPlane {
1954 samples: component.container.truncated(),
1955 bit_depth: component.bit_depth,
1956 })
1957 .collect();
1958
1959 Ok(DecodedComponents {
1960 dimensions: (self.width(), self.height()),
1961 color_space: self.color_space.clone(),
1962 has_alpha: self.has_alpha,
1963 planes,
1964 })
1965 }
1966
1967 pub fn decode_region_components_with_context<'ctx>(
1970 &self,
1971 roi: (u32, u32, u32, u32),
1972 decoder_context: &'ctx mut DecoderContext<'a>,
1973 ) -> Result<DecodedComponents<'ctx>> {
1974 validate_roi((self.width(), self.height()), roi)?;
1975 let (_x, _y, width, height) = roi;
1976 let decoded_image = self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
1977 let planes = decoded_image
1978 .decoded_components
1979 .iter()
1980 .map(|component| ComponentPlane {
1981 samples: component.container.truncated(),
1982 bit_depth: component.bit_depth,
1983 })
1984 .collect();
1985
1986 Ok(DecodedComponents {
1987 dimensions: (width, height),
1988 color_space: self.color_space.clone(),
1989 has_alpha: self.has_alpha,
1990 planes,
1991 })
1992 }
1993
1994 pub fn decode_region_components_with_ht_decoder<'ctx>(
1997 &self,
1998 decoder_context: &'ctx mut DecoderContext<'a>,
1999 roi: (u32, u32, u32, u32),
2000 ht_decoder: &mut dyn HtCodeBlockDecoder,
2001 ) -> Result<DecodedComponents<'ctx>> {
2002 validate_roi((self.width(), self.height()), roi)?;
2003 let (_x, _y, width, height) = roi;
2004 let decoded_image = self.prepare_decoded_image_with_region_and_ht_decoder(
2005 decoder_context,
2006 Some(roi),
2007 Some(ht_decoder),
2008 )?;
2009 let planes = decoded_image
2010 .decoded_components
2011 .iter()
2012 .map(|component| ComponentPlane {
2013 samples: component.container.truncated(),
2014 bit_depth: component.bit_depth,
2015 })
2016 .collect();
2017
2018 Ok(DecodedComponents {
2019 dimensions: (width, height),
2020 color_space: self.color_space.clone(),
2021 has_alpha: self.has_alpha,
2022 planes,
2023 })
2024 }
2025
2026 pub fn decode_region(&self, roi: (u32, u32, u32, u32)) -> Result<Bitmap> {
2028 self.decode_region_with_context(roi, &mut DecoderContext::default())
2029 }
2030
2031 pub fn decode_region_with_context(
2034 &self,
2035 roi: (u32, u32, u32, u32),
2036 decoder_context: &mut DecoderContext<'a>,
2037 ) -> Result<Bitmap> {
2038 validate_roi((self.width(), self.height()), roi)?;
2039 let mut decoded_image =
2040 self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
2041 let (_x, _y, width, height) = roi;
2042 let channels =
2043 self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 };
2044 let data_len = checked_decode_byte_len3(width as usize, height as usize, channels)?;
2045 let mut data = vec![0; data_len];
2046 interleave_and_convert_region(
2047 &mut decoded_image,
2048 width as usize,
2049 (0, 0, width, height),
2050 &mut data,
2051 );
2052 Ok(Bitmap {
2053 color_space: self.color_space.clone(),
2054 data,
2055 has_alpha: self.has_alpha,
2056 width,
2057 height,
2058 original_bit_depth: self.original_bit_depth(),
2059 })
2060 }
2061
2062 pub fn decode_native(&self) -> Result<RawBitmap> {
2071 let mut decoder_context = DecoderContext::default();
2072 self.decode_native_with_context(&mut decoder_context)
2073 }
2074
2075 pub fn decode_reversible_53_coefficients(&self) -> Result<Reversible53CoefficientImage> {
2081 let mut decoder_context = DecoderContext::default();
2082 self.decode_reversible_53_coefficients_with_context(&mut decoder_context)
2083 }
2084
2085 pub fn decode_reversible_53_coefficients_with_context(
2088 &self,
2089 decoder_context: &mut DecoderContext<'a>,
2090 ) -> Result<Reversible53CoefficientImage> {
2091 j2c::recode::extract_reversible_53_coefficients(
2092 self.codestream,
2093 &self.header,
2094 decoder_context,
2095 )
2096 }
2097
2098 pub fn decode_native_region(&self, roi: (u32, u32, u32, u32)) -> Result<RawBitmap> {
2100 self.decode_native_region_with_context(roi, &mut DecoderContext::default())
2101 }
2102
2103 pub fn decode_native_with_context(
2106 &self,
2107 decoder_context: &mut DecoderContext<'a>,
2108 ) -> Result<RawBitmap> {
2109 self.decode_with_output_region(decoder_context, None)?;
2110
2111 let components = &decoder_context.tile_decode_context.channel_data;
2112 let bit_depth = self.original_bit_depth();
2113 let num_components =
2114 u8::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2115 let width = self.width();
2116 let height = self.height();
2117 let pixel_count = checked_decode_sample_count(width, height)?;
2118
2119 if bit_depth <= 8 {
2120 let max_val = ((1u32 << bit_depth) - 1) as f32;
2121 let capacity = checked_decode_byte_len2(pixel_count, num_components as usize)?;
2122 let mut data = Vec::with_capacity(capacity);
2123 for i in 0..pixel_count {
2124 for component in components.iter() {
2125 let v = math::round_f32(component.container.truncated()[i]);
2126 let clamped = if v < 0.0 {
2127 0.0
2128 } else if v > max_val {
2129 max_val
2130 } else {
2131 v
2132 };
2133 data.push(clamped as u8);
2134 }
2135 }
2136 Ok(RawBitmap {
2137 data,
2138 width,
2139 height,
2140 bit_depth,
2141 num_components,
2142 bytes_per_sample: 1,
2143 })
2144 } else {
2145 let max_val = ((1u32 << bit_depth) - 1) as f32;
2146 let capacity = checked_decode_byte_len3(pixel_count, num_components as usize, 2)?;
2147 let mut data = Vec::with_capacity(capacity);
2148 for i in 0..pixel_count {
2149 for component in components.iter() {
2150 let v = math::round_f32(component.container.truncated()[i]);
2151 let clamped = if v < 0.0 {
2152 0.0
2153 } else if v > max_val {
2154 max_val
2155 } else {
2156 v
2157 };
2158 let val = clamped as u16;
2159 data.extend_from_slice(&val.to_le_bytes());
2160 }
2161 }
2162 Ok(RawBitmap {
2163 data,
2164 width,
2165 height,
2166 bit_depth,
2167 num_components,
2168 bytes_per_sample: 2,
2169 })
2170 }
2171 }
2172
2173 pub fn decode_native_region_with_context(
2176 &self,
2177 roi: (u32, u32, u32, u32),
2178 decoder_context: &mut DecoderContext<'a>,
2179 ) -> Result<RawBitmap> {
2180 validate_roi((self.width(), self.height()), roi)?;
2181 self.decode_with_output_region(decoder_context, Some(roi))?;
2182
2183 let components = &decoder_context.tile_decode_context.channel_data;
2184 let bit_depth = self.original_bit_depth();
2185 let num_components =
2186 u8::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2187 let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
2188 let (_x, _y, width, height) = roi;
2189 let capacity = checked_decode_byte_len4(
2190 width as usize,
2191 height as usize,
2192 num_components as usize,
2193 bytes_per_sample,
2194 )?;
2195 let mut data = Vec::with_capacity(capacity);
2196 let max_val = ((1u32 << bit_depth) - 1) as f32;
2197
2198 for row in 0..height as usize {
2199 for col in 0..width as usize {
2200 let idx = row * width as usize + col;
2201 for component in components {
2202 let v = math::round_f32(component.container.truncated()[idx]);
2203 let clamped = if v < 0.0 {
2204 0.0
2205 } else if v > max_val {
2206 max_val
2207 } else {
2208 v
2209 };
2210 if bit_depth <= 8 {
2211 data.push(clamped as u8);
2212 } else {
2213 data.extend_from_slice(&(clamped as u16).to_le_bytes());
2214 }
2215 }
2216 }
2217 }
2218
2219 Ok(RawBitmap {
2220 data,
2221 width,
2222 height,
2223 bit_depth,
2224 num_components,
2225 bytes_per_sample: bytes_per_sample as u8,
2226 })
2227 }
2228
2229 pub fn decode_into(
2239 &self,
2240 buf: &mut [u8],
2241 decoder_context: &mut DecoderContext<'a>,
2242 ) -> Result<()> {
2243 let mut decoded_image = self.prepare_decoded_image(decoder_context)?;
2244 validate_interleaved_output_buffer(&decoded_image, buf)?;
2245 interleave_and_convert(&mut decoded_image, buf)?;
2246
2247 Ok(())
2248 }
2249
2250 fn prepare_decoded_image<'ctx>(
2251 &self,
2252 decoder_context: &'ctx mut DecoderContext<'a>,
2253 ) -> Result<DecodedImage<'ctx>> {
2254 self.prepare_decoded_image_with_region(decoder_context, None)
2255 }
2256
2257 fn prepare_decoded_image_with_ht_decoder<'ctx>(
2258 &self,
2259 decoder_context: &'ctx mut DecoderContext<'a>,
2260 ht_decoder: &mut dyn HtCodeBlockDecoder,
2261 ) -> Result<DecodedImage<'ctx>> {
2262 self.prepare_decoded_image_with_region_and_ht_decoder(
2263 decoder_context,
2264 None,
2265 Some(ht_decoder),
2266 )
2267 }
2268
2269 fn prepare_decoded_image_with_region<'ctx>(
2270 &self,
2271 decoder_context: &'ctx mut DecoderContext<'a>,
2272 output_region: Option<(u32, u32, u32, u32)>,
2273 ) -> Result<DecodedImage<'ctx>> {
2274 self.prepare_decoded_image_with_region_and_ht_decoder(decoder_context, output_region, None)
2275 }
2276
2277 fn prepare_decoded_image_with_region_and_ht_decoder<'ctx>(
2278 &self,
2279 decoder_context: &'ctx mut DecoderContext<'a>,
2280 output_region: Option<(u32, u32, u32, u32)>,
2281 ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2282 ) -> Result<DecodedImage<'ctx>> {
2283 let settings = &self.settings;
2284 self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, ht_decoder)?;
2285 let mut decoded_image = DecodedImage {
2286 decoded_components: &mut decoder_context.tile_decode_context.channel_data,
2287 boxes: self.boxes.clone(),
2288 };
2289
2290 if settings.resolve_palette_indices {
2291 let components = core::mem::take(decoded_image.decoded_components);
2292 *decoded_image.decoded_components =
2293 resolve_palette_indices(components, &decoded_image.boxes)?;
2294 }
2295
2296 if let Some(cdef) = &decoded_image.boxes.channel_definition {
2297 validate_channel_definition(cdef, decoded_image.decoded_components.len())?;
2298 let mut components = decoded_image
2299 .decoded_components
2300 .iter()
2301 .cloned()
2302 .zip(
2303 cdef.channel_definitions
2304 .iter()
2305 .map(|c| match c._association {
2306 ChannelAssociation::WholeImage => u16::MAX,
2307 ChannelAssociation::Colour(c) => c,
2308 }),
2309 )
2310 .collect::<Vec<_>>();
2311 components.sort_by_key(|component| component.1);
2312 *decoded_image.decoded_components = components.into_iter().map(|c| c.0).collect();
2313 }
2314
2315 let bit_depth = decoded_image.decoded_components[0].bit_depth;
2316 convert_color_space(&mut decoded_image, bit_depth)?;
2317 Ok(decoded_image)
2318 }
2319
2320 fn decode_with_output_region(
2321 &self,
2322 decoder_context: &mut DecoderContext<'a>,
2323 output_region: Option<(u32, u32, u32, u32)>,
2324 ) -> Result<()> {
2325 self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, None)
2326 }
2327
2328 fn decode_with_output_region_and_ht_decoder(
2329 &self,
2330 decoder_context: &mut DecoderContext<'a>,
2331 output_region: Option<(u32, u32, u32, u32)>,
2332 mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2333 ) -> Result<()> {
2334 decoder_context.set_output_region(output_region);
2335 let decode_result = j2c::decode(
2336 self.codestream,
2337 &self.header,
2338 decoder_context,
2339 &mut ht_decoder,
2340 );
2341 decoder_context.set_output_region(None);
2342 decode_result
2343 }
2344}
2345
2346fn validate_channel_definition(
2347 cdef: &jp2::cdef::ChannelDefinitionBox,
2348 component_count: usize,
2349) -> Result<()> {
2350 if cdef.channel_definitions.len() != component_count {
2351 bail!(ValidationError::InvalidChannelDefinition);
2352 }
2353
2354 let mut seen_color_associations = vec![false; component_count];
2355 for definition in &cdef.channel_definitions {
2356 if let ChannelAssociation::Colour(association) = definition._association {
2357 let Some(index) = association.checked_sub(1).map(usize::from) else {
2358 bail!(ValidationError::InvalidChannelDefinition);
2359 };
2360 if index >= component_count || seen_color_associations[index] {
2361 bail!(ValidationError::InvalidChannelDefinition);
2362 }
2363 seen_color_associations[index] = true;
2364 }
2365 }
2366
2367 Ok(())
2368}
2369
2370pub(crate) fn resolve_alpha_and_color_space(
2371 boxes: &ImageBoxes,
2372 header: &Header<'_>,
2373 settings: &DecodeSettings,
2374) -> Result<(ColorSpace, bool)> {
2375 let mut num_components = header.component_infos.len();
2376
2377 if settings.resolve_palette_indices {
2380 if let Some(palette_box) = &boxes.palette {
2381 num_components = palette_box.columns.len();
2382 }
2383 }
2384
2385 let mut has_alpha = false;
2386
2387 if let Some(cdef) = &boxes.channel_definition {
2388 let last = cdef.channel_definitions.last().unwrap();
2389 has_alpha = last.channel_type == ChannelType::Opacity;
2390 }
2391
2392 let mut color_space = get_color_space(boxes, num_components)?;
2393
2394 if !settings.resolve_palette_indices && boxes.palette.is_some() {
2396 has_alpha = false;
2397 color_space = ColorSpace::Gray;
2398 }
2399
2400 let actual_num_components = header.component_infos.len();
2401
2402 if boxes.palette.is_none()
2404 && actual_num_components
2405 != (color_space.num_channels() + if has_alpha { 1 } else { 0 }) as usize
2406 {
2407 if !settings.strict
2408 && actual_num_components == color_space.num_channels() as usize + 1
2409 && !has_alpha
2410 {
2411 has_alpha = true;
2414 } else {
2415 if actual_num_components == 1 || (actual_num_components == 2 && has_alpha) {
2417 color_space = ColorSpace::Gray;
2418 } else if actual_num_components == 3 {
2419 color_space = ColorSpace::RGB;
2420 } else if actual_num_components == 4 {
2421 if has_alpha {
2422 color_space = ColorSpace::RGB;
2423 } else {
2424 color_space = ColorSpace::CMYK;
2425 }
2426 } else {
2427 bail!(ValidationError::TooManyChannels);
2428 }
2429 }
2430 }
2431
2432 Ok((color_space, has_alpha))
2433}
2434
2435#[derive(Debug, Clone)]
2437pub enum ColorSpace {
2438 Gray,
2440 RGB,
2442 CMYK,
2444 Unknown {
2446 num_channels: u8,
2448 },
2449 Icc {
2451 profile: Vec<u8>,
2453 num_channels: u8,
2455 },
2456}
2457
2458impl ColorSpace {
2459 pub fn num_channels(&self) -> u8 {
2461 match self {
2462 Self::Gray => 1,
2463 Self::RGB => 3,
2464 Self::CMYK => 4,
2465 Self::Unknown { num_channels } => *num_channels,
2466 Self::Icc {
2467 num_channels: num_components,
2468 ..
2469 } => *num_components,
2470 }
2471 }
2472}
2473
2474pub struct Bitmap {
2476 pub color_space: ColorSpace,
2478 pub data: Vec<u8>,
2487 pub has_alpha: bool,
2489 pub width: u32,
2491 pub height: u32,
2493 pub original_bit_depth: u8,
2496}
2497
2498pub struct RawBitmap {
2507 pub data: Vec<u8>,
2509 pub width: u32,
2511 pub height: u32,
2513 pub bit_depth: u8,
2515 pub num_components: u8,
2517 pub bytes_per_sample: u8,
2519}
2520
2521pub struct ComponentPlane<'a> {
2523 samples: &'a [f32],
2524 bit_depth: u8,
2525}
2526
2527impl<'a> ComponentPlane<'a> {
2528 pub fn samples(&self) -> &'a [f32] {
2530 self.samples
2531 }
2532
2533 pub fn bit_depth(&self) -> u8 {
2535 self.bit_depth
2536 }
2537}
2538
2539pub struct DecodedComponents<'a> {
2541 dimensions: (u32, u32),
2542 color_space: ColorSpace,
2543 has_alpha: bool,
2544 planes: Vec<ComponentPlane<'a>>,
2545}
2546
2547impl<'a> DecodedComponents<'a> {
2548 pub fn dimensions(&self) -> (u32, u32) {
2550 self.dimensions
2551 }
2552
2553 pub fn color_space(&self) -> &ColorSpace {
2555 &self.color_space
2556 }
2557
2558 pub fn has_alpha(&self) -> bool {
2560 self.has_alpha
2561 }
2562
2563 pub fn planes(&self) -> &[ComponentPlane<'a>] {
2565 &self.planes
2566 }
2567}
2568
2569fn validate_interleaved_output_buffer(image: &DecodedImage<'_>, buf: &[u8]) -> Result<()> {
2570 let required_len = interleaved_output_len(image)?;
2571 if buf.len() < required_len {
2572 bail!(DecodingError::OutputBufferTooSmall);
2573 }
2574 Ok(())
2575}
2576
2577fn interleaved_output_len(image: &DecodedImage<'_>) -> Result<usize> {
2578 let Some(first) = image.decoded_components.first() else {
2579 bail!(DecodingError::CodeBlockDecodeFailure);
2580 };
2581 first
2582 .container
2583 .truncated()
2584 .len()
2585 .checked_mul(image.decoded_components.len())
2586 .ok_or(ValidationError::ImageTooLarge.into())
2587}
2588
2589fn interleave_and_convert(image: &mut DecodedImage<'_>, buf: &mut [u8]) -> Result<()> {
2590 let components = &mut *image.decoded_components;
2591 let num_components = components.len();
2592
2593 let mut all_same_bit_depth = Some(components[0].bit_depth);
2594
2595 for component in components.iter().skip(1) {
2596 if Some(component.bit_depth) != all_same_bit_depth {
2597 all_same_bit_depth = None;
2598 }
2599 }
2600
2601 let max_len = components[0].container.truncated().len();
2602
2603 let mut output_iter = buf.iter_mut();
2604
2605 if all_same_bit_depth == Some(8) && num_components <= 4 {
2606 match num_components {
2608 1 => {
2610 for (output, input) in output_iter.zip(
2611 components[0]
2612 .container
2613 .iter()
2614 .map(|v| math::round_f32(*v) as u8),
2615 ) {
2616 *output = input;
2617 }
2618 }
2619 2 => {
2621 let c0 = &components[0];
2622 let c1 = &components[1];
2623
2624 let c0 = &c0.container[..max_len];
2625 let c1 = &c1.container[..max_len];
2626
2627 for i in 0..max_len {
2628 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2629 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2630 }
2631 }
2632 3 => {
2634 let c0 = &components[0];
2635 let c1 = &components[1];
2636 let c2 = &components[2];
2637
2638 let c0 = &c0.container[..max_len];
2639 let c1 = &c1.container[..max_len];
2640 let c2 = &c2.container[..max_len];
2641
2642 for i in 0..max_len {
2643 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2644 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2645 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
2646 }
2647 }
2648 4 => {
2650 let c0 = &components[0];
2651 let c1 = &components[1];
2652 let c2 = &components[2];
2653 let c3 = &components[3];
2654
2655 let c0 = &c0.container[..max_len];
2656 let c1 = &c1.container[..max_len];
2657 let c2 = &c2.container[..max_len];
2658 let c3 = &c3.container[..max_len];
2659
2660 for i in 0..max_len {
2661 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2662 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2663 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
2664 *output_iter.next().unwrap() = math::round_f32(c3[i]) as u8;
2665 }
2666 }
2667 _ => bail!(ValidationError::TooManyChannels),
2668 }
2669 } else {
2670 let mul_factor = ((1 << 8) - 1) as f32;
2672
2673 for sample in 0..max_len {
2674 for channel in components.iter() {
2675 *output_iter.next().unwrap() = math::round_f32(
2676 (channel.container[sample] / ((1_u32 << channel.bit_depth) - 1) as f32)
2677 * mul_factor,
2678 ) as u8;
2679 }
2680 }
2681 }
2682
2683 Ok(())
2684}
2685
2686fn interleave_and_convert_region(
2687 image: &mut DecodedImage<'_>,
2688 image_width: usize,
2689 roi: (u32, u32, u32, u32),
2690 buf: &mut [u8],
2691) {
2692 let components = &mut *image.decoded_components;
2693 let num_components = components.len();
2694 let (x, y, width, height) = roi;
2695 let mut output_iter = buf.iter_mut();
2696
2697 let mut all_same_bit_depth = Some(components[0].bit_depth);
2698 for component in components.iter().skip(1) {
2699 if Some(component.bit_depth) != all_same_bit_depth {
2700 all_same_bit_depth = None;
2701 }
2702 }
2703
2704 if all_same_bit_depth == Some(8) && num_components <= 4 {
2705 for row in y as usize..(y + height) as usize {
2706 let row_base = row * image_width;
2707 for col in x as usize..(x + width) as usize {
2708 let idx = row_base + col;
2709 for component in components.iter() {
2710 *output_iter.next().unwrap() = math::round_f32(component.container[idx]) as u8;
2711 }
2712 }
2713 }
2714 } else {
2715 let mul_factor = ((1 << 8) - 1) as f32;
2716 for row in y as usize..(y + height) as usize {
2717 let row_base = row * image_width;
2718 for col in x as usize..(x + width) as usize {
2719 let idx = row_base + col;
2720 for component in components.iter() {
2721 *output_iter.next().unwrap() = math::round_f32(
2722 (component.container[idx] / ((1_u32 << component.bit_depth) - 1) as f32)
2723 * mul_factor,
2724 ) as u8;
2725 }
2726 }
2727 }
2728 }
2729}
2730
2731fn validate_roi(dims: (u32, u32), roi: (u32, u32, u32, u32)) -> Result<()> {
2732 let (image_width, image_height) = dims;
2733 let (x, y, width, height) = roi;
2734 let x_end = x
2735 .checked_add(width)
2736 .ok_or(ValidationError::InvalidDimensions)?;
2737 let y_end = y
2738 .checked_add(height)
2739 .ok_or(ValidationError::InvalidDimensions)?;
2740 if x_end > image_width || y_end > image_height {
2741 return Err(ValidationError::InvalidDimensions.into());
2742 }
2743 Ok(())
2744}
2745
2746fn convert_color_space(image: &mut DecodedImage<'_>, bit_depth: u8) -> Result<()> {
2747 if let Some(jp2::colr::ColorSpace::Enumerated(e)) = &image
2748 .boxes
2749 .color_specification
2750 .as_ref()
2751 .map(|i| &i.color_space)
2752 {
2753 match e {
2754 EnumeratedColorspace::Sycc => {
2755 dispatch!(Level::new(), simd => {
2756 sycc_to_rgb(simd, image.decoded_components, bit_depth)
2757 })?;
2758 }
2759 EnumeratedColorspace::CieLab(cielab) => {
2760 dispatch!(Level::new(), simd => {
2761 cielab_to_rgb(simd, image.decoded_components, bit_depth, cielab)
2762 })?;
2763 }
2764 _ => {}
2765 }
2766 }
2767
2768 Ok(())
2769}
2770
2771fn get_color_space(boxes: &ImageBoxes, num_components: usize) -> Result<ColorSpace> {
2772 let cs = match boxes
2773 .color_specification
2774 .as_ref()
2775 .map(|c| &c.color_space)
2776 .unwrap_or(&jp2::colr::ColorSpace::Unknown)
2777 {
2778 jp2::colr::ColorSpace::Enumerated(e) => {
2779 match e {
2780 EnumeratedColorspace::Cmyk => ColorSpace::CMYK,
2781 EnumeratedColorspace::Srgb => ColorSpace::RGB,
2782 EnumeratedColorspace::RommRgb => {
2783 ColorSpace::Icc {
2785 profile: include_bytes!("../assets/ProPhoto-v2-micro.icc").to_vec(),
2786 num_channels: 3,
2787 }
2788 }
2789 EnumeratedColorspace::EsRgb => ColorSpace::RGB,
2790 EnumeratedColorspace::Greyscale => ColorSpace::Gray,
2791 EnumeratedColorspace::Sycc => ColorSpace::RGB,
2792 EnumeratedColorspace::CieLab(_) => ColorSpace::Icc {
2793 profile: include_bytes!("../assets/LAB.icc").to_vec(),
2794 num_channels: 3,
2795 },
2796 _ => bail!(FormatError::Unsupported),
2797 }
2798 }
2799 jp2::colr::ColorSpace::Icc(icc) => {
2800 if let Some(metadata) = ICCMetadata::from_data(icc) {
2801 ColorSpace::Icc {
2802 profile: icc.clone(),
2803 num_channels: metadata.color_space.num_components(),
2804 }
2805 } else {
2806 ColorSpace::RGB
2811 }
2812 }
2813 jp2::colr::ColorSpace::Unknown => match num_components {
2814 1 => ColorSpace::Gray,
2815 3 => ColorSpace::RGB,
2816 4 => ColorSpace::CMYK,
2817 _ => ColorSpace::Unknown {
2818 num_channels: num_components as u8,
2819 },
2820 },
2821 };
2822
2823 Ok(cs)
2824}
2825
2826fn resolve_palette_indices(
2827 components: Vec<ComponentData>,
2828 boxes: &ImageBoxes,
2829) -> Result<Vec<ComponentData>> {
2830 let Some(palette) = boxes.palette.as_ref() else {
2831 return Ok(components);
2833 };
2834
2835 let Some(mapping) = boxes.component_mapping.as_ref() else {
2836 bail!(ColorError::PaletteResolutionFailed);
2837 };
2838 if mapping.entries.is_empty() {
2839 bail!(ColorError::PaletteResolutionFailed);
2840 }
2841
2842 let mut resolved = Vec::with_capacity(mapping.entries.len());
2843
2844 for entry in &mapping.entries {
2845 let component_idx = entry.component_index as usize;
2846 let component = components
2847 .get(component_idx)
2848 .ok_or(ColorError::PaletteResolutionFailed)?;
2849
2850 match entry.mapping_type {
2851 ComponentMappingType::Direct => resolved.push(component.clone()),
2852 ComponentMappingType::Palette { column } => {
2853 let column_idx = column as usize;
2854 let column_info = palette
2855 .columns
2856 .get(column_idx)
2857 .ok_or(ColorError::PaletteResolutionFailed)?;
2858
2859 let mut mapped =
2860 Vec::with_capacity(component.container.truncated().len() + SIMD_WIDTH);
2861
2862 for &sample in component.container.truncated() {
2863 let index = math::round_f32(sample) as i64;
2864 let value = palette
2865 .map(index as usize, column_idx)
2866 .ok_or(ColorError::PaletteResolutionFailed)?;
2867 mapped.push(value as f32);
2868 }
2869
2870 resolved.push(ComponentData {
2871 container: math::SimdBuffer::new(mapped),
2872 bit_depth: column_info.bit_depth,
2873 });
2874 }
2875 }
2876 }
2877
2878 Ok(resolved)
2879}
2880
2881#[inline(always)]
2882fn cielab_to_rgb<S: Simd>(
2883 simd: S,
2884 components: &mut [ComponentData],
2885 bit_depth: u8,
2886 lab: &CieLab,
2887) -> Result<()> {
2888 let (head, _) = components
2889 .split_at_mut_checked(3)
2890 .ok_or(ColorError::LabConversionFailed)?;
2891
2892 let [l, a, b] = head else {
2893 bail!(ColorError::LabConversionFailed);
2894 };
2895
2896 let prec0 = l.bit_depth;
2897 let prec1 = a.bit_depth;
2898 let prec2 = b.bit_depth;
2899
2900 if prec0 < 4 || prec1 < 4 || prec2 < 4 {
2902 bail!(ColorError::LabConversionFailed);
2903 }
2904
2905 let rl = lab.rl.unwrap_or(100);
2906 let ra = lab.ra.unwrap_or(170);
2907 let rb = lab.ra.unwrap_or(200);
2908 let ol = lab.ol.unwrap_or(0);
2909 let oa = lab.oa.unwrap_or(1 << (bit_depth - 1));
2910 let ob = lab
2911 .ob
2912 .unwrap_or((1 << (bit_depth - 2)) + (1 << (bit_depth - 3)));
2913
2914 let min_l = -(rl as f32 * ol as f32) / ((1 << prec0) - 1) as f32;
2916 let max_l = min_l + rl as f32;
2917 let min_a = -(ra as f32 * oa as f32) / ((1 << prec1) - 1) as f32;
2918 let max_a = min_a + ra as f32;
2919 let min_b = -(rb as f32 * ob as f32) / ((1 << prec2) - 1) as f32;
2920 let max_b = min_b + rb as f32;
2921
2922 let bit_max = (1_u32 << bit_depth) - 1;
2923
2924 let divisor_l = ((1 << prec0) - 1) as f32;
2928 let divisor_a = ((1 << prec1) - 1) as f32;
2929 let divisor_b = ((1 << prec2) - 1) as f32;
2930
2931 let scale_l_final = bit_max as f32 / 100.0;
2932 let scale_ab_final = bit_max as f32 / 255.0;
2933
2934 let l_offset = min_l * scale_l_final;
2935 let l_scale = (max_l - min_l) / divisor_l * scale_l_final;
2936 let a_offset = (min_a + 128.0) * scale_ab_final;
2937 let a_scale = (max_a - min_a) / divisor_a * scale_ab_final;
2938 let b_offset = (min_b + 128.0) * scale_ab_final;
2939 let b_scale = (max_b - min_b) / divisor_b * scale_ab_final;
2940
2941 let l_offset_v = f32x8::splat(simd, l_offset);
2942 let l_scale_v = f32x8::splat(simd, l_scale);
2943 let a_offset_v = f32x8::splat(simd, a_offset);
2944 let a_scale_v = f32x8::splat(simd, a_scale);
2945 let b_offset_v = f32x8::splat(simd, b_offset);
2946 let b_scale_v = f32x8::splat(simd, b_scale);
2947
2948 for ((l_chunk, a_chunk), b_chunk) in l
2952 .container
2953 .chunks_exact_mut(SIMD_WIDTH)
2954 .zip(a.container.chunks_exact_mut(SIMD_WIDTH))
2955 .zip(b.container.chunks_exact_mut(SIMD_WIDTH))
2956 {
2957 let l_v = f32x8::from_slice(simd, l_chunk);
2958 let a_v = f32x8::from_slice(simd, a_chunk);
2959 let b_v = f32x8::from_slice(simd, b_chunk);
2960
2961 l_v.mul_add(l_scale_v, l_offset_v).store(l_chunk);
2962 a_v.mul_add(a_scale_v, a_offset_v).store(a_chunk);
2963 b_v.mul_add(b_scale_v, b_offset_v).store(b_chunk);
2964 }
2965
2966 Ok(())
2967}
2968
2969#[inline(always)]
2970fn sycc_to_rgb<S: Simd>(simd: S, components: &mut [ComponentData], bit_depth: u8) -> Result<()> {
2971 let offset = (1_u32 << (bit_depth as u32 - 1)) as f32;
2972 let max_value = ((1_u32 << bit_depth as u32) - 1) as f32;
2973
2974 let (head, _) = components
2975 .split_at_mut_checked(3)
2976 .ok_or(ColorError::SyccConversionFailed)?;
2977
2978 let [y, cb, cr] = head else {
2979 bail!(ColorError::SyccConversionFailed);
2980 };
2981
2982 let offset_v = f32x8::splat(simd, offset);
2983 let max_v = f32x8::splat(simd, max_value);
2984 let zero_v = f32x8::splat(simd, 0.0);
2985 let cr_to_r = f32x8::splat(simd, 1.402);
2986 let cb_to_g = f32x8::splat(simd, -0.344136);
2987 let cr_to_g = f32x8::splat(simd, -0.714136);
2988 let cb_to_b = f32x8::splat(simd, 1.772);
2989
2990 for ((y_chunk, cb_chunk), cr_chunk) in y
2991 .container
2992 .chunks_exact_mut(SIMD_WIDTH)
2993 .zip(cb.container.chunks_exact_mut(SIMD_WIDTH))
2994 .zip(cr.container.chunks_exact_mut(SIMD_WIDTH))
2995 {
2996 let y_v = f32x8::from_slice(simd, y_chunk);
2997 let cb_v = f32x8::from_slice(simd, cb_chunk) - offset_v;
2998 let cr_v = f32x8::from_slice(simd, cr_chunk) - offset_v;
2999
3000 let r = cr_v.mul_add(cr_to_r, y_v);
3002 let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
3004 let b = cb_v.mul_add(cb_to_b, y_v);
3006
3007 r.min(max_v).max(zero_v).store(y_chunk);
3008 g.min(max_v).max(zero_v).store(cb_chunk);
3009 b.min(max_v).max(zero_v).store(cr_chunk);
3010 }
3011
3012 Ok(())
3013}
3014
3015#[cfg(test)]
3016mod tests {
3017 use super::*;
3018
3019 #[test]
3020 fn ht_uvlc_encode_table_bytes_match_entry_packing_order() {
3021 let entries = ht_uvlc_encode_table();
3022 let bytes = ht_uvlc_encode_table_bytes();
3023
3024 assert_eq!(bytes.len(), entries.len() * 6);
3025 for (index, entry) in entries.iter().enumerate() {
3026 let offset = index * 6;
3027 assert_eq!(
3028 &bytes[offset..offset + 6],
3029 &[
3030 entry.pre,
3031 entry.pre_len,
3032 entry.suf,
3033 entry.suf_len,
3034 entry.ext,
3035 entry.ext_len
3036 ],
3037 );
3038 }
3039 }
3040
3041 #[test]
3042 fn roi_maxshift_inverse_preserves_background_and_unshifts_roi_coefficients() {
3043 assert_eq!(apply_roi_maxshift_inverse_i32(127, 7), 127);
3044 assert_eq!(apply_roi_maxshift_inverse_i32(-127, 7), -127);
3045 assert_eq!(apply_roi_maxshift_inverse_i32(128, 7), 1);
3046 assert_eq!(apply_roi_maxshift_inverse_i32(-128, 7), -1);
3047 assert_eq!(apply_roi_maxshift_inverse_i32(255, 7), 1);
3048 assert_eq!(apply_roi_maxshift_inverse_i32(-255, 7), -1);
3049 assert_eq!(apply_roi_maxshift_inverse_i32(256, 7), 2);
3050 assert_eq!(apply_roi_maxshift_inverse_i32(-256, 7), -2);
3051 assert_eq!(apply_roi_maxshift_inverse_i32(42, 0), 42);
3052 }
3053
3054 #[test]
3055 fn classic_scalar_decode_applies_nonzero_roi_maxshift() {
3056 let roi_shift = 3;
3057 let total_bitplanes = 3;
3058 let style = J2kCodeBlockStyle {
3059 selective_arithmetic_coding_bypass: false,
3060 reset_context_probabilities: false,
3061 termination_on_each_pass: false,
3062 vertically_causal_context: false,
3063 segmentation_symbols: false,
3064 };
3065 let coded_coefficients = [0, 5, 1 << roi_shift, -(2 << roi_shift)];
3066 let encoded = encode_j2k_code_block_scalar_with_style(
3067 &coded_coefficients,
3068 2,
3069 2,
3070 J2kSubBandType::LowLow,
3071 total_bitplanes + roi_shift,
3072 style,
3073 )
3074 .expect("encode ROI-shifted code block");
3075 let job = J2kCodeBlockDecodeJob {
3076 data: &encoded.data,
3077 segments: &encoded.segments,
3078 width: 2,
3079 height: 2,
3080 output_stride: 2,
3081 missing_bit_planes: encoded.missing_bit_planes,
3082 number_of_coding_passes: encoded.number_of_coding_passes,
3083 total_bitplanes,
3084 roi_shift,
3085 sub_band_type: J2kSubBandType::LowLow,
3086 style,
3087 strict: true,
3088 dequantization_step: 1.0,
3089 };
3090 let mut output = [0.0; 4];
3091
3092 decode_j2k_code_block_scalar(job, &mut output).expect("decode ROI-shifted code block");
3093
3094 assert_eq!(output, [0.0, 5.0, 1.0, -2.0]);
3095 }
3096
3097 #[test]
3098 fn classic_scalar_token_pack_matches_scalar_single_cleanup_block() {
3099 let style = J2kCodeBlockStyle {
3100 selective_arithmetic_coding_bypass: true,
3101 reset_context_probabilities: false,
3102 termination_on_each_pass: false,
3103 vertically_causal_context: false,
3104 segmentation_symbols: false,
3105 };
3106 let scalar =
3107 encode_j2k_code_block_scalar_with_style(&[1], 1, 1, J2kSubBandType::LowLow, 1, style)
3108 .expect("encode scalar");
3109 let token_bytes = pack_mq_test_tokens(&[(0, 1), (9, 0)]);
3110 let packed = pack_j2k_code_block_scalar_from_tier1_tokens(
3111 &token_bytes,
3112 &[J2kTier1TokenSegment {
3113 token_bit_offset: 0,
3114 token_bit_count: 12,
3115 start_coding_pass: 0,
3116 end_coding_pass: 1,
3117 use_arithmetic: true,
3118 }],
3119 scalar.number_of_coding_passes,
3120 scalar.missing_bit_planes,
3121 )
3122 .expect("pack tokens");
3123
3124 assert_eq!(packed.data, scalar.data);
3125 assert_eq!(packed.segments, scalar.segments);
3126 assert_eq!(
3127 packed.number_of_coding_passes,
3128 scalar.number_of_coding_passes
3129 );
3130 assert_eq!(packed.missing_bit_planes, scalar.missing_bit_planes);
3131 }
3132
3133 fn pack_mq_test_tokens(tokens: &[(u8, u8)]) -> Vec<u8> {
3134 let mut bytes = Vec::new();
3135 let mut current = 0u8;
3136 let mut bits = 0u8;
3137 for &(ctx, bit) in tokens {
3138 let value = (ctx & 0x1F) | ((bit & 1) << 5);
3139 for shift in (0..6).rev() {
3140 current = (current << 1) | ((value >> shift) & 1);
3141 bits += 1;
3142 if bits == 8 {
3143 bytes.push(current);
3144 current = 0;
3145 bits = 0;
3146 }
3147 }
3148 }
3149 if bits != 0 {
3150 bytes.push(current << (8 - bits));
3151 }
3152 bytes
3153 }
3154
3155 #[test]
3156 fn classic_scalar_profiled_decode_matches_unprofiled_decode() {
3157 let width = 64u32;
3158 let height = 64u32;
3159 let sample_count = width as usize * height as usize;
3160 let total_bitplanes = 12;
3161 let style = J2kCodeBlockStyle {
3162 selective_arithmetic_coding_bypass: false,
3163 reset_context_probabilities: false,
3164 termination_on_each_pass: false,
3165 vertically_causal_context: false,
3166 segmentation_symbols: false,
3167 };
3168 let coefficients = (0..sample_count)
3169 .map(|idx| {
3170 let value = i32::try_from((idx * 37) % 4095).expect("sample value fits i32") - 2048;
3171 if idx % 17 == 0 {
3172 0
3173 } else {
3174 value
3175 }
3176 })
3177 .collect::<Vec<_>>();
3178 let encoded = encode_j2k_code_block_scalar_with_style(
3179 &coefficients,
3180 width,
3181 height,
3182 J2kSubBandType::LowLow,
3183 total_bitplanes,
3184 style,
3185 )
3186 .expect("encode classic block");
3187 let job = J2kCodeBlockDecodeJob {
3188 data: &encoded.data,
3189 segments: &encoded.segments,
3190 width,
3191 height,
3192 output_stride: width as usize,
3193 missing_bit_planes: encoded.missing_bit_planes,
3194 number_of_coding_passes: encoded.number_of_coding_passes,
3195 total_bitplanes,
3196 roi_shift: 0,
3197 sub_band_type: J2kSubBandType::LowLow,
3198 style,
3199 strict: true,
3200 dequantization_step: 1.0,
3201 };
3202 let mut expected = vec![0.0_f32; sample_count];
3203 let mut actual = vec![0.0_f32; sample_count];
3204 let mut profile = J2kCodeBlockDecodeProfile::default();
3205
3206 decode_j2k_code_block_scalar(job, &mut expected).expect("unprofiled classic decode");
3207 decode_j2k_code_block_scalar_profiled(job, &mut actual, &mut profile)
3208 .expect("profiled classic decode");
3209
3210 assert_eq!(actual, expected);
3211 assert!(profile.cleanup_us > 0);
3212 }
3213
3214 #[test]
3215 fn classic_scalar_workspace_reuse_matches_fresh_decode() {
3216 let total_bitplanes = 6;
3217 let style = J2kCodeBlockStyle {
3218 selective_arithmetic_coding_bypass: false,
3219 reset_context_probabilities: false,
3220 termination_on_each_pass: false,
3221 vertically_causal_context: false,
3222 segmentation_symbols: false,
3223 };
3224 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
3225
3226 for (width, height, seed) in [(8, 8, 0x31), (4, 16, 0x47)] {
3227 let coefficients = (0..width * height)
3228 .map(|idx| {
3229 let value = ((idx as i32 * seed) % 23) - 11;
3230 if idx % 7 == 0 {
3231 0
3232 } else {
3233 value
3234 }
3235 })
3236 .collect::<Vec<_>>();
3237 let encoded = encode_j2k_code_block_scalar_with_style(
3238 &coefficients,
3239 width,
3240 height,
3241 J2kSubBandType::LowLow,
3242 total_bitplanes,
3243 style,
3244 )
3245 .expect("encode classic block");
3246 let job = J2kCodeBlockDecodeJob {
3247 data: &encoded.data,
3248 segments: &encoded.segments,
3249 width,
3250 height,
3251 output_stride: width as usize,
3252 missing_bit_planes: encoded.missing_bit_planes,
3253 number_of_coding_passes: encoded.number_of_coding_passes,
3254 total_bitplanes,
3255 roi_shift: 0,
3256 sub_band_type: J2kSubBandType::LowLow,
3257 style,
3258 strict: true,
3259 dequantization_step: 1.0,
3260 };
3261 let mut fresh = vec![0.0_f32; width as usize * height as usize];
3262 let mut reused = vec![0.0_f32; width as usize * height as usize];
3263
3264 decode_j2k_code_block_scalar(job, &mut fresh).expect("fresh classic decode");
3265 decode_j2k_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
3266 .expect("workspace classic decode");
3267
3268 assert_eq!(reused, fresh);
3269 }
3270 }
3271
3272 #[test]
3273 fn scalar_packetization_rejects_overflowing_ht_refinement_lengths_without_panic() {
3274 let payload = [0x12];
3275 let block = J2kPacketizationCodeBlock {
3276 data: &payload,
3277 ht_cleanup_length: u32::MAX,
3278 ht_refinement_length: 1,
3279 num_coding_passes: 3,
3280 num_zero_bitplanes: 2,
3281 previously_included: false,
3282 l_block: 3,
3283 block_coding_mode: J2kPacketizationBlockCodingMode::HighThroughput,
3284 };
3285 let subband = J2kPacketizationSubband {
3286 code_blocks: vec![block],
3287 num_cbs_x: 1,
3288 num_cbs_y: 1,
3289 };
3290 let resolution = J2kPacketizationResolution {
3291 subbands: vec![subband],
3292 };
3293 let resolutions = [resolution];
3294 let job = J2kPacketizationEncodeJob {
3295 resolution_count: 1,
3296 num_layers: 1,
3297 num_components: 1,
3298 code_block_count: 1,
3299 progression_order: J2kPacketizationProgressionOrder::Lrcp,
3300 packet_descriptors: &[],
3301 resolutions: &resolutions,
3302 };
3303
3304 let err = encode_j2k_packetization_scalar(job)
3305 .expect_err("overflowing HT packetization segment lengths rejected");
3306
3307 assert_eq!(err, "multi-pass HTJ2K packet contribution length overflow");
3308 }
3309
3310 #[derive(Default)]
3311 struct DecodeWorkCounter {
3312 classic_code_blocks: usize,
3313 ht_code_blocks: usize,
3314 idwt_output_samples: usize,
3315 }
3316
3317 impl DecodeWorkCounter {
3318 fn code_blocks(&self) -> usize {
3319 self.classic_code_blocks + self.ht_code_blocks
3320 }
3321 }
3322
3323 struct FailingHtDecoder {
3324 called: bool,
3325 }
3326
3327 impl HtCodeBlockDecoder for FailingHtDecoder {
3328 fn decode_code_block(
3329 &mut self,
3330 _job: HtCodeBlockDecodeJob<'_>,
3331 _output: &mut [f32],
3332 ) -> Result<()> {
3333 self.called = true;
3334 Err(DecodingError::CodeBlockDecodeFailure.into())
3335 }
3336 }
3337
3338 struct FailingClassicDecoder {
3339 called: bool,
3340 }
3341
3342 impl HtCodeBlockDecoder for FailingClassicDecoder {
3343 fn decode_code_block(
3344 &mut self,
3345 _job: HtCodeBlockDecodeJob<'_>,
3346 _output: &mut [f32],
3347 ) -> Result<()> {
3348 panic!("HT hook must not be used for classic J2K test")
3349 }
3350
3351 fn decode_j2k_code_block(
3352 &mut self,
3353 _job: J2kCodeBlockDecodeJob<'_>,
3354 _output: &mut [f32],
3355 ) -> Result<bool> {
3356 self.called = true;
3357 Err(DecodingError::CodeBlockDecodeFailure.into())
3358 }
3359 }
3360
3361 struct FailingClassicBatchDecoder {
3362 called: bool,
3363 }
3364
3365 #[derive(Default)]
3366 struct CapturingHtDecoder {
3367 called: bool,
3368 blocks: usize,
3369 refinement_jobs: usize,
3370 max_coding_passes: u8,
3371 }
3372
3373 impl HtCodeBlockDecoder for CapturingHtDecoder {
3374 fn decode_code_block(
3375 &mut self,
3376 job: HtCodeBlockDecodeJob<'_>,
3377 output: &mut [f32],
3378 ) -> Result<()> {
3379 self.called = true;
3380 self.blocks += 1;
3381 self.max_coding_passes = self.max_coding_passes.max(job.number_of_coding_passes);
3382 if job.refinement_length > 0 {
3383 self.refinement_jobs += 1;
3384 assert!(
3385 job.number_of_coding_passes > 1,
3386 "refinement bytes must correspond to refinement coding passes"
3387 );
3388 }
3389
3390 decode_ht_code_block_scalar(job, output)
3391 }
3392 }
3393
3394 #[derive(Clone)]
3395 struct CapturedHtDecodeJob {
3396 data: Vec<u8>,
3397 cleanup_length: u32,
3398 refinement_length: u32,
3399 width: u32,
3400 height: u32,
3401 output_stride: usize,
3402 missing_bit_planes: u8,
3403 number_of_coding_passes: u8,
3404 num_bitplanes: u8,
3405 roi_shift: u8,
3406 stripe_causal: bool,
3407 strict: bool,
3408 dequantization_step: f32,
3409 }
3410
3411 impl CapturedHtDecodeJob {
3412 fn from_job(job: HtCodeBlockDecodeJob<'_>) -> Self {
3413 Self {
3414 data: job.data.to_vec(),
3415 cleanup_length: job.cleanup_length,
3416 refinement_length: job.refinement_length,
3417 width: job.width,
3418 height: job.height,
3419 output_stride: job.output_stride,
3420 missing_bit_planes: job.missing_bit_planes,
3421 number_of_coding_passes: job.number_of_coding_passes,
3422 num_bitplanes: job.num_bitplanes,
3423 roi_shift: job.roi_shift,
3424 stripe_causal: job.stripe_causal,
3425 strict: job.strict,
3426 dequantization_step: job.dequantization_step,
3427 }
3428 }
3429
3430 fn borrowed(&self) -> HtCodeBlockDecodeJob<'_> {
3431 HtCodeBlockDecodeJob {
3432 data: &self.data,
3433 cleanup_length: self.cleanup_length,
3434 refinement_length: self.refinement_length,
3435 width: self.width,
3436 height: self.height,
3437 output_stride: self.output_stride,
3438 missing_bit_planes: self.missing_bit_planes,
3439 number_of_coding_passes: self.number_of_coding_passes,
3440 num_bitplanes: self.num_bitplanes,
3441 roi_shift: self.roi_shift,
3442 stripe_causal: self.stripe_causal,
3443 strict: self.strict,
3444 dequantization_step: self.dequantization_step,
3445 }
3446 }
3447 }
3448
3449 #[derive(Default)]
3450 struct FirstHtJobDecoder {
3451 job: Option<CapturedHtDecodeJob>,
3452 }
3453
3454 impl HtCodeBlockDecoder for FirstHtJobDecoder {
3455 fn decode_code_block(
3456 &mut self,
3457 job: HtCodeBlockDecodeJob<'_>,
3458 output: &mut [f32],
3459 ) -> Result<()> {
3460 if self.job.is_none() {
3461 self.job = Some(CapturedHtDecodeJob::from_job(job));
3462 }
3463 decode_ht_code_block_scalar(job, output)
3464 }
3465 }
3466
3467 struct ZeroRefinementHtDecoder;
3468
3469 impl HtCodeBlockDecoder for ZeroRefinementHtDecoder {
3470 fn decode_code_block(
3471 &mut self,
3472 job: HtCodeBlockDecodeJob<'_>,
3473 output: &mut [f32],
3474 ) -> Result<()> {
3475 let mut data = job.data.to_vec();
3476 let cleanup_len = job.cleanup_length as usize;
3477 let refinement_len = job.refinement_length as usize;
3478 data[cleanup_len..cleanup_len + refinement_len].fill(0);
3479 let zeroed = HtCodeBlockDecodeJob { data: &data, ..job };
3480
3481 decode_ht_code_block_scalar(zeroed, output)
3482 }
3483 }
3484
3485 #[derive(Default)]
3486 struct CleanupLimitedHtDecoder {
3487 blocks: usize,
3488 refinement_blocks: usize,
3489 cleanup_bytes: usize,
3490 refinement_bytes: usize,
3491 }
3492
3493 impl HtCodeBlockDecoder for CleanupLimitedHtDecoder {
3494 fn decode_code_block(
3495 &mut self,
3496 job: HtCodeBlockDecodeJob<'_>,
3497 output: &mut [f32],
3498 ) -> Result<()> {
3499 self.blocks += 1;
3500 self.cleanup_bytes += job.cleanup_length as usize;
3501 if job.refinement_length > 0 {
3502 self.refinement_blocks += 1;
3503 self.refinement_bytes += job.refinement_length as usize;
3504 }
3505
3506 decode_ht_code_block_scalar_until_phase(
3507 job,
3508 output,
3509 HtCodeBlockDecodePhaseLimit::Cleanup,
3510 )
3511 }
3512 }
3513
3514 impl HtCodeBlockDecoder for FailingClassicBatchDecoder {
3515 fn decode_code_block(
3516 &mut self,
3517 _job: HtCodeBlockDecodeJob<'_>,
3518 _output: &mut [f32],
3519 ) -> Result<()> {
3520 panic!("HT hook must not be used for classic J2K batch test")
3521 }
3522
3523 fn decode_j2k_code_block(
3524 &mut self,
3525 _job: J2kCodeBlockDecodeJob<'_>,
3526 _output: &mut [f32],
3527 ) -> Result<bool> {
3528 panic!(
3529 "per-block classic hook must not be used when the batch hook handles the sub-band"
3530 )
3531 }
3532
3533 fn decode_j2k_sub_band(
3534 &mut self,
3535 _job: J2kSubBandDecodeJob<'_>,
3536 _output: &mut [f32],
3537 ) -> Result<bool> {
3538 self.called = true;
3539 Err(DecodingError::CodeBlockDecodeFailure.into())
3540 }
3541 }
3542
3543 fn fixture() -> Vec<u8> {
3544 let pixels = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
3545 let options = EncodeOptions {
3546 reversible: true,
3547 num_decomposition_levels: 1,
3548 ..EncodeOptions::default()
3549 };
3550 encode(&pixels, 2, 2, 3, 8, false, &options).expect("encode")
3551 }
3552
3553 #[test]
3554 fn decode_into_rejects_short_output_buffer() {
3555 let bytes = fixture();
3556 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3557 let mut context = DecoderContext::default();
3558 let mut output = vec![0; 11];
3559
3560 let err = image
3561 .decode_into(&mut output, &mut context)
3562 .expect_err("short output buffer must be rejected");
3563
3564 assert_eq!(
3565 err,
3566 DecodeError::Decoding(DecodingError::OutputBufferTooSmall)
3567 );
3568 }
3569
3570 fn fixture_multi_block() -> Vec<u8> {
3571 let pixels: Vec<u8> = (0..64).collect();
3572 let options = EncodeOptions {
3573 reversible: true,
3574 num_decomposition_levels: 0,
3575 code_block_width_exp: 0,
3576 code_block_height_exp: 0,
3577 ..EncodeOptions::default()
3578 };
3579 encode(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block classic")
3580 }
3581
3582 fn fixture_gray() -> Vec<u8> {
3583 let pixels: Vec<u8> = (0..16).collect();
3584 let options = EncodeOptions {
3585 reversible: true,
3586 num_decomposition_levels: 1,
3587 ..EncodeOptions::default()
3588 };
3589 encode(&pixels, 4, 4, 1, 8, false, &options).expect("encode classic gray8")
3590 }
3591
3592 fn rewrite_siz_to_single_large_tile(codestream: &mut [u8], dimensions: u32) {
3593 let siz = codestream
3594 .windows(2)
3595 .position(|w| w == [0xFF, 0x51])
3596 .expect("SIZ marker");
3597 codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.to_be_bytes());
3598 codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.to_be_bytes());
3599 codestream[siz + 22..siz + 26].copy_from_slice(&dimensions.to_be_bytes());
3600 codestream[siz + 26..siz + 30].copy_from_slice(&dimensions.to_be_bytes());
3601 }
3602
3603 fn rewrite_siz_tile_grid(codestream: &mut [u8], dimensions: (u32, u32), tile_size: (u32, u32)) {
3604 let siz = codestream
3605 .windows(2)
3606 .position(|w| w == [0xFF, 0x51])
3607 .expect("SIZ marker");
3608 codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.0.to_be_bytes());
3609 codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.1.to_be_bytes());
3610 codestream[siz + 22..siz + 26].copy_from_slice(&tile_size.0.to_be_bytes());
3611 codestream[siz + 26..siz + 30].copy_from_slice(&tile_size.1.to_be_bytes());
3612 }
3613
3614 fn rewrite_siz_component_count(codestream: &mut Vec<u8>, component_count: u16) {
3615 let siz = codestream
3616 .windows(2)
3617 .position(|w| w == [0xFF, 0x51])
3618 .expect("SIZ marker");
3619 let old_component_count =
3620 u16::from_be_bytes([codestream[siz + 38], codestream[siz + 39]]) as usize;
3621 let component_start = siz + 40;
3622 let component_end = component_start + old_component_count * 3;
3623 let descriptor = codestream[component_start..component_start + 3].to_vec();
3624 let mut descriptors = Vec::with_capacity(usize::from(component_count) * 3);
3625 for _ in 0..component_count {
3626 descriptors.extend_from_slice(&descriptor);
3627 }
3628
3629 let siz_len = 38_u16
3630 .checked_add(
3631 component_count
3632 .checked_mul(3)
3633 .expect("SIZ component bytes fit"),
3634 )
3635 .expect("SIZ length fits");
3636 codestream[siz + 2..siz + 4].copy_from_slice(&siz_len.to_be_bytes());
3637 codestream[siz + 38..siz + 40].copy_from_slice(&component_count.to_be_bytes());
3638 codestream.splice(component_start..component_end, descriptors);
3639 }
3640
3641 #[test]
3642 fn inspect_rejects_component_count_above_j2k_spec_cap() {
3643 let mut bytes = fixture_gray();
3644 rewrite_siz_component_count(&mut bytes, MAX_J2K_SPEC_COMPONENTS + 1);
3645
3646 let err = inspect_j2k_codestream_header(&bytes)
3647 .expect_err("SIZ component count above spec cap must be rejected");
3648
3649 assert_eq!(
3650 err,
3651 J2kCodestreamHeaderError::InvalidSiz {
3652 what: "component count exceeds JPEG 2000 limit"
3653 }
3654 );
3655 }
3656
3657 #[test]
3658 fn native_decode_rejects_component_count_above_u8_before_bitmap_truncation() {
3659 let mut bytes = fixture_gray();
3660 rewrite_siz_component_count(&mut bytes, MAX_NATIVE_DECODE_COMPONENTS + 1);
3661
3662 let err = match Image::new(&bytes, &DecodeSettings::default()) {
3663 Err(err) => err,
3664 Ok(_) => {
3665 panic!("native decode must reject component counts that cannot fit RawBitmap")
3666 }
3667 };
3668
3669 assert_eq!(
3670 err,
3671 DecodeError::Validation(ValidationError::TooManyChannels)
3672 );
3673 }
3674
3675 #[test]
3676 fn tile_parse_rejects_component_tile_structural_bomb_before_allocation() {
3677 let mut bytes = fixture_gray();
3678 rewrite_siz_component_count(&mut bytes, MAX_NATIVE_DECODE_COMPONENTS);
3679 rewrite_siz_tile_grid(&mut bytes, (256, 256), (1, 1));
3680 let parsed = j2c::parse_raw(&bytes, &DecodeSettings::default()).expect("raw header parses");
3681 let mut context = j2c::DecoderContext::default();
3682 let mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder> = None;
3683
3684 let err = j2c::decode(parsed.data, &parsed.header, &mut context, &mut ht_decoder)
3685 .expect_err("tile structural budget must reject before tile allocation");
3686
3687 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3688 }
3689
3690 #[test]
3691 fn owned_decode_rejects_large_siz_before_allocating_output() {
3692 let mut bytes = fixture_gray();
3693 rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
3694 let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
3695
3696 let err = match image.decode() {
3697 Err(err) => err,
3698 Ok(_) => panic!("large owned decode must be capped"),
3699 };
3700
3701 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3702 }
3703
3704 #[test]
3705 fn decode_into_rejects_large_siz_before_allocating_component_storage() {
3706 let mut bytes = fixture_gray();
3707 rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
3708 let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
3709 let mut context = DecoderContext::default();
3710 let mut out = [];
3711
3712 let err = match image.decode_into(&mut out, &mut context) {
3713 Err(err) => err,
3714 Ok(_) => panic!("component storage must be capped before allocation"),
3715 };
3716
3717 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3718 }
3719
3720 fn fixture_ht_gray() -> Vec<u8> {
3721 let pixels: Vec<u8> = (0..16).collect();
3722 let options = EncodeOptions {
3723 reversible: true,
3724 num_decomposition_levels: 1,
3725 ..EncodeOptions::default()
3726 };
3727 encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht gray8")
3728 }
3729
3730 fn fixture_ht_multi_block() -> Vec<u8> {
3731 let pixels: Vec<u8> = (0..64).collect();
3732 let options = EncodeOptions {
3733 reversible: true,
3734 num_decomposition_levels: 0,
3735 code_block_width_exp: 0,
3736 code_block_height_exp: 0,
3737 ..EncodeOptions::default()
3738 };
3739 encode_htj2k(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block HT gray8")
3740 }
3741
3742 fn fixture_ht_rgb_multi_block() -> Vec<u8> {
3743 let pixels = gradient_pixels(8, 8, 3);
3744 let options = EncodeOptions {
3745 reversible: true,
3746 num_decomposition_levels: 0,
3747 code_block_width_exp: 0,
3748 code_block_height_exp: 0,
3749 ..EncodeOptions::default()
3750 };
3751 encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode multi-block HT RGB8")
3752 }
3753
3754 fn direct_ht_job_count(plan: &J2kDirectGrayscalePlan) -> usize {
3755 plan.steps
3756 .iter()
3757 .map(|step| match step {
3758 J2kDirectGrayscaleStep::HtSubBand(sub_band) => sub_band.jobs.len(),
3759 _ => 0,
3760 })
3761 .sum()
3762 }
3763
3764 fn direct_color_ht_job_count(plan: &J2kDirectColorPlan) -> usize {
3765 plan.component_plans.iter().map(direct_ht_job_count).sum()
3766 }
3767
3768 fn fixture_openhtj2k_ht_refinement() -> &'static [u8] {
3769 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.j2k")
3770 }
3771
3772 fn fixture_openhtj2k_ht_refinement_pixels() -> &'static [u8] {
3773 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.gray")
3774 }
3775
3776 fn fixture_openhtj2k_ht_refinement_odd() -> &'static [u8] {
3777 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.j2k")
3778 }
3779
3780 fn fixture_openhtj2k_ht_refinement_odd_pixels() -> &'static [u8] {
3781 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.gray")
3782 }
3783
3784 fn gradient_pixels(width: u32, height: u32, components: u8) -> Vec<u8> {
3785 let mut pixels = Vec::with_capacity(width as usize * height as usize * components as usize);
3786 for y in 0..height {
3787 for x in 0..width {
3788 for component in 0..components {
3789 pixels.push(((x * 3 + y * 5 + u32::from(component) * 41) & 0xff) as u8);
3790 }
3791 }
3792 }
3793 pixels
3794 }
3795
3796 fn roi_fixture(classic: bool, components: u8) -> Vec<u8> {
3797 let width = 64;
3798 let height = 64;
3799 let pixels = gradient_pixels(width, height, components);
3800 let options = EncodeOptions {
3801 reversible: true,
3802 num_decomposition_levels: 2,
3803 code_block_width_exp: 0,
3804 code_block_height_exp: 0,
3805 ..EncodeOptions::default()
3806 };
3807 if classic {
3808 encode(&pixels, width, height, components, 8, false, &options)
3809 .expect("encode ROI classic fixture")
3810 } else {
3811 encode_htj2k(&pixels, width, height, components, 8, false, &options)
3812 .expect("encode ROI HT fixture")
3813 }
3814 }
3815
3816 fn crop_interleaved(
3817 full: &[u8],
3818 full_width: u32,
3819 channels: usize,
3820 roi: (u32, u32, u32, u32),
3821 ) -> Vec<u8> {
3822 let (x, y, width, height) = roi;
3823 let mut out = Vec::with_capacity(width as usize * height as usize * channels);
3824 let row_bytes = full_width as usize * channels;
3825 let roi_row_bytes = width as usize * channels;
3826 for row in y as usize..(y + height) as usize {
3827 let start = row * row_bytes + x as usize * channels;
3828 out.extend_from_slice(&full[start..start + roi_row_bytes]);
3829 }
3830 out
3831 }
3832
3833 fn count_decode_work(bytes: &[u8], roi: Option<(u32, u32, u32, u32)>) -> DecodeWorkCounter {
3834 let image = Image::new(bytes, &DecodeSettings::default()).expect("image");
3835 let mut context = DecoderContext::default();
3836 match roi {
3837 Some(roi) => {
3838 image
3839 .decode_region_with_context(roi, &mut context)
3840 .expect("region decode with counter");
3841 }
3842 None => {
3843 image
3844 .decode_with_context(&mut context)
3845 .expect("full decode with counter");
3846 }
3847 }
3848 let counters = context.tile_decode_context.debug_counters;
3849 DecodeWorkCounter {
3850 classic_code_blocks: counters.decoded_code_blocks,
3851 ht_code_blocks: 0,
3852 idwt_output_samples: counters.idwt_output_samples,
3853 }
3854 }
3855
3856 #[test]
3857 fn roi_decode_matches_full_crop_for_classic_and_htj2k_gray_and_rgb() {
3858 let cases = [
3859 (true, 1_u8, true, false),
3860 (true, 3_u8, false, false),
3861 (false, 1_u8, true, false),
3862 (false, 3_u8, false, false),
3863 ];
3864 let rois = [
3865 (20, 18, 17, 19),
3866 (0, 0, 9, 11),
3867 (63, 63, 1, 1),
3868 (7, 5, 13, 9),
3869 (0, 0, 64, 64),
3870 ];
3871
3872 for (classic, components, expect_gray, has_alpha) in cases {
3873 let bytes = roi_fixture(classic, components);
3874 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3875 let full = image.decode().expect("full decode");
3876 let channels = components as usize;
3877 for roi in rois {
3878 let region = image.decode_region(roi).expect("region decode");
3879 assert_eq!(matches!(region.color_space, ColorSpace::Gray), expect_gray);
3880 assert_eq!(region.has_alpha, has_alpha);
3881 assert_eq!(
3882 region.data,
3883 crop_interleaved(&full, 64, channels, roi),
3884 "classic={classic} components={components} roi={roi:?}"
3885 );
3886 }
3887 }
3888 }
3889
3890 #[test]
3891 fn roi_decode_prunes_code_blocks_and_idwt_work_for_classic_and_htj2k() {
3892 let roi = (48, 48, 16, 16);
3893 for classic in [true, false] {
3894 let bytes = {
3895 let pixels = gradient_pixels(128, 128, 1);
3896 let options = EncodeOptions {
3897 reversible: true,
3898 num_decomposition_levels: 3,
3899 code_block_width_exp: 0,
3900 code_block_height_exp: 0,
3901 ..EncodeOptions::default()
3902 };
3903 if classic {
3904 encode(&pixels, 128, 128, 1, 8, false, &options)
3905 .expect("encode classic work fixture")
3906 } else {
3907 encode_htj2k(&pixels, 128, 128, 1, 8, false, &options)
3908 .expect("encode ht work fixture")
3909 }
3910 };
3911 let full = count_decode_work(&bytes, None);
3912 let region = count_decode_work(&bytes, Some(roi));
3913
3914 assert!(
3915 region.code_blocks() > 0 && region.code_blocks() < full.code_blocks(),
3916 "ROI should decode fewer code-blocks for classic={classic}; full={}, region={}",
3917 full.code_blocks(),
3918 region.code_blocks()
3919 );
3920 assert!(
3921 region.idwt_output_samples > 0
3922 && region.idwt_output_samples < full.idwt_output_samples,
3923 "ROI should produce fewer IDWT output samples for classic={classic}; full={}, region={}",
3924 full.idwt_output_samples,
3925 region.idwt_output_samples
3926 );
3927 }
3928 }
3929
3930 #[test]
3931 fn region_decode_reuses_region_sized_component_storage() {
3932 let bytes = fixture();
3933 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3934 let mut context = DecoderContext::default();
3935
3936 let bitmap = image
3937 .decode_region_with_context((1, 0, 1, 2), &mut context)
3938 .expect("region decode");
3939
3940 assert_eq!((bitmap.width, bitmap.height), (1, 2));
3941 assert!(context
3942 .tile_decode_context
3943 .channel_data
3944 .iter()
3945 .all(|component| component.container.truncated().len() == 2));
3946 }
3947
3948 #[test]
3949 fn native_region_decode_reuses_region_sized_component_storage() {
3950 let bytes = fixture();
3951 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3952 let mut context = DecoderContext::default();
3953
3954 let bitmap = image
3955 .decode_native_region_with_context((1, 0, 1, 2), &mut context)
3956 .expect("native region decode");
3957
3958 assert_eq!((bitmap.width, bitmap.height), (1, 2));
3959 assert!(context
3960 .tile_decode_context
3961 .channel_data
3962 .iter()
3963 .all(|component| component.container.truncated().len() == 2));
3964 }
3965
3966 #[test]
3967 fn decoder_context_defaults_to_auto_cpu_parallelism() {
3968 let context = DecoderContext::default();
3969
3970 assert_eq!(context.cpu_decode_parallelism(), CpuDecodeParallelism::Auto);
3971 }
3972
3973 #[test]
3974 fn classic_j2k_auto_and_serial_cpu_parallelism_match_pixels() {
3975 let bytes = fixture_multi_block();
3976 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3977 let mut auto_context = DecoderContext::default();
3978 let mut serial_context = DecoderContext::default();
3979 serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
3980
3981 let auto = image
3982 .decode_with_context(&mut auto_context)
3983 .expect("auto decode");
3984 let serial = image
3985 .decode_with_context(&mut serial_context)
3986 .expect("serial decode");
3987
3988 assert_eq!(auto.data, serial.data);
3989 }
3990
3991 #[test]
3992 fn htj2k_97_auto_and_serial_cpu_parallelism_match_pixels() {
3993 let width = 128_u32;
3994 let height = 128_u32;
3995 let pixels = (0..width * height)
3996 .map(|idx| ((idx * 17 + idx / width * 31) & 0xff) as u8)
3997 .collect::<Vec<_>>();
3998 let bytes = encode_htj2k(
3999 &pixels,
4000 width,
4001 height,
4002 1,
4003 8,
4004 false,
4005 &EncodeOptions {
4006 reversible: false,
4007 guard_bits: 2,
4008 num_decomposition_levels: 5,
4009 ..EncodeOptions::default()
4010 },
4011 )
4012 .expect("encode HTJ2K 9/7");
4013 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4014 let mut auto_context = DecoderContext::default();
4015 let mut serial_context = DecoderContext::default();
4016 serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
4017
4018 let auto = image
4019 .decode_with_context(&mut auto_context)
4020 .expect("auto decode");
4021 let serial = image
4022 .decode_with_context(&mut serial_context)
4023 .expect("serial decode");
4024
4025 assert_eq!(auto.data, serial.data);
4026 }
4027
4028 #[test]
4029 fn serial_cpu_parallelism_disables_classic_sub_band_parallel_branch() {
4030 assert!(!j2c::should_decode_classic_sub_band_in_parallel(
4031 CpuDecodeParallelism::Serial,
4032 16
4033 ));
4034 }
4035
4036 #[test]
4037 fn grayscale_direct_plan_is_built_without_materializing_channel_data() {
4038 let bytes = fixture_gray();
4039 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4040 let mut context = DecoderContext::default();
4041
4042 let plan = image
4043 .build_direct_grayscale_plan_with_context(&mut context)
4044 .expect("build direct plan");
4045
4046 assert_eq!(plan.dimensions, (4, 4));
4047 assert_eq!(plan.bit_depth, 8);
4048 assert!(
4049 !plan.steps.is_empty(),
4050 "direct plan must contain executable steps"
4051 );
4052 assert!(
4053 plan.steps.iter().any(|step| matches!(
4054 step,
4055 J2kDirectGrayscaleStep::ClassicSubBand(plan) if !plan.jobs.is_empty()
4056 )),
4057 "classic J2K direct plan must contain at least one non-empty classic sub-band job"
4058 );
4059 assert!(
4060 context.tile_decode_context.channel_data.is_empty(),
4061 "building a direct plan must not materialize host component planes"
4062 );
4063 }
4064
4065 #[test]
4066 fn grayscale_direct_plan_honors_target_resolution() {
4067 let bytes = fixture_ht_gray();
4068 let image = Image::new(
4069 &bytes,
4070 &DecodeSettings {
4071 target_resolution: Some((2, 2)),
4072 ..DecodeSettings::default()
4073 },
4074 )
4075 .expect("scaled image");
4076 let mut context = DecoderContext::default();
4077
4078 let plan = image
4079 .build_direct_grayscale_plan_with_context(&mut context)
4080 .expect("build scaled direct plan");
4081
4082 assert_eq!(plan.dimensions, (2, 2));
4083 assert!(plan.steps.iter().any(|step| matches!(
4084 step,
4085 J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4086 )));
4087 assert!(plan.steps.iter().any(|step| matches!(
4088 step,
4089 J2kDirectGrayscaleStep::Store(store)
4090 if store.output_width == 2 && store.output_height == 2
4091 )));
4092 assert!(
4093 context.tile_decode_context.channel_data.is_empty(),
4094 "building a scaled direct plan must not materialize host component planes"
4095 );
4096 }
4097
4098 #[test]
4099 fn grayscale_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4100 let bytes = fixture_ht_multi_block();
4101 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4102 let mut full_context = DecoderContext::default();
4103 let mut roi_context = DecoderContext::default();
4104
4105 let full = image
4106 .build_direct_grayscale_plan_with_context(&mut full_context)
4107 .expect("build full direct plan");
4108 let roi = image
4109 .build_direct_grayscale_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4110 .expect("build ROI direct plan");
4111
4112 let full_jobs = direct_ht_job_count(&full);
4113 let roi_jobs = direct_ht_job_count(&roi);
4114 assert!(full_jobs > 1, "fixture must expose multiple HT jobs");
4115 assert!(
4116 roi_jobs < full_jobs,
4117 "ROI direct plan must prune HT jobs before device preparation"
4118 );
4119 }
4120
4121 #[test]
4122 fn color_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4123 let bytes = fixture_ht_rgb_multi_block();
4124 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4125 let mut full_context = DecoderContext::default();
4126 let mut roi_context = DecoderContext::default();
4127
4128 let full = image
4129 .build_direct_color_plan_with_context(&mut full_context)
4130 .expect("build full RGB direct plan");
4131 let roi = image
4132 .build_direct_color_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4133 .expect("build ROI RGB direct plan");
4134
4135 let full_jobs = direct_color_ht_job_count(&full);
4136 let roi_jobs = direct_color_ht_job_count(&roi);
4137 assert!(full_jobs > 3, "fixture must expose multiple RGB HT jobs");
4138 assert!(
4139 roi_jobs < full_jobs,
4140 "RGB ROI direct plan must prune HT jobs before device preparation"
4141 );
4142 }
4143
4144 #[test]
4145 fn color_direct_plan_honors_target_resolution() {
4146 for (name, bytes) in [
4147 ("classic", {
4148 let pixels = gradient_pixels(8, 8, 3);
4149 let options = EncodeOptions {
4150 reversible: true,
4151 num_decomposition_levels: 2,
4152 ..EncodeOptions::default()
4153 };
4154 encode(&pixels, 8, 8, 3, 8, false, &options).expect("encode classic rgb8")
4155 }),
4156 ("htj2k", {
4157 let pixels = gradient_pixels(8, 8, 3);
4158 let options = EncodeOptions {
4159 reversible: true,
4160 num_decomposition_levels: 2,
4161 ..EncodeOptions::default()
4162 };
4163 encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode ht rgb8")
4164 }),
4165 ] {
4166 let image = Image::new(
4167 &bytes,
4168 &DecodeSettings {
4169 target_resolution: Some((4, 4)),
4170 ..DecodeSettings::default()
4171 },
4172 )
4173 .expect("scaled RGB image");
4174 let mut context = DecoderContext::default();
4175
4176 let plan = image
4177 .build_direct_color_plan_with_context(&mut context)
4178 .expect("build scaled direct color plan");
4179
4180 assert_eq!(plan.dimensions, (4, 4), "{name}: output dimensions");
4181 assert_eq!(plan.component_plans.len(), 3, "{name}: component count");
4182 for component_plan in &plan.component_plans {
4183 assert_eq!(component_plan.dimensions, (4, 4), "{name}: component dims");
4184 assert!(component_plan.steps.iter().any(|step| matches!(
4185 step,
4186 J2kDirectGrayscaleStep::Store(store)
4187 if store.output_width == 4 && store.output_height == 4
4188 )));
4189 }
4190 assert!(
4191 context.tile_decode_context.channel_data.is_empty(),
4192 "{name}: building a scaled color direct plan must not materialize host component planes"
4193 );
4194 }
4195 }
4196
4197 #[test]
4198 fn direct_color_cpu_rgb8_executor_matches_scaled_region_decode() {
4199 for (name, bytes) in [
4200 ("classic", {
4201 let pixels = gradient_pixels(16, 16, 3);
4202 let options = EncodeOptions {
4203 reversible: true,
4204 num_decomposition_levels: 2,
4205 ..EncodeOptions::default()
4206 };
4207 encode(&pixels, 16, 16, 3, 8, false, &options).expect("encode classic rgb8")
4208 }),
4209 ("htj2k", {
4210 let pixels = gradient_pixels(16, 16, 3);
4211 let options = EncodeOptions {
4212 reversible: true,
4213 num_decomposition_levels: 2,
4214 ..EncodeOptions::default()
4215 };
4216 encode_htj2k(&pixels, 16, 16, 3, 8, false, &options).expect("encode ht rgb8")
4217 }),
4218 ] {
4219 let image = Image::new(
4220 &bytes,
4221 &DecodeSettings {
4222 target_resolution: Some((4, 4)),
4223 ..DecodeSettings::default()
4224 },
4225 )
4226 .expect("scaled RGB image");
4227 let mut expected_context = DecoderContext::default();
4228 let expected_full = image
4229 .decode_with_context(&mut expected_context)
4230 .expect("decode scaled reference");
4231 let output_region = J2kRect {
4232 x0: 1,
4233 y0: 1,
4234 x1: 3,
4235 y1: 3,
4236 };
4237 let mut direct_context = DecoderContext::default();
4238 let plan = image
4239 .build_direct_color_plan_region_with_context(
4240 &mut direct_context,
4241 (
4242 output_region.x0,
4243 output_region.y0,
4244 output_region.width(),
4245 output_region.height(),
4246 ),
4247 )
4248 .expect("build direct RGB region plan");
4249
4250 let stride = output_region.width() as usize * 3;
4251 let mut direct = vec![0_u8; stride * output_region.height() as usize];
4252 let mut scratch = J2kDirectCpuScratch::new();
4253 execute_direct_color_plan_rgb8_into(
4254 &plan,
4255 output_region,
4256 &mut scratch,
4257 &mut direct,
4258 stride,
4259 )
4260 .expect("execute direct RGB plan");
4261
4262 let mut expected = Vec::with_capacity(direct.len());
4263 let full_stride = image.width() as usize * 3;
4264 for y in output_region.y0..output_region.y1 {
4265 let start = y as usize * full_stride + output_region.x0 as usize * 3;
4266 expected.extend_from_slice(&expected_full.data[start..start + stride]);
4267 }
4268
4269 assert_eq!(direct, expected, "{name}: direct RGB output");
4270
4271 let rgba_stride = output_region.width() as usize * 4;
4272 let mut direct_rgba = vec![0_u8; rgba_stride * output_region.height() as usize];
4273 execute_direct_color_plan_rgba8_into(
4274 &plan,
4275 output_region,
4276 &mut scratch,
4277 &mut direct_rgba,
4278 rgba_stride,
4279 )
4280 .expect("execute direct RGBA plan");
4281
4282 let mut expected_rgba = Vec::with_capacity(direct_rgba.len());
4283 for rgb in expected.chunks_exact(3) {
4284 expected_rgba.extend_from_slice(rgb);
4285 expected_rgba.push(255);
4286 }
4287 assert_eq!(direct_rgba, expected_rgba, "{name}: direct RGBA output");
4288 }
4289 }
4290
4291 #[test]
4292 fn htj2k_grayscale_direct_plan_contains_ht_sub_band_steps() {
4293 let bytes = fixture_ht_gray();
4294 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4295 let mut context = DecoderContext::default();
4296
4297 let plan = image
4298 .build_direct_grayscale_plan_with_context(&mut context)
4299 .expect("build direct plan");
4300
4301 assert!(
4302 plan.steps.iter().any(|step| matches!(
4303 step,
4304 J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4305 )),
4306 "HTJ2K direct plan must contain at least one non-empty HT sub-band decode step"
4307 );
4308 }
4309
4310 #[test]
4311 fn ht_decoder_hook_is_used_for_htj2k_codeblocks() {
4312 let pixels: Vec<u8> = (0..16).collect();
4313 let options = EncodeOptions {
4314 reversible: true,
4315 num_decomposition_levels: 1,
4316 ..EncodeOptions::default()
4317 };
4318 let bytes = encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht");
4319 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4320 let mut hooked_context = DecoderContext::default();
4321 let mut hook = FailingHtDecoder { called: false };
4322 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4323 Ok(_) => panic!("hooked decode must use external HT decoder"),
4324 Err(error) => error,
4325 };
4326
4327 assert!(hook.called, "HT decoder hook must be invoked");
4328 assert_eq!(
4329 error,
4330 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4331 );
4332 }
4333
4334 #[test]
4335 fn openhtj2k_conformance_fixture_exercises_refinement_passes() {
4336 for fixture in [
4337 (
4338 "ds0_ht_12_b11",
4339 fixture_openhtj2k_ht_refinement(),
4340 fixture_openhtj2k_ht_refinement_pixels(),
4341 (3, 5),
4342 8,
4343 2,
4344 4,
4345 ),
4346 (
4347 "ds0_ht_09_b11",
4348 fixture_openhtj2k_ht_refinement_odd(),
4349 fixture_openhtj2k_ht_refinement_odd_pixels(),
4350 (17, 37),
4351 14,
4352 14,
4353 629,
4354 ),
4355 ] {
4356 let (
4357 name,
4358 codestream,
4359 expected_pixels,
4360 dimensions,
4361 blocks,
4362 refinement_jobs,
4363 zero_diffs,
4364 ) = fixture;
4365 let image = Image::new(codestream, &DecodeSettings::default()).expect("image");
4366 let mut context = DecoderContext::default();
4367 let mut hook = CapturingHtDecoder::default();
4368
4369 let components = image
4370 .decode_components_with_ht_decoder(&mut context, &mut hook)
4371 .expect("decode OpenHTJ2K HTJ2K fixture");
4372
4373 assert!(
4374 hook.called,
4375 "{name}: HTJ2K fixture must use HT code-block decode"
4376 );
4377 assert!(
4378 hook.refinement_jobs > 0,
4379 "{name}: OpenHTJ2K fixture must contain non-empty refinement segments"
4380 );
4381 assert!(
4382 hook.max_coding_passes > 1,
4383 "{name}: OpenHTJ2K fixture must exercise more than the cleanup pass"
4384 );
4385 assert_eq!(hook.blocks, blocks, "{name}: HT code-block count");
4386 assert_eq!(
4387 hook.refinement_jobs, refinement_jobs,
4388 "{name}: refinement job count"
4389 );
4390 assert_eq!(hook.max_coding_passes, 3, "{name}: max HT coding passes");
4391 assert_eq!(components.dimensions(), dimensions, "{name}: dimensions");
4392 assert_eq!(components.planes().len(), 1, "{name}: component planes");
4393
4394 let decoded: Vec<u8> = components.planes()[0]
4395 .samples()
4396 .iter()
4397 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4398 .collect();
4399 assert_eq!(decoded, expected_pixels, "{name}: decoded pixels");
4400
4401 let mut zero_context = DecoderContext::default();
4402 let mut zero_hook = ZeroRefinementHtDecoder;
4403 let zeroed_components = image
4404 .decode_components_with_ht_decoder(&mut zero_context, &mut zero_hook)
4405 .expect("decode OpenHTJ2K fixture with zeroed refinement bytes");
4406 let actual_zero_diffs = components.planes()[0]
4407 .samples()
4408 .iter()
4409 .zip(zeroed_components.planes()[0].samples())
4410 .filter(|(actual, zeroed)| (*actual - *zeroed).abs() > f32::EPSILON)
4411 .count();
4412 assert_eq!(
4413 actual_zero_diffs, zero_diffs,
4414 "{name}: zeroing refinement bytes must change decoded samples"
4415 );
4416 }
4417 }
4418
4419 #[test]
4420 fn openhtj2k_refinement_phase_limited_decode_differs_and_records_ht_stats() {
4421 let image = Image::new(
4422 fixture_openhtj2k_ht_refinement_odd(),
4423 &DecodeSettings::default(),
4424 )
4425 .expect("image");
4426 let mut full_context = DecoderContext::default();
4427
4428 let (full_samples, full_decoded) = {
4429 let full_components = image
4430 .decode_components_with_context(&mut full_context)
4431 .expect("full native decode of OpenHTJ2K refinement fixture");
4432 let full_samples = full_components.planes()[0].samples().to_vec();
4433 let full_decoded: Vec<u8> = full_samples
4434 .iter()
4435 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4436 .collect();
4437 (full_samples, full_decoded)
4438 };
4439 assert_eq!(
4440 full_decoded,
4441 fixture_openhtj2k_ht_refinement_odd_pixels(),
4442 "full decode must match the checked-in OpenHTJ2K oracle"
4443 );
4444
4445 let stats = full_context
4446 .tile_decode_context
4447 .debug_counters
4448 .ht_phase_stats;
4449 assert_eq!(stats.blocks, 14, "HT block count");
4450 assert_eq!(stats.refinement_blocks, 14, "HT refinement block count");
4451 assert!(stats.cleanup_bytes > 0, "cleanup byte total");
4452 assert!(stats.refinement_bytes > 0, "refinement byte total");
4453
4454 let mut cleanup_context = DecoderContext::default();
4455 let mut cleanup_hook = CleanupLimitedHtDecoder::default();
4456 let cleanup_components = image
4457 .decode_components_with_ht_decoder(&mut cleanup_context, &mut cleanup_hook)
4458 .expect("cleanup-limited decode of OpenHTJ2K refinement fixture");
4459 let cleanup_decoded: Vec<u8> = cleanup_components.planes()[0]
4460 .samples()
4461 .iter()
4462 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4463 .collect();
4464 let cleanup_sample_diffs = full_samples
4465 .iter()
4466 .zip(cleanup_components.planes()[0].samples())
4467 .filter(|(full, cleanup)| (*full - *cleanup).abs() > f32::EPSILON)
4468 .count();
4469
4470 assert!(
4471 cleanup_sample_diffs > 0,
4472 "cleanup-limited decode must omit refinement effects"
4473 );
4474 assert_eq!(
4475 cleanup_decoded, full_decoded,
4476 "fixture refinement differences are below final u8 clamping"
4477 );
4478 assert_eq!(cleanup_hook.blocks, 14, "hook HT block count");
4479 assert_eq!(
4480 cleanup_hook.refinement_blocks, 14,
4481 "hook HT refinement block count"
4482 );
4483 assert!(cleanup_hook.cleanup_bytes > 0, "hook cleanup byte total");
4484 assert!(
4485 cleanup_hook.refinement_bytes > 0,
4486 "hook refinement byte total"
4487 );
4488 }
4489
4490 #[test]
4491 fn scalar_htj2k_encoder_contract_is_cleanup_only() {
4492 let coefficients = (0..64)
4493 .map(|index| {
4494 let magnitude = (index % 7) + 1;
4495 if index % 2 == 0 {
4496 magnitude
4497 } else {
4498 -magnitude
4499 }
4500 })
4501 .collect::<Vec<_>>();
4502
4503 let encoded =
4504 encode_ht_code_block_scalar(&coefficients, 8, 8, 8).expect("encode HT code block");
4505
4506 assert_eq!(
4507 encoded.num_coding_passes, 1,
4508 "current scalar HTJ2K encoder emits only the cleanup pass"
4509 );
4510 assert_eq!(
4511 encoded.num_zero_bitplanes, 7,
4512 "current cleanup-only HTJ2K encoder includes one bitplane"
4513 );
4514 assert!(
4515 !encoded.data.is_empty(),
4516 "non-zero cleanup-only block must still produce payload bytes"
4517 );
4518 }
4519
4520 #[test]
4521 fn scalar_htj2k_decode_workspace_matches_fresh_decode_and_reuses_capacity() {
4522 let image = Image::new(
4523 fixture_openhtj2k_ht_refinement_odd(),
4524 &DecodeSettings::default(),
4525 )
4526 .expect("image");
4527 let mut context = DecoderContext::default();
4528 let mut hook = FirstHtJobDecoder::default();
4529 image
4530 .decode_components_with_ht_decoder(&mut context, &mut hook)
4531 .expect("decode fixture while collecting HT jobs");
4532 let job = hook
4533 .job
4534 .as_ref()
4535 .expect("fixture must expose an HT decode job")
4536 .borrowed();
4537 let mut fresh = vec![0.0_f32; job.width as usize * job.height as usize];
4538 let mut reused = vec![0.0_f32; fresh.len()];
4539 let mut profiled = vec![0.0_f32; fresh.len()];
4540 let mut workspace = HtCodeBlockDecodeWorkspace::default();
4541 let mut profile = HtCodeBlockDecodeProfile::default();
4542
4543 decode_ht_code_block_scalar(job, &mut fresh).expect("fresh HT decode");
4544 decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
4545 .expect("workspace HT decode");
4546 let first_capacity = workspace.coefficient_capacity();
4547 decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
4548 .expect("second workspace HT decode");
4549 decode_ht_code_block_scalar_with_workspace_profiled(
4550 job,
4551 &mut profiled,
4552 &mut workspace,
4553 &mut profile,
4554 )
4555 .expect("profiled workspace HT decode");
4556
4557 assert_eq!(reused, fresh);
4558 assert_eq!(profiled, fresh);
4559 assert!(first_capacity >= fresh.len());
4560 assert_eq!(workspace.coefficient_capacity(), first_capacity);
4561 assert_eq!(profile.blocks, 1);
4562 assert!(profile.cleanup_bytes > 0);
4563 }
4564
4565 #[test]
4566 fn classic_decoder_hook_is_used_for_j2k_codeblocks() {
4567 let bytes = fixture();
4568 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4569 let mut hooked_context = DecoderContext::default();
4570 let mut hook = FailingClassicDecoder { called: false };
4571 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4572 Ok(_) => panic!("hooked decode must use external classic decoder"),
4573 Err(error) => error,
4574 };
4575
4576 assert!(hook.called, "classic decoder hook must be invoked");
4577 assert_eq!(
4578 error,
4579 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4580 );
4581 }
4582
4583 #[test]
4584 fn classic_sub_band_decoder_hook_is_used_for_j2k_codeblocks() {
4585 let bytes = fixture_multi_block();
4586 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4587 let mut hooked_context = DecoderContext::default();
4588 let mut hook = FailingClassicBatchDecoder { called: false };
4589 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4590 Ok(_) => panic!("hooked decode must use external classic batch decoder"),
4591 Err(error) => error,
4592 };
4593
4594 assert!(hook.called, "classic sub-band decoder hook must be invoked");
4595 assert_eq!(
4596 error,
4597 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4598 );
4599 }
4600
4601 #[test]
4606 fn forward_dwt53_reference_matches_internal_path() {
4607 let samples: Vec<f32> = (0..16).map(|i| i as f32).collect();
4609 let out = forward_dwt53_reference(&samples, 4, 4, 1);
4610
4611 let internal = j2c::fdwt::forward_dwt(&samples, 4, 4, 1, true);
4613
4614 assert_eq!(out.ll, internal.ll, "LL subband mismatch");
4615 assert_eq!(out.ll_width, internal.ll_width, "LL width mismatch");
4616 assert_eq!(out.ll_height, internal.ll_height, "LL height mismatch");
4617 assert_eq!(out.levels.len(), internal.levels.len(), "level count");
4618 for (pub_lvl, int_lvl) in out.levels.iter().zip(internal.levels.iter()) {
4619 assert_eq!(pub_lvl.hl, int_lvl.hl, "HL mismatch");
4620 assert_eq!(pub_lvl.lh, int_lvl.lh, "LH mismatch");
4621 assert_eq!(pub_lvl.hh, int_lvl.hh, "HH mismatch");
4622 }
4623 }
4624
4625 #[test]
4626 fn forward_rct_reference_matches_internal_path() {
4627 let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
4629 let result = forward_rct_reference(planes.clone());
4630
4631 let mut internal = planes;
4633 j2c::forward_mct::forward_rct(&mut internal);
4634
4635 assert_eq!(result, internal, "RCT output mismatch");
4636 assert_eq!(result[0][0], 150.0, "Y component");
4638 assert_eq!(result[1][0], 50.0, "Cb component");
4639 assert_eq!(result[2][0], -50.0, "Cr component");
4640 }
4641
4642 #[test]
4643 fn forward_ict_reference_matches_internal_path() {
4644 let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
4645 let result = forward_ict_reference(planes.clone());
4646
4647 let mut internal = planes;
4648 j2c::forward_mct::forward_ict(&mut internal);
4649
4650 assert_eq!(result, internal, "ICT output mismatch");
4651 }
4652
4653 #[test]
4654 fn forward_dwt97_reference_matches_internal_path() {
4655 let samples = (0..64)
4656 .map(|idx| {
4657 f32::from(u8::try_from((idx * 19 + idx / 3) & 0xff).expect("masked sample fits u8"))
4658 - 128.0
4659 })
4660 .collect::<Vec<_>>();
4661 let result = forward_dwt97_reference(&samples, 8, 8, 2);
4662 let internal = j2c::fdwt::forward_dwt(&samples, 8, 8, 2, false);
4663
4664 assert_eq!(result.ll, internal.ll, "DWT 9/7 LL mismatch");
4665 assert_eq!(result.ll_width, internal.ll_width);
4666 assert_eq!(result.ll_height, internal.ll_height);
4667 assert_eq!(result.levels.len(), internal.levels.len());
4668 for (actual, expected) in result.levels.iter().zip(internal.levels.iter()) {
4669 assert_eq!(actual.hl, expected.hl, "DWT 9/7 HL mismatch");
4670 assert_eq!(actual.lh, expected.lh, "DWT 9/7 LH mismatch");
4671 assert_eq!(actual.hh, expected.hh, "DWT 9/7 HH mismatch");
4672 }
4673 }
4674
4675 #[test]
4676 fn quantize_reversible_reference_matches_internal_path() {
4677 let coefficients = vec![3.7f32, -8.2, 0.5, -0.5, 10.0];
4678 let exponent = 8u16;
4679 let mantissa = 0u16;
4680 let range_bits = 8u8;
4681
4682 let result =
4683 quantize_reversible_reference(&coefficients, exponent, mantissa, range_bits, true);
4684
4685 let step = j2c::quantize::QuantStepSize { exponent, mantissa };
4687 let internal = j2c::quantize::quantize_subband(&coefficients, &step, range_bits, true);
4688
4689 assert_eq!(result, internal, "quantize output mismatch");
4690 assert_eq!(result[0], 4, "3.7 rounds to 4");
4692 assert_eq!(result[1], -8, "-8.2 rounds to -8");
4693 }
4694
4695 #[test]
4696 fn quantize_subband_reference_matches_irreversible_internal_path() {
4697 let coefficients = vec![3.7f32, -8.2, 0.5, -0.5, 10.0];
4698 let exponent = 8u16;
4699 let mantissa = 256u16;
4700 let range_bits = 8u8;
4701
4702 let result =
4703 quantize_subband_reference(&coefficients, exponent, mantissa, range_bits, false);
4704
4705 let step = j2c::quantize::QuantStepSize { exponent, mantissa };
4706 let internal = j2c::quantize::quantize_subband(&coefficients, &step, range_bits, false);
4707
4708 assert_eq!(result, internal, "irreversible quantize output mismatch");
4709 }
4710
4711 #[test]
4712 fn deinterleave_reference_matches_internal_path() {
4713 let pixels: Vec<u8> = vec![128, 64, 200, 10, 20, 30];
4715 let result = deinterleave_reference(&pixels, 2, 3, 8, false);
4716
4717 let internal = j2c::encode::deinterleave_to_f32(&pixels, 2, 3, 8, false);
4718
4719 assert_eq!(result, internal, "deinterleave output mismatch");
4720 assert_eq!(result.len(), 3, "three component planes");
4721 assert_eq!(result[0].len(), 2, "two pixels per plane");
4722 assert!((result[0][0] - 0.0f32).abs() < 1e-6, "R0 level-shifted");
4724 assert!((result[1][0] - (-64.0f32)).abs() < 1e-6, "G0 level-shifted");
4725 assert!((result[2][0] - 72.0f32).abs() < 1e-6, "B0 level-shifted");
4726 }
4727}