1use crate::allocation::{
11 checked_add_allocation_bytes, checked_allocation_bytes, try_vec_filled, try_vec_with_capacity,
12};
13use crate::dct_grid::validate_dct_block_grid;
14use crate::reversible53::{
15 reversible_lift_53_high_at, reversible_lift_53_i32, reversible_lift_53_low_at,
16};
17use crate::{
18 DctGridToReversibleDwt53Job, Dwt53TwoDimensional, Dwt97BatchStageTimings, Dwt97TwoDimensional,
19 ReversibleDwt53FirstLevel, TranscodeStageError,
20};
21pub use j2k::{
22 EncodedHtJ2kCodeBlock, IrreversibleQuantizationSubbandScales, J2kSubBandType,
23 PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
24 PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
25 PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
26 PreencodedHtj2k97Component, PreencodedHtj2k97Resolution, PreencodedHtj2k97Subband,
27 PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component, PrequantizedHtj2k97Image,
28 PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
29};
30use j2k_jpeg::transcode::idct_islow_block;
31use rayon::prelude::{
32 IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator,
33 ParallelSliceMut,
34};
35
36const REVERSIBLE_DWT53_UNSUPPORTED_GRID: &str =
37 "reversible DCT 5/3 job has unsupported grid geometry";
38
39#[derive(Debug, Clone, Copy)]
41pub struct DctGridToDwt53Job<'a> {
42 pub blocks: &'a [[[f64; 8]; 8]],
44 pub block_cols: usize,
46 pub block_rows: usize,
48 pub width: usize,
50 pub height: usize,
52}
53
54#[derive(Debug, Clone, Copy)]
56pub struct DctGridToDwt97Job<'a> {
57 pub blocks: &'a [[[f64; 8]; 8]],
59 pub block_cols: usize,
61 pub block_rows: usize,
63 pub width: usize,
65 pub height: usize,
67}
68
69#[derive(Debug, Clone, Copy)]
71pub struct DctGridToHtj2k97CodeBlockJob<'a> {
72 pub blocks: &'a [[[f64; 8]; 8]],
74 pub block_cols: usize,
76 pub block_rows: usize,
78 pub width: usize,
80 pub height: usize,
82 pub x_rsiz: u8,
84 pub y_rsiz: u8,
86}
87
88#[derive(Debug, Clone, Copy)]
93pub struct DctGridI16ToHtj2k97CodeBlockJob<'a> {
94 pub dequantized_blocks: &'a [[i16; 64]],
96 pub block_cols: usize,
98 pub block_rows: usize,
100 pub width: usize,
102 pub height: usize,
104 pub x_rsiz: u8,
106 pub y_rsiz: u8,
108}
109
110#[derive(Debug, Clone, Copy)]
112pub struct DctGridI16ToHtj2k97CodeBlockBatch<'a, 'j> {
113 pub jobs: &'j [DctGridI16ToHtj2k97CodeBlockJob<'a>],
115}
116
117#[derive(Debug)]
119pub struct PreencodedHtj2k97CompactBatch {
120 pub payload: Vec<u8>,
122 pub components: Vec<PreencodedHtj2k97CompactComponent>,
124}
125
126#[derive(Debug)]
128pub struct PreencodedHtj2k97CompactBatchGroups {
129 pub payload: Vec<u8>,
131 pub groups: Vec<Vec<PreencodedHtj2k97CompactComponent>>,
133}
134
135crate::move_only::assert_move_only!(
136 PreencodedHtj2k97CompactBatch,
137 PreencodedHtj2k97CompactBatchGroups,
138);
139
140#[derive(Debug, Clone, Copy, PartialEq)]
143pub struct Htj2k97CodeBlockOptions {
144 pub bit_depth: u8,
146 pub guard_bits: u8,
148 pub code_block_width_exp: u8,
150 pub code_block_height_exp: u8,
152 pub irreversible_quantization_scale: f32,
154 pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum DctToWaveletStageCounterEvent {
162 ReversibleDwt53Attempt,
164 ReversibleDwt53Dispatch,
166 ReversibleDwt53BatchAttempt,
168 ReversibleDwt53BatchDispatch,
170 Dwt53Attempt,
172 Dwt53Dispatch,
174 Dwt97Attempt,
176 Dwt97Dispatch,
178 Dwt97BatchAttempt,
180 Dwt97BatchDispatch,
182 Htj2k97CodeblockBatchAttempt,
184 Htj2k97CodeblockBatchDispatch,
186}
187
188#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
190pub struct DctToWaveletStageCounters {
191 reversible_dwt53_attempts: usize,
192 reversible_dwt53_dispatches: usize,
193 reversible_dwt53_batch_attempts: usize,
194 reversible_dwt53_batch_dispatches: usize,
195 dwt53_attempts: usize,
196 dwt53_dispatches: usize,
197 dwt97_attempts: usize,
198 dwt97_dispatches: usize,
199 dwt97_batch_attempts: usize,
200 dwt97_batch_dispatches: usize,
201 htj2k97_codeblock_batch_attempts: usize,
202 htj2k97_codeblock_batch_dispatches: usize,
203}
204
205impl DctToWaveletStageCounters {
206 #[must_use]
208 pub const fn new() -> Self {
209 Self {
210 reversible_dwt53_attempts: 0,
211 reversible_dwt53_dispatches: 0,
212 reversible_dwt53_batch_attempts: 0,
213 reversible_dwt53_batch_dispatches: 0,
214 dwt53_attempts: 0,
215 dwt53_dispatches: 0,
216 dwt97_attempts: 0,
217 dwt97_dispatches: 0,
218 dwt97_batch_attempts: 0,
219 dwt97_batch_dispatches: 0,
220 htj2k97_codeblock_batch_attempts: 0,
221 htj2k97_codeblock_batch_dispatches: 0,
222 }
223 }
224
225 #[must_use]
227 pub const fn reversible_dwt53_attempts(&self) -> usize {
228 self.reversible_dwt53_attempts
229 }
230
231 #[must_use]
233 pub const fn reversible_dwt53_dispatches(&self) -> usize {
234 self.reversible_dwt53_dispatches
235 }
236
237 #[must_use]
239 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
240 self.reversible_dwt53_batch_attempts
241 }
242
243 #[must_use]
245 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
246 self.reversible_dwt53_batch_dispatches
247 }
248
249 #[must_use]
251 pub const fn dwt53_attempts(&self) -> usize {
252 self.dwt53_attempts
253 }
254
255 #[must_use]
257 pub const fn dwt53_dispatches(&self) -> usize {
258 self.dwt53_dispatches
259 }
260
261 #[must_use]
263 pub const fn dwt97_attempts(&self) -> usize {
264 self.dwt97_attempts
265 }
266
267 #[must_use]
269 pub const fn dwt97_dispatches(&self) -> usize {
270 self.dwt97_dispatches
271 }
272
273 #[must_use]
275 pub const fn dwt97_batch_attempts(&self) -> usize {
276 self.dwt97_batch_attempts
277 }
278
279 #[must_use]
281 pub const fn dwt97_batch_dispatches(&self) -> usize {
282 self.dwt97_batch_dispatches
283 }
284
285 #[must_use]
287 pub const fn htj2k97_codeblock_batch_attempts(&self) -> usize {
288 self.htj2k97_codeblock_batch_attempts
289 }
290
291 #[must_use]
293 pub const fn htj2k97_codeblock_batch_dispatches(&self) -> usize {
294 self.htj2k97_codeblock_batch_dispatches
295 }
296
297 pub fn record(&mut self, event: DctToWaveletStageCounterEvent, count: usize) {
299 match event {
300 DctToWaveletStageCounterEvent::ReversibleDwt53Attempt => {
301 self.reversible_dwt53_attempts =
302 self.reversible_dwt53_attempts.saturating_add(count);
303 }
304 DctToWaveletStageCounterEvent::ReversibleDwt53Dispatch => {
305 self.reversible_dwt53_dispatches =
306 self.reversible_dwt53_dispatches.saturating_add(count);
307 }
308 DctToWaveletStageCounterEvent::ReversibleDwt53BatchAttempt => {
309 self.reversible_dwt53_batch_attempts =
310 self.reversible_dwt53_batch_attempts.saturating_add(count);
311 }
312 DctToWaveletStageCounterEvent::ReversibleDwt53BatchDispatch => {
313 self.reversible_dwt53_batch_dispatches =
314 self.reversible_dwt53_batch_dispatches.saturating_add(count);
315 }
316 DctToWaveletStageCounterEvent::Dwt53Attempt => {
317 self.dwt53_attempts = self.dwt53_attempts.saturating_add(count);
318 }
319 DctToWaveletStageCounterEvent::Dwt53Dispatch => {
320 self.dwt53_dispatches = self.dwt53_dispatches.saturating_add(count);
321 }
322 DctToWaveletStageCounterEvent::Dwt97Attempt => {
323 self.dwt97_attempts = self.dwt97_attempts.saturating_add(count);
324 }
325 DctToWaveletStageCounterEvent::Dwt97Dispatch => {
326 self.dwt97_dispatches = self.dwt97_dispatches.saturating_add(count);
327 }
328 DctToWaveletStageCounterEvent::Dwt97BatchAttempt => {
329 self.dwt97_batch_attempts = self.dwt97_batch_attempts.saturating_add(count);
330 }
331 DctToWaveletStageCounterEvent::Dwt97BatchDispatch => {
332 self.dwt97_batch_dispatches = self.dwt97_batch_dispatches.saturating_add(count);
333 }
334 DctToWaveletStageCounterEvent::Htj2k97CodeblockBatchAttempt => {
335 self.htj2k97_codeblock_batch_attempts =
336 self.htj2k97_codeblock_batch_attempts.saturating_add(count);
337 }
338 DctToWaveletStageCounterEvent::Htj2k97CodeblockBatchDispatch => {
339 self.htj2k97_codeblock_batch_dispatches = self
340 .htj2k97_codeblock_batch_dispatches
341 .saturating_add(count);
342 }
343 }
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum TranscodeStageDispatchMode {
350 Explicit,
352 Auto,
355}
356
357impl TranscodeStageDispatchMode {
358 #[must_use]
361 pub const fn is_auto(self) -> bool {
362 matches!(self, Self::Auto)
363 }
364
365 #[doc(hidden)]
368 pub const fn unavailable<T>(self) -> Result<Option<T>, TranscodeStageError> {
369 match self {
370 Self::Explicit => Err(TranscodeStageError::DeviceUnavailable),
371 Self::Auto => Ok(None),
372 }
373 }
374
375 #[doc(hidden)]
381 pub fn recover<T, E>(
382 self,
383 error: E,
384 is_recoverable: impl FnOnce(&E) -> bool,
385 ) -> Result<Option<T>, TranscodeStageError>
386 where
387 E: Into<TranscodeStageError>,
388 {
389 if self.is_auto() && is_recoverable(&error) {
390 Ok(None)
391 } else {
392 Err(error.into())
393 }
394 }
395}
396
397pub trait DctToWaveletStageAccelerator {
399 fn supports_dwt97_batch(&self) -> bool {
405 false
406 }
407
408 fn supports_htj2k97_codeblock_batch(&self) -> bool {
411 false
412 }
413
414 fn supports_htj2k97_i16_preencoded_batch(&self) -> bool {
418 false
419 }
420
421 fn supports_htj2k97_compact_preencoded_batch(&self) -> bool {
424 self.supports_htj2k97_i16_preencoded_batch()
425 }
426
427 fn dct_grid_to_reversible_dwt53(
434 &mut self,
435 _job: DctGridToReversibleDwt53Job<'_>,
436 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
437 Ok(None)
438 }
439
440 fn dct_grid_to_reversible_dwt53_batch(
446 &mut self,
447 _jobs: &[DctGridToReversibleDwt53Job<'_>],
448 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
449 Ok(None)
450 }
451
452 fn dct_grid_to_dwt53(
457 &mut self,
458 _job: DctGridToDwt53Job<'_>,
459 ) -> Result<Option<Dwt53TwoDimensional<f64>>, TranscodeStageError> {
460 Ok(None)
461 }
462
463 fn dct_grid_to_dwt97(
468 &mut self,
469 _job: DctGridToDwt97Job<'_>,
470 ) -> Result<Option<Dwt97TwoDimensional<f64>>, TranscodeStageError> {
471 Ok(None)
472 }
473
474 fn dct_grid_to_dwt97_batch(
480 &mut self,
481 _jobs: &[DctGridToDwt97Job<'_>],
482 ) -> Result<Option<Vec<Dwt97TwoDimensional<f64>>>, TranscodeStageError> {
483 Ok(None)
484 }
485
486 fn dct_grid_to_htj2k97_codeblock_batch(
492 &mut self,
493 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
494 _options: Htj2k97CodeBlockOptions,
495 ) -> Result<Option<Vec<PrequantizedHtj2k97Component>>, TranscodeStageError> {
496 Ok(None)
497 }
498
499 fn dct_grid_to_htj2k97_preencoded_batch(
505 &mut self,
506 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
507 _options: Htj2k97CodeBlockOptions,
508 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
509 Ok(None)
510 }
511
512 fn dct_grid_i16_to_htj2k97_preencoded_batch(
518 &mut self,
519 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
520 _options: Htj2k97CodeBlockOptions,
521 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
522 Ok(None)
523 }
524
525 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch(
532 &mut self,
533 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
534 _options: Htj2k97CodeBlockOptions,
535 ) -> Result<Option<PreencodedHtj2k97CompactBatch>, TranscodeStageError> {
536 Ok(None)
537 }
538
539 fn dct_grid_i16_to_htj2k97_preencoded_batch_groups(
547 &mut self,
548 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
549 _options: Htj2k97CodeBlockOptions,
550 ) -> Result<Option<Vec<Vec<PreencodedHtj2k97Component>>>, TranscodeStageError> {
551 Ok(None)
552 }
553
554 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch_groups(
561 &mut self,
562 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
563 _options: Htj2k97CodeBlockOptions,
564 ) -> Result<Option<PreencodedHtj2k97CompactBatchGroups>, TranscodeStageError> {
565 Ok(None)
566 }
567
568 fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
570 None
571 }
572}
573
574#[derive(Debug, Default, Clone, Copy)]
576pub struct CpuOnlyDctToWaveletStageAccelerator;
577
578#[doc(hidden)]
579impl DctToWaveletStageAccelerator for CpuOnlyDctToWaveletStageAccelerator {}
580
581#[derive(Debug, Default, Clone)]
587pub struct RayonReversibleDwt53Accelerator {
588 attempts: usize,
589 dispatches: usize,
590 batch_attempts: usize,
591 batch_dispatches: usize,
592}
593
594impl RayonReversibleDwt53Accelerator {
595 #[must_use]
597 pub const fn reversible_dwt53_attempts(&self) -> usize {
598 self.attempts
599 }
600
601 #[must_use]
603 pub const fn reversible_dwt53_dispatches(&self) -> usize {
604 self.dispatches
605 }
606
607 #[must_use]
609 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
610 self.batch_attempts
611 }
612
613 #[must_use]
615 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
616 self.batch_dispatches
617 }
618}
619
620#[doc(hidden)]
621impl DctToWaveletStageAccelerator for RayonReversibleDwt53Accelerator {
622 fn dct_grid_to_reversible_dwt53(
623 &mut self,
624 job: DctGridToReversibleDwt53Job<'_>,
625 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
626 self.attempts = self.attempts.saturating_add(1);
627 let output = reversible_dwt53_first_level_rayon(job)?;
628 self.dispatches = self.dispatches.saturating_add(1);
629 Ok(Some(output))
630 }
631
632 fn dct_grid_to_reversible_dwt53_batch(
633 &mut self,
634 jobs: &[DctGridToReversibleDwt53Job<'_>],
635 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
636 self.batch_attempts = self.batch_attempts.saturating_add(1);
637 validate_reversible_batch_workspace(jobs)?;
638 let mut output = try_vec_with_capacity(jobs.len()).map_err(TranscodeStageError::from)?;
639 for job in jobs {
640 output.push(reversible_dwt53_first_level_rayon(*job)?);
641 }
642 self.batch_dispatches = self.batch_dispatches.saturating_add(1);
643 Ok(Some(output))
644 }
645}
646
647#[doc(hidden)]
653pub fn idct_blocks_to_signed_samples_rayon(
654 blocks: &[[i16; 64]],
655) -> Result<Vec<[i32; 64]>, TranscodeStageError> {
656 let mut output = try_vec_filled(blocks.len(), [0i32; 64]).map_err(TranscodeStageError::from)?;
657 output
658 .par_iter_mut()
659 .zip(blocks.par_iter())
660 .for_each(|(output, block)| {
661 let decoded = idct_islow_block(block);
662 *output = decoded.map(|sample| i32::from(sample) - 128);
663 });
664 Ok(output)
665}
666
667pub(crate) fn reversible_dwt53_first_level_from_block_samples(
670 block_samples: &[[i32; 64]],
671 block_cols: usize,
672 block_rows: usize,
673 width: usize,
674 height: usize,
675) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
676 validate_reversible_grid(block_samples.len(), block_cols, block_rows, width, height)?;
677 validate_reversible_output_workspace(width, height)?;
678
679 let low_width = width.div_ceil(2);
680 let low_height = height.div_ceil(2);
681 let high_width = width / 2;
682 let high_height = height / 2;
683
684 let low_row_count = checked_stage_product(width, low_height)?;
685 let mut low_rows = try_vec_filled(low_row_count, 0i32).map_err(TranscodeStageError::from)?;
686 low_rows
687 .par_chunks_mut(width)
688 .enumerate()
689 .for_each(|(output_y, row)| {
690 for (x, sample) in row.iter_mut().enumerate() {
691 *sample =
692 vertical_low_53_i32_at(block_samples, block_cols, width, height, x, output_y);
693 }
694 reversible_lift_53_i32(row);
695 });
696 let high_row_count = checked_stage_product(width, high_height)?;
697 let mut high_rows = try_vec_filled(high_row_count, 0i32).map_err(TranscodeStageError::from)?;
698 high_rows
699 .par_chunks_mut(width)
700 .enumerate()
701 .for_each(|(output_y, row)| {
702 for (x, sample) in row.iter_mut().enumerate() {
703 *sample =
704 vertical_high_53_i32_at(block_samples, block_cols, width, height, x, output_y);
705 }
706 reversible_lift_53_i32(row);
707 });
708
709 let mut ll = try_vec_with_capacity(checked_stage_product(low_width, low_height)?)
710 .map_err(TranscodeStageError::from)?;
711 let mut hl = try_vec_with_capacity(checked_stage_product(high_width, low_height)?)
712 .map_err(TranscodeStageError::from)?;
713 for row in low_rows.chunks_exact(width) {
714 ll.extend(row.iter().step_by(2).copied());
715 hl.extend(row.iter().skip(1).step_by(2).copied());
716 }
717
718 let mut lh = try_vec_with_capacity(checked_stage_product(low_width, high_height)?)
719 .map_err(TranscodeStageError::from)?;
720 let mut hh = try_vec_with_capacity(checked_stage_product(high_width, high_height)?)
721 .map_err(TranscodeStageError::from)?;
722 for row in high_rows.chunks_exact(width) {
723 lh.extend(row.iter().step_by(2).copied());
724 hh.extend(row.iter().skip(1).step_by(2).copied());
725 }
726
727 Ok(ReversibleDwt53FirstLevel {
728 ll,
729 hl,
730 lh,
731 hh,
732 low_width,
733 low_height,
734 high_width,
735 high_height,
736 })
737}
738
739fn reversible_dwt53_first_level_rayon(
740 job: DctGridToReversibleDwt53Job<'_>,
741) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
742 validate_reversible_grid(
743 job.dequantized_blocks.len(),
744 job.block_cols,
745 job.block_rows,
746 job.width,
747 job.height,
748 )?;
749 validate_reversible_job_workspace(job)?;
750 let block_samples = idct_blocks_to_signed_samples_rayon(job.dequantized_blocks)?;
751 reversible_dwt53_first_level_from_block_samples(
752 &block_samples,
753 job.block_cols,
754 job.block_rows,
755 job.width,
756 job.height,
757 )
758}
759
760fn validate_reversible_output_workspace(
761 width: usize,
762 height: usize,
763) -> Result<(), TranscodeStageError> {
764 let sample_count = checked_stage_product(width, height)?;
765 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
766 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
767 checked_add_allocation_bytes(row_bytes, band_bytes)
768 .map(|_| ())
769 .map_err(TranscodeStageError::from)
770}
771
772fn validate_reversible_job_workspace(
773 job: DctGridToReversibleDwt53Job<'_>,
774) -> Result<(), TranscodeStageError> {
775 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
776 let sample_count = checked_stage_product(job.width, job.height)?;
777 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
778 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
779 let workspace = checked_add_allocation_bytes(block_bytes, row_bytes)?;
780 checked_add_allocation_bytes(workspace, band_bytes)?;
781 Ok(())
782}
783
784fn validate_reversible_batch_workspace(
785 jobs: &[DctGridToReversibleDwt53Job<'_>],
786) -> Result<(), TranscodeStageError> {
787 let mut retained_output_bytes = 0usize;
788 let mut max_transient_bytes = 0usize;
789 for job in jobs {
790 validate_reversible_grid(
791 job.dequantized_blocks.len(),
792 job.block_cols,
793 job.block_rows,
794 job.width,
795 job.height,
796 )?;
797 let sample_count = checked_stage_product(job.width, job.height)?;
798 let output_bytes = checked_allocation_bytes::<i32>(sample_count)?;
799 retained_output_bytes = checked_add_allocation_bytes(retained_output_bytes, output_bytes)?;
800 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
801 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
802 max_transient_bytes =
803 max_transient_bytes.max(checked_add_allocation_bytes(block_bytes, row_bytes)?);
804 }
805 checked_add_allocation_bytes(retained_output_bytes, max_transient_bytes)?;
806 Ok(())
807}
808
809fn checked_stage_product(left: usize, right: usize) -> Result<usize, TranscodeStageError> {
810 left.checked_mul(right)
811 .ok_or(TranscodeStageError::MemoryCapExceeded {
812 requested: usize::MAX,
813 cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
814 })
815}
816
817fn validate_reversible_grid(
818 block_count: usize,
819 block_cols: usize,
820 block_rows: usize,
821 width: usize,
822 height: usize,
823) -> Result<(), TranscodeStageError> {
824 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
825 .map_err(|_| TranscodeStageError::Unsupported(REVERSIBLE_DWT53_UNSUPPORTED_GRID))
826}
827
828fn vertical_low_53_i32_at(
829 block_samples: &[[i32; 64]],
830 block_cols: usize,
831 width: usize,
832 height: usize,
833 x: usize,
834 low_idx: usize,
835) -> i32 {
836 reversible_lift_53_low_at(height, low_idx, |y| {
837 component_sample_i32(block_samples, block_cols, width, height, x, y)
838 })
839}
840
841fn vertical_high_53_i32_at(
842 block_samples: &[[i32; 64]],
843 block_cols: usize,
844 width: usize,
845 height: usize,
846 x: usize,
847 high_idx: usize,
848) -> i32 {
849 reversible_lift_53_high_at(height, high_idx, |y| {
850 component_sample_i32(block_samples, block_cols, width, height, x, y)
851 })
852}
853
854fn component_sample_i32(
855 block_samples: &[[i32; 64]],
856 block_cols: usize,
857 width: usize,
858 height: usize,
859 x: usize,
860 y: usize,
861) -> i32 {
862 debug_assert!(x < width);
863 debug_assert!(y < height);
864 let block_x = x / 8;
865 let block_y = y / 8;
866 let block_idx = block_y * block_cols + block_x;
867 let local_idx = (y % 8) * 8 + (x % 8);
868 block_samples[block_idx][local_idx]
869}
870
871#[cfg(test)]
872mod allocation_tests {
873 use super::{
874 idct_blocks_to_signed_samples_rayon, validate_reversible_grid,
875 validate_reversible_output_workspace, TranscodeStageError,
876 REVERSIBLE_DWT53_UNSUPPORTED_GRID,
877 };
878
879 #[test]
880 fn malformed_reversible_grid_is_explicitly_unsupported() {
881 assert!(matches!(
882 validate_reversible_grid(0, 1, 1, 8, 8),
883 Err(TranscodeStageError::Unsupported(
884 REVERSIBLE_DWT53_UNSUPPORTED_GRID
885 ))
886 ));
887 }
888
889 #[test]
890 fn reversible_workspace_overflow_is_typed() {
891 assert!(matches!(
892 validate_reversible_output_workspace(usize::MAX, 2),
893 Err(TranscodeStageError::MemoryCapExceeded {
894 requested: usize::MAX,
895 ..
896 })
897 ));
898 }
899
900 #[test]
901 fn fallible_parallel_idct_preserves_signed_samples() {
902 let blocks = [[0i16; 64]; 2];
903 let samples = idct_blocks_to_signed_samples_rayon(&blocks)
904 .expect("two block outputs fit the host cap");
905 assert_eq!(samples, [[0i32; 64]; 2]);
906 }
907}
908
909#[cfg(test)]
910mod ground_truth_tests {
911 use super::{
921 reversible_dwt53_first_level_from_block_samples, reversible_lift_53_i32,
922 ReversibleDwt53FirstLevel,
923 };
924
925 fn floor2(a: i32, b: i32) -> i32 {
926 a.div_euclid(b)
927 }
928
929 fn ws_reflect(i: isize, n: usize) -> usize {
932 if n == 1 {
933 return 0;
934 }
935 let n = isize::try_from(n).unwrap();
936 let period = 2 * (n - 1);
937 let mut k = i.rem_euclid(period);
938 if k >= n {
939 k = period - k;
940 }
941 usize::try_from(k).unwrap()
942 }
943
944 fn ref_53_forward(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
949 let n = signal.len();
950 if n < 2 {
951 return (signal.to_vec(), Vec::new());
952 }
953 let sig = |i: isize| signal[ws_reflect(i, n)];
954 let detail = |m: isize| {
955 let c = 2 * m + 1;
956 sig(c) - floor2(sig(c - 1) + sig(c + 1), 2)
957 };
958 let low: Vec<i32> = (0..n.div_ceil(2))
959 .map(|m| {
960 let mi = isize::try_from(m).unwrap();
961 sig(2 * mi) + floor2(detail(mi - 1) + detail(mi) + 2, 4)
962 })
963 .collect();
964 let high: Vec<i32> = (0..n / 2)
965 .map(|m| detail(isize::try_from(m).unwrap()))
966 .collect();
967 (low, high)
968 }
969
970 fn ref_53_2d(plane: &[i32], width: usize, height: usize) -> ReversibleDwt53FirstLevel {
973 let low_width = width.div_ceil(2);
974 let high_width = width / 2;
975 let low_height = height.div_ceil(2);
976 let high_height = height / 2;
977
978 let mut v_low = vec![0i32; width * low_height];
979 let mut v_high = vec![0i32; width * high_height];
980 for x in 0..width {
981 let column: Vec<i32> = (0..height).map(|y| plane[y * width + x]).collect();
982 let (lo, hi) = ref_53_forward(&column);
983 for (oy, &value) in lo.iter().enumerate() {
984 v_low[oy * width + x] = value;
985 }
986 for (oy, &value) in hi.iter().enumerate() {
987 v_high[oy * width + x] = value;
988 }
989 }
990
991 let horizontal = |source: &[i32], rows: usize| -> (Vec<i32>, Vec<i32>) {
992 let mut low = vec![0i32; low_width * rows];
993 let mut high = vec![0i32; high_width * rows];
994 for oy in 0..rows {
995 let (lo, hi) = ref_53_forward(&source[oy * width..oy * width + width]);
996 low[oy * low_width..oy * low_width + low_width].copy_from_slice(&lo);
997 high[oy * high_width..oy * high_width + high_width].copy_from_slice(&hi);
998 }
999 (low, high)
1000 };
1001
1002 let (ll, hl) = horizontal(&v_low, low_height);
1003 let (lh, hh) = horizontal(&v_high, high_height);
1004
1005 ReversibleDwt53FirstLevel {
1006 ll,
1007 hl,
1008 lh,
1009 hh,
1010 low_width,
1011 low_height,
1012 high_width,
1013 high_height,
1014 }
1015 }
1016
1017 fn pack_plane(plane: &[i32], width: usize, height: usize) -> (Vec<[i32; 64]>, usize, usize) {
1021 let block_cols = width.div_ceil(8);
1022 let block_rows = height.div_ceil(8);
1023 let mut blocks = vec![[0i32; 64]; block_cols * block_rows];
1024 for y in 0..height {
1025 for x in 0..width {
1026 let block = (y / 8) * block_cols + (x / 8);
1027 blocks[block][(y % 8) * 8 + (x % 8)] = plane[y * width + x];
1028 }
1029 }
1030 (blocks, block_cols, block_rows)
1031 }
1032
1033 fn next_sample(state: &mut u64) -> i32 {
1034 *state = state
1035 .wrapping_mul(6_364_136_223_846_793_005)
1036 .wrapping_add(1_442_695_040_888_963_407);
1037 ((*state >> 40) & 0x1ff) as i32 - 256
1038 }
1039
1040 #[test]
1041 fn reversible_lift_53_matches_canonical_formula_1d() {
1042 let mut state = 0x0a11_ce5e_ed00_d001u64;
1043 for n in [2usize, 3, 4, 5, 8, 9, 12, 15, 16, 23, 32, 33, 64, 65] {
1044 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1045 let mut lifted = signal.clone();
1046 reversible_lift_53_i32(&mut lifted);
1047 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1048 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1049 let (low, high) = ref_53_forward(&signal);
1050 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1051 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1052 }
1053 }
1054
1055 #[test]
1056 fn reversible_lift_53_shared_helper_matches_canonical_formula_1d() {
1057 let mut state = 0x5a53_5a53_5a53_5a53u64;
1058 for n in [2usize, 3, 4, 5, 8, 9, 16, 17, 31, 32, 65] {
1059 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1060 let mut lifted = signal.clone();
1061 crate::reversible53::reversible_lift_53_i32(&mut lifted);
1062 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1063 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1064 let (low, high) = ref_53_forward(&signal);
1065 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1066 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1067 }
1068 }
1069
1070 #[test]
1071 fn reversible_dwt53_2d_matches_canonical_separable() {
1072 let mut state = 0xfeed_5eed_d00d_face_u64;
1073 for (width, height) in [
1074 (8usize, 8usize),
1075 (16, 16),
1076 (24, 16),
1077 (15, 13),
1078 (16, 23),
1079 (9, 7),
1080 (32, 32),
1081 ] {
1082 let plane: Vec<i32> = (0..width * height)
1083 .map(|_| next_sample(&mut state))
1084 .collect();
1085 let (blocks, block_cols, block_rows) = pack_plane(&plane, width, height);
1086 let got = reversible_dwt53_first_level_from_block_samples(
1087 &blocks, block_cols, block_rows, width, height,
1088 )
1089 .expect("oracle accepts the packed grid");
1090 let want = ref_53_2d(&plane, width, height);
1091 assert_eq!(
1092 (
1093 got.low_width,
1094 got.low_height,
1095 got.high_width,
1096 got.high_height
1097 ),
1098 (
1099 want.low_width,
1100 want.low_height,
1101 want.high_width,
1102 want.high_height
1103 ),
1104 "band dimensions for {width}x{height}"
1105 );
1106 assert_eq!(got.ll, want.ll, "LL mismatch for {width}x{height}");
1107 assert_eq!(got.hl, want.hl, "HL mismatch for {width}x{height}");
1108 assert_eq!(got.lh, want.lh, "LH mismatch for {width}x{height}");
1109 assert_eq!(got.hh, want.hh, "HH mismatch for {width}x{height}");
1110 }
1111 }
1112
1113 #[test]
1114 fn reversible_lift_53_kills_dc_and_linear_detail() {
1115 let mut constant = vec![7i32; 32];
1117 reversible_lift_53_i32(&mut constant);
1118 assert!(
1119 constant.iter().skip(1).step_by(2).all(|&v| v == 0),
1120 "constant produced nonzero detail"
1121 );
1122 assert!(
1123 constant.iter().step_by(2).all(|&v| v == 7),
1124 "constant low band drifted from 7"
1125 );
1126
1127 let ramp: Vec<i32> = (0..40_i32).map(|k| 3 * k - 5).collect();
1129 let mut lifted = ramp;
1130 reversible_lift_53_i32(&mut lifted);
1131 let detail: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1132 for &value in &detail[1..detail.len() - 1] {
1133 assert_eq!(value, 0, "linear ramp produced interior detail {value}");
1134 }
1135 }
1136
1137 #[test]
1138 fn reversible_dwt53_2d_separates_horizontal_and_vertical_detail() {
1139 let (width, height) = (16usize, 16usize);
1141 let varies_in_x: Vec<i32> = (0..width * height)
1142 .map(|i| 3 * i32::try_from(i % width).unwrap() - 7)
1143 .collect();
1144 let (blocks, bc, br) = pack_plane(&varies_in_x, width, height);
1145 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1146 .expect("oracle accepts grid");
1147 assert!(
1148 t.lh.iter().all(|&v| v == 0),
1149 "x-only plane produced LH detail"
1150 );
1151 assert!(
1152 t.hh.iter().all(|&v| v == 0),
1153 "x-only plane produced HH detail"
1154 );
1155
1156 let varies_in_y: Vec<i32> = (0..width * height)
1158 .map(|i| 3 * i32::try_from(i / width).unwrap() - 7)
1159 .collect();
1160 let (blocks, bc, br) = pack_plane(&varies_in_y, width, height);
1161 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1162 .expect("oracle accepts grid");
1163 assert!(
1164 t.hl.iter().all(|&v| v == 0),
1165 "y-only plane produced HL detail"
1166 );
1167 assert!(
1168 t.hh.iter().all(|&v| v == 0),
1169 "y-only plane produced HH detail"
1170 );
1171 }
1172}