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 signinum_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_rct_reference(mut planes: Vec<Vec<f32>>) -> Vec<Vec<f32>> {
976 j2c::forward_mct::forward_rct(&mut planes);
977 planes
978}
979
980pub fn quantize_reversible_reference(
989 coefficients: &[f32],
990 step_exponent: u16,
991 step_mantissa: u16,
992 range_bits: u8,
993 reversible: bool,
994) -> Vec<i32> {
995 let step = j2c::quantize::QuantStepSize {
996 exponent: step_exponent,
997 mantissa: step_mantissa,
998 };
999 j2c::quantize::quantize_subband(coefficients, &step, range_bits, reversible)
1000}
1001
1002pub fn deinterleave_reference(
1008 pixels: &[u8],
1009 num_pixels: usize,
1010 num_components: u8,
1011 bit_depth: u8,
1012 signed: bool,
1013) -> Vec<Vec<f32>> {
1014 j2c::encode::deinterleave_to_f32(pixels, num_pixels, num_components, bit_depth, signed)
1015}
1016
1017pub fn encode_j2k_packetization_scalar(
1019 job: J2kPacketizationEncodeJob<'_>,
1020) -> core::result::Result<Vec<u8>, &'static str> {
1021 let mut resolutions = job
1022 .resolutions
1023 .iter()
1024 .map(|resolution| j2c::packet_encode::ResolutionPacket {
1025 subbands: resolution
1026 .subbands
1027 .iter()
1028 .map(|subband| j2c::packet_encode::SubbandPrecinct {
1029 code_blocks: subband
1030 .code_blocks
1031 .iter()
1032 .map(|code_block| j2c::packet_encode::CodeBlockPacketData {
1033 data: code_block.data.to_vec(),
1034 ht_cleanup_length: code_block.ht_cleanup_length,
1035 ht_refinement_length: code_block.ht_refinement_length,
1036 num_coding_passes: code_block.num_coding_passes,
1037 classic_segment_lengths: Vec::new(),
1038 num_zero_bitplanes: code_block.num_zero_bitplanes,
1039 previously_included: code_block.previously_included,
1040 l_block: code_block.l_block,
1041 block_coding_mode: match code_block.block_coding_mode {
1042 J2kPacketizationBlockCodingMode::Classic => {
1043 j2c::codestream_write::BlockCodingMode::Classic
1044 }
1045 J2kPacketizationBlockCodingMode::HighThroughput => {
1046 j2c::codestream_write::BlockCodingMode::HighThroughput
1047 }
1048 },
1049 })
1050 .collect(),
1051 num_cbs_x: subband.num_cbs_x,
1052 num_cbs_y: subband.num_cbs_y,
1053 })
1054 .collect(),
1055 })
1056 .collect::<Vec<_>>();
1057
1058 let descriptors = job
1059 .packet_descriptors
1060 .iter()
1061 .map(|descriptor| j2c::packet_encode::PacketDescriptor {
1062 packet_index: descriptor.packet_index,
1063 state_index: descriptor.state_index,
1064 layer: descriptor.layer,
1065 resolution: descriptor.resolution,
1066 component: descriptor.component,
1067 precinct: descriptor.precinct,
1068 })
1069 .collect::<Vec<_>>();
1070
1071 j2c::packet_encode::validate_ht_segment_lengths(&resolutions)?;
1072
1073 if descriptors.is_empty() {
1074 Ok(j2c::packet_encode::form_tile_bitstream_for_progression(
1075 &mut resolutions,
1076 job.num_layers,
1077 job.num_components,
1078 job.progression_order,
1079 ))
1080 } else {
1081 j2c::packet_encode::form_tile_bitstream_with_descriptors(&mut resolutions, &descriptors)
1082 }
1083}
1084
1085pub fn decode_j2k_code_block_scalar(
1087 job: J2kCodeBlockDecodeJob<'_>,
1088 output: &mut [f32],
1089) -> Result<()> {
1090 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1091 decode_j2k_code_block_scalar_with_workspace(job, output, &mut workspace)
1092}
1093
1094#[derive(Default)]
1096pub struct J2kCodeBlockDecodeWorkspace {
1097 bit_plane_decode_context: j2c::bitplane::BitPlaneDecodeContext,
1098}
1099
1100pub fn decode_j2k_code_block_scalar_with_workspace(
1102 job: J2kCodeBlockDecodeJob<'_>,
1103 output: &mut [f32],
1104 workspace: &mut J2kCodeBlockDecodeWorkspace,
1105) -> Result<()> {
1106 let required_len = if job.height == 0 {
1107 0
1108 } else {
1109 job.output_stride
1110 .checked_mul(job.height as usize - 1)
1111 .and_then(|prefix| prefix.checked_add(job.width as usize))
1112 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1113 };
1114 if output.len() < required_len {
1115 bail!(DecodingError::CodeBlockDecodeFailure);
1116 }
1117
1118 let style = internal_j2k_code_block_style(job.style);
1119 let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1120 let code_block_stride =
1121 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1122 let coded_bitplanes = add_roi_shift_to_bitplanes(
1123 job.total_bitplanes,
1124 job.roi_shift,
1125 MAX_CLASSIC_DECODE_BITPLANES,
1126 )?;
1127
1128 j2c::bitplane::decode_code_block_segments_validated(
1129 job.data,
1130 job.segments,
1131 job.width,
1132 job.height,
1133 job.missing_bit_planes,
1134 job.number_of_coding_passes,
1135 coded_bitplanes,
1136 sub_band_type,
1137 &style,
1138 job.strict,
1139 &mut workspace.bit_plane_decode_context,
1140 )?;
1141
1142 for (row_idx, coeff_row) in workspace
1143 .bit_plane_decode_context
1144 .coefficient_rows()
1145 .enumerate()
1146 .take(job.height as usize)
1147 {
1148 let row_start = row_idx * job.output_stride;
1149 let output_row = &mut output[row_start..row_start + code_block_stride];
1150 for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1151 let coefficient = apply_roi_maxshift_inverse_i32(coefficient.get(), job.roi_shift);
1152 *sample = coefficient as f32 * job.dequantization_step;
1153 }
1154 }
1155
1156 Ok(())
1157}
1158
1159#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1161pub struct J2kCodeBlockDecodeProfile {
1162 pub sigprop_us: u128,
1164 pub magref_us: u128,
1166 pub cleanup_us: u128,
1168 pub bypass_us: u128,
1170 pub output_convert_us: u128,
1172}
1173
1174impl J2kCodeBlockDecodeProfile {
1175 fn add_native_stats(&mut self, stats: j2c::bitplane::J2kBlockDecodeStats) {
1176 self.sigprop_us += stats.sigprop_us;
1177 self.magref_us += stats.magref_us;
1178 self.cleanup_us += stats.cleanup_us;
1179 self.bypass_us += stats.bypass_us;
1180 }
1181}
1182
1183pub fn decode_j2k_code_block_scalar_profiled(
1185 job: J2kCodeBlockDecodeJob<'_>,
1186 output: &mut [f32],
1187 profile: &mut J2kCodeBlockDecodeProfile,
1188) -> Result<()> {
1189 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
1190 decode_j2k_code_block_scalar_with_workspace_profiled(job, output, &mut workspace, profile)
1191}
1192
1193pub fn decode_j2k_code_block_scalar_with_workspace_profiled(
1195 job: J2kCodeBlockDecodeJob<'_>,
1196 output: &mut [f32],
1197 workspace: &mut J2kCodeBlockDecodeWorkspace,
1198 profile: &mut J2kCodeBlockDecodeProfile,
1199) -> Result<()> {
1200 let required_len = if job.height == 0 {
1201 0
1202 } else {
1203 job.output_stride
1204 .checked_mul(job.height as usize - 1)
1205 .and_then(|prefix| prefix.checked_add(job.width as usize))
1206 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1207 };
1208 if output.len() < required_len {
1209 bail!(DecodingError::CodeBlockDecodeFailure);
1210 }
1211
1212 let style = internal_j2k_code_block_style(job.style);
1213 let sub_band_type = internal_j2k_sub_band_type(job.sub_band_type);
1214 let code_block_stride =
1215 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1216 let coded_bitplanes = add_roi_shift_to_bitplanes(
1217 job.total_bitplanes,
1218 job.roi_shift,
1219 MAX_CLASSIC_DECODE_BITPLANES,
1220 )?;
1221 let mut stats = j2c::bitplane::J2kBlockDecodeStats::default();
1222
1223 j2c::bitplane::decode_code_block_segments_validated_profiled(
1224 job.data,
1225 job.segments,
1226 job.width,
1227 job.height,
1228 job.missing_bit_planes,
1229 job.number_of_coding_passes,
1230 coded_bitplanes,
1231 sub_band_type,
1232 &style,
1233 job.strict,
1234 &mut workspace.bit_plane_decode_context,
1235 &mut stats,
1236 true,
1237 )?;
1238 profile.add_native_stats(stats);
1239
1240 let output_convert_started = profile::profile_now(true);
1241 for (row_idx, coeff_row) in workspace
1242 .bit_plane_decode_context
1243 .coefficient_rows()
1244 .enumerate()
1245 .take(job.height as usize)
1246 {
1247 let row_start = row_idx * job.output_stride;
1248 let output_row = &mut output[row_start..row_start + code_block_stride];
1249 for (coefficient, sample) in coeff_row.iter().zip(output_row.iter_mut()) {
1250 let coefficient = apply_roi_maxshift_inverse_i32(coefficient.get(), job.roi_shift);
1251 *sample = coefficient as f32 * job.dequantization_step;
1252 }
1253 }
1254 profile.output_convert_us += profile::elapsed_us(output_convert_started);
1255
1256 Ok(())
1257}
1258
1259pub fn decode_j2k_sub_band_scalar(job: J2kSubBandDecodeJob<'_>, output: &mut [f32]) -> Result<()> {
1261 let required_len = if job.height == 0 {
1262 0
1263 } else {
1264 usize::try_from(job.width)
1265 .ok()
1266 .and_then(|width| width.checked_mul(job.height as usize))
1267 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1268 };
1269 if output.len() < required_len {
1270 bail!(DecodingError::CodeBlockDecodeFailure);
1271 }
1272
1273 let sub_band_width =
1274 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1275
1276 for batch_job in job.jobs {
1277 let code_block = batch_job.code_block;
1278 if code_block.output_stride != sub_band_width {
1279 bail!(DecodingError::CodeBlockDecodeFailure);
1280 }
1281 if batch_job
1282 .output_x
1283 .checked_add(code_block.width)
1284 .is_none_or(|x1| x1 > job.width)
1285 || batch_job
1286 .output_y
1287 .checked_add(code_block.height)
1288 .is_none_or(|y1| y1 > job.height)
1289 {
1290 bail!(DecodingError::CodeBlockDecodeFailure);
1291 }
1292
1293 let base_idx = usize::try_from(batch_job.output_y)
1294 .ok()
1295 .and_then(|y| y.checked_mul(sub_band_width))
1296 .and_then(|row| row.checked_add(batch_job.output_x as usize))
1297 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1298 let block_output_len = if code_block.height == 0 {
1299 0
1300 } else {
1301 code_block
1302 .output_stride
1303 .checked_mul(code_block.height as usize - 1)
1304 .and_then(|prefix| prefix.checked_add(code_block.width as usize))
1305 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1306 };
1307 let end_idx = base_idx
1308 .checked_add(block_output_len)
1309 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1310 if end_idx > output.len() {
1311 bail!(DecodingError::CodeBlockDecodeFailure);
1312 }
1313
1314 decode_j2k_code_block_scalar(code_block, &mut output[base_idx..end_idx])?;
1315 }
1316
1317 Ok(())
1318}
1319
1320pub fn decode_ht_code_block_scalar(
1322 job: HtCodeBlockDecodeJob<'_>,
1323 output: &mut [f32],
1324) -> Result<()> {
1325 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1326 job, output,
1327 )
1328}
1329
1330pub fn decode_ht_code_block_scalar_until_phase(
1332 job: HtCodeBlockDecodeJob<'_>,
1333 output: &mut [f32],
1334 phase_limit: HtCodeBlockDecodePhaseLimit,
1335) -> Result<()> {
1336 match phase_limit {
1337 HtCodeBlockDecodePhaseLimit::Cleanup => decode_ht_code_block_scalar_for_phase::<
1338 { j2c::ht_block_decode::PHASE_LIMIT_CLEANUP },
1339 >(job, output),
1340 HtCodeBlockDecodePhaseLimit::SignificancePropagation => {
1341 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_SIGPROP }>(
1342 job, output,
1343 )
1344 }
1345 HtCodeBlockDecodePhaseLimit::MagnitudeRefinement => {
1346 decode_ht_code_block_scalar_for_phase::<{ j2c::ht_block_decode::PHASE_LIMIT_MAGREF }>(
1347 job, output,
1348 )
1349 }
1350 }
1351}
1352
1353#[derive(Default)]
1355pub struct HtCodeBlockDecodeWorkspace {
1356 coefficients: Vec<u32>,
1357 scratch: j2c::ht_block_decode::HtBlockDecodeScratch,
1358}
1359
1360impl HtCodeBlockDecodeWorkspace {
1361 pub fn coefficient_capacity(&self) -> usize {
1363 self.coefficients.capacity()
1364 }
1365}
1366
1367#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1369pub struct HtCodeBlockDecodeProfile {
1370 pub blocks: u128,
1372 pub refinement_blocks: u128,
1374 pub cleanup_bytes: u128,
1376 pub refinement_bytes: u128,
1378 pub cleanup_us: u128,
1380 pub mag_sgn_us: u128,
1382 pub sigma_us: u128,
1384 pub sigprop_us: u128,
1386 pub magref_us: u128,
1388}
1389
1390impl HtCodeBlockDecodeProfile {
1391 fn add_native_stats(&mut self, stats: j2c::ht_block_decode::HtBlockDecodeStats) {
1392 self.blocks += stats.blocks;
1393 self.refinement_blocks += stats.refinement_blocks;
1394 self.cleanup_bytes += stats.cleanup_bytes;
1395 self.refinement_bytes += stats.refinement_bytes;
1396 self.cleanup_us += stats.ht_cleanup_us;
1397 self.mag_sgn_us += stats.ht_mag_sgn_us;
1398 self.sigma_us += stats.ht_sigma_us;
1399 self.sigprop_us += stats.ht_sigprop_us;
1400 self.magref_us += stats.ht_magref_us;
1401 }
1402}
1403
1404pub fn decode_ht_code_block_scalar_with_workspace(
1406 job: HtCodeBlockDecodeJob<'_>,
1407 output: &mut [f32],
1408 workspace: &mut HtCodeBlockDecodeWorkspace,
1409) -> Result<()> {
1410 decode_ht_code_block_scalar_for_phase_with_workspace::<
1411 { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1412 >(job, output, workspace)
1413}
1414
1415pub fn decode_ht_code_block_scalar_with_workspace_profiled(
1417 job: HtCodeBlockDecodeJob<'_>,
1418 output: &mut [f32],
1419 workspace: &mut HtCodeBlockDecodeWorkspace,
1420 profile: &mut HtCodeBlockDecodeProfile,
1421) -> Result<()> {
1422 decode_ht_code_block_scalar_for_phase_with_workspace_profiled::<
1423 { j2c::ht_block_decode::PHASE_LIMIT_MAGREF },
1424 >(job, output, workspace, profile)
1425}
1426
1427fn decode_ht_code_block_scalar_for_phase<const PHASE_LIMIT: u8>(
1428 job: HtCodeBlockDecodeJob<'_>,
1429 output: &mut [f32],
1430) -> Result<()> {
1431 let mut workspace = HtCodeBlockDecodeWorkspace::default();
1432 decode_ht_code_block_scalar_for_phase_with_workspace::<PHASE_LIMIT>(job, output, &mut workspace)
1433}
1434
1435fn decode_ht_code_block_scalar_for_phase_with_workspace<const PHASE_LIMIT: u8>(
1436 job: HtCodeBlockDecodeJob<'_>,
1437 output: &mut [f32],
1438 workspace: &mut HtCodeBlockDecodeWorkspace,
1439) -> Result<()> {
1440 let required_len = if job.height == 0 {
1441 0
1442 } else {
1443 job.output_stride
1444 .checked_mul(job.height as usize - 1)
1445 .and_then(|prefix| prefix.checked_add(job.width as usize))
1446 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1447 };
1448 if output.len() < required_len {
1449 bail!(DecodingError::CodeBlockDecodeFailure);
1450 }
1451 let code_block_stride =
1452 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1453 let code_block_len = code_block_stride
1454 .checked_mul(job.height as usize)
1455 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1456
1457 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1458 job.data,
1459 job.cleanup_length,
1460 job.refinement_length,
1461 )?;
1462 let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1463 workspace.coefficients.clear();
1464 workspace.coefficients.resize(code_block_len, 0);
1465 j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1466 &segments,
1467 job.missing_bit_planes,
1468 coded_bitplanes,
1469 job.number_of_coding_passes,
1470 job.stripe_causal,
1471 job.strict,
1472 &mut workspace.coefficients,
1473 job.width,
1474 job.height,
1475 job.width,
1476 &mut workspace.scratch,
1477 None,
1478 false,
1479 )?;
1480
1481 for (row_idx, coeff_row) in workspace
1482 .coefficients
1483 .chunks_exact(code_block_stride)
1484 .enumerate()
1485 .take(job.height as usize)
1486 {
1487 let row_start = row_idx * job.output_stride;
1488 let output_row = &mut output[row_start..row_start + code_block_stride];
1489 for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1490 let coefficient =
1491 j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1492 let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1493 *sample = coefficient as f32 * job.dequantization_step;
1494 }
1495 }
1496
1497 Ok(())
1498}
1499
1500fn decode_ht_code_block_scalar_for_phase_with_workspace_profiled<const PHASE_LIMIT: u8>(
1501 job: HtCodeBlockDecodeJob<'_>,
1502 output: &mut [f32],
1503 workspace: &mut HtCodeBlockDecodeWorkspace,
1504 profile: &mut HtCodeBlockDecodeProfile,
1505) -> Result<()> {
1506 let required_len = if job.height == 0 {
1507 0
1508 } else {
1509 job.output_stride
1510 .checked_mul(job.height as usize - 1)
1511 .and_then(|prefix| prefix.checked_add(job.width as usize))
1512 .ok_or(DecodingError::CodeBlockDecodeFailure)?
1513 };
1514 if output.len() < required_len {
1515 bail!(DecodingError::CodeBlockDecodeFailure);
1516 }
1517 let code_block_stride =
1518 usize::try_from(job.width).map_err(|_| DecodingError::CodeBlockDecodeFailure)?;
1519 let code_block_len = code_block_stride
1520 .checked_mul(job.height as usize)
1521 .ok_or(DecodingError::CodeBlockDecodeFailure)?;
1522
1523 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1524 job.data,
1525 job.cleanup_length,
1526 job.refinement_length,
1527 )?;
1528 let coded_bitplanes = add_roi_shift_to_bitplanes(job.num_bitplanes, job.roi_shift, 31)?;
1529 workspace.coefficients.clear();
1530 workspace.coefficients.resize(code_block_len, 0);
1531 let mut stats = j2c::ht_block_decode::HtBlockDecodeStats::default();
1532 j2c::ht_block_decode::decode_segments_validated_with_scratch_for_phase::<PHASE_LIMIT>(
1533 &segments,
1534 job.missing_bit_planes,
1535 coded_bitplanes,
1536 job.number_of_coding_passes,
1537 job.stripe_causal,
1538 job.strict,
1539 &mut workspace.coefficients,
1540 job.width,
1541 job.height,
1542 job.width,
1543 &mut workspace.scratch,
1544 Some(&mut stats),
1545 true,
1546 )?;
1547 profile.add_native_stats(stats);
1548
1549 for (row_idx, coeff_row) in workspace
1550 .coefficients
1551 .chunks_exact(code_block_stride)
1552 .enumerate()
1553 .take(job.height as usize)
1554 {
1555 let row_start = row_idx * job.output_stride;
1556 let output_row = &mut output[row_start..row_start + code_block_stride];
1557 for (coefficient, sample) in coeff_row.iter().copied().zip(output_row.iter_mut()) {
1558 let coefficient =
1559 j2c::ht_block_decode::coefficient_to_i32(coefficient, coded_bitplanes);
1560 let coefficient = apply_roi_maxshift_inverse_i32(coefficient, job.roi_shift);
1561 *sample = coefficient as f32 * job.dequantization_step;
1562 }
1563 }
1564
1565 Ok(())
1566}
1567
1568pub struct HtSigPropBenchmarkState(j2c::ht_block_decode::HtSigPropBenchmarkState);
1570
1571impl HtSigPropBenchmarkState {
1572 pub fn output_len(&self) -> usize {
1574 self.0.output_len()
1575 }
1576}
1577
1578pub fn prepare_ht_sigprop_benchmark_state(
1580 job: HtCodeBlockDecodeJob<'_>,
1581) -> Result<HtSigPropBenchmarkState> {
1582 let segments = j2c::ht_block_decode::HtCodeBlockSegments::from_combined_payload(
1583 job.data,
1584 job.cleanup_length,
1585 job.refinement_length,
1586 )?;
1587 let state = j2c::ht_block_decode::prepare_sigprop_benchmark_state(
1588 &segments,
1589 job.missing_bit_planes,
1590 job.num_bitplanes,
1591 job.number_of_coding_passes,
1592 job.stripe_causal,
1593 job.strict,
1594 job.width,
1595 job.height,
1596 job.width,
1597 )?;
1598 Ok(HtSigPropBenchmarkState(state))
1599}
1600
1601pub fn decode_ht_sigprop_benchmark_state(
1603 state: &mut HtSigPropBenchmarkState,
1604 output: &mut [u32],
1605) -> Result<()> {
1606 j2c::ht_block_decode::decode_sigprop_benchmark_state(&mut state.0, output)
1607}
1608
1609pub fn ht_vlc_table0() -> &'static [u16; 1024] {
1611 &j2c::ht_tables::VLC_TABLE0
1612}
1613
1614pub fn ht_vlc_table1() -> &'static [u16; 1024] {
1616 &j2c::ht_tables::VLC_TABLE1
1617}
1618
1619pub fn ht_uvlc_table0() -> &'static [u16; 320] {
1621 &j2c::ht_tables::UVLC_TABLE0
1622}
1623
1624pub fn ht_uvlc_table1() -> &'static [u16; 256] {
1626 &j2c::ht_tables::UVLC_TABLE1
1627}
1628
1629pub fn ht_vlc_encode_table0() -> &'static [u16; 2048] {
1631 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE0
1632}
1633
1634pub fn ht_vlc_encode_table1() -> &'static [u16; 2048] {
1636 &j2c::ht_encode_tables::HT_VLC_ENCODE_TABLE1
1637}
1638
1639pub fn ht_uvlc_encode_table() -> &'static [HtUvlcTableEntry; 75] {
1641 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE
1642}
1643
1644pub fn ht_uvlc_encode_table_bytes() -> &'static [u8] {
1646 &j2c::ht_encode_tables::HT_UVLC_ENCODE_TABLE_BYTES
1647}
1648
1649pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
1651pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
1653
1654#[derive(Debug, Copy, Clone)]
1656pub struct DecodeSettings {
1657 pub resolve_palette_indices: bool,
1669 pub strict: bool,
1674 pub target_resolution: Option<(u32, u32)>,
1676}
1677
1678impl Default for DecodeSettings {
1679 fn default() -> Self {
1680 Self {
1681 resolve_palette_indices: true,
1682 strict: false,
1683 target_resolution: None,
1684 }
1685 }
1686}
1687
1688pub struct Image<'a> {
1690 pub(crate) codestream: &'a [u8],
1692 pub(crate) header: Header<'a>,
1694 pub(crate) boxes: ImageBoxes,
1697 pub(crate) settings: DecodeSettings,
1699 pub(crate) has_alpha: bool,
1701 pub(crate) color_space: ColorSpace,
1703}
1704
1705impl<'a> Image<'a> {
1706 pub fn new(data: &'a [u8], settings: &DecodeSettings) -> Result<Self> {
1708 if data.starts_with(JP2_MAGIC) {
1709 jp2::parse(data, *settings)
1710 } else if data.starts_with(CODESTREAM_MAGIC) {
1711 j2c::parse(data, settings)
1712 } else {
1713 err!(FormatError::InvalidSignature)
1714 }
1715 }
1716
1717 pub fn has_alpha(&self) -> bool {
1719 self.has_alpha
1720 }
1721
1722 pub fn color_space(&self) -> &ColorSpace {
1724 &self.color_space
1725 }
1726
1727 pub fn width(&self) -> u32 {
1729 self.header.size_data.image_width()
1730 }
1731
1732 pub fn height(&self) -> u32 {
1734 self.header.size_data.image_height()
1735 }
1736
1737 pub fn original_bit_depth(&self) -> u8 {
1740 self.header.component_infos[0].size_info.precision
1742 }
1743
1744 pub fn supports_direct_device_plane_reuse(&self) -> bool {
1746 if self.settings.resolve_palette_indices && self.boxes.palette.is_some() {
1747 return false;
1748 }
1749 if self.boxes.channel_definition.is_some() {
1750 return false;
1751 }
1752 !matches!(
1753 self.boxes
1754 .color_specification
1755 .as_ref()
1756 .map(|spec| &spec.color_space),
1757 Some(jp2::colr::ColorSpace::Enumerated(
1758 EnumeratedColorspace::Sycc | EnumeratedColorspace::CieLab(_)
1759 ))
1760 )
1761 }
1762
1763 pub fn decode(&self) -> Result<Vec<u8>> {
1766 let bitmap = self.decode_with_context(&mut DecoderContext::default())?;
1767 Ok(bitmap.data)
1768 }
1769
1770 pub fn decode_with_context(&self, decoder_context: &mut DecoderContext<'a>) -> Result<Bitmap> {
1773 let buffer_size = checked_decode_byte_len3(
1774 self.width() as usize,
1775 self.height() as usize,
1776 self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 },
1777 )?;
1778 let mut buf = vec![0; buffer_size];
1779 self.decode_into(&mut buf, decoder_context)?;
1780
1781 Ok(Bitmap {
1782 color_space: self.color_space.clone(),
1783 data: buf,
1784 has_alpha: self.has_alpha,
1785 width: self.width(),
1786 height: self.height(),
1787 original_bit_depth: self.original_bit_depth(),
1788 })
1789 }
1790
1791 pub fn decode_components_with_context<'ctx>(
1794 &self,
1795 decoder_context: &'ctx mut DecoderContext<'a>,
1796 ) -> Result<DecodedComponents<'ctx>> {
1797 let decoded_image = self.prepare_decoded_image(decoder_context)?;
1798 let planes = decoded_image
1799 .decoded_components
1800 .iter()
1801 .map(|component| ComponentPlane {
1802 samples: component.container.truncated(),
1803 bit_depth: component.bit_depth,
1804 })
1805 .collect();
1806
1807 Ok(DecodedComponents {
1808 dimensions: (self.width(), self.height()),
1809 color_space: self.color_space.clone(),
1810 has_alpha: self.has_alpha,
1811 planes,
1812 })
1813 }
1814
1815 pub fn build_direct_grayscale_plan_with_context(
1817 &self,
1818 decoder_context: &mut DecoderContext<'a>,
1819 ) -> Result<J2kDirectGrayscalePlan> {
1820 if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1821 bail!(DecodingError::UnsupportedFeature(
1822 "direct grayscale plan only supports grayscale images without alpha"
1823 ));
1824 }
1825
1826 j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context)
1827 }
1828
1829 pub fn build_direct_grayscale_plan_region_with_context(
1831 &self,
1832 decoder_context: &mut DecoderContext<'a>,
1833 output_region: (u32, u32, u32, u32),
1834 ) -> Result<J2kDirectGrayscalePlan> {
1835 if !matches!(self.color_space, ColorSpace::Gray) || self.has_alpha {
1836 bail!(DecodingError::UnsupportedFeature(
1837 "direct grayscale plan only supports grayscale images without alpha"
1838 ));
1839 }
1840
1841 decoder_context.set_output_region(Some(output_region));
1842 let result =
1843 j2c::build_direct_grayscale_plan(self.codestream, &self.header, decoder_context);
1844 decoder_context.set_output_region(None);
1845 result
1846 }
1847
1848 pub fn build_direct_color_plan_with_context(
1850 &self,
1851 decoder_context: &mut DecoderContext<'a>,
1852 ) -> Result<J2kDirectColorPlan> {
1853 if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1854 bail!(DecodingError::UnsupportedFeature(
1855 "direct color plan only supports RGB images without alpha"
1856 ));
1857 }
1858
1859 j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context)
1860 }
1861
1862 pub fn build_direct_color_plan_region_with_context(
1864 &self,
1865 decoder_context: &mut DecoderContext<'a>,
1866 output_region: (u32, u32, u32, u32),
1867 ) -> Result<J2kDirectColorPlan> {
1868 if !matches!(self.color_space, ColorSpace::RGB) || self.has_alpha {
1869 bail!(DecodingError::UnsupportedFeature(
1870 "direct color plan only supports RGB images without alpha"
1871 ));
1872 }
1873
1874 decoder_context.set_output_region(Some(output_region));
1875 let result = j2c::build_direct_color_plan(self.codestream, &self.header, decoder_context);
1876 decoder_context.set_output_region(None);
1877 result
1878 }
1879
1880 pub fn decode_components_with_ht_decoder<'ctx>(
1882 &self,
1883 decoder_context: &'ctx mut DecoderContext<'a>,
1884 ht_decoder: &mut dyn HtCodeBlockDecoder,
1885 ) -> Result<DecodedComponents<'ctx>> {
1886 let decoded_image =
1887 self.prepare_decoded_image_with_ht_decoder(decoder_context, ht_decoder)?;
1888 let planes = decoded_image
1889 .decoded_components
1890 .iter()
1891 .map(|component| ComponentPlane {
1892 samples: component.container.truncated(),
1893 bit_depth: component.bit_depth,
1894 })
1895 .collect();
1896
1897 Ok(DecodedComponents {
1898 dimensions: (self.width(), self.height()),
1899 color_space: self.color_space.clone(),
1900 has_alpha: self.has_alpha,
1901 planes,
1902 })
1903 }
1904
1905 pub fn decode_region_components_with_context<'ctx>(
1908 &self,
1909 roi: (u32, u32, u32, u32),
1910 decoder_context: &'ctx mut DecoderContext<'a>,
1911 ) -> Result<DecodedComponents<'ctx>> {
1912 validate_roi((self.width(), self.height()), roi)?;
1913 let (_x, _y, width, height) = roi;
1914 let decoded_image = self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
1915 let planes = decoded_image
1916 .decoded_components
1917 .iter()
1918 .map(|component| ComponentPlane {
1919 samples: component.container.truncated(),
1920 bit_depth: component.bit_depth,
1921 })
1922 .collect();
1923
1924 Ok(DecodedComponents {
1925 dimensions: (width, height),
1926 color_space: self.color_space.clone(),
1927 has_alpha: self.has_alpha,
1928 planes,
1929 })
1930 }
1931
1932 pub fn decode_region_components_with_ht_decoder<'ctx>(
1935 &self,
1936 decoder_context: &'ctx mut DecoderContext<'a>,
1937 roi: (u32, u32, u32, u32),
1938 ht_decoder: &mut dyn HtCodeBlockDecoder,
1939 ) -> Result<DecodedComponents<'ctx>> {
1940 validate_roi((self.width(), self.height()), roi)?;
1941 let (_x, _y, width, height) = roi;
1942 let decoded_image = self.prepare_decoded_image_with_region_and_ht_decoder(
1943 decoder_context,
1944 Some(roi),
1945 Some(ht_decoder),
1946 )?;
1947 let planes = decoded_image
1948 .decoded_components
1949 .iter()
1950 .map(|component| ComponentPlane {
1951 samples: component.container.truncated(),
1952 bit_depth: component.bit_depth,
1953 })
1954 .collect();
1955
1956 Ok(DecodedComponents {
1957 dimensions: (width, height),
1958 color_space: self.color_space.clone(),
1959 has_alpha: self.has_alpha,
1960 planes,
1961 })
1962 }
1963
1964 pub fn decode_region(&self, roi: (u32, u32, u32, u32)) -> Result<Bitmap> {
1966 self.decode_region_with_context(roi, &mut DecoderContext::default())
1967 }
1968
1969 pub fn decode_region_with_context(
1972 &self,
1973 roi: (u32, u32, u32, u32),
1974 decoder_context: &mut DecoderContext<'a>,
1975 ) -> Result<Bitmap> {
1976 validate_roi((self.width(), self.height()), roi)?;
1977 let mut decoded_image =
1978 self.prepare_decoded_image_with_region(decoder_context, Some(roi))?;
1979 let (_x, _y, width, height) = roi;
1980 let channels =
1981 self.color_space.num_channels() as usize + if self.has_alpha { 1 } else { 0 };
1982 let data_len = checked_decode_byte_len3(width as usize, height as usize, channels)?;
1983 let mut data = vec![0; data_len];
1984 interleave_and_convert_region(
1985 &mut decoded_image,
1986 width as usize,
1987 (0, 0, width, height),
1988 &mut data,
1989 );
1990 Ok(Bitmap {
1991 color_space: self.color_space.clone(),
1992 data,
1993 has_alpha: self.has_alpha,
1994 width,
1995 height,
1996 original_bit_depth: self.original_bit_depth(),
1997 })
1998 }
1999
2000 pub fn decode_native(&self) -> Result<RawBitmap> {
2009 let mut decoder_context = DecoderContext::default();
2010 self.decode_native_with_context(&mut decoder_context)
2011 }
2012
2013 pub fn decode_reversible_53_coefficients(&self) -> Result<Reversible53CoefficientImage> {
2019 let mut decoder_context = DecoderContext::default();
2020 self.decode_reversible_53_coefficients_with_context(&mut decoder_context)
2021 }
2022
2023 pub fn decode_reversible_53_coefficients_with_context(
2026 &self,
2027 decoder_context: &mut DecoderContext<'a>,
2028 ) -> Result<Reversible53CoefficientImage> {
2029 j2c::recode::extract_reversible_53_coefficients(
2030 self.codestream,
2031 &self.header,
2032 decoder_context,
2033 )
2034 }
2035
2036 pub fn decode_native_region(&self, roi: (u32, u32, u32, u32)) -> Result<RawBitmap> {
2038 self.decode_native_region_with_context(roi, &mut DecoderContext::default())
2039 }
2040
2041 pub fn decode_native_with_context(
2044 &self,
2045 decoder_context: &mut DecoderContext<'a>,
2046 ) -> Result<RawBitmap> {
2047 self.decode_with_output_region(decoder_context, None)?;
2048
2049 let components = &decoder_context.tile_decode_context.channel_data;
2050 let bit_depth = self.original_bit_depth();
2051 let num_components =
2052 u8::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2053 let width = self.width();
2054 let height = self.height();
2055 let pixel_count = checked_decode_sample_count(width, height)?;
2056
2057 if bit_depth <= 8 {
2058 let max_val = ((1u32 << bit_depth) - 1) as f32;
2059 let capacity = checked_decode_byte_len2(pixel_count, num_components as usize)?;
2060 let mut data = Vec::with_capacity(capacity);
2061 for i in 0..pixel_count {
2062 for component in components.iter() {
2063 let v = math::round_f32(component.container.truncated()[i]);
2064 let clamped = if v < 0.0 {
2065 0.0
2066 } else if v > max_val {
2067 max_val
2068 } else {
2069 v
2070 };
2071 data.push(clamped as u8);
2072 }
2073 }
2074 Ok(RawBitmap {
2075 data,
2076 width,
2077 height,
2078 bit_depth,
2079 num_components,
2080 bytes_per_sample: 1,
2081 })
2082 } else {
2083 let max_val = ((1u32 << bit_depth) - 1) as f32;
2084 let capacity = checked_decode_byte_len3(pixel_count, num_components as usize, 2)?;
2085 let mut data = Vec::with_capacity(capacity);
2086 for i in 0..pixel_count {
2087 for component in components.iter() {
2088 let v = math::round_f32(component.container.truncated()[i]);
2089 let clamped = if v < 0.0 {
2090 0.0
2091 } else if v > max_val {
2092 max_val
2093 } else {
2094 v
2095 };
2096 let val = clamped as u16;
2097 data.extend_from_slice(&val.to_le_bytes());
2098 }
2099 }
2100 Ok(RawBitmap {
2101 data,
2102 width,
2103 height,
2104 bit_depth,
2105 num_components,
2106 bytes_per_sample: 2,
2107 })
2108 }
2109 }
2110
2111 pub fn decode_native_region_with_context(
2114 &self,
2115 roi: (u32, u32, u32, u32),
2116 decoder_context: &mut DecoderContext<'a>,
2117 ) -> Result<RawBitmap> {
2118 validate_roi((self.width(), self.height()), roi)?;
2119 self.decode_with_output_region(decoder_context, Some(roi))?;
2120
2121 let components = &decoder_context.tile_decode_context.channel_data;
2122 let bit_depth = self.original_bit_depth();
2123 let num_components =
2124 u8::try_from(components.len()).map_err(|_| ValidationError::TooManyChannels)?;
2125 let bytes_per_sample = if bit_depth <= 8 { 1 } else { 2 };
2126 let (_x, _y, width, height) = roi;
2127 let capacity = checked_decode_byte_len4(
2128 width as usize,
2129 height as usize,
2130 num_components as usize,
2131 bytes_per_sample,
2132 )?;
2133 let mut data = Vec::with_capacity(capacity);
2134 let max_val = ((1u32 << bit_depth) - 1) as f32;
2135
2136 for row in 0..height as usize {
2137 for col in 0..width as usize {
2138 let idx = row * width as usize + col;
2139 for component in components {
2140 let v = math::round_f32(component.container.truncated()[idx]);
2141 let clamped = if v < 0.0 {
2142 0.0
2143 } else if v > max_val {
2144 max_val
2145 } else {
2146 v
2147 };
2148 if bit_depth <= 8 {
2149 data.push(clamped as u8);
2150 } else {
2151 data.extend_from_slice(&(clamped as u16).to_le_bytes());
2152 }
2153 }
2154 }
2155 }
2156
2157 Ok(RawBitmap {
2158 data,
2159 width,
2160 height,
2161 bit_depth,
2162 num_components,
2163 bytes_per_sample: bytes_per_sample as u8,
2164 })
2165 }
2166
2167 pub fn decode_into(
2177 &self,
2178 buf: &mut [u8],
2179 decoder_context: &mut DecoderContext<'a>,
2180 ) -> Result<()> {
2181 let mut decoded_image = self.prepare_decoded_image(decoder_context)?;
2182 validate_interleaved_output_buffer(&decoded_image, buf)?;
2183 interleave_and_convert(&mut decoded_image, buf)?;
2184
2185 Ok(())
2186 }
2187
2188 fn prepare_decoded_image<'ctx>(
2189 &self,
2190 decoder_context: &'ctx mut DecoderContext<'a>,
2191 ) -> Result<DecodedImage<'ctx>> {
2192 self.prepare_decoded_image_with_region(decoder_context, None)
2193 }
2194
2195 fn prepare_decoded_image_with_ht_decoder<'ctx>(
2196 &self,
2197 decoder_context: &'ctx mut DecoderContext<'a>,
2198 ht_decoder: &mut dyn HtCodeBlockDecoder,
2199 ) -> Result<DecodedImage<'ctx>> {
2200 self.prepare_decoded_image_with_region_and_ht_decoder(
2201 decoder_context,
2202 None,
2203 Some(ht_decoder),
2204 )
2205 }
2206
2207 fn prepare_decoded_image_with_region<'ctx>(
2208 &self,
2209 decoder_context: &'ctx mut DecoderContext<'a>,
2210 output_region: Option<(u32, u32, u32, u32)>,
2211 ) -> Result<DecodedImage<'ctx>> {
2212 self.prepare_decoded_image_with_region_and_ht_decoder(decoder_context, output_region, None)
2213 }
2214
2215 fn prepare_decoded_image_with_region_and_ht_decoder<'ctx>(
2216 &self,
2217 decoder_context: &'ctx mut DecoderContext<'a>,
2218 output_region: Option<(u32, u32, u32, u32)>,
2219 ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2220 ) -> Result<DecodedImage<'ctx>> {
2221 let settings = &self.settings;
2222 self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, ht_decoder)?;
2223 let mut decoded_image = DecodedImage {
2224 decoded_components: &mut decoder_context.tile_decode_context.channel_data,
2225 boxes: self.boxes.clone(),
2226 };
2227
2228 if settings.resolve_palette_indices {
2229 let components = core::mem::take(decoded_image.decoded_components);
2230 *decoded_image.decoded_components =
2231 resolve_palette_indices(components, &decoded_image.boxes)?;
2232 }
2233
2234 if let Some(cdef) = &decoded_image.boxes.channel_definition {
2235 validate_channel_definition(cdef, decoded_image.decoded_components.len())?;
2236 let mut components = decoded_image
2237 .decoded_components
2238 .iter()
2239 .cloned()
2240 .zip(
2241 cdef.channel_definitions
2242 .iter()
2243 .map(|c| match c._association {
2244 ChannelAssociation::WholeImage => u16::MAX,
2245 ChannelAssociation::Colour(c) => c,
2246 }),
2247 )
2248 .collect::<Vec<_>>();
2249 components.sort_by_key(|component| component.1);
2250 *decoded_image.decoded_components = components.into_iter().map(|c| c.0).collect();
2251 }
2252
2253 let bit_depth = decoded_image.decoded_components[0].bit_depth;
2254 convert_color_space(&mut decoded_image, bit_depth)?;
2255 Ok(decoded_image)
2256 }
2257
2258 fn decode_with_output_region(
2259 &self,
2260 decoder_context: &mut DecoderContext<'a>,
2261 output_region: Option<(u32, u32, u32, u32)>,
2262 ) -> Result<()> {
2263 self.decode_with_output_region_and_ht_decoder(decoder_context, output_region, None)
2264 }
2265
2266 fn decode_with_output_region_and_ht_decoder(
2267 &self,
2268 decoder_context: &mut DecoderContext<'a>,
2269 output_region: Option<(u32, u32, u32, u32)>,
2270 mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder>,
2271 ) -> Result<()> {
2272 decoder_context.set_output_region(output_region);
2273 let decode_result = j2c::decode(
2274 self.codestream,
2275 &self.header,
2276 decoder_context,
2277 &mut ht_decoder,
2278 );
2279 decoder_context.set_output_region(None);
2280 decode_result
2281 }
2282}
2283
2284fn validate_channel_definition(
2285 cdef: &jp2::cdef::ChannelDefinitionBox,
2286 component_count: usize,
2287) -> Result<()> {
2288 if cdef.channel_definitions.len() != component_count {
2289 bail!(ValidationError::InvalidChannelDefinition);
2290 }
2291
2292 let mut seen_color_associations = vec![false; component_count];
2293 for definition in &cdef.channel_definitions {
2294 if let ChannelAssociation::Colour(association) = definition._association {
2295 let Some(index) = association.checked_sub(1).map(usize::from) else {
2296 bail!(ValidationError::InvalidChannelDefinition);
2297 };
2298 if index >= component_count || seen_color_associations[index] {
2299 bail!(ValidationError::InvalidChannelDefinition);
2300 }
2301 seen_color_associations[index] = true;
2302 }
2303 }
2304
2305 Ok(())
2306}
2307
2308pub(crate) fn resolve_alpha_and_color_space(
2309 boxes: &ImageBoxes,
2310 header: &Header<'_>,
2311 settings: &DecodeSettings,
2312) -> Result<(ColorSpace, bool)> {
2313 let mut num_components = header.component_infos.len();
2314
2315 if settings.resolve_palette_indices {
2318 if let Some(palette_box) = &boxes.palette {
2319 num_components = palette_box.columns.len();
2320 }
2321 }
2322
2323 let mut has_alpha = false;
2324
2325 if let Some(cdef) = &boxes.channel_definition {
2326 let last = cdef.channel_definitions.last().unwrap();
2327 has_alpha = last.channel_type == ChannelType::Opacity;
2328 }
2329
2330 let mut color_space = get_color_space(boxes, num_components)?;
2331
2332 if !settings.resolve_palette_indices && boxes.palette.is_some() {
2334 has_alpha = false;
2335 color_space = ColorSpace::Gray;
2336 }
2337
2338 let actual_num_components = header.component_infos.len();
2339
2340 if boxes.palette.is_none()
2342 && actual_num_components
2343 != (color_space.num_channels() + if has_alpha { 1 } else { 0 }) as usize
2344 {
2345 if !settings.strict
2346 && actual_num_components == color_space.num_channels() as usize + 1
2347 && !has_alpha
2348 {
2349 has_alpha = true;
2352 } else {
2353 if actual_num_components == 1 || (actual_num_components == 2 && has_alpha) {
2355 color_space = ColorSpace::Gray;
2356 } else if actual_num_components == 3 {
2357 color_space = ColorSpace::RGB;
2358 } else if actual_num_components == 4 {
2359 if has_alpha {
2360 color_space = ColorSpace::RGB;
2361 } else {
2362 color_space = ColorSpace::CMYK;
2363 }
2364 } else {
2365 bail!(ValidationError::TooManyChannels);
2366 }
2367 }
2368 }
2369
2370 Ok((color_space, has_alpha))
2371}
2372
2373#[derive(Debug, Clone)]
2375pub enum ColorSpace {
2376 Gray,
2378 RGB,
2380 CMYK,
2382 Unknown {
2384 num_channels: u8,
2386 },
2387 Icc {
2389 profile: Vec<u8>,
2391 num_channels: u8,
2393 },
2394}
2395
2396impl ColorSpace {
2397 pub fn num_channels(&self) -> u8 {
2399 match self {
2400 Self::Gray => 1,
2401 Self::RGB => 3,
2402 Self::CMYK => 4,
2403 Self::Unknown { num_channels } => *num_channels,
2404 Self::Icc {
2405 num_channels: num_components,
2406 ..
2407 } => *num_components,
2408 }
2409 }
2410}
2411
2412pub struct Bitmap {
2414 pub color_space: ColorSpace,
2416 pub data: Vec<u8>,
2425 pub has_alpha: bool,
2427 pub width: u32,
2429 pub height: u32,
2431 pub original_bit_depth: u8,
2434}
2435
2436pub struct RawBitmap {
2445 pub data: Vec<u8>,
2447 pub width: u32,
2449 pub height: u32,
2451 pub bit_depth: u8,
2453 pub num_components: u8,
2455 pub bytes_per_sample: u8,
2457}
2458
2459pub struct ComponentPlane<'a> {
2461 samples: &'a [f32],
2462 bit_depth: u8,
2463}
2464
2465impl<'a> ComponentPlane<'a> {
2466 pub fn samples(&self) -> &'a [f32] {
2468 self.samples
2469 }
2470
2471 pub fn bit_depth(&self) -> u8 {
2473 self.bit_depth
2474 }
2475}
2476
2477pub struct DecodedComponents<'a> {
2479 dimensions: (u32, u32),
2480 color_space: ColorSpace,
2481 has_alpha: bool,
2482 planes: Vec<ComponentPlane<'a>>,
2483}
2484
2485impl<'a> DecodedComponents<'a> {
2486 pub fn dimensions(&self) -> (u32, u32) {
2488 self.dimensions
2489 }
2490
2491 pub fn color_space(&self) -> &ColorSpace {
2493 &self.color_space
2494 }
2495
2496 pub fn has_alpha(&self) -> bool {
2498 self.has_alpha
2499 }
2500
2501 pub fn planes(&self) -> &[ComponentPlane<'a>] {
2503 &self.planes
2504 }
2505}
2506
2507fn validate_interleaved_output_buffer(image: &DecodedImage<'_>, buf: &[u8]) -> Result<()> {
2508 let required_len = interleaved_output_len(image)?;
2509 if buf.len() < required_len {
2510 bail!(DecodingError::OutputBufferTooSmall);
2511 }
2512 Ok(())
2513}
2514
2515fn interleaved_output_len(image: &DecodedImage<'_>) -> Result<usize> {
2516 let Some(first) = image.decoded_components.first() else {
2517 bail!(DecodingError::CodeBlockDecodeFailure);
2518 };
2519 first
2520 .container
2521 .truncated()
2522 .len()
2523 .checked_mul(image.decoded_components.len())
2524 .ok_or(ValidationError::ImageTooLarge.into())
2525}
2526
2527fn interleave_and_convert(image: &mut DecodedImage<'_>, buf: &mut [u8]) -> Result<()> {
2528 let components = &mut *image.decoded_components;
2529 let num_components = components.len();
2530
2531 let mut all_same_bit_depth = Some(components[0].bit_depth);
2532
2533 for component in components.iter().skip(1) {
2534 if Some(component.bit_depth) != all_same_bit_depth {
2535 all_same_bit_depth = None;
2536 }
2537 }
2538
2539 let max_len = components[0].container.truncated().len();
2540
2541 let mut output_iter = buf.iter_mut();
2542
2543 if all_same_bit_depth == Some(8) && num_components <= 4 {
2544 match num_components {
2546 1 => {
2548 for (output, input) in output_iter.zip(
2549 components[0]
2550 .container
2551 .iter()
2552 .map(|v| math::round_f32(*v) as u8),
2553 ) {
2554 *output = input;
2555 }
2556 }
2557 2 => {
2559 let c0 = &components[0];
2560 let c1 = &components[1];
2561
2562 let c0 = &c0.container[..max_len];
2563 let c1 = &c1.container[..max_len];
2564
2565 for i in 0..max_len {
2566 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2567 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2568 }
2569 }
2570 3 => {
2572 let c0 = &components[0];
2573 let c1 = &components[1];
2574 let c2 = &components[2];
2575
2576 let c0 = &c0.container[..max_len];
2577 let c1 = &c1.container[..max_len];
2578 let c2 = &c2.container[..max_len];
2579
2580 for i in 0..max_len {
2581 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2582 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2583 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
2584 }
2585 }
2586 4 => {
2588 let c0 = &components[0];
2589 let c1 = &components[1];
2590 let c2 = &components[2];
2591 let c3 = &components[3];
2592
2593 let c0 = &c0.container[..max_len];
2594 let c1 = &c1.container[..max_len];
2595 let c2 = &c2.container[..max_len];
2596 let c3 = &c3.container[..max_len];
2597
2598 for i in 0..max_len {
2599 *output_iter.next().unwrap() = math::round_f32(c0[i]) as u8;
2600 *output_iter.next().unwrap() = math::round_f32(c1[i]) as u8;
2601 *output_iter.next().unwrap() = math::round_f32(c2[i]) as u8;
2602 *output_iter.next().unwrap() = math::round_f32(c3[i]) as u8;
2603 }
2604 }
2605 _ => bail!(ValidationError::TooManyChannels),
2606 }
2607 } else {
2608 let mul_factor = ((1 << 8) - 1) as f32;
2610
2611 for sample in 0..max_len {
2612 for channel in components.iter() {
2613 *output_iter.next().unwrap() = math::round_f32(
2614 (channel.container[sample] / ((1_u32 << channel.bit_depth) - 1) as f32)
2615 * mul_factor,
2616 ) as u8;
2617 }
2618 }
2619 }
2620
2621 Ok(())
2622}
2623
2624fn interleave_and_convert_region(
2625 image: &mut DecodedImage<'_>,
2626 image_width: usize,
2627 roi: (u32, u32, u32, u32),
2628 buf: &mut [u8],
2629) {
2630 let components = &mut *image.decoded_components;
2631 let num_components = components.len();
2632 let (x, y, width, height) = roi;
2633 let mut output_iter = buf.iter_mut();
2634
2635 let mut all_same_bit_depth = Some(components[0].bit_depth);
2636 for component in components.iter().skip(1) {
2637 if Some(component.bit_depth) != all_same_bit_depth {
2638 all_same_bit_depth = None;
2639 }
2640 }
2641
2642 if all_same_bit_depth == Some(8) && num_components <= 4 {
2643 for row in y as usize..(y + height) as usize {
2644 let row_base = row * image_width;
2645 for col in x as usize..(x + width) as usize {
2646 let idx = row_base + col;
2647 for component in components.iter() {
2648 *output_iter.next().unwrap() = math::round_f32(component.container[idx]) as u8;
2649 }
2650 }
2651 }
2652 } else {
2653 let mul_factor = ((1 << 8) - 1) as f32;
2654 for row in y as usize..(y + height) as usize {
2655 let row_base = row * image_width;
2656 for col in x as usize..(x + width) as usize {
2657 let idx = row_base + col;
2658 for component in components.iter() {
2659 *output_iter.next().unwrap() = math::round_f32(
2660 (component.container[idx] / ((1_u32 << component.bit_depth) - 1) as f32)
2661 * mul_factor,
2662 ) as u8;
2663 }
2664 }
2665 }
2666 }
2667}
2668
2669fn validate_roi(dims: (u32, u32), roi: (u32, u32, u32, u32)) -> Result<()> {
2670 let (image_width, image_height) = dims;
2671 let (x, y, width, height) = roi;
2672 let x_end = x
2673 .checked_add(width)
2674 .ok_or(ValidationError::InvalidDimensions)?;
2675 let y_end = y
2676 .checked_add(height)
2677 .ok_or(ValidationError::InvalidDimensions)?;
2678 if x_end > image_width || y_end > image_height {
2679 return Err(ValidationError::InvalidDimensions.into());
2680 }
2681 Ok(())
2682}
2683
2684fn convert_color_space(image: &mut DecodedImage<'_>, bit_depth: u8) -> Result<()> {
2685 if let Some(jp2::colr::ColorSpace::Enumerated(e)) = &image
2686 .boxes
2687 .color_specification
2688 .as_ref()
2689 .map(|i| &i.color_space)
2690 {
2691 match e {
2692 EnumeratedColorspace::Sycc => {
2693 dispatch!(Level::new(), simd => {
2694 sycc_to_rgb(simd, image.decoded_components, bit_depth)
2695 })?;
2696 }
2697 EnumeratedColorspace::CieLab(cielab) => {
2698 dispatch!(Level::new(), simd => {
2699 cielab_to_rgb(simd, image.decoded_components, bit_depth, cielab)
2700 })?;
2701 }
2702 _ => {}
2703 }
2704 }
2705
2706 Ok(())
2707}
2708
2709fn get_color_space(boxes: &ImageBoxes, num_components: usize) -> Result<ColorSpace> {
2710 let cs = match boxes
2711 .color_specification
2712 .as_ref()
2713 .map(|c| &c.color_space)
2714 .unwrap_or(&jp2::colr::ColorSpace::Unknown)
2715 {
2716 jp2::colr::ColorSpace::Enumerated(e) => {
2717 match e {
2718 EnumeratedColorspace::Cmyk => ColorSpace::CMYK,
2719 EnumeratedColorspace::Srgb => ColorSpace::RGB,
2720 EnumeratedColorspace::RommRgb => {
2721 ColorSpace::Icc {
2723 profile: include_bytes!("../assets/ProPhoto-v2-micro.icc").to_vec(),
2724 num_channels: 3,
2725 }
2726 }
2727 EnumeratedColorspace::EsRgb => ColorSpace::RGB,
2728 EnumeratedColorspace::Greyscale => ColorSpace::Gray,
2729 EnumeratedColorspace::Sycc => ColorSpace::RGB,
2730 EnumeratedColorspace::CieLab(_) => ColorSpace::Icc {
2731 profile: include_bytes!("../assets/LAB.icc").to_vec(),
2732 num_channels: 3,
2733 },
2734 _ => bail!(FormatError::Unsupported),
2735 }
2736 }
2737 jp2::colr::ColorSpace::Icc(icc) => {
2738 if let Some(metadata) = ICCMetadata::from_data(icc) {
2739 ColorSpace::Icc {
2740 profile: icc.clone(),
2741 num_channels: metadata.color_space.num_components(),
2742 }
2743 } else {
2744 ColorSpace::RGB
2749 }
2750 }
2751 jp2::colr::ColorSpace::Unknown => match num_components {
2752 1 => ColorSpace::Gray,
2753 3 => ColorSpace::RGB,
2754 4 => ColorSpace::CMYK,
2755 _ => ColorSpace::Unknown {
2756 num_channels: num_components as u8,
2757 },
2758 },
2759 };
2760
2761 Ok(cs)
2762}
2763
2764fn resolve_palette_indices(
2765 components: Vec<ComponentData>,
2766 boxes: &ImageBoxes,
2767) -> Result<Vec<ComponentData>> {
2768 let Some(palette) = boxes.palette.as_ref() else {
2769 return Ok(components);
2771 };
2772
2773 let Some(mapping) = boxes.component_mapping.as_ref() else {
2774 bail!(ColorError::PaletteResolutionFailed);
2775 };
2776 if mapping.entries.is_empty() {
2777 bail!(ColorError::PaletteResolutionFailed);
2778 }
2779
2780 let mut resolved = Vec::with_capacity(mapping.entries.len());
2781
2782 for entry in &mapping.entries {
2783 let component_idx = entry.component_index as usize;
2784 let component = components
2785 .get(component_idx)
2786 .ok_or(ColorError::PaletteResolutionFailed)?;
2787
2788 match entry.mapping_type {
2789 ComponentMappingType::Direct => resolved.push(component.clone()),
2790 ComponentMappingType::Palette { column } => {
2791 let column_idx = column as usize;
2792 let column_info = palette
2793 .columns
2794 .get(column_idx)
2795 .ok_or(ColorError::PaletteResolutionFailed)?;
2796
2797 let mut mapped =
2798 Vec::with_capacity(component.container.truncated().len() + SIMD_WIDTH);
2799
2800 for &sample in component.container.truncated() {
2801 let index = math::round_f32(sample) as i64;
2802 let value = palette
2803 .map(index as usize, column_idx)
2804 .ok_or(ColorError::PaletteResolutionFailed)?;
2805 mapped.push(value as f32);
2806 }
2807
2808 resolved.push(ComponentData {
2809 container: math::SimdBuffer::new(mapped),
2810 bit_depth: column_info.bit_depth,
2811 });
2812 }
2813 }
2814 }
2815
2816 Ok(resolved)
2817}
2818
2819#[inline(always)]
2820fn cielab_to_rgb<S: Simd>(
2821 simd: S,
2822 components: &mut [ComponentData],
2823 bit_depth: u8,
2824 lab: &CieLab,
2825) -> Result<()> {
2826 let (head, _) = components
2827 .split_at_mut_checked(3)
2828 .ok_or(ColorError::LabConversionFailed)?;
2829
2830 let [l, a, b] = head else {
2831 bail!(ColorError::LabConversionFailed);
2832 };
2833
2834 let prec0 = l.bit_depth;
2835 let prec1 = a.bit_depth;
2836 let prec2 = b.bit_depth;
2837
2838 if prec0 < 4 || prec1 < 4 || prec2 < 4 {
2840 bail!(ColorError::LabConversionFailed);
2841 }
2842
2843 let rl = lab.rl.unwrap_or(100);
2844 let ra = lab.ra.unwrap_or(170);
2845 let rb = lab.ra.unwrap_or(200);
2846 let ol = lab.ol.unwrap_or(0);
2847 let oa = lab.oa.unwrap_or(1 << (bit_depth - 1));
2848 let ob = lab
2849 .ob
2850 .unwrap_or((1 << (bit_depth - 2)) + (1 << (bit_depth - 3)));
2851
2852 let min_l = -(rl as f32 * ol as f32) / ((1 << prec0) - 1) as f32;
2854 let max_l = min_l + rl as f32;
2855 let min_a = -(ra as f32 * oa as f32) / ((1 << prec1) - 1) as f32;
2856 let max_a = min_a + ra as f32;
2857 let min_b = -(rb as f32 * ob as f32) / ((1 << prec2) - 1) as f32;
2858 let max_b = min_b + rb as f32;
2859
2860 let bit_max = (1_u32 << bit_depth) - 1;
2861
2862 let divisor_l = ((1 << prec0) - 1) as f32;
2866 let divisor_a = ((1 << prec1) - 1) as f32;
2867 let divisor_b = ((1 << prec2) - 1) as f32;
2868
2869 let scale_l_final = bit_max as f32 / 100.0;
2870 let scale_ab_final = bit_max as f32 / 255.0;
2871
2872 let l_offset = min_l * scale_l_final;
2873 let l_scale = (max_l - min_l) / divisor_l * scale_l_final;
2874 let a_offset = (min_a + 128.0) * scale_ab_final;
2875 let a_scale = (max_a - min_a) / divisor_a * scale_ab_final;
2876 let b_offset = (min_b + 128.0) * scale_ab_final;
2877 let b_scale = (max_b - min_b) / divisor_b * scale_ab_final;
2878
2879 let l_offset_v = f32x8::splat(simd, l_offset);
2880 let l_scale_v = f32x8::splat(simd, l_scale);
2881 let a_offset_v = f32x8::splat(simd, a_offset);
2882 let a_scale_v = f32x8::splat(simd, a_scale);
2883 let b_offset_v = f32x8::splat(simd, b_offset);
2884 let b_scale_v = f32x8::splat(simd, b_scale);
2885
2886 for ((l_chunk, a_chunk), b_chunk) in l
2890 .container
2891 .chunks_exact_mut(SIMD_WIDTH)
2892 .zip(a.container.chunks_exact_mut(SIMD_WIDTH))
2893 .zip(b.container.chunks_exact_mut(SIMD_WIDTH))
2894 {
2895 let l_v = f32x8::from_slice(simd, l_chunk);
2896 let a_v = f32x8::from_slice(simd, a_chunk);
2897 let b_v = f32x8::from_slice(simd, b_chunk);
2898
2899 l_v.mul_add(l_scale_v, l_offset_v).store(l_chunk);
2900 a_v.mul_add(a_scale_v, a_offset_v).store(a_chunk);
2901 b_v.mul_add(b_scale_v, b_offset_v).store(b_chunk);
2902 }
2903
2904 Ok(())
2905}
2906
2907#[inline(always)]
2908fn sycc_to_rgb<S: Simd>(simd: S, components: &mut [ComponentData], bit_depth: u8) -> Result<()> {
2909 let offset = (1_u32 << (bit_depth as u32 - 1)) as f32;
2910 let max_value = ((1_u32 << bit_depth as u32) - 1) as f32;
2911
2912 let (head, _) = components
2913 .split_at_mut_checked(3)
2914 .ok_or(ColorError::SyccConversionFailed)?;
2915
2916 let [y, cb, cr] = head else {
2917 bail!(ColorError::SyccConversionFailed);
2918 };
2919
2920 let offset_v = f32x8::splat(simd, offset);
2921 let max_v = f32x8::splat(simd, max_value);
2922 let zero_v = f32x8::splat(simd, 0.0);
2923 let cr_to_r = f32x8::splat(simd, 1.402);
2924 let cb_to_g = f32x8::splat(simd, -0.344136);
2925 let cr_to_g = f32x8::splat(simd, -0.714136);
2926 let cb_to_b = f32x8::splat(simd, 1.772);
2927
2928 for ((y_chunk, cb_chunk), cr_chunk) in y
2929 .container
2930 .chunks_exact_mut(SIMD_WIDTH)
2931 .zip(cb.container.chunks_exact_mut(SIMD_WIDTH))
2932 .zip(cr.container.chunks_exact_mut(SIMD_WIDTH))
2933 {
2934 let y_v = f32x8::from_slice(simd, y_chunk);
2935 let cb_v = f32x8::from_slice(simd, cb_chunk) - offset_v;
2936 let cr_v = f32x8::from_slice(simd, cr_chunk) - offset_v;
2937
2938 let r = cr_v.mul_add(cr_to_r, y_v);
2940 let g = cr_v.mul_add(cr_to_g, cb_v.mul_add(cb_to_g, y_v));
2942 let b = cb_v.mul_add(cb_to_b, y_v);
2944
2945 r.min(max_v).max(zero_v).store(y_chunk);
2946 g.min(max_v).max(zero_v).store(cb_chunk);
2947 b.min(max_v).max(zero_v).store(cr_chunk);
2948 }
2949
2950 Ok(())
2951}
2952
2953#[cfg(test)]
2954mod tests {
2955 use super::*;
2956
2957 #[test]
2958 fn ht_uvlc_encode_table_bytes_match_entry_packing_order() {
2959 let entries = ht_uvlc_encode_table();
2960 let bytes = ht_uvlc_encode_table_bytes();
2961
2962 assert_eq!(bytes.len(), entries.len() * 6);
2963 for (index, entry) in entries.iter().enumerate() {
2964 let offset = index * 6;
2965 assert_eq!(
2966 &bytes[offset..offset + 6],
2967 &[
2968 entry.pre,
2969 entry.pre_len,
2970 entry.suf,
2971 entry.suf_len,
2972 entry.ext,
2973 entry.ext_len
2974 ],
2975 );
2976 }
2977 }
2978
2979 #[test]
2980 fn roi_maxshift_inverse_preserves_background_and_unshifts_roi_coefficients() {
2981 assert_eq!(apply_roi_maxshift_inverse_i32(127, 7), 127);
2982 assert_eq!(apply_roi_maxshift_inverse_i32(-127, 7), -127);
2983 assert_eq!(apply_roi_maxshift_inverse_i32(128, 7), 1);
2984 assert_eq!(apply_roi_maxshift_inverse_i32(-128, 7), -1);
2985 assert_eq!(apply_roi_maxshift_inverse_i32(255, 7), 1);
2986 assert_eq!(apply_roi_maxshift_inverse_i32(-255, 7), -1);
2987 assert_eq!(apply_roi_maxshift_inverse_i32(256, 7), 2);
2988 assert_eq!(apply_roi_maxshift_inverse_i32(-256, 7), -2);
2989 assert_eq!(apply_roi_maxshift_inverse_i32(42, 0), 42);
2990 }
2991
2992 #[test]
2993 fn classic_scalar_decode_applies_nonzero_roi_maxshift() {
2994 let roi_shift = 3;
2995 let total_bitplanes = 3;
2996 let style = J2kCodeBlockStyle {
2997 selective_arithmetic_coding_bypass: false,
2998 reset_context_probabilities: false,
2999 termination_on_each_pass: false,
3000 vertically_causal_context: false,
3001 segmentation_symbols: false,
3002 };
3003 let coded_coefficients = [0, 5, 1 << roi_shift, -(2 << roi_shift)];
3004 let encoded = encode_j2k_code_block_scalar_with_style(
3005 &coded_coefficients,
3006 2,
3007 2,
3008 J2kSubBandType::LowLow,
3009 total_bitplanes + roi_shift,
3010 style,
3011 )
3012 .expect("encode ROI-shifted code block");
3013 let job = J2kCodeBlockDecodeJob {
3014 data: &encoded.data,
3015 segments: &encoded.segments,
3016 width: 2,
3017 height: 2,
3018 output_stride: 2,
3019 missing_bit_planes: encoded.missing_bit_planes,
3020 number_of_coding_passes: encoded.number_of_coding_passes,
3021 total_bitplanes,
3022 roi_shift,
3023 sub_band_type: J2kSubBandType::LowLow,
3024 style,
3025 strict: true,
3026 dequantization_step: 1.0,
3027 };
3028 let mut output = [0.0; 4];
3029
3030 decode_j2k_code_block_scalar(job, &mut output).expect("decode ROI-shifted code block");
3031
3032 assert_eq!(output, [0.0, 5.0, 1.0, -2.0]);
3033 }
3034
3035 #[test]
3036 fn classic_scalar_token_pack_matches_scalar_single_cleanup_block() {
3037 let style = J2kCodeBlockStyle {
3038 selective_arithmetic_coding_bypass: true,
3039 reset_context_probabilities: false,
3040 termination_on_each_pass: false,
3041 vertically_causal_context: false,
3042 segmentation_symbols: false,
3043 };
3044 let scalar =
3045 encode_j2k_code_block_scalar_with_style(&[1], 1, 1, J2kSubBandType::LowLow, 1, style)
3046 .expect("encode scalar");
3047 let token_bytes = pack_mq_test_tokens(&[(0, 1), (9, 0)]);
3048 let packed = pack_j2k_code_block_scalar_from_tier1_tokens(
3049 &token_bytes,
3050 &[J2kTier1TokenSegment {
3051 token_bit_offset: 0,
3052 token_bit_count: 12,
3053 start_coding_pass: 0,
3054 end_coding_pass: 1,
3055 use_arithmetic: true,
3056 }],
3057 scalar.number_of_coding_passes,
3058 scalar.missing_bit_planes,
3059 )
3060 .expect("pack tokens");
3061
3062 assert_eq!(packed.data, scalar.data);
3063 assert_eq!(packed.segments, scalar.segments);
3064 assert_eq!(
3065 packed.number_of_coding_passes,
3066 scalar.number_of_coding_passes
3067 );
3068 assert_eq!(packed.missing_bit_planes, scalar.missing_bit_planes);
3069 }
3070
3071 fn pack_mq_test_tokens(tokens: &[(u8, u8)]) -> Vec<u8> {
3072 let mut bytes = Vec::new();
3073 let mut current = 0u8;
3074 let mut bits = 0u8;
3075 for &(ctx, bit) in tokens {
3076 let value = (ctx & 0x1F) | ((bit & 1) << 5);
3077 for shift in (0..6).rev() {
3078 current = (current << 1) | ((value >> shift) & 1);
3079 bits += 1;
3080 if bits == 8 {
3081 bytes.push(current);
3082 current = 0;
3083 bits = 0;
3084 }
3085 }
3086 }
3087 if bits != 0 {
3088 bytes.push(current << (8 - bits));
3089 }
3090 bytes
3091 }
3092
3093 #[test]
3094 fn classic_scalar_profiled_decode_matches_unprofiled_decode() {
3095 let width = 64u32;
3096 let height = 64u32;
3097 let sample_count = width as usize * height as usize;
3098 let total_bitplanes = 12;
3099 let style = J2kCodeBlockStyle {
3100 selective_arithmetic_coding_bypass: false,
3101 reset_context_probabilities: false,
3102 termination_on_each_pass: false,
3103 vertically_causal_context: false,
3104 segmentation_symbols: false,
3105 };
3106 let coefficients = (0..sample_count)
3107 .map(|idx| {
3108 let value = i32::try_from((idx * 37) % 4095).expect("sample value fits i32") - 2048;
3109 if idx % 17 == 0 {
3110 0
3111 } else {
3112 value
3113 }
3114 })
3115 .collect::<Vec<_>>();
3116 let encoded = encode_j2k_code_block_scalar_with_style(
3117 &coefficients,
3118 width,
3119 height,
3120 J2kSubBandType::LowLow,
3121 total_bitplanes,
3122 style,
3123 )
3124 .expect("encode classic block");
3125 let job = J2kCodeBlockDecodeJob {
3126 data: &encoded.data,
3127 segments: &encoded.segments,
3128 width,
3129 height,
3130 output_stride: width as usize,
3131 missing_bit_planes: encoded.missing_bit_planes,
3132 number_of_coding_passes: encoded.number_of_coding_passes,
3133 total_bitplanes,
3134 roi_shift: 0,
3135 sub_band_type: J2kSubBandType::LowLow,
3136 style,
3137 strict: true,
3138 dequantization_step: 1.0,
3139 };
3140 let mut expected = vec![0.0_f32; sample_count];
3141 let mut actual = vec![0.0_f32; sample_count];
3142 let mut profile = J2kCodeBlockDecodeProfile::default();
3143
3144 decode_j2k_code_block_scalar(job, &mut expected).expect("unprofiled classic decode");
3145 decode_j2k_code_block_scalar_profiled(job, &mut actual, &mut profile)
3146 .expect("profiled classic decode");
3147
3148 assert_eq!(actual, expected);
3149 assert!(profile.cleanup_us > 0);
3150 }
3151
3152 #[test]
3153 fn classic_scalar_workspace_reuse_matches_fresh_decode() {
3154 let total_bitplanes = 6;
3155 let style = J2kCodeBlockStyle {
3156 selective_arithmetic_coding_bypass: false,
3157 reset_context_probabilities: false,
3158 termination_on_each_pass: false,
3159 vertically_causal_context: false,
3160 segmentation_symbols: false,
3161 };
3162 let mut workspace = J2kCodeBlockDecodeWorkspace::default();
3163
3164 for (width, height, seed) in [(8, 8, 0x31), (4, 16, 0x47)] {
3165 let coefficients = (0..width * height)
3166 .map(|idx| {
3167 let value = ((idx as i32 * seed) % 23) - 11;
3168 if idx % 7 == 0 {
3169 0
3170 } else {
3171 value
3172 }
3173 })
3174 .collect::<Vec<_>>();
3175 let encoded = encode_j2k_code_block_scalar_with_style(
3176 &coefficients,
3177 width,
3178 height,
3179 J2kSubBandType::LowLow,
3180 total_bitplanes,
3181 style,
3182 )
3183 .expect("encode classic block");
3184 let job = J2kCodeBlockDecodeJob {
3185 data: &encoded.data,
3186 segments: &encoded.segments,
3187 width,
3188 height,
3189 output_stride: width as usize,
3190 missing_bit_planes: encoded.missing_bit_planes,
3191 number_of_coding_passes: encoded.number_of_coding_passes,
3192 total_bitplanes,
3193 roi_shift: 0,
3194 sub_band_type: J2kSubBandType::LowLow,
3195 style,
3196 strict: true,
3197 dequantization_step: 1.0,
3198 };
3199 let mut fresh = vec![0.0_f32; width as usize * height as usize];
3200 let mut reused = vec![0.0_f32; width as usize * height as usize];
3201
3202 decode_j2k_code_block_scalar(job, &mut fresh).expect("fresh classic decode");
3203 decode_j2k_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
3204 .expect("workspace classic decode");
3205
3206 assert_eq!(reused, fresh);
3207 }
3208 }
3209
3210 #[test]
3211 fn scalar_packetization_rejects_overflowing_ht_refinement_lengths_without_panic() {
3212 let payload = [0x12];
3213 let block = J2kPacketizationCodeBlock {
3214 data: &payload,
3215 ht_cleanup_length: u32::MAX,
3216 ht_refinement_length: 1,
3217 num_coding_passes: 3,
3218 num_zero_bitplanes: 2,
3219 previously_included: false,
3220 l_block: 3,
3221 block_coding_mode: J2kPacketizationBlockCodingMode::HighThroughput,
3222 };
3223 let subband = J2kPacketizationSubband {
3224 code_blocks: vec![block],
3225 num_cbs_x: 1,
3226 num_cbs_y: 1,
3227 };
3228 let resolution = J2kPacketizationResolution {
3229 subbands: vec![subband],
3230 };
3231 let resolutions = [resolution];
3232 let job = J2kPacketizationEncodeJob {
3233 resolution_count: 1,
3234 num_layers: 1,
3235 num_components: 1,
3236 code_block_count: 1,
3237 progression_order: J2kPacketizationProgressionOrder::Lrcp,
3238 packet_descriptors: &[],
3239 resolutions: &resolutions,
3240 };
3241
3242 let err = encode_j2k_packetization_scalar(job)
3243 .expect_err("overflowing HT packetization segment lengths rejected");
3244
3245 assert_eq!(err, "multi-pass HTJ2K packet contribution length overflow");
3246 }
3247
3248 #[derive(Default)]
3249 struct DecodeWorkCounter {
3250 classic_code_blocks: usize,
3251 ht_code_blocks: usize,
3252 idwt_output_samples: usize,
3253 }
3254
3255 impl DecodeWorkCounter {
3256 fn code_blocks(&self) -> usize {
3257 self.classic_code_blocks + self.ht_code_blocks
3258 }
3259 }
3260
3261 struct FailingHtDecoder {
3262 called: bool,
3263 }
3264
3265 impl HtCodeBlockDecoder for FailingHtDecoder {
3266 fn decode_code_block(
3267 &mut self,
3268 _job: HtCodeBlockDecodeJob<'_>,
3269 _output: &mut [f32],
3270 ) -> Result<()> {
3271 self.called = true;
3272 Err(DecodingError::CodeBlockDecodeFailure.into())
3273 }
3274 }
3275
3276 struct FailingClassicDecoder {
3277 called: bool,
3278 }
3279
3280 impl HtCodeBlockDecoder for FailingClassicDecoder {
3281 fn decode_code_block(
3282 &mut self,
3283 _job: HtCodeBlockDecodeJob<'_>,
3284 _output: &mut [f32],
3285 ) -> Result<()> {
3286 panic!("HT hook must not be used for classic J2K test")
3287 }
3288
3289 fn decode_j2k_code_block(
3290 &mut self,
3291 _job: J2kCodeBlockDecodeJob<'_>,
3292 _output: &mut [f32],
3293 ) -> Result<bool> {
3294 self.called = true;
3295 Err(DecodingError::CodeBlockDecodeFailure.into())
3296 }
3297 }
3298
3299 struct FailingClassicBatchDecoder {
3300 called: bool,
3301 }
3302
3303 #[derive(Default)]
3304 struct CapturingHtDecoder {
3305 called: bool,
3306 blocks: usize,
3307 refinement_jobs: usize,
3308 max_coding_passes: u8,
3309 }
3310
3311 impl HtCodeBlockDecoder for CapturingHtDecoder {
3312 fn decode_code_block(
3313 &mut self,
3314 job: HtCodeBlockDecodeJob<'_>,
3315 output: &mut [f32],
3316 ) -> Result<()> {
3317 self.called = true;
3318 self.blocks += 1;
3319 self.max_coding_passes = self.max_coding_passes.max(job.number_of_coding_passes);
3320 if job.refinement_length > 0 {
3321 self.refinement_jobs += 1;
3322 assert!(
3323 job.number_of_coding_passes > 1,
3324 "refinement bytes must correspond to refinement coding passes"
3325 );
3326 }
3327
3328 decode_ht_code_block_scalar(job, output)
3329 }
3330 }
3331
3332 #[derive(Clone)]
3333 struct CapturedHtDecodeJob {
3334 data: Vec<u8>,
3335 cleanup_length: u32,
3336 refinement_length: u32,
3337 width: u32,
3338 height: u32,
3339 output_stride: usize,
3340 missing_bit_planes: u8,
3341 number_of_coding_passes: u8,
3342 num_bitplanes: u8,
3343 roi_shift: u8,
3344 stripe_causal: bool,
3345 strict: bool,
3346 dequantization_step: f32,
3347 }
3348
3349 impl CapturedHtDecodeJob {
3350 fn from_job(job: HtCodeBlockDecodeJob<'_>) -> Self {
3351 Self {
3352 data: job.data.to_vec(),
3353 cleanup_length: job.cleanup_length,
3354 refinement_length: job.refinement_length,
3355 width: job.width,
3356 height: job.height,
3357 output_stride: job.output_stride,
3358 missing_bit_planes: job.missing_bit_planes,
3359 number_of_coding_passes: job.number_of_coding_passes,
3360 num_bitplanes: job.num_bitplanes,
3361 roi_shift: job.roi_shift,
3362 stripe_causal: job.stripe_causal,
3363 strict: job.strict,
3364 dequantization_step: job.dequantization_step,
3365 }
3366 }
3367
3368 fn borrowed(&self) -> HtCodeBlockDecodeJob<'_> {
3369 HtCodeBlockDecodeJob {
3370 data: &self.data,
3371 cleanup_length: self.cleanup_length,
3372 refinement_length: self.refinement_length,
3373 width: self.width,
3374 height: self.height,
3375 output_stride: self.output_stride,
3376 missing_bit_planes: self.missing_bit_planes,
3377 number_of_coding_passes: self.number_of_coding_passes,
3378 num_bitplanes: self.num_bitplanes,
3379 roi_shift: self.roi_shift,
3380 stripe_causal: self.stripe_causal,
3381 strict: self.strict,
3382 dequantization_step: self.dequantization_step,
3383 }
3384 }
3385 }
3386
3387 #[derive(Default)]
3388 struct FirstHtJobDecoder {
3389 job: Option<CapturedHtDecodeJob>,
3390 }
3391
3392 impl HtCodeBlockDecoder for FirstHtJobDecoder {
3393 fn decode_code_block(
3394 &mut self,
3395 job: HtCodeBlockDecodeJob<'_>,
3396 output: &mut [f32],
3397 ) -> Result<()> {
3398 if self.job.is_none() {
3399 self.job = Some(CapturedHtDecodeJob::from_job(job));
3400 }
3401 decode_ht_code_block_scalar(job, output)
3402 }
3403 }
3404
3405 struct ZeroRefinementHtDecoder;
3406
3407 impl HtCodeBlockDecoder for ZeroRefinementHtDecoder {
3408 fn decode_code_block(
3409 &mut self,
3410 job: HtCodeBlockDecodeJob<'_>,
3411 output: &mut [f32],
3412 ) -> Result<()> {
3413 let mut data = job.data.to_vec();
3414 let cleanup_len = job.cleanup_length as usize;
3415 let refinement_len = job.refinement_length as usize;
3416 data[cleanup_len..cleanup_len + refinement_len].fill(0);
3417 let zeroed = HtCodeBlockDecodeJob { data: &data, ..job };
3418
3419 decode_ht_code_block_scalar(zeroed, output)
3420 }
3421 }
3422
3423 #[derive(Default)]
3424 struct CleanupLimitedHtDecoder {
3425 blocks: usize,
3426 refinement_blocks: usize,
3427 cleanup_bytes: usize,
3428 refinement_bytes: usize,
3429 }
3430
3431 impl HtCodeBlockDecoder for CleanupLimitedHtDecoder {
3432 fn decode_code_block(
3433 &mut self,
3434 job: HtCodeBlockDecodeJob<'_>,
3435 output: &mut [f32],
3436 ) -> Result<()> {
3437 self.blocks += 1;
3438 self.cleanup_bytes += job.cleanup_length as usize;
3439 if job.refinement_length > 0 {
3440 self.refinement_blocks += 1;
3441 self.refinement_bytes += job.refinement_length as usize;
3442 }
3443
3444 decode_ht_code_block_scalar_until_phase(
3445 job,
3446 output,
3447 HtCodeBlockDecodePhaseLimit::Cleanup,
3448 )
3449 }
3450 }
3451
3452 impl HtCodeBlockDecoder for FailingClassicBatchDecoder {
3453 fn decode_code_block(
3454 &mut self,
3455 _job: HtCodeBlockDecodeJob<'_>,
3456 _output: &mut [f32],
3457 ) -> Result<()> {
3458 panic!("HT hook must not be used for classic J2K batch test")
3459 }
3460
3461 fn decode_j2k_code_block(
3462 &mut self,
3463 _job: J2kCodeBlockDecodeJob<'_>,
3464 _output: &mut [f32],
3465 ) -> Result<bool> {
3466 panic!(
3467 "per-block classic hook must not be used when the batch hook handles the sub-band"
3468 )
3469 }
3470
3471 fn decode_j2k_sub_band(
3472 &mut self,
3473 _job: J2kSubBandDecodeJob<'_>,
3474 _output: &mut [f32],
3475 ) -> Result<bool> {
3476 self.called = true;
3477 Err(DecodingError::CodeBlockDecodeFailure.into())
3478 }
3479 }
3480
3481 fn fixture() -> Vec<u8> {
3482 let pixels = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
3483 let options = EncodeOptions {
3484 reversible: true,
3485 num_decomposition_levels: 1,
3486 ..EncodeOptions::default()
3487 };
3488 encode(&pixels, 2, 2, 3, 8, false, &options).expect("encode")
3489 }
3490
3491 #[test]
3492 fn decode_into_rejects_short_output_buffer() {
3493 let bytes = fixture();
3494 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3495 let mut context = DecoderContext::default();
3496 let mut output = vec![0; 11];
3497
3498 let err = image
3499 .decode_into(&mut output, &mut context)
3500 .expect_err("short output buffer must be rejected");
3501
3502 assert_eq!(
3503 err,
3504 DecodeError::Decoding(DecodingError::OutputBufferTooSmall)
3505 );
3506 }
3507
3508 fn fixture_multi_block() -> Vec<u8> {
3509 let pixels: Vec<u8> = (0..64).collect();
3510 let options = EncodeOptions {
3511 reversible: true,
3512 num_decomposition_levels: 0,
3513 code_block_width_exp: 0,
3514 code_block_height_exp: 0,
3515 ..EncodeOptions::default()
3516 };
3517 encode(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block classic")
3518 }
3519
3520 fn fixture_gray() -> Vec<u8> {
3521 let pixels: Vec<u8> = (0..16).collect();
3522 let options = EncodeOptions {
3523 reversible: true,
3524 num_decomposition_levels: 1,
3525 ..EncodeOptions::default()
3526 };
3527 encode(&pixels, 4, 4, 1, 8, false, &options).expect("encode classic gray8")
3528 }
3529
3530 fn rewrite_siz_to_single_large_tile(codestream: &mut [u8], dimensions: u32) {
3531 let siz = codestream
3532 .windows(2)
3533 .position(|w| w == [0xFF, 0x51])
3534 .expect("SIZ marker");
3535 codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.to_be_bytes());
3536 codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.to_be_bytes());
3537 codestream[siz + 22..siz + 26].copy_from_slice(&dimensions.to_be_bytes());
3538 codestream[siz + 26..siz + 30].copy_from_slice(&dimensions.to_be_bytes());
3539 }
3540
3541 fn rewrite_siz_tile_grid(codestream: &mut [u8], dimensions: (u32, u32), tile_size: (u32, u32)) {
3542 let siz = codestream
3543 .windows(2)
3544 .position(|w| w == [0xFF, 0x51])
3545 .expect("SIZ marker");
3546 codestream[siz + 6..siz + 10].copy_from_slice(&dimensions.0.to_be_bytes());
3547 codestream[siz + 10..siz + 14].copy_from_slice(&dimensions.1.to_be_bytes());
3548 codestream[siz + 22..siz + 26].copy_from_slice(&tile_size.0.to_be_bytes());
3549 codestream[siz + 26..siz + 30].copy_from_slice(&tile_size.1.to_be_bytes());
3550 }
3551
3552 fn rewrite_siz_component_count(codestream: &mut Vec<u8>, component_count: u16) {
3553 let siz = codestream
3554 .windows(2)
3555 .position(|w| w == [0xFF, 0x51])
3556 .expect("SIZ marker");
3557 let old_component_count =
3558 u16::from_be_bytes([codestream[siz + 38], codestream[siz + 39]]) as usize;
3559 let component_start = siz + 40;
3560 let component_end = component_start + old_component_count * 3;
3561 let descriptor = codestream[component_start..component_start + 3].to_vec();
3562 let mut descriptors = Vec::with_capacity(usize::from(component_count) * 3);
3563 for _ in 0..component_count {
3564 descriptors.extend_from_slice(&descriptor);
3565 }
3566
3567 let siz_len = 38_u16
3568 .checked_add(
3569 component_count
3570 .checked_mul(3)
3571 .expect("SIZ component bytes fit"),
3572 )
3573 .expect("SIZ length fits");
3574 codestream[siz + 2..siz + 4].copy_from_slice(&siz_len.to_be_bytes());
3575 codestream[siz + 38..siz + 40].copy_from_slice(&component_count.to_be_bytes());
3576 codestream.splice(component_start..component_end, descriptors);
3577 }
3578
3579 #[test]
3580 fn inspect_rejects_component_count_above_j2k_spec_cap() {
3581 let mut bytes = fixture_gray();
3582 rewrite_siz_component_count(&mut bytes, MAX_J2K_SPEC_COMPONENTS + 1);
3583
3584 let err = inspect_j2k_codestream_header(&bytes)
3585 .expect_err("SIZ component count above spec cap must be rejected");
3586
3587 assert_eq!(
3588 err,
3589 J2kCodestreamHeaderError::InvalidSiz {
3590 what: "component count exceeds JPEG 2000 limit"
3591 }
3592 );
3593 }
3594
3595 #[test]
3596 fn native_decode_rejects_component_count_above_u8_before_bitmap_truncation() {
3597 let mut bytes = fixture_gray();
3598 rewrite_siz_component_count(&mut bytes, MAX_NATIVE_DECODE_COMPONENTS + 1);
3599
3600 let err = match Image::new(&bytes, &DecodeSettings::default()) {
3601 Err(err) => err,
3602 Ok(_) => {
3603 panic!("native decode must reject component counts that cannot fit RawBitmap")
3604 }
3605 };
3606
3607 assert_eq!(
3608 err,
3609 DecodeError::Validation(ValidationError::TooManyChannels)
3610 );
3611 }
3612
3613 #[test]
3614 fn tile_parse_rejects_component_tile_structural_bomb_before_allocation() {
3615 let mut bytes = fixture_gray();
3616 rewrite_siz_component_count(&mut bytes, MAX_NATIVE_DECODE_COMPONENTS);
3617 rewrite_siz_tile_grid(&mut bytes, (256, 256), (1, 1));
3618 let parsed = j2c::parse_raw(&bytes, &DecodeSettings::default()).expect("raw header parses");
3619 let mut context = j2c::DecoderContext::default();
3620 let mut ht_decoder: Option<&mut dyn HtCodeBlockDecoder> = None;
3621
3622 let err = j2c::decode(parsed.data, &parsed.header, &mut context, &mut ht_decoder)
3623 .expect_err("tile structural budget must reject before tile allocation");
3624
3625 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3626 }
3627
3628 #[test]
3629 fn owned_decode_rejects_large_siz_before_allocating_output() {
3630 let mut bytes = fixture_gray();
3631 rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
3632 let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
3633
3634 let err = match image.decode() {
3635 Err(err) => err,
3636 Ok(_) => panic!("large owned decode must be capped"),
3637 };
3638
3639 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3640 }
3641
3642 #[test]
3643 fn decode_into_rejects_large_siz_before_allocating_component_storage() {
3644 let mut bytes = fixture_gray();
3645 rewrite_siz_to_single_large_tile(&mut bytes, 60_000);
3646 let image = Image::new(&bytes, &DecodeSettings::default()).expect("large SIZ parses");
3647 let mut context = DecoderContext::default();
3648 let mut out = [];
3649
3650 let err = match image.decode_into(&mut out, &mut context) {
3651 Err(err) => err,
3652 Ok(_) => panic!("component storage must be capped before allocation"),
3653 };
3654
3655 assert_eq!(err, DecodeError::Validation(ValidationError::ImageTooLarge));
3656 }
3657
3658 fn fixture_ht_gray() -> Vec<u8> {
3659 let pixels: Vec<u8> = (0..16).collect();
3660 let options = EncodeOptions {
3661 reversible: true,
3662 num_decomposition_levels: 1,
3663 ..EncodeOptions::default()
3664 };
3665 encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht gray8")
3666 }
3667
3668 fn fixture_ht_multi_block() -> Vec<u8> {
3669 let pixels: Vec<u8> = (0..64).collect();
3670 let options = EncodeOptions {
3671 reversible: true,
3672 num_decomposition_levels: 0,
3673 code_block_width_exp: 0,
3674 code_block_height_exp: 0,
3675 ..EncodeOptions::default()
3676 };
3677 encode_htj2k(&pixels, 8, 8, 1, 8, false, &options).expect("encode multi-block HT gray8")
3678 }
3679
3680 fn fixture_ht_rgb_multi_block() -> Vec<u8> {
3681 let pixels = gradient_pixels(8, 8, 3);
3682 let options = EncodeOptions {
3683 reversible: true,
3684 num_decomposition_levels: 0,
3685 code_block_width_exp: 0,
3686 code_block_height_exp: 0,
3687 ..EncodeOptions::default()
3688 };
3689 encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode multi-block HT RGB8")
3690 }
3691
3692 fn direct_ht_job_count(plan: &J2kDirectGrayscalePlan) -> usize {
3693 plan.steps
3694 .iter()
3695 .map(|step| match step {
3696 J2kDirectGrayscaleStep::HtSubBand(sub_band) => sub_band.jobs.len(),
3697 _ => 0,
3698 })
3699 .sum()
3700 }
3701
3702 fn direct_color_ht_job_count(plan: &J2kDirectColorPlan) -> usize {
3703 plan.component_plans.iter().map(direct_ht_job_count).sum()
3704 }
3705
3706 fn fixture_openhtj2k_ht_refinement() -> &'static [u8] {
3707 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.j2k")
3708 }
3709
3710 fn fixture_openhtj2k_ht_refinement_pixels() -> &'static [u8] {
3711 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_12_b11.gray")
3712 }
3713
3714 fn fixture_openhtj2k_ht_refinement_odd() -> &'static [u8] {
3715 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.j2k")
3716 }
3717
3718 fn fixture_openhtj2k_ht_refinement_odd_pixels() -> &'static [u8] {
3719 include_bytes!("../fixtures/htj2k/openhtj2k_ds0_ht_09_b11.gray")
3720 }
3721
3722 fn gradient_pixels(width: u32, height: u32, components: u8) -> Vec<u8> {
3723 let mut pixels = Vec::with_capacity(width as usize * height as usize * components as usize);
3724 for y in 0..height {
3725 for x in 0..width {
3726 for component in 0..components {
3727 pixels.push(((x * 3 + y * 5 + u32::from(component) * 41) & 0xff) as u8);
3728 }
3729 }
3730 }
3731 pixels
3732 }
3733
3734 fn roi_fixture(classic: bool, components: u8) -> Vec<u8> {
3735 let width = 64;
3736 let height = 64;
3737 let pixels = gradient_pixels(width, height, components);
3738 let options = EncodeOptions {
3739 reversible: true,
3740 num_decomposition_levels: 2,
3741 code_block_width_exp: 0,
3742 code_block_height_exp: 0,
3743 ..EncodeOptions::default()
3744 };
3745 if classic {
3746 encode(&pixels, width, height, components, 8, false, &options)
3747 .expect("encode ROI classic fixture")
3748 } else {
3749 encode_htj2k(&pixels, width, height, components, 8, false, &options)
3750 .expect("encode ROI HT fixture")
3751 }
3752 }
3753
3754 fn crop_interleaved(
3755 full: &[u8],
3756 full_width: u32,
3757 channels: usize,
3758 roi: (u32, u32, u32, u32),
3759 ) -> Vec<u8> {
3760 let (x, y, width, height) = roi;
3761 let mut out = Vec::with_capacity(width as usize * height as usize * channels);
3762 let row_bytes = full_width as usize * channels;
3763 let roi_row_bytes = width as usize * channels;
3764 for row in y as usize..(y + height) as usize {
3765 let start = row * row_bytes + x as usize * channels;
3766 out.extend_from_slice(&full[start..start + roi_row_bytes]);
3767 }
3768 out
3769 }
3770
3771 fn count_decode_work(bytes: &[u8], roi: Option<(u32, u32, u32, u32)>) -> DecodeWorkCounter {
3772 let image = Image::new(bytes, &DecodeSettings::default()).expect("image");
3773 let mut context = DecoderContext::default();
3774 match roi {
3775 Some(roi) => {
3776 image
3777 .decode_region_with_context(roi, &mut context)
3778 .expect("region decode with counter");
3779 }
3780 None => {
3781 image
3782 .decode_with_context(&mut context)
3783 .expect("full decode with counter");
3784 }
3785 }
3786 let counters = context.tile_decode_context.debug_counters;
3787 DecodeWorkCounter {
3788 classic_code_blocks: counters.decoded_code_blocks,
3789 ht_code_blocks: 0,
3790 idwt_output_samples: counters.idwt_output_samples,
3791 }
3792 }
3793
3794 #[test]
3795 fn roi_decode_matches_full_crop_for_classic_and_htj2k_gray_and_rgb() {
3796 let cases = [
3797 (true, 1_u8, true, false),
3798 (true, 3_u8, false, false),
3799 (false, 1_u8, true, false),
3800 (false, 3_u8, false, false),
3801 ];
3802 let rois = [
3803 (20, 18, 17, 19),
3804 (0, 0, 9, 11),
3805 (63, 63, 1, 1),
3806 (7, 5, 13, 9),
3807 (0, 0, 64, 64),
3808 ];
3809
3810 for (classic, components, expect_gray, has_alpha) in cases {
3811 let bytes = roi_fixture(classic, components);
3812 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3813 let full = image.decode().expect("full decode");
3814 let channels = components as usize;
3815 for roi in rois {
3816 let region = image.decode_region(roi).expect("region decode");
3817 assert_eq!(matches!(region.color_space, ColorSpace::Gray), expect_gray);
3818 assert_eq!(region.has_alpha, has_alpha);
3819 assert_eq!(
3820 region.data,
3821 crop_interleaved(&full, 64, channels, roi),
3822 "classic={classic} components={components} roi={roi:?}"
3823 );
3824 }
3825 }
3826 }
3827
3828 #[test]
3829 fn roi_decode_prunes_code_blocks_and_idwt_work_for_classic_and_htj2k() {
3830 let roi = (48, 48, 16, 16);
3831 for classic in [true, false] {
3832 let bytes = {
3833 let pixels = gradient_pixels(128, 128, 1);
3834 let options = EncodeOptions {
3835 reversible: true,
3836 num_decomposition_levels: 3,
3837 code_block_width_exp: 0,
3838 code_block_height_exp: 0,
3839 ..EncodeOptions::default()
3840 };
3841 if classic {
3842 encode(&pixels, 128, 128, 1, 8, false, &options)
3843 .expect("encode classic work fixture")
3844 } else {
3845 encode_htj2k(&pixels, 128, 128, 1, 8, false, &options)
3846 .expect("encode ht work fixture")
3847 }
3848 };
3849 let full = count_decode_work(&bytes, None);
3850 let region = count_decode_work(&bytes, Some(roi));
3851
3852 assert!(
3853 region.code_blocks() > 0 && region.code_blocks() < full.code_blocks(),
3854 "ROI should decode fewer code-blocks for classic={classic}; full={}, region={}",
3855 full.code_blocks(),
3856 region.code_blocks()
3857 );
3858 assert!(
3859 region.idwt_output_samples > 0
3860 && region.idwt_output_samples < full.idwt_output_samples,
3861 "ROI should produce fewer IDWT output samples for classic={classic}; full={}, region={}",
3862 full.idwt_output_samples,
3863 region.idwt_output_samples
3864 );
3865 }
3866 }
3867
3868 #[test]
3869 fn region_decode_reuses_region_sized_component_storage() {
3870 let bytes = fixture();
3871 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3872 let mut context = DecoderContext::default();
3873
3874 let bitmap = image
3875 .decode_region_with_context((1, 0, 1, 2), &mut context)
3876 .expect("region decode");
3877
3878 assert_eq!((bitmap.width, bitmap.height), (1, 2));
3879 assert!(context
3880 .tile_decode_context
3881 .channel_data
3882 .iter()
3883 .all(|component| component.container.truncated().len() == 2));
3884 }
3885
3886 #[test]
3887 fn native_region_decode_reuses_region_sized_component_storage() {
3888 let bytes = fixture();
3889 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3890 let mut context = DecoderContext::default();
3891
3892 let bitmap = image
3893 .decode_native_region_with_context((1, 0, 1, 2), &mut context)
3894 .expect("native region decode");
3895
3896 assert_eq!((bitmap.width, bitmap.height), (1, 2));
3897 assert!(context
3898 .tile_decode_context
3899 .channel_data
3900 .iter()
3901 .all(|component| component.container.truncated().len() == 2));
3902 }
3903
3904 #[test]
3905 fn decoder_context_defaults_to_auto_cpu_parallelism() {
3906 let context = DecoderContext::default();
3907
3908 assert_eq!(context.cpu_decode_parallelism(), CpuDecodeParallelism::Auto);
3909 }
3910
3911 #[test]
3912 fn classic_j2k_auto_and_serial_cpu_parallelism_match_pixels() {
3913 let bytes = fixture_multi_block();
3914 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3915 let mut auto_context = DecoderContext::default();
3916 let mut serial_context = DecoderContext::default();
3917 serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
3918
3919 let auto = image
3920 .decode_with_context(&mut auto_context)
3921 .expect("auto decode");
3922 let serial = image
3923 .decode_with_context(&mut serial_context)
3924 .expect("serial decode");
3925
3926 assert_eq!(auto.data, serial.data);
3927 }
3928
3929 #[test]
3930 fn htj2k_97_auto_and_serial_cpu_parallelism_match_pixels() {
3931 let width = 128_u32;
3932 let height = 128_u32;
3933 let pixels = (0..width * height)
3934 .map(|idx| ((idx * 17 + idx / width * 31) & 0xff) as u8)
3935 .collect::<Vec<_>>();
3936 let bytes = encode_htj2k(
3937 &pixels,
3938 width,
3939 height,
3940 1,
3941 8,
3942 false,
3943 &EncodeOptions {
3944 reversible: false,
3945 guard_bits: 2,
3946 num_decomposition_levels: 5,
3947 ..EncodeOptions::default()
3948 },
3949 )
3950 .expect("encode HTJ2K 9/7");
3951 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3952 let mut auto_context = DecoderContext::default();
3953 let mut serial_context = DecoderContext::default();
3954 serial_context.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
3955
3956 let auto = image
3957 .decode_with_context(&mut auto_context)
3958 .expect("auto decode");
3959 let serial = image
3960 .decode_with_context(&mut serial_context)
3961 .expect("serial decode");
3962
3963 assert_eq!(auto.data, serial.data);
3964 }
3965
3966 #[test]
3967 fn serial_cpu_parallelism_disables_classic_sub_band_parallel_branch() {
3968 assert!(!j2c::should_decode_classic_sub_band_in_parallel(
3969 CpuDecodeParallelism::Serial,
3970 16
3971 ));
3972 }
3973
3974 #[test]
3975 fn grayscale_direct_plan_is_built_without_materializing_channel_data() {
3976 let bytes = fixture_gray();
3977 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
3978 let mut context = DecoderContext::default();
3979
3980 let plan = image
3981 .build_direct_grayscale_plan_with_context(&mut context)
3982 .expect("build direct plan");
3983
3984 assert_eq!(plan.dimensions, (4, 4));
3985 assert_eq!(plan.bit_depth, 8);
3986 assert!(
3987 !plan.steps.is_empty(),
3988 "direct plan must contain executable steps"
3989 );
3990 assert!(
3991 plan.steps.iter().any(|step| matches!(
3992 step,
3993 J2kDirectGrayscaleStep::ClassicSubBand(plan) if !plan.jobs.is_empty()
3994 )),
3995 "classic J2K direct plan must contain at least one non-empty classic sub-band job"
3996 );
3997 assert!(
3998 context.tile_decode_context.channel_data.is_empty(),
3999 "building a direct plan must not materialize host component planes"
4000 );
4001 }
4002
4003 #[test]
4004 fn grayscale_direct_plan_honors_target_resolution() {
4005 let bytes = fixture_ht_gray();
4006 let image = Image::new(
4007 &bytes,
4008 &DecodeSettings {
4009 target_resolution: Some((2, 2)),
4010 ..DecodeSettings::default()
4011 },
4012 )
4013 .expect("scaled image");
4014 let mut context = DecoderContext::default();
4015
4016 let plan = image
4017 .build_direct_grayscale_plan_with_context(&mut context)
4018 .expect("build scaled direct plan");
4019
4020 assert_eq!(plan.dimensions, (2, 2));
4021 assert!(plan.steps.iter().any(|step| matches!(
4022 step,
4023 J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4024 )));
4025 assert!(plan.steps.iter().any(|step| matches!(
4026 step,
4027 J2kDirectGrayscaleStep::Store(store)
4028 if store.output_width == 2 && store.output_height == 2
4029 )));
4030 assert!(
4031 context.tile_decode_context.channel_data.is_empty(),
4032 "building a scaled direct plan must not materialize host component planes"
4033 );
4034 }
4035
4036 #[test]
4037 fn grayscale_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4038 let bytes = fixture_ht_multi_block();
4039 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4040 let mut full_context = DecoderContext::default();
4041 let mut roi_context = DecoderContext::default();
4042
4043 let full = image
4044 .build_direct_grayscale_plan_with_context(&mut full_context)
4045 .expect("build full direct plan");
4046 let roi = image
4047 .build_direct_grayscale_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4048 .expect("build ROI direct plan");
4049
4050 let full_jobs = direct_ht_job_count(&full);
4051 let roi_jobs = direct_ht_job_count(&roi);
4052 assert!(full_jobs > 1, "fixture must expose multiple HT jobs");
4053 assert!(
4054 roi_jobs < full_jobs,
4055 "ROI direct plan must prune HT jobs before device preparation"
4056 );
4057 }
4058
4059 #[test]
4060 fn color_direct_plan_region_prunes_unneeded_ht_code_blocks() {
4061 let bytes = fixture_ht_rgb_multi_block();
4062 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4063 let mut full_context = DecoderContext::default();
4064 let mut roi_context = DecoderContext::default();
4065
4066 let full = image
4067 .build_direct_color_plan_with_context(&mut full_context)
4068 .expect("build full RGB direct plan");
4069 let roi = image
4070 .build_direct_color_plan_region_with_context(&mut roi_context, (0, 0, 2, 2))
4071 .expect("build ROI RGB direct plan");
4072
4073 let full_jobs = direct_color_ht_job_count(&full);
4074 let roi_jobs = direct_color_ht_job_count(&roi);
4075 assert!(full_jobs > 3, "fixture must expose multiple RGB HT jobs");
4076 assert!(
4077 roi_jobs < full_jobs,
4078 "RGB ROI direct plan must prune HT jobs before device preparation"
4079 );
4080 }
4081
4082 #[test]
4083 fn color_direct_plan_honors_target_resolution() {
4084 for (name, bytes) in [
4085 ("classic", {
4086 let pixels = gradient_pixels(8, 8, 3);
4087 let options = EncodeOptions {
4088 reversible: true,
4089 num_decomposition_levels: 2,
4090 ..EncodeOptions::default()
4091 };
4092 encode(&pixels, 8, 8, 3, 8, false, &options).expect("encode classic rgb8")
4093 }),
4094 ("htj2k", {
4095 let pixels = gradient_pixels(8, 8, 3);
4096 let options = EncodeOptions {
4097 reversible: true,
4098 num_decomposition_levels: 2,
4099 ..EncodeOptions::default()
4100 };
4101 encode_htj2k(&pixels, 8, 8, 3, 8, false, &options).expect("encode ht rgb8")
4102 }),
4103 ] {
4104 let image = Image::new(
4105 &bytes,
4106 &DecodeSettings {
4107 target_resolution: Some((4, 4)),
4108 ..DecodeSettings::default()
4109 },
4110 )
4111 .expect("scaled RGB image");
4112 let mut context = DecoderContext::default();
4113
4114 let plan = image
4115 .build_direct_color_plan_with_context(&mut context)
4116 .expect("build scaled direct color plan");
4117
4118 assert_eq!(plan.dimensions, (4, 4), "{name}: output dimensions");
4119 assert_eq!(plan.component_plans.len(), 3, "{name}: component count");
4120 for component_plan in &plan.component_plans {
4121 assert_eq!(component_plan.dimensions, (4, 4), "{name}: component dims");
4122 assert!(component_plan.steps.iter().any(|step| matches!(
4123 step,
4124 J2kDirectGrayscaleStep::Store(store)
4125 if store.output_width == 4 && store.output_height == 4
4126 )));
4127 }
4128 assert!(
4129 context.tile_decode_context.channel_data.is_empty(),
4130 "{name}: building a scaled color direct plan must not materialize host component planes"
4131 );
4132 }
4133 }
4134
4135 #[test]
4136 fn direct_color_cpu_rgb8_executor_matches_scaled_region_decode() {
4137 for (name, bytes) in [
4138 ("classic", {
4139 let pixels = gradient_pixels(16, 16, 3);
4140 let options = EncodeOptions {
4141 reversible: true,
4142 num_decomposition_levels: 2,
4143 ..EncodeOptions::default()
4144 };
4145 encode(&pixels, 16, 16, 3, 8, false, &options).expect("encode classic rgb8")
4146 }),
4147 ("htj2k", {
4148 let pixels = gradient_pixels(16, 16, 3);
4149 let options = EncodeOptions {
4150 reversible: true,
4151 num_decomposition_levels: 2,
4152 ..EncodeOptions::default()
4153 };
4154 encode_htj2k(&pixels, 16, 16, 3, 8, false, &options).expect("encode ht rgb8")
4155 }),
4156 ] {
4157 let image = Image::new(
4158 &bytes,
4159 &DecodeSettings {
4160 target_resolution: Some((4, 4)),
4161 ..DecodeSettings::default()
4162 },
4163 )
4164 .expect("scaled RGB image");
4165 let mut expected_context = DecoderContext::default();
4166 let expected_full = image
4167 .decode_with_context(&mut expected_context)
4168 .expect("decode scaled reference");
4169 let output_region = J2kRect {
4170 x0: 1,
4171 y0: 1,
4172 x1: 3,
4173 y1: 3,
4174 };
4175 let mut direct_context = DecoderContext::default();
4176 let plan = image
4177 .build_direct_color_plan_region_with_context(
4178 &mut direct_context,
4179 (
4180 output_region.x0,
4181 output_region.y0,
4182 output_region.width(),
4183 output_region.height(),
4184 ),
4185 )
4186 .expect("build direct RGB region plan");
4187
4188 let stride = output_region.width() as usize * 3;
4189 let mut direct = vec![0_u8; stride * output_region.height() as usize];
4190 let mut scratch = J2kDirectCpuScratch::new();
4191 execute_direct_color_plan_rgb8_into(
4192 &plan,
4193 output_region,
4194 &mut scratch,
4195 &mut direct,
4196 stride,
4197 )
4198 .expect("execute direct RGB plan");
4199
4200 let mut expected = Vec::with_capacity(direct.len());
4201 let full_stride = image.width() as usize * 3;
4202 for y in output_region.y0..output_region.y1 {
4203 let start = y as usize * full_stride + output_region.x0 as usize * 3;
4204 expected.extend_from_slice(&expected_full.data[start..start + stride]);
4205 }
4206
4207 assert_eq!(direct, expected, "{name}: direct RGB output");
4208
4209 let rgba_stride = output_region.width() as usize * 4;
4210 let mut direct_rgba = vec![0_u8; rgba_stride * output_region.height() as usize];
4211 execute_direct_color_plan_rgba8_into(
4212 &plan,
4213 output_region,
4214 &mut scratch,
4215 &mut direct_rgba,
4216 rgba_stride,
4217 )
4218 .expect("execute direct RGBA plan");
4219
4220 let mut expected_rgba = Vec::with_capacity(direct_rgba.len());
4221 for rgb in expected.chunks_exact(3) {
4222 expected_rgba.extend_from_slice(rgb);
4223 expected_rgba.push(255);
4224 }
4225 assert_eq!(direct_rgba, expected_rgba, "{name}: direct RGBA output");
4226 }
4227 }
4228
4229 #[test]
4230 fn htj2k_grayscale_direct_plan_contains_ht_sub_band_steps() {
4231 let bytes = fixture_ht_gray();
4232 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4233 let mut context = DecoderContext::default();
4234
4235 let plan = image
4236 .build_direct_grayscale_plan_with_context(&mut context)
4237 .expect("build direct plan");
4238
4239 assert!(
4240 plan.steps.iter().any(|step| matches!(
4241 step,
4242 J2kDirectGrayscaleStep::HtSubBand(plan) if !plan.jobs.is_empty()
4243 )),
4244 "HTJ2K direct plan must contain at least one non-empty HT sub-band decode step"
4245 );
4246 }
4247
4248 #[test]
4249 fn ht_decoder_hook_is_used_for_htj2k_codeblocks() {
4250 let pixels: Vec<u8> = (0..16).collect();
4251 let options = EncodeOptions {
4252 reversible: true,
4253 num_decomposition_levels: 1,
4254 ..EncodeOptions::default()
4255 };
4256 let bytes = encode_htj2k(&pixels, 4, 4, 1, 8, false, &options).expect("encode ht");
4257 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4258 let mut hooked_context = DecoderContext::default();
4259 let mut hook = FailingHtDecoder { called: false };
4260 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4261 Ok(_) => panic!("hooked decode must use external HT decoder"),
4262 Err(error) => error,
4263 };
4264
4265 assert!(hook.called, "HT decoder hook must be invoked");
4266 assert_eq!(
4267 error,
4268 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4269 );
4270 }
4271
4272 #[test]
4273 fn openhtj2k_conformance_fixture_exercises_refinement_passes() {
4274 for fixture in [
4275 (
4276 "ds0_ht_12_b11",
4277 fixture_openhtj2k_ht_refinement(),
4278 fixture_openhtj2k_ht_refinement_pixels(),
4279 (3, 5),
4280 8,
4281 2,
4282 4,
4283 ),
4284 (
4285 "ds0_ht_09_b11",
4286 fixture_openhtj2k_ht_refinement_odd(),
4287 fixture_openhtj2k_ht_refinement_odd_pixels(),
4288 (17, 37),
4289 14,
4290 14,
4291 629,
4292 ),
4293 ] {
4294 let (
4295 name,
4296 codestream,
4297 expected_pixels,
4298 dimensions,
4299 blocks,
4300 refinement_jobs,
4301 zero_diffs,
4302 ) = fixture;
4303 let image = Image::new(codestream, &DecodeSettings::default()).expect("image");
4304 let mut context = DecoderContext::default();
4305 let mut hook = CapturingHtDecoder::default();
4306
4307 let components = image
4308 .decode_components_with_ht_decoder(&mut context, &mut hook)
4309 .expect("decode OpenHTJ2K HTJ2K fixture");
4310
4311 assert!(
4312 hook.called,
4313 "{name}: HTJ2K fixture must use HT code-block decode"
4314 );
4315 assert!(
4316 hook.refinement_jobs > 0,
4317 "{name}: OpenHTJ2K fixture must contain non-empty refinement segments"
4318 );
4319 assert!(
4320 hook.max_coding_passes > 1,
4321 "{name}: OpenHTJ2K fixture must exercise more than the cleanup pass"
4322 );
4323 assert_eq!(hook.blocks, blocks, "{name}: HT code-block count");
4324 assert_eq!(
4325 hook.refinement_jobs, refinement_jobs,
4326 "{name}: refinement job count"
4327 );
4328 assert_eq!(hook.max_coding_passes, 3, "{name}: max HT coding passes");
4329 assert_eq!(components.dimensions(), dimensions, "{name}: dimensions");
4330 assert_eq!(components.planes().len(), 1, "{name}: component planes");
4331
4332 let decoded: Vec<u8> = components.planes()[0]
4333 .samples()
4334 .iter()
4335 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4336 .collect();
4337 assert_eq!(decoded, expected_pixels, "{name}: decoded pixels");
4338
4339 let mut zero_context = DecoderContext::default();
4340 let mut zero_hook = ZeroRefinementHtDecoder;
4341 let zeroed_components = image
4342 .decode_components_with_ht_decoder(&mut zero_context, &mut zero_hook)
4343 .expect("decode OpenHTJ2K fixture with zeroed refinement bytes");
4344 let actual_zero_diffs = components.planes()[0]
4345 .samples()
4346 .iter()
4347 .zip(zeroed_components.planes()[0].samples())
4348 .filter(|(actual, zeroed)| (*actual - *zeroed).abs() > f32::EPSILON)
4349 .count();
4350 assert_eq!(
4351 actual_zero_diffs, zero_diffs,
4352 "{name}: zeroing refinement bytes must change decoded samples"
4353 );
4354 }
4355 }
4356
4357 #[test]
4358 fn openhtj2k_refinement_phase_limited_decode_differs_and_records_ht_stats() {
4359 let image = Image::new(
4360 fixture_openhtj2k_ht_refinement_odd(),
4361 &DecodeSettings::default(),
4362 )
4363 .expect("image");
4364 let mut full_context = DecoderContext::default();
4365
4366 let (full_samples, full_decoded) = {
4367 let full_components = image
4368 .decode_components_with_context(&mut full_context)
4369 .expect("full native decode of OpenHTJ2K refinement fixture");
4370 let full_samples = full_components.planes()[0].samples().to_vec();
4371 let full_decoded: Vec<u8> = full_samples
4372 .iter()
4373 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4374 .collect();
4375 (full_samples, full_decoded)
4376 };
4377 assert_eq!(
4378 full_decoded,
4379 fixture_openhtj2k_ht_refinement_odd_pixels(),
4380 "full decode must match the checked-in OpenHTJ2K oracle"
4381 );
4382
4383 let stats = full_context
4384 .tile_decode_context
4385 .debug_counters
4386 .ht_phase_stats;
4387 assert_eq!(stats.blocks, 14, "HT block count");
4388 assert_eq!(stats.refinement_blocks, 14, "HT refinement block count");
4389 assert!(stats.cleanup_bytes > 0, "cleanup byte total");
4390 assert!(stats.refinement_bytes > 0, "refinement byte total");
4391
4392 let mut cleanup_context = DecoderContext::default();
4393 let mut cleanup_hook = CleanupLimitedHtDecoder::default();
4394 let cleanup_components = image
4395 .decode_components_with_ht_decoder(&mut cleanup_context, &mut cleanup_hook)
4396 .expect("cleanup-limited decode of OpenHTJ2K refinement fixture");
4397 let cleanup_decoded: Vec<u8> = cleanup_components.planes()[0]
4398 .samples()
4399 .iter()
4400 .map(|sample| sample.round().clamp(0.0, 255.0) as u8)
4401 .collect();
4402 let cleanup_sample_diffs = full_samples
4403 .iter()
4404 .zip(cleanup_components.planes()[0].samples())
4405 .filter(|(full, cleanup)| (*full - *cleanup).abs() > f32::EPSILON)
4406 .count();
4407
4408 assert!(
4409 cleanup_sample_diffs > 0,
4410 "cleanup-limited decode must omit refinement effects"
4411 );
4412 assert_eq!(
4413 cleanup_decoded, full_decoded,
4414 "fixture refinement differences are below final u8 clamping"
4415 );
4416 assert_eq!(cleanup_hook.blocks, 14, "hook HT block count");
4417 assert_eq!(
4418 cleanup_hook.refinement_blocks, 14,
4419 "hook HT refinement block count"
4420 );
4421 assert!(cleanup_hook.cleanup_bytes > 0, "hook cleanup byte total");
4422 assert!(
4423 cleanup_hook.refinement_bytes > 0,
4424 "hook refinement byte total"
4425 );
4426 }
4427
4428 #[test]
4429 fn scalar_htj2k_encoder_contract_is_cleanup_only() {
4430 let coefficients = (0..64)
4431 .map(|index| {
4432 let magnitude = (index % 7) + 1;
4433 if index % 2 == 0 {
4434 magnitude
4435 } else {
4436 -magnitude
4437 }
4438 })
4439 .collect::<Vec<_>>();
4440
4441 let encoded =
4442 encode_ht_code_block_scalar(&coefficients, 8, 8, 8).expect("encode HT code block");
4443
4444 assert_eq!(
4445 encoded.num_coding_passes, 1,
4446 "current scalar HTJ2K encoder emits only the cleanup pass"
4447 );
4448 assert_eq!(
4449 encoded.num_zero_bitplanes, 7,
4450 "current cleanup-only HTJ2K encoder includes one bitplane"
4451 );
4452 assert!(
4453 !encoded.data.is_empty(),
4454 "non-zero cleanup-only block must still produce payload bytes"
4455 );
4456 }
4457
4458 #[test]
4459 fn scalar_htj2k_decode_workspace_matches_fresh_decode_and_reuses_capacity() {
4460 let image = Image::new(
4461 fixture_openhtj2k_ht_refinement_odd(),
4462 &DecodeSettings::default(),
4463 )
4464 .expect("image");
4465 let mut context = DecoderContext::default();
4466 let mut hook = FirstHtJobDecoder::default();
4467 image
4468 .decode_components_with_ht_decoder(&mut context, &mut hook)
4469 .expect("decode fixture while collecting HT jobs");
4470 let job = hook
4471 .job
4472 .as_ref()
4473 .expect("fixture must expose an HT decode job")
4474 .borrowed();
4475 let mut fresh = vec![0.0_f32; job.width as usize * job.height as usize];
4476 let mut reused = vec![0.0_f32; fresh.len()];
4477 let mut profiled = vec![0.0_f32; fresh.len()];
4478 let mut workspace = HtCodeBlockDecodeWorkspace::default();
4479 let mut profile = HtCodeBlockDecodeProfile::default();
4480
4481 decode_ht_code_block_scalar(job, &mut fresh).expect("fresh HT decode");
4482 decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
4483 .expect("workspace HT decode");
4484 let first_capacity = workspace.coefficient_capacity();
4485 decode_ht_code_block_scalar_with_workspace(job, &mut reused, &mut workspace)
4486 .expect("second workspace HT decode");
4487 decode_ht_code_block_scalar_with_workspace_profiled(
4488 job,
4489 &mut profiled,
4490 &mut workspace,
4491 &mut profile,
4492 )
4493 .expect("profiled workspace HT decode");
4494
4495 assert_eq!(reused, fresh);
4496 assert_eq!(profiled, fresh);
4497 assert!(first_capacity >= fresh.len());
4498 assert_eq!(workspace.coefficient_capacity(), first_capacity);
4499 assert_eq!(profile.blocks, 1);
4500 assert!(profile.cleanup_bytes > 0);
4501 }
4502
4503 #[test]
4504 fn classic_decoder_hook_is_used_for_j2k_codeblocks() {
4505 let bytes = fixture();
4506 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4507 let mut hooked_context = DecoderContext::default();
4508 let mut hook = FailingClassicDecoder { called: false };
4509 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4510 Ok(_) => panic!("hooked decode must use external classic decoder"),
4511 Err(error) => error,
4512 };
4513
4514 assert!(hook.called, "classic decoder hook must be invoked");
4515 assert_eq!(
4516 error,
4517 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4518 );
4519 }
4520
4521 #[test]
4522 fn classic_sub_band_decoder_hook_is_used_for_j2k_codeblocks() {
4523 let bytes = fixture_multi_block();
4524 let image = Image::new(&bytes, &DecodeSettings::default()).expect("image");
4525 let mut hooked_context = DecoderContext::default();
4526 let mut hook = FailingClassicBatchDecoder { called: false };
4527 let error = match image.decode_components_with_ht_decoder(&mut hooked_context, &mut hook) {
4528 Ok(_) => panic!("hooked decode must use external classic batch decoder"),
4529 Err(error) => error,
4530 };
4531
4532 assert!(hook.called, "classic sub-band decoder hook must be invoked");
4533 assert_eq!(
4534 error,
4535 DecodeError::Decoding(DecodingError::CodeBlockDecodeFailure)
4536 );
4537 }
4538
4539 #[test]
4544 fn forward_dwt53_reference_matches_internal_path() {
4545 let samples: Vec<f32> = (0..16).map(|i| i as f32).collect();
4547 let out = forward_dwt53_reference(&samples, 4, 4, 1);
4548
4549 let internal = j2c::fdwt::forward_dwt(&samples, 4, 4, 1, true);
4551
4552 assert_eq!(out.ll, internal.ll, "LL subband mismatch");
4553 assert_eq!(out.ll_width, internal.ll_width, "LL width mismatch");
4554 assert_eq!(out.ll_height, internal.ll_height, "LL height mismatch");
4555 assert_eq!(out.levels.len(), internal.levels.len(), "level count");
4556 for (pub_lvl, int_lvl) in out.levels.iter().zip(internal.levels.iter()) {
4557 assert_eq!(pub_lvl.hl, int_lvl.hl, "HL mismatch");
4558 assert_eq!(pub_lvl.lh, int_lvl.lh, "LH mismatch");
4559 assert_eq!(pub_lvl.hh, int_lvl.hh, "HH mismatch");
4560 }
4561 }
4562
4563 #[test]
4564 fn forward_rct_reference_matches_internal_path() {
4565 let planes = vec![vec![100.0f32], vec![150.0f32], vec![200.0f32]];
4567 let result = forward_rct_reference(planes.clone());
4568
4569 let mut internal = planes;
4571 j2c::forward_mct::forward_rct(&mut internal);
4572
4573 assert_eq!(result, internal, "RCT output mismatch");
4574 assert_eq!(result[0][0], 150.0, "Y component");
4576 assert_eq!(result[1][0], 50.0, "Cb component");
4577 assert_eq!(result[2][0], -50.0, "Cr component");
4578 }
4579
4580 #[test]
4581 fn quantize_reversible_reference_matches_internal_path() {
4582 let coefficients = vec![3.7f32, -8.2, 0.5, -0.5, 10.0];
4583 let exponent = 8u16;
4584 let mantissa = 0u16;
4585 let range_bits = 8u8;
4586
4587 let result =
4588 quantize_reversible_reference(&coefficients, exponent, mantissa, range_bits, true);
4589
4590 let step = j2c::quantize::QuantStepSize { exponent, mantissa };
4592 let internal = j2c::quantize::quantize_subband(&coefficients, &step, range_bits, true);
4593
4594 assert_eq!(result, internal, "quantize output mismatch");
4595 assert_eq!(result[0], 4, "3.7 rounds to 4");
4597 assert_eq!(result[1], -8, "-8.2 rounds to -8");
4598 }
4599
4600 #[test]
4601 fn deinterleave_reference_matches_internal_path() {
4602 let pixels: Vec<u8> = vec![128, 64, 200, 10, 20, 30];
4604 let result = deinterleave_reference(&pixels, 2, 3, 8, false);
4605
4606 let internal = j2c::encode::deinterleave_to_f32(&pixels, 2, 3, 8, false);
4607
4608 assert_eq!(result, internal, "deinterleave output mismatch");
4609 assert_eq!(result.len(), 3, "three component planes");
4610 assert_eq!(result[0].len(), 2, "two pixels per plane");
4611 assert!((result[0][0] - 0.0f32).abs() < 1e-6, "R0 level-shifted");
4613 assert!((result[1][0] - (-64.0f32)).abs() < 1e-6, "G0 level-shifted");
4614 assert!((result[2][0] - 72.0f32).abs() < 1e-6, "B0 level-shifted");
4615 }
4616}