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 fn last_htj2k97_required_magnitude_bounds(&self) -> &[u8] {
576 &[]
577 }
578}
579
580#[derive(Debug, Default, Clone, Copy)]
582pub struct CpuOnlyDctToWaveletStageAccelerator;
583
584#[doc(hidden)]
585impl DctToWaveletStageAccelerator for CpuOnlyDctToWaveletStageAccelerator {}
586
587#[derive(Debug, Default, Clone)]
593pub struct RayonReversibleDwt53Accelerator {
594 attempts: usize,
595 dispatches: usize,
596 batch_attempts: usize,
597 batch_dispatches: usize,
598}
599
600impl RayonReversibleDwt53Accelerator {
601 #[must_use]
603 pub const fn reversible_dwt53_attempts(&self) -> usize {
604 self.attempts
605 }
606
607 #[must_use]
609 pub const fn reversible_dwt53_dispatches(&self) -> usize {
610 self.dispatches
611 }
612
613 #[must_use]
615 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
616 self.batch_attempts
617 }
618
619 #[must_use]
621 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
622 self.batch_dispatches
623 }
624}
625
626#[doc(hidden)]
627impl DctToWaveletStageAccelerator for RayonReversibleDwt53Accelerator {
628 fn dct_grid_to_reversible_dwt53(
629 &mut self,
630 job: DctGridToReversibleDwt53Job<'_>,
631 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
632 self.attempts = self.attempts.saturating_add(1);
633 let output = reversible_dwt53_first_level_rayon(job)?;
634 self.dispatches = self.dispatches.saturating_add(1);
635 Ok(Some(output))
636 }
637
638 fn dct_grid_to_reversible_dwt53_batch(
639 &mut self,
640 jobs: &[DctGridToReversibleDwt53Job<'_>],
641 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
642 self.batch_attempts = self.batch_attempts.saturating_add(1);
643 validate_reversible_batch_workspace(jobs)?;
644 let mut output = try_vec_with_capacity(jobs.len()).map_err(TranscodeStageError::from)?;
645 for job in jobs {
646 output.push(reversible_dwt53_first_level_rayon(*job)?);
647 }
648 self.batch_dispatches = self.batch_dispatches.saturating_add(1);
649 Ok(Some(output))
650 }
651}
652
653#[doc(hidden)]
659pub fn idct_blocks_to_signed_samples_rayon(
660 blocks: &[[i16; 64]],
661) -> Result<Vec<[i32; 64]>, TranscodeStageError> {
662 let mut output = try_vec_filled(blocks.len(), [0i32; 64]).map_err(TranscodeStageError::from)?;
663 output
664 .par_iter_mut()
665 .zip(blocks.par_iter())
666 .for_each(|(output, block)| {
667 let decoded = idct_islow_block(block);
668 *output = decoded.map(|sample| i32::from(sample) - 128);
669 });
670 Ok(output)
671}
672
673pub(crate) fn reversible_dwt53_first_level_from_block_samples(
676 block_samples: &[[i32; 64]],
677 block_cols: usize,
678 block_rows: usize,
679 width: usize,
680 height: usize,
681) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
682 validate_reversible_grid(block_samples.len(), block_cols, block_rows, width, height)?;
683 validate_reversible_output_workspace(width, height)?;
684
685 let low_width = width.div_ceil(2);
686 let low_height = height.div_ceil(2);
687 let high_width = width / 2;
688 let high_height = height / 2;
689
690 let low_row_count = checked_stage_product(width, low_height)?;
691 let mut low_rows = try_vec_filled(low_row_count, 0i32).map_err(TranscodeStageError::from)?;
692 low_rows
693 .par_chunks_mut(width)
694 .enumerate()
695 .for_each(|(output_y, row)| {
696 for (x, sample) in row.iter_mut().enumerate() {
697 *sample =
698 vertical_low_53_i32_at(block_samples, block_cols, width, height, x, output_y);
699 }
700 reversible_lift_53_i32(row);
701 });
702 let high_row_count = checked_stage_product(width, high_height)?;
703 let mut high_rows = try_vec_filled(high_row_count, 0i32).map_err(TranscodeStageError::from)?;
704 high_rows
705 .par_chunks_mut(width)
706 .enumerate()
707 .for_each(|(output_y, row)| {
708 for (x, sample) in row.iter_mut().enumerate() {
709 *sample =
710 vertical_high_53_i32_at(block_samples, block_cols, width, height, x, output_y);
711 }
712 reversible_lift_53_i32(row);
713 });
714
715 let mut ll = try_vec_with_capacity(checked_stage_product(low_width, low_height)?)
716 .map_err(TranscodeStageError::from)?;
717 let mut hl = try_vec_with_capacity(checked_stage_product(high_width, low_height)?)
718 .map_err(TranscodeStageError::from)?;
719 for row in low_rows.chunks_exact(width) {
720 ll.extend(row.iter().step_by(2).copied());
721 hl.extend(row.iter().skip(1).step_by(2).copied());
722 }
723
724 let mut lh = try_vec_with_capacity(checked_stage_product(low_width, high_height)?)
725 .map_err(TranscodeStageError::from)?;
726 let mut hh = try_vec_with_capacity(checked_stage_product(high_width, high_height)?)
727 .map_err(TranscodeStageError::from)?;
728 for row in high_rows.chunks_exact(width) {
729 lh.extend(row.iter().step_by(2).copied());
730 hh.extend(row.iter().skip(1).step_by(2).copied());
731 }
732
733 Ok(ReversibleDwt53FirstLevel {
734 ll,
735 hl,
736 lh,
737 hh,
738 low_width,
739 low_height,
740 high_width,
741 high_height,
742 })
743}
744
745fn reversible_dwt53_first_level_rayon(
746 job: DctGridToReversibleDwt53Job<'_>,
747) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
748 validate_reversible_grid(
749 job.dequantized_blocks.len(),
750 job.block_cols,
751 job.block_rows,
752 job.width,
753 job.height,
754 )?;
755 validate_reversible_job_workspace(job)?;
756 let block_samples = idct_blocks_to_signed_samples_rayon(job.dequantized_blocks)?;
757 reversible_dwt53_first_level_from_block_samples(
758 &block_samples,
759 job.block_cols,
760 job.block_rows,
761 job.width,
762 job.height,
763 )
764}
765
766fn validate_reversible_output_workspace(
767 width: usize,
768 height: usize,
769) -> Result<(), TranscodeStageError> {
770 let sample_count = checked_stage_product(width, height)?;
771 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
772 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
773 checked_add_allocation_bytes(row_bytes, band_bytes)
774 .map(|_| ())
775 .map_err(TranscodeStageError::from)
776}
777
778fn validate_reversible_job_workspace(
779 job: DctGridToReversibleDwt53Job<'_>,
780) -> Result<(), TranscodeStageError> {
781 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
782 let sample_count = checked_stage_product(job.width, job.height)?;
783 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
784 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
785 let workspace = checked_add_allocation_bytes(block_bytes, row_bytes)?;
786 checked_add_allocation_bytes(workspace, band_bytes)?;
787 Ok(())
788}
789
790fn validate_reversible_batch_workspace(
791 jobs: &[DctGridToReversibleDwt53Job<'_>],
792) -> Result<(), TranscodeStageError> {
793 let mut retained_output_bytes = 0usize;
794 let mut max_transient_bytes = 0usize;
795 for job in jobs {
796 validate_reversible_grid(
797 job.dequantized_blocks.len(),
798 job.block_cols,
799 job.block_rows,
800 job.width,
801 job.height,
802 )?;
803 let sample_count = checked_stage_product(job.width, job.height)?;
804 let output_bytes = checked_allocation_bytes::<i32>(sample_count)?;
805 retained_output_bytes = checked_add_allocation_bytes(retained_output_bytes, output_bytes)?;
806 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
807 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
808 max_transient_bytes =
809 max_transient_bytes.max(checked_add_allocation_bytes(block_bytes, row_bytes)?);
810 }
811 checked_add_allocation_bytes(retained_output_bytes, max_transient_bytes)?;
812 Ok(())
813}
814
815fn checked_stage_product(left: usize, right: usize) -> Result<usize, TranscodeStageError> {
816 left.checked_mul(right)
817 .ok_or(TranscodeStageError::MemoryCapExceeded {
818 requested: usize::MAX,
819 cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
820 })
821}
822
823fn validate_reversible_grid(
824 block_count: usize,
825 block_cols: usize,
826 block_rows: usize,
827 width: usize,
828 height: usize,
829) -> Result<(), TranscodeStageError> {
830 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
831 .map_err(|_| TranscodeStageError::Unsupported(REVERSIBLE_DWT53_UNSUPPORTED_GRID))
832}
833
834fn vertical_low_53_i32_at(
835 block_samples: &[[i32; 64]],
836 block_cols: usize,
837 width: usize,
838 height: usize,
839 x: usize,
840 low_idx: usize,
841) -> i32 {
842 reversible_lift_53_low_at(height, low_idx, |y| {
843 component_sample_i32(block_samples, block_cols, width, height, x, y)
844 })
845}
846
847fn vertical_high_53_i32_at(
848 block_samples: &[[i32; 64]],
849 block_cols: usize,
850 width: usize,
851 height: usize,
852 x: usize,
853 high_idx: usize,
854) -> i32 {
855 reversible_lift_53_high_at(height, high_idx, |y| {
856 component_sample_i32(block_samples, block_cols, width, height, x, y)
857 })
858}
859
860fn component_sample_i32(
861 block_samples: &[[i32; 64]],
862 block_cols: usize,
863 width: usize,
864 height: usize,
865 x: usize,
866 y: usize,
867) -> i32 {
868 debug_assert!(x < width);
869 debug_assert!(y < height);
870 let block_x = x / 8;
871 let block_y = y / 8;
872 let block_idx = block_y * block_cols + block_x;
873 let local_idx = (y % 8) * 8 + (x % 8);
874 block_samples[block_idx][local_idx]
875}
876
877#[cfg(test)]
878mod allocation_tests {
879 use super::{
880 idct_blocks_to_signed_samples_rayon, validate_reversible_grid,
881 validate_reversible_output_workspace, TranscodeStageError,
882 REVERSIBLE_DWT53_UNSUPPORTED_GRID,
883 };
884
885 #[test]
886 fn malformed_reversible_grid_is_explicitly_unsupported() {
887 assert!(matches!(
888 validate_reversible_grid(0, 1, 1, 8, 8),
889 Err(TranscodeStageError::Unsupported(
890 REVERSIBLE_DWT53_UNSUPPORTED_GRID
891 ))
892 ));
893 }
894
895 #[test]
896 fn reversible_workspace_overflow_is_typed() {
897 assert!(matches!(
898 validate_reversible_output_workspace(usize::MAX, 2),
899 Err(TranscodeStageError::MemoryCapExceeded {
900 requested: usize::MAX,
901 ..
902 })
903 ));
904 }
905
906 #[test]
907 fn fallible_parallel_idct_preserves_signed_samples() {
908 let blocks = [[0i16; 64]; 2];
909 let samples = idct_blocks_to_signed_samples_rayon(&blocks)
910 .expect("two block outputs fit the host cap");
911 assert_eq!(samples, [[0i32; 64]; 2]);
912 }
913}
914
915#[cfg(test)]
916mod ground_truth_tests {
917 use super::{
927 reversible_dwt53_first_level_from_block_samples, reversible_lift_53_i32,
928 ReversibleDwt53FirstLevel,
929 };
930
931 fn floor2(a: i32, b: i32) -> i32 {
932 a.div_euclid(b)
933 }
934
935 fn ws_reflect(i: isize, n: usize) -> usize {
938 if n == 1 {
939 return 0;
940 }
941 let n = isize::try_from(n).unwrap();
942 let period = 2 * (n - 1);
943 let mut k = i.rem_euclid(period);
944 if k >= n {
945 k = period - k;
946 }
947 usize::try_from(k).unwrap()
948 }
949
950 fn ref_53_forward(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
955 let n = signal.len();
956 if n < 2 {
957 return (signal.to_vec(), Vec::new());
958 }
959 let sig = |i: isize| signal[ws_reflect(i, n)];
960 let detail = |m: isize| {
961 let c = 2 * m + 1;
962 sig(c) - floor2(sig(c - 1) + sig(c + 1), 2)
963 };
964 let low: Vec<i32> = (0..n.div_ceil(2))
965 .map(|m| {
966 let mi = isize::try_from(m).unwrap();
967 sig(2 * mi) + floor2(detail(mi - 1) + detail(mi) + 2, 4)
968 })
969 .collect();
970 let high: Vec<i32> = (0..n / 2)
971 .map(|m| detail(isize::try_from(m).unwrap()))
972 .collect();
973 (low, high)
974 }
975
976 fn ref_53_2d(plane: &[i32], width: usize, height: usize) -> ReversibleDwt53FirstLevel {
979 let low_width = width.div_ceil(2);
980 let high_width = width / 2;
981 let low_height = height.div_ceil(2);
982 let high_height = height / 2;
983
984 let mut v_low = vec![0i32; width * low_height];
985 let mut v_high = vec![0i32; width * high_height];
986 for x in 0..width {
987 let column: Vec<i32> = (0..height).map(|y| plane[y * width + x]).collect();
988 let (lo, hi) = ref_53_forward(&column);
989 for (oy, &value) in lo.iter().enumerate() {
990 v_low[oy * width + x] = value;
991 }
992 for (oy, &value) in hi.iter().enumerate() {
993 v_high[oy * width + x] = value;
994 }
995 }
996
997 let horizontal = |source: &[i32], rows: usize| -> (Vec<i32>, Vec<i32>) {
998 let mut low = vec![0i32; low_width * rows];
999 let mut high = vec![0i32; high_width * rows];
1000 for oy in 0..rows {
1001 let (lo, hi) = ref_53_forward(&source[oy * width..oy * width + width]);
1002 low[oy * low_width..oy * low_width + low_width].copy_from_slice(&lo);
1003 high[oy * high_width..oy * high_width + high_width].copy_from_slice(&hi);
1004 }
1005 (low, high)
1006 };
1007
1008 let (ll, hl) = horizontal(&v_low, low_height);
1009 let (lh, hh) = horizontal(&v_high, high_height);
1010
1011 ReversibleDwt53FirstLevel {
1012 ll,
1013 hl,
1014 lh,
1015 hh,
1016 low_width,
1017 low_height,
1018 high_width,
1019 high_height,
1020 }
1021 }
1022
1023 fn pack_plane(plane: &[i32], width: usize, height: usize) -> (Vec<[i32; 64]>, usize, usize) {
1027 let block_cols = width.div_ceil(8);
1028 let block_rows = height.div_ceil(8);
1029 let mut blocks = vec![[0i32; 64]; block_cols * block_rows];
1030 for y in 0..height {
1031 for x in 0..width {
1032 let block = (y / 8) * block_cols + (x / 8);
1033 blocks[block][(y % 8) * 8 + (x % 8)] = plane[y * width + x];
1034 }
1035 }
1036 (blocks, block_cols, block_rows)
1037 }
1038
1039 fn next_sample(state: &mut u64) -> i32 {
1040 *state = state
1041 .wrapping_mul(6_364_136_223_846_793_005)
1042 .wrapping_add(1_442_695_040_888_963_407);
1043 ((*state >> 40) & 0x1ff) as i32 - 256
1044 }
1045
1046 #[test]
1047 fn reversible_lift_53_matches_canonical_formula_1d() {
1048 let mut state = 0x0a11_ce5e_ed00_d001u64;
1049 for n in [2usize, 3, 4, 5, 8, 9, 12, 15, 16, 23, 32, 33, 64, 65] {
1050 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1051 let mut lifted = signal.clone();
1052 reversible_lift_53_i32(&mut lifted);
1053 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1054 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1055 let (low, high) = ref_53_forward(&signal);
1056 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1057 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1058 }
1059 }
1060
1061 #[test]
1062 fn reversible_lift_53_shared_helper_matches_canonical_formula_1d() {
1063 let mut state = 0x5a53_5a53_5a53_5a53u64;
1064 for n in [2usize, 3, 4, 5, 8, 9, 16, 17, 31, 32, 65] {
1065 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1066 let mut lifted = signal.clone();
1067 crate::reversible53::reversible_lift_53_i32(&mut lifted);
1068 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1069 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1070 let (low, high) = ref_53_forward(&signal);
1071 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1072 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1073 }
1074 }
1075
1076 #[test]
1077 fn reversible_dwt53_2d_matches_canonical_separable() {
1078 let mut state = 0xfeed_5eed_d00d_face_u64;
1079 for (width, height) in [
1080 (8usize, 8usize),
1081 (16, 16),
1082 (24, 16),
1083 (15, 13),
1084 (16, 23),
1085 (9, 7),
1086 (32, 32),
1087 ] {
1088 let plane: Vec<i32> = (0..width * height)
1089 .map(|_| next_sample(&mut state))
1090 .collect();
1091 let (blocks, block_cols, block_rows) = pack_plane(&plane, width, height);
1092 let got = reversible_dwt53_first_level_from_block_samples(
1093 &blocks, block_cols, block_rows, width, height,
1094 )
1095 .expect("oracle accepts the packed grid");
1096 let want = ref_53_2d(&plane, width, height);
1097 assert_eq!(
1098 (
1099 got.low_width,
1100 got.low_height,
1101 got.high_width,
1102 got.high_height
1103 ),
1104 (
1105 want.low_width,
1106 want.low_height,
1107 want.high_width,
1108 want.high_height
1109 ),
1110 "band dimensions for {width}x{height}"
1111 );
1112 assert_eq!(got.ll, want.ll, "LL mismatch for {width}x{height}");
1113 assert_eq!(got.hl, want.hl, "HL mismatch for {width}x{height}");
1114 assert_eq!(got.lh, want.lh, "LH mismatch for {width}x{height}");
1115 assert_eq!(got.hh, want.hh, "HH mismatch for {width}x{height}");
1116 }
1117 }
1118
1119 #[test]
1120 fn reversible_lift_53_kills_dc_and_linear_detail() {
1121 let mut constant = vec![7i32; 32];
1123 reversible_lift_53_i32(&mut constant);
1124 assert!(
1125 constant.iter().skip(1).step_by(2).all(|&v| v == 0),
1126 "constant produced nonzero detail"
1127 );
1128 assert!(
1129 constant.iter().step_by(2).all(|&v| v == 7),
1130 "constant low band drifted from 7"
1131 );
1132
1133 let ramp: Vec<i32> = (0..40_i32).map(|k| 3 * k - 5).collect();
1135 let mut lifted = ramp;
1136 reversible_lift_53_i32(&mut lifted);
1137 let detail: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1138 for &value in &detail[1..detail.len() - 1] {
1139 assert_eq!(value, 0, "linear ramp produced interior detail {value}");
1140 }
1141 }
1142
1143 #[test]
1144 fn reversible_dwt53_2d_separates_horizontal_and_vertical_detail() {
1145 let (width, height) = (16usize, 16usize);
1147 let varies_in_x: Vec<i32> = (0..width * height)
1148 .map(|i| 3 * i32::try_from(i % width).unwrap() - 7)
1149 .collect();
1150 let (blocks, bc, br) = pack_plane(&varies_in_x, width, height);
1151 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1152 .expect("oracle accepts grid");
1153 assert!(
1154 t.lh.iter().all(|&v| v == 0),
1155 "x-only plane produced LH detail"
1156 );
1157 assert!(
1158 t.hh.iter().all(|&v| v == 0),
1159 "x-only plane produced HH detail"
1160 );
1161
1162 let varies_in_y: Vec<i32> = (0..width * height)
1164 .map(|i| 3 * i32::try_from(i / width).unwrap() - 7)
1165 .collect();
1166 let (blocks, bc, br) = pack_plane(&varies_in_y, width, height);
1167 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1168 .expect("oracle accepts grid");
1169 assert!(
1170 t.hl.iter().all(|&v| v == 0),
1171 "y-only plane produced HL detail"
1172 );
1173 assert!(
1174 t.hh.iter().all(|&v| v == 0),
1175 "y-only plane produced HH detail"
1176 );
1177 }
1178}