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
135#[derive(Debug, Clone, Copy, PartialEq)]
138pub struct Htj2k97CodeBlockOptions {
139 pub bit_depth: u8,
141 pub guard_bits: u8,
143 pub code_block_width_exp: u8,
145 pub code_block_height_exp: u8,
147 pub irreversible_quantization_scale: f32,
149 pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum DctToWaveletStageCounterEvent {
157 ReversibleDwt53Attempt,
159 ReversibleDwt53Dispatch,
161 ReversibleDwt53BatchAttempt,
163 ReversibleDwt53BatchDispatch,
165 Dwt53Attempt,
167 Dwt53Dispatch,
169 Dwt97Attempt,
171 Dwt97Dispatch,
173 Dwt97BatchAttempt,
175 Dwt97BatchDispatch,
177 Htj2k97CodeblockBatchAttempt,
179 Htj2k97CodeblockBatchDispatch,
181}
182
183#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
185pub struct DctToWaveletStageCounters {
186 reversible_dwt53_attempts: usize,
187 reversible_dwt53_dispatches: usize,
188 reversible_dwt53_batch_attempts: usize,
189 reversible_dwt53_batch_dispatches: usize,
190 dwt53_attempts: usize,
191 dwt53_dispatches: usize,
192 dwt97_attempts: usize,
193 dwt97_dispatches: usize,
194 dwt97_batch_attempts: usize,
195 dwt97_batch_dispatches: usize,
196 htj2k97_codeblock_batch_attempts: usize,
197 htj2k97_codeblock_batch_dispatches: usize,
198}
199
200impl DctToWaveletStageCounters {
201 #[must_use]
203 pub const fn new() -> Self {
204 Self {
205 reversible_dwt53_attempts: 0,
206 reversible_dwt53_dispatches: 0,
207 reversible_dwt53_batch_attempts: 0,
208 reversible_dwt53_batch_dispatches: 0,
209 dwt53_attempts: 0,
210 dwt53_dispatches: 0,
211 dwt97_attempts: 0,
212 dwt97_dispatches: 0,
213 dwt97_batch_attempts: 0,
214 dwt97_batch_dispatches: 0,
215 htj2k97_codeblock_batch_attempts: 0,
216 htj2k97_codeblock_batch_dispatches: 0,
217 }
218 }
219
220 #[must_use]
222 pub const fn reversible_dwt53_attempts(&self) -> usize {
223 self.reversible_dwt53_attempts
224 }
225
226 #[must_use]
228 pub const fn reversible_dwt53_dispatches(&self) -> usize {
229 self.reversible_dwt53_dispatches
230 }
231
232 #[must_use]
234 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
235 self.reversible_dwt53_batch_attempts
236 }
237
238 #[must_use]
240 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
241 self.reversible_dwt53_batch_dispatches
242 }
243
244 #[must_use]
246 pub const fn dwt53_attempts(&self) -> usize {
247 self.dwt53_attempts
248 }
249
250 #[must_use]
252 pub const fn dwt53_dispatches(&self) -> usize {
253 self.dwt53_dispatches
254 }
255
256 #[must_use]
258 pub const fn dwt97_attempts(&self) -> usize {
259 self.dwt97_attempts
260 }
261
262 #[must_use]
264 pub const fn dwt97_dispatches(&self) -> usize {
265 self.dwt97_dispatches
266 }
267
268 #[must_use]
270 pub const fn dwt97_batch_attempts(&self) -> usize {
271 self.dwt97_batch_attempts
272 }
273
274 #[must_use]
276 pub const fn dwt97_batch_dispatches(&self) -> usize {
277 self.dwt97_batch_dispatches
278 }
279
280 #[must_use]
282 pub const fn htj2k97_codeblock_batch_attempts(&self) -> usize {
283 self.htj2k97_codeblock_batch_attempts
284 }
285
286 #[must_use]
288 pub const fn htj2k97_codeblock_batch_dispatches(&self) -> usize {
289 self.htj2k97_codeblock_batch_dispatches
290 }
291
292 pub fn record(&mut self, event: DctToWaveletStageCounterEvent, count: usize) {
294 match event {
295 DctToWaveletStageCounterEvent::ReversibleDwt53Attempt => {
296 self.reversible_dwt53_attempts =
297 self.reversible_dwt53_attempts.saturating_add(count);
298 }
299 DctToWaveletStageCounterEvent::ReversibleDwt53Dispatch => {
300 self.reversible_dwt53_dispatches =
301 self.reversible_dwt53_dispatches.saturating_add(count);
302 }
303 DctToWaveletStageCounterEvent::ReversibleDwt53BatchAttempt => {
304 self.reversible_dwt53_batch_attempts =
305 self.reversible_dwt53_batch_attempts.saturating_add(count);
306 }
307 DctToWaveletStageCounterEvent::ReversibleDwt53BatchDispatch => {
308 self.reversible_dwt53_batch_dispatches =
309 self.reversible_dwt53_batch_dispatches.saturating_add(count);
310 }
311 DctToWaveletStageCounterEvent::Dwt53Attempt => {
312 self.dwt53_attempts = self.dwt53_attempts.saturating_add(count);
313 }
314 DctToWaveletStageCounterEvent::Dwt53Dispatch => {
315 self.dwt53_dispatches = self.dwt53_dispatches.saturating_add(count);
316 }
317 DctToWaveletStageCounterEvent::Dwt97Attempt => {
318 self.dwt97_attempts = self.dwt97_attempts.saturating_add(count);
319 }
320 DctToWaveletStageCounterEvent::Dwt97Dispatch => {
321 self.dwt97_dispatches = self.dwt97_dispatches.saturating_add(count);
322 }
323 DctToWaveletStageCounterEvent::Dwt97BatchAttempt => {
324 self.dwt97_batch_attempts = self.dwt97_batch_attempts.saturating_add(count);
325 }
326 DctToWaveletStageCounterEvent::Dwt97BatchDispatch => {
327 self.dwt97_batch_dispatches = self.dwt97_batch_dispatches.saturating_add(count);
328 }
329 DctToWaveletStageCounterEvent::Htj2k97CodeblockBatchAttempt => {
330 self.htj2k97_codeblock_batch_attempts =
331 self.htj2k97_codeblock_batch_attempts.saturating_add(count);
332 }
333 DctToWaveletStageCounterEvent::Htj2k97CodeblockBatchDispatch => {
334 self.htj2k97_codeblock_batch_dispatches = self
335 .htj2k97_codeblock_batch_dispatches
336 .saturating_add(count);
337 }
338 }
339 }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub enum TranscodeStageDispatchMode {
345 Explicit,
347 Auto,
350}
351
352impl TranscodeStageDispatchMode {
353 #[must_use]
356 pub const fn is_auto(self) -> bool {
357 matches!(self, Self::Auto)
358 }
359
360 #[doc(hidden)]
363 pub const fn unavailable<T>(self) -> Result<Option<T>, TranscodeStageError> {
364 match self {
365 Self::Explicit => Err(TranscodeStageError::DeviceUnavailable),
366 Self::Auto => Ok(None),
367 }
368 }
369
370 #[doc(hidden)]
376 pub fn recover<T, E>(
377 self,
378 error: E,
379 is_recoverable: impl FnOnce(&E) -> bool,
380 ) -> Result<Option<T>, TranscodeStageError>
381 where
382 E: Into<TranscodeStageError>,
383 {
384 if self.is_auto() && is_recoverable(&error) {
385 Ok(None)
386 } else {
387 Err(error.into())
388 }
389 }
390}
391
392pub trait DctToWaveletStageAccelerator {
394 fn supports_dwt97_batch(&self) -> bool {
400 false
401 }
402
403 fn supports_htj2k97_codeblock_batch(&self) -> bool {
406 false
407 }
408
409 fn supports_htj2k97_i16_preencoded_batch(&self) -> bool {
413 false
414 }
415
416 fn supports_htj2k97_compact_preencoded_batch(&self) -> bool {
419 self.supports_htj2k97_i16_preencoded_batch()
420 }
421
422 fn dct_grid_to_reversible_dwt53(
429 &mut self,
430 _job: DctGridToReversibleDwt53Job<'_>,
431 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
432 Ok(None)
433 }
434
435 fn dct_grid_to_reversible_dwt53_batch(
441 &mut self,
442 _jobs: &[DctGridToReversibleDwt53Job<'_>],
443 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
444 Ok(None)
445 }
446
447 fn dct_grid_to_dwt53(
452 &mut self,
453 _job: DctGridToDwt53Job<'_>,
454 ) -> Result<Option<Dwt53TwoDimensional<f64>>, TranscodeStageError> {
455 Ok(None)
456 }
457
458 fn dct_grid_to_dwt97(
463 &mut self,
464 _job: DctGridToDwt97Job<'_>,
465 ) -> Result<Option<Dwt97TwoDimensional<f64>>, TranscodeStageError> {
466 Ok(None)
467 }
468
469 fn dct_grid_to_dwt97_batch(
475 &mut self,
476 _jobs: &[DctGridToDwt97Job<'_>],
477 ) -> Result<Option<Vec<Dwt97TwoDimensional<f64>>>, TranscodeStageError> {
478 Ok(None)
479 }
480
481 fn dct_grid_to_htj2k97_codeblock_batch(
487 &mut self,
488 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
489 _options: Htj2k97CodeBlockOptions,
490 ) -> Result<Option<Vec<PrequantizedHtj2k97Component>>, TranscodeStageError> {
491 Ok(None)
492 }
493
494 fn dct_grid_to_htj2k97_preencoded_batch(
500 &mut self,
501 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
502 _options: Htj2k97CodeBlockOptions,
503 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
504 Ok(None)
505 }
506
507 fn dct_grid_i16_to_htj2k97_preencoded_batch(
513 &mut self,
514 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
515 _options: Htj2k97CodeBlockOptions,
516 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
517 Ok(None)
518 }
519
520 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch(
527 &mut self,
528 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
529 _options: Htj2k97CodeBlockOptions,
530 ) -> Result<Option<PreencodedHtj2k97CompactBatch>, TranscodeStageError> {
531 Ok(None)
532 }
533
534 fn dct_grid_i16_to_htj2k97_preencoded_batch_groups(
542 &mut self,
543 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
544 _options: Htj2k97CodeBlockOptions,
545 ) -> Result<Option<Vec<Vec<PreencodedHtj2k97Component>>>, TranscodeStageError> {
546 Ok(None)
547 }
548
549 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch_groups(
556 &mut self,
557 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
558 _options: Htj2k97CodeBlockOptions,
559 ) -> Result<Option<PreencodedHtj2k97CompactBatchGroups>, TranscodeStageError> {
560 Ok(None)
561 }
562
563 fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
565 None
566 }
567}
568
569#[derive(Debug, Default, Clone, Copy)]
571pub struct CpuOnlyDctToWaveletStageAccelerator;
572
573#[doc(hidden)]
574impl DctToWaveletStageAccelerator for CpuOnlyDctToWaveletStageAccelerator {}
575
576#[derive(Debug, Default, Clone)]
582pub struct RayonReversibleDwt53Accelerator {
583 attempts: usize,
584 dispatches: usize,
585 batch_attempts: usize,
586 batch_dispatches: usize,
587}
588
589impl RayonReversibleDwt53Accelerator {
590 #[must_use]
592 pub const fn reversible_dwt53_attempts(&self) -> usize {
593 self.attempts
594 }
595
596 #[must_use]
598 pub const fn reversible_dwt53_dispatches(&self) -> usize {
599 self.dispatches
600 }
601
602 #[must_use]
604 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
605 self.batch_attempts
606 }
607
608 #[must_use]
610 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
611 self.batch_dispatches
612 }
613}
614
615#[doc(hidden)]
616impl DctToWaveletStageAccelerator for RayonReversibleDwt53Accelerator {
617 fn dct_grid_to_reversible_dwt53(
618 &mut self,
619 job: DctGridToReversibleDwt53Job<'_>,
620 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
621 self.attempts = self.attempts.saturating_add(1);
622 let output = reversible_dwt53_first_level_rayon(job)?;
623 self.dispatches = self.dispatches.saturating_add(1);
624 Ok(Some(output))
625 }
626
627 fn dct_grid_to_reversible_dwt53_batch(
628 &mut self,
629 jobs: &[DctGridToReversibleDwt53Job<'_>],
630 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
631 self.batch_attempts = self.batch_attempts.saturating_add(1);
632 validate_reversible_batch_workspace(jobs)?;
633 let mut output = try_vec_with_capacity(jobs.len()).map_err(TranscodeStageError::from)?;
634 for job in jobs {
635 output.push(reversible_dwt53_first_level_rayon(*job)?);
636 }
637 self.batch_dispatches = self.batch_dispatches.saturating_add(1);
638 Ok(Some(output))
639 }
640}
641
642#[doc(hidden)]
648pub fn idct_blocks_to_signed_samples_rayon(
649 blocks: &[[i16; 64]],
650) -> Result<Vec<[i32; 64]>, TranscodeStageError> {
651 let mut output = try_vec_filled(blocks.len(), [0i32; 64]).map_err(TranscodeStageError::from)?;
652 output
653 .par_iter_mut()
654 .zip(blocks.par_iter())
655 .for_each(|(output, block)| {
656 let decoded = idct_islow_block(block);
657 *output = decoded.map(|sample| i32::from(sample) - 128);
658 });
659 Ok(output)
660}
661
662pub(crate) fn reversible_dwt53_first_level_from_block_samples(
665 block_samples: &[[i32; 64]],
666 block_cols: usize,
667 block_rows: usize,
668 width: usize,
669 height: usize,
670) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
671 validate_reversible_grid(block_samples.len(), block_cols, block_rows, width, height)?;
672 validate_reversible_output_workspace(width, height)?;
673
674 let low_width = width.div_ceil(2);
675 let low_height = height.div_ceil(2);
676 let high_width = width / 2;
677 let high_height = height / 2;
678
679 let low_row_count = checked_stage_product(width, low_height)?;
680 let mut low_rows = try_vec_filled(low_row_count, 0i32).map_err(TranscodeStageError::from)?;
681 low_rows
682 .par_chunks_mut(width)
683 .enumerate()
684 .for_each(|(output_y, row)| {
685 for (x, sample) in row.iter_mut().enumerate() {
686 *sample =
687 vertical_low_53_i32_at(block_samples, block_cols, width, height, x, output_y);
688 }
689 reversible_lift_53_i32(row);
690 });
691 let high_row_count = checked_stage_product(width, high_height)?;
692 let mut high_rows = try_vec_filled(high_row_count, 0i32).map_err(TranscodeStageError::from)?;
693 high_rows
694 .par_chunks_mut(width)
695 .enumerate()
696 .for_each(|(output_y, row)| {
697 for (x, sample) in row.iter_mut().enumerate() {
698 *sample =
699 vertical_high_53_i32_at(block_samples, block_cols, width, height, x, output_y);
700 }
701 reversible_lift_53_i32(row);
702 });
703
704 let mut ll = try_vec_with_capacity(checked_stage_product(low_width, low_height)?)
705 .map_err(TranscodeStageError::from)?;
706 let mut hl = try_vec_with_capacity(checked_stage_product(high_width, low_height)?)
707 .map_err(TranscodeStageError::from)?;
708 for row in low_rows.chunks_exact(width) {
709 ll.extend(row.iter().step_by(2).copied());
710 hl.extend(row.iter().skip(1).step_by(2).copied());
711 }
712
713 let mut lh = try_vec_with_capacity(checked_stage_product(low_width, high_height)?)
714 .map_err(TranscodeStageError::from)?;
715 let mut hh = try_vec_with_capacity(checked_stage_product(high_width, high_height)?)
716 .map_err(TranscodeStageError::from)?;
717 for row in high_rows.chunks_exact(width) {
718 lh.extend(row.iter().step_by(2).copied());
719 hh.extend(row.iter().skip(1).step_by(2).copied());
720 }
721
722 Ok(ReversibleDwt53FirstLevel {
723 ll,
724 hl,
725 lh,
726 hh,
727 low_width,
728 low_height,
729 high_width,
730 high_height,
731 })
732}
733
734fn reversible_dwt53_first_level_rayon(
735 job: DctGridToReversibleDwt53Job<'_>,
736) -> Result<ReversibleDwt53FirstLevel, TranscodeStageError> {
737 validate_reversible_grid(
738 job.dequantized_blocks.len(),
739 job.block_cols,
740 job.block_rows,
741 job.width,
742 job.height,
743 )?;
744 validate_reversible_job_workspace(job)?;
745 let block_samples = idct_blocks_to_signed_samples_rayon(job.dequantized_blocks)?;
746 reversible_dwt53_first_level_from_block_samples(
747 &block_samples,
748 job.block_cols,
749 job.block_rows,
750 job.width,
751 job.height,
752 )
753}
754
755fn validate_reversible_output_workspace(
756 width: usize,
757 height: usize,
758) -> Result<(), TranscodeStageError> {
759 let sample_count = checked_stage_product(width, height)?;
760 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
761 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
762 checked_add_allocation_bytes(row_bytes, band_bytes)
763 .map(|_| ())
764 .map_err(TranscodeStageError::from)
765}
766
767fn validate_reversible_job_workspace(
768 job: DctGridToReversibleDwt53Job<'_>,
769) -> Result<(), TranscodeStageError> {
770 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
771 let sample_count = checked_stage_product(job.width, job.height)?;
772 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
773 let band_bytes = checked_allocation_bytes::<i32>(sample_count)?;
774 let workspace = checked_add_allocation_bytes(block_bytes, row_bytes)?;
775 checked_add_allocation_bytes(workspace, band_bytes)?;
776 Ok(())
777}
778
779fn validate_reversible_batch_workspace(
780 jobs: &[DctGridToReversibleDwt53Job<'_>],
781) -> Result<(), TranscodeStageError> {
782 let mut retained_output_bytes = 0usize;
783 let mut max_transient_bytes = 0usize;
784 for job in jobs {
785 validate_reversible_grid(
786 job.dequantized_blocks.len(),
787 job.block_cols,
788 job.block_rows,
789 job.width,
790 job.height,
791 )?;
792 let sample_count = checked_stage_product(job.width, job.height)?;
793 let output_bytes = checked_allocation_bytes::<i32>(sample_count)?;
794 retained_output_bytes = checked_add_allocation_bytes(retained_output_bytes, output_bytes)?;
795 let block_bytes = checked_allocation_bytes::<[i32; 64]>(job.dequantized_blocks.len())?;
796 let row_bytes = checked_allocation_bytes::<i32>(sample_count)?;
797 max_transient_bytes =
798 max_transient_bytes.max(checked_add_allocation_bytes(block_bytes, row_bytes)?);
799 }
800 checked_add_allocation_bytes(retained_output_bytes, max_transient_bytes)?;
801 Ok(())
802}
803
804fn checked_stage_product(left: usize, right: usize) -> Result<usize, TranscodeStageError> {
805 left.checked_mul(right)
806 .ok_or(TranscodeStageError::MemoryCapExceeded {
807 requested: usize::MAX,
808 cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
809 })
810}
811
812fn validate_reversible_grid(
813 block_count: usize,
814 block_cols: usize,
815 block_rows: usize,
816 width: usize,
817 height: usize,
818) -> Result<(), TranscodeStageError> {
819 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
820 .map_err(|_| TranscodeStageError::Unsupported(REVERSIBLE_DWT53_UNSUPPORTED_GRID))
821}
822
823fn vertical_low_53_i32_at(
824 block_samples: &[[i32; 64]],
825 block_cols: usize,
826 width: usize,
827 height: usize,
828 x: usize,
829 low_idx: usize,
830) -> i32 {
831 reversible_lift_53_low_at(height, low_idx, |y| {
832 component_sample_i32(block_samples, block_cols, width, height, x, y)
833 })
834}
835
836fn vertical_high_53_i32_at(
837 block_samples: &[[i32; 64]],
838 block_cols: usize,
839 width: usize,
840 height: usize,
841 x: usize,
842 high_idx: usize,
843) -> i32 {
844 reversible_lift_53_high_at(height, high_idx, |y| {
845 component_sample_i32(block_samples, block_cols, width, height, x, y)
846 })
847}
848
849fn component_sample_i32(
850 block_samples: &[[i32; 64]],
851 block_cols: usize,
852 width: usize,
853 height: usize,
854 x: usize,
855 y: usize,
856) -> i32 {
857 debug_assert!(x < width);
858 debug_assert!(y < height);
859 let block_x = x / 8;
860 let block_y = y / 8;
861 let block_idx = block_y * block_cols + block_x;
862 let local_idx = (y % 8) * 8 + (x % 8);
863 block_samples[block_idx][local_idx]
864}
865
866#[cfg(test)]
867mod allocation_tests {
868 use super::{
869 idct_blocks_to_signed_samples_rayon, validate_reversible_grid,
870 validate_reversible_output_workspace, TranscodeStageError,
871 REVERSIBLE_DWT53_UNSUPPORTED_GRID,
872 };
873
874 #[test]
875 fn malformed_reversible_grid_is_explicitly_unsupported() {
876 assert!(matches!(
877 validate_reversible_grid(0, 1, 1, 8, 8),
878 Err(TranscodeStageError::Unsupported(
879 REVERSIBLE_DWT53_UNSUPPORTED_GRID
880 ))
881 ));
882 }
883
884 #[test]
885 fn reversible_workspace_overflow_is_typed() {
886 assert!(matches!(
887 validate_reversible_output_workspace(usize::MAX, 2),
888 Err(TranscodeStageError::MemoryCapExceeded {
889 requested: usize::MAX,
890 ..
891 })
892 ));
893 }
894
895 #[test]
896 fn fallible_parallel_idct_preserves_signed_samples() {
897 let blocks = [[0i16; 64]; 2];
898 let samples = idct_blocks_to_signed_samples_rayon(&blocks)
899 .expect("two block outputs fit the host cap");
900 assert_eq!(samples, [[0i32; 64]; 2]);
901 }
902}
903
904#[cfg(test)]
905mod ground_truth_tests {
906 use super::{
916 reversible_dwt53_first_level_from_block_samples, reversible_lift_53_i32,
917 ReversibleDwt53FirstLevel,
918 };
919
920 fn floor2(a: i32, b: i32) -> i32 {
921 a.div_euclid(b)
922 }
923
924 fn ws_reflect(i: isize, n: usize) -> usize {
927 if n == 1 {
928 return 0;
929 }
930 let n = isize::try_from(n).unwrap();
931 let period = 2 * (n - 1);
932 let mut k = i.rem_euclid(period);
933 if k >= n {
934 k = period - k;
935 }
936 usize::try_from(k).unwrap()
937 }
938
939 fn ref_53_forward(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
944 let n = signal.len();
945 if n < 2 {
946 return (signal.to_vec(), Vec::new());
947 }
948 let sig = |i: isize| signal[ws_reflect(i, n)];
949 let detail = |m: isize| {
950 let c = 2 * m + 1;
951 sig(c) - floor2(sig(c - 1) + sig(c + 1), 2)
952 };
953 let low: Vec<i32> = (0..n.div_ceil(2))
954 .map(|m| {
955 let mi = isize::try_from(m).unwrap();
956 sig(2 * mi) + floor2(detail(mi - 1) + detail(mi) + 2, 4)
957 })
958 .collect();
959 let high: Vec<i32> = (0..n / 2)
960 .map(|m| detail(isize::try_from(m).unwrap()))
961 .collect();
962 (low, high)
963 }
964
965 fn ref_53_2d(plane: &[i32], width: usize, height: usize) -> ReversibleDwt53FirstLevel {
968 let low_width = width.div_ceil(2);
969 let high_width = width / 2;
970 let low_height = height.div_ceil(2);
971 let high_height = height / 2;
972
973 let mut v_low = vec![0i32; width * low_height];
974 let mut v_high = vec![0i32; width * high_height];
975 for x in 0..width {
976 let column: Vec<i32> = (0..height).map(|y| plane[y * width + x]).collect();
977 let (lo, hi) = ref_53_forward(&column);
978 for (oy, &value) in lo.iter().enumerate() {
979 v_low[oy * width + x] = value;
980 }
981 for (oy, &value) in hi.iter().enumerate() {
982 v_high[oy * width + x] = value;
983 }
984 }
985
986 let horizontal = |source: &[i32], rows: usize| -> (Vec<i32>, Vec<i32>) {
987 let mut low = vec![0i32; low_width * rows];
988 let mut high = vec![0i32; high_width * rows];
989 for oy in 0..rows {
990 let (lo, hi) = ref_53_forward(&source[oy * width..oy * width + width]);
991 low[oy * low_width..oy * low_width + low_width].copy_from_slice(&lo);
992 high[oy * high_width..oy * high_width + high_width].copy_from_slice(&hi);
993 }
994 (low, high)
995 };
996
997 let (ll, hl) = horizontal(&v_low, low_height);
998 let (lh, hh) = horizontal(&v_high, high_height);
999
1000 ReversibleDwt53FirstLevel {
1001 ll,
1002 hl,
1003 lh,
1004 hh,
1005 low_width,
1006 low_height,
1007 high_width,
1008 high_height,
1009 }
1010 }
1011
1012 fn pack_plane(plane: &[i32], width: usize, height: usize) -> (Vec<[i32; 64]>, usize, usize) {
1016 let block_cols = width.div_ceil(8);
1017 let block_rows = height.div_ceil(8);
1018 let mut blocks = vec![[0i32; 64]; block_cols * block_rows];
1019 for y in 0..height {
1020 for x in 0..width {
1021 let block = (y / 8) * block_cols + (x / 8);
1022 blocks[block][(y % 8) * 8 + (x % 8)] = plane[y * width + x];
1023 }
1024 }
1025 (blocks, block_cols, block_rows)
1026 }
1027
1028 fn next_sample(state: &mut u64) -> i32 {
1029 *state = state
1030 .wrapping_mul(6_364_136_223_846_793_005)
1031 .wrapping_add(1_442_695_040_888_963_407);
1032 ((*state >> 40) & 0x1ff) as i32 - 256
1033 }
1034
1035 #[test]
1036 fn reversible_lift_53_matches_canonical_formula_1d() {
1037 let mut state = 0x0a11_ce5e_ed00_d001u64;
1038 for n in [2usize, 3, 4, 5, 8, 9, 12, 15, 16, 23, 32, 33, 64, 65] {
1039 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1040 let mut lifted = signal.clone();
1041 reversible_lift_53_i32(&mut lifted);
1042 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1043 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1044 let (low, high) = ref_53_forward(&signal);
1045 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1046 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1047 }
1048 }
1049
1050 #[test]
1051 fn reversible_lift_53_shared_helper_matches_canonical_formula_1d() {
1052 let mut state = 0x5a53_5a53_5a53_5a53u64;
1053 for n in [2usize, 3, 4, 5, 8, 9, 16, 17, 31, 32, 65] {
1054 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
1055 let mut lifted = signal.clone();
1056 crate::reversible53::reversible_lift_53_i32(&mut lifted);
1057 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
1058 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1059 let (low, high) = ref_53_forward(&signal);
1060 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
1061 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
1062 }
1063 }
1064
1065 #[test]
1066 fn reversible_dwt53_2d_matches_canonical_separable() {
1067 let mut state = 0xfeed_5eed_d00d_face_u64;
1068 for (width, height) in [
1069 (8usize, 8usize),
1070 (16, 16),
1071 (24, 16),
1072 (15, 13),
1073 (16, 23),
1074 (9, 7),
1075 (32, 32),
1076 ] {
1077 let plane: Vec<i32> = (0..width * height)
1078 .map(|_| next_sample(&mut state))
1079 .collect();
1080 let (blocks, block_cols, block_rows) = pack_plane(&plane, width, height);
1081 let got = reversible_dwt53_first_level_from_block_samples(
1082 &blocks, block_cols, block_rows, width, height,
1083 )
1084 .expect("oracle accepts the packed grid");
1085 let want = ref_53_2d(&plane, width, height);
1086 assert_eq!(
1087 (
1088 got.low_width,
1089 got.low_height,
1090 got.high_width,
1091 got.high_height
1092 ),
1093 (
1094 want.low_width,
1095 want.low_height,
1096 want.high_width,
1097 want.high_height
1098 ),
1099 "band dimensions for {width}x{height}"
1100 );
1101 assert_eq!(got.ll, want.ll, "LL mismatch for {width}x{height}");
1102 assert_eq!(got.hl, want.hl, "HL mismatch for {width}x{height}");
1103 assert_eq!(got.lh, want.lh, "LH mismatch for {width}x{height}");
1104 assert_eq!(got.hh, want.hh, "HH mismatch for {width}x{height}");
1105 }
1106 }
1107
1108 #[test]
1109 fn reversible_lift_53_kills_dc_and_linear_detail() {
1110 let mut constant = vec![7i32; 32];
1112 reversible_lift_53_i32(&mut constant);
1113 assert!(
1114 constant.iter().skip(1).step_by(2).all(|&v| v == 0),
1115 "constant produced nonzero detail"
1116 );
1117 assert!(
1118 constant.iter().step_by(2).all(|&v| v == 7),
1119 "constant low band drifted from 7"
1120 );
1121
1122 let ramp: Vec<i32> = (0..40_i32).map(|k| 3 * k - 5).collect();
1124 let mut lifted = ramp;
1125 reversible_lift_53_i32(&mut lifted);
1126 let detail: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
1127 for &value in &detail[1..detail.len() - 1] {
1128 assert_eq!(value, 0, "linear ramp produced interior detail {value}");
1129 }
1130 }
1131
1132 #[test]
1133 fn reversible_dwt53_2d_separates_horizontal_and_vertical_detail() {
1134 let (width, height) = (16usize, 16usize);
1136 let varies_in_x: Vec<i32> = (0..width * height)
1137 .map(|i| 3 * i32::try_from(i % width).unwrap() - 7)
1138 .collect();
1139 let (blocks, bc, br) = pack_plane(&varies_in_x, width, height);
1140 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1141 .expect("oracle accepts grid");
1142 assert!(
1143 t.lh.iter().all(|&v| v == 0),
1144 "x-only plane produced LH detail"
1145 );
1146 assert!(
1147 t.hh.iter().all(|&v| v == 0),
1148 "x-only plane produced HH detail"
1149 );
1150
1151 let varies_in_y: Vec<i32> = (0..width * height)
1153 .map(|i| 3 * i32::try_from(i / width).unwrap() - 7)
1154 .collect();
1155 let (blocks, bc, br) = pack_plane(&varies_in_y, width, height);
1156 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
1157 .expect("oracle accepts grid");
1158 assert!(
1159 t.hl.iter().all(|&v| v == 0),
1160 "y-only plane produced HL detail"
1161 );
1162 assert!(
1163 t.hh.iter().all(|&v| v == 0),
1164 "y-only plane produced HH detail"
1165 );
1166 }
1167}