1use core::fmt;
10
11use crate::dct53_2d::Dwt53TwoDimensional;
12use crate::dct97_2d::Dwt97TwoDimensional;
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};
17pub use j2k::adapter::encode_stage::{
18 EncodedHtJ2kCodeBlock, IrreversibleQuantizationSubbandScales, J2kSubBandType,
19 PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
20 PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
21 PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
22 PreencodedHtj2k97Component, PreencodedHtj2k97Resolution, PreencodedHtj2k97Subband,
23 PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component, PrequantizedHtj2k97Image,
24 PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
25};
26use j2k_jpeg::transcode::idct_islow_block;
27use rayon::prelude::*;
28
29const REVERSIBLE_DWT53_UNSUPPORTED_GRID: &str =
30 "reversible DCT 5/3 job has unsupported grid geometry";
31
32#[derive(Debug, Clone, Copy)]
34pub struct DctGridToReversibleDwt53Job<'a> {
35 pub dequantized_blocks: &'a [[i16; 64]],
37 pub block_cols: usize,
39 pub block_rows: usize,
41 pub width: usize,
43 pub height: usize,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ReversibleDwt53FirstLevel {
50 pub ll: Vec<i32>,
52 pub hl: Vec<i32>,
54 pub lh: Vec<i32>,
56 pub hh: Vec<i32>,
58 pub low_width: usize,
60 pub low_height: usize,
62 pub high_width: usize,
64 pub high_height: usize,
66}
67
68#[derive(Debug, Clone, Copy)]
70pub struct DctGridToDwt53Job<'a> {
71 pub blocks: &'a [[[f64; 8]; 8]],
73 pub block_cols: usize,
75 pub block_rows: usize,
77 pub width: usize,
79 pub height: usize,
81}
82
83#[derive(Debug, Clone, Copy)]
85pub struct DctGridToDwt97Job<'a> {
86 pub blocks: &'a [[[f64; 8]; 8]],
88 pub block_cols: usize,
90 pub block_rows: usize,
92 pub width: usize,
94 pub height: usize,
96}
97
98#[derive(Debug, Clone, Copy)]
100pub struct DctGridToHtj2k97CodeBlockJob<'a> {
101 pub blocks: &'a [[[f64; 8]; 8]],
103 pub block_cols: usize,
105 pub block_rows: usize,
107 pub width: usize,
109 pub height: usize,
111 pub x_rsiz: u8,
113 pub y_rsiz: u8,
115}
116
117#[derive(Debug, Clone, Copy)]
122pub struct DctGridI16ToHtj2k97CodeBlockJob<'a> {
123 pub dequantized_blocks: &'a [[i16; 64]],
125 pub block_cols: usize,
127 pub block_rows: usize,
129 pub width: usize,
131 pub height: usize,
133 pub x_rsiz: u8,
135 pub y_rsiz: u8,
137}
138
139#[derive(Debug, Clone, Copy)]
141pub struct DctGridI16ToHtj2k97CodeBlockBatch<'a, 'j> {
142 pub jobs: &'j [DctGridI16ToHtj2k97CodeBlockJob<'a>],
144}
145
146#[derive(Debug, Clone)]
148pub struct PreencodedHtj2k97CompactBatch {
149 pub payload: Vec<u8>,
151 pub components: Vec<PreencodedHtj2k97CompactComponent>,
153}
154
155#[derive(Debug, Clone)]
157pub struct PreencodedHtj2k97CompactBatchGroups {
158 pub payload: Vec<u8>,
160 pub groups: Vec<Vec<PreencodedHtj2k97CompactComponent>>,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq)]
167pub struct Htj2k97CodeBlockOptions {
168 pub bit_depth: u8,
170 pub guard_bits: u8,
172 pub code_block_width_exp: u8,
174 pub code_block_height_exp: u8,
176 pub irreversible_quantization_scale: f32,
178 pub irreversible_quantization_subband_scales: IrreversibleQuantizationSubbandScales,
181}
182
183#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
185pub struct Dwt97BatchStageTimings {
186 pub pack_upload_us: u128,
188 pub idct_row_lift_us: u128,
190 pub column_lift_us: u128,
192 pub quantize_codeblock_us: u128,
194 pub ht_encode_us: u128,
196 pub ht_kernel_us: u128,
198 pub ht_status_readback_us: u128,
200 pub ht_compact_us: u128,
202 pub ht_output_readback_us: u128,
204 pub ht_codeblock_dispatches: usize,
206 pub readback_us: u128,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
212pub enum TranscodeStageError {
213 Unsupported(&'static str),
216 Backend(String),
218 DeviceUnavailable,
220}
221
222impl fmt::Display for TranscodeStageError {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 match self {
225 Self::Unsupported(reason) => f.write_str(reason),
226 Self::Backend(reason) => f.write_str(reason),
227 Self::DeviceUnavailable => f.write_str("accelerator device is unavailable"),
228 }
229 }
230}
231
232impl std::error::Error for TranscodeStageError {}
233
234impl From<&'static str> for TranscodeStageError {
235 fn from(reason: &'static str) -> Self {
236 Self::Unsupported(reason)
237 }
238}
239
240pub trait DctToWaveletStageAccelerator {
242 fn supports_dwt97_batch(&self) -> bool {
248 false
249 }
250
251 fn supports_htj2k97_codeblock_batch(&self) -> bool {
254 false
255 }
256
257 fn supports_htj2k97_i16_preencoded_batch(&self) -> bool {
261 false
262 }
263
264 fn supports_htj2k97_compact_preencoded_batch(&self) -> bool {
267 self.supports_htj2k97_i16_preencoded_batch()
268 }
269
270 fn dct_grid_to_reversible_dwt53(
277 &mut self,
278 _job: DctGridToReversibleDwt53Job<'_>,
279 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
280 Ok(None)
281 }
282
283 fn dct_grid_to_reversible_dwt53_batch(
289 &mut self,
290 _jobs: &[DctGridToReversibleDwt53Job<'_>],
291 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
292 Ok(None)
293 }
294
295 fn dct_grid_to_dwt53(
300 &mut self,
301 _job: DctGridToDwt53Job<'_>,
302 ) -> Result<Option<Dwt53TwoDimensional<f64>>, TranscodeStageError> {
303 Ok(None)
304 }
305
306 fn dct_grid_to_dwt97(
311 &mut self,
312 _job: DctGridToDwt97Job<'_>,
313 ) -> Result<Option<Dwt97TwoDimensional<f64>>, TranscodeStageError> {
314 Ok(None)
315 }
316
317 fn dct_grid_to_dwt97_batch(
323 &mut self,
324 _jobs: &[DctGridToDwt97Job<'_>],
325 ) -> Result<Option<Vec<Dwt97TwoDimensional<f64>>>, TranscodeStageError> {
326 Ok(None)
327 }
328
329 fn dct_grid_to_htj2k97_codeblock_batch(
335 &mut self,
336 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
337 _options: Htj2k97CodeBlockOptions,
338 ) -> Result<Option<Vec<PrequantizedHtj2k97Component>>, TranscodeStageError> {
339 Ok(None)
340 }
341
342 fn dct_grid_to_htj2k97_preencoded_batch(
348 &mut self,
349 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
350 _options: Htj2k97CodeBlockOptions,
351 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
352 Ok(None)
353 }
354
355 fn dct_grid_i16_to_htj2k97_preencoded_batch(
361 &mut self,
362 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
363 _options: Htj2k97CodeBlockOptions,
364 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
365 Ok(None)
366 }
367
368 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch(
375 &mut self,
376 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
377 _options: Htj2k97CodeBlockOptions,
378 ) -> Result<Option<PreencodedHtj2k97CompactBatch>, TranscodeStageError> {
379 Ok(None)
380 }
381
382 fn dct_grid_i16_to_htj2k97_preencoded_batch_groups(
390 &mut self,
391 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
392 _options: Htj2k97CodeBlockOptions,
393 ) -> Result<Option<Vec<Vec<PreencodedHtj2k97Component>>>, TranscodeStageError> {
394 Ok(None)
395 }
396
397 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch_groups(
404 &mut self,
405 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
406 _options: Htj2k97CodeBlockOptions,
407 ) -> Result<Option<PreencodedHtj2k97CompactBatchGroups>, TranscodeStageError> {
408 Ok(None)
409 }
410
411 fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
413 None
414 }
415}
416
417#[derive(Debug, Default, Clone, Copy)]
419pub struct CpuOnlyDctToWaveletStageAccelerator;
420
421impl DctToWaveletStageAccelerator for CpuOnlyDctToWaveletStageAccelerator {}
422
423#[derive(Debug, Default, Clone)]
429pub struct RayonReversibleDwt53Accelerator {
430 attempts: usize,
431 dispatches: usize,
432 batch_attempts: usize,
433 batch_dispatches: usize,
434}
435
436impl RayonReversibleDwt53Accelerator {
437 #[must_use]
439 pub const fn reversible_dwt53_attempts(&self) -> usize {
440 self.attempts
441 }
442
443 #[must_use]
445 pub const fn reversible_dwt53_dispatches(&self) -> usize {
446 self.dispatches
447 }
448
449 #[must_use]
451 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
452 self.batch_attempts
453 }
454
455 #[must_use]
457 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
458 self.batch_dispatches
459 }
460}
461
462impl DctToWaveletStageAccelerator for RayonReversibleDwt53Accelerator {
463 fn dct_grid_to_reversible_dwt53(
464 &mut self,
465 job: DctGridToReversibleDwt53Job<'_>,
466 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
467 self.attempts = self.attempts.saturating_add(1);
468 let output = reversible_dwt53_first_level_rayon(job)?;
469 self.dispatches = self.dispatches.saturating_add(1);
470 Ok(Some(output))
471 }
472
473 fn dct_grid_to_reversible_dwt53_batch(
474 &mut self,
475 jobs: &[DctGridToReversibleDwt53Job<'_>],
476 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
477 self.batch_attempts = self.batch_attempts.saturating_add(1);
478 let mut output = Vec::with_capacity(jobs.len());
479 for job in jobs {
480 output.push(reversible_dwt53_first_level_rayon(*job)?);
481 }
482 self.batch_dispatches = self.batch_dispatches.saturating_add(1);
483 Ok(Some(output))
484 }
485}
486
487pub fn idct_blocks_to_signed_samples_rayon(blocks: &[[i16; 64]]) -> Vec<[i32; 64]> {
493 blocks
494 .par_iter()
495 .map(|block| {
496 let decoded = idct_islow_block(block);
497 decoded.map(|sample| i32::from(sample) - 128)
498 })
499 .collect()
500}
501
502pub fn reversible_dwt53_first_level_from_block_samples(
505 block_samples: &[[i32; 64]],
506 block_cols: usize,
507 block_rows: usize,
508 width: usize,
509 height: usize,
510) -> Result<ReversibleDwt53FirstLevel, &'static str> {
511 validate_reversible_grid(block_samples.len(), block_cols, block_rows, width, height)?;
512
513 let low_width = width.div_ceil(2);
514 let low_height = height.div_ceil(2);
515 let high_width = width / 2;
516 let high_height = height / 2;
517
518 let low_rows: Vec<(Vec<i32>, Vec<i32>)> = (0..low_height)
519 .into_par_iter()
520 .map(|output_y| {
521 let mut row = Vec::with_capacity(width);
522 for x in 0..width {
523 row.push(vertical_low_53_i32_at(
524 block_samples,
525 block_cols,
526 width,
527 height,
528 x,
529 output_y,
530 ));
531 }
532 reversible_lift_53_i32(&mut row);
533 (
534 row.iter().step_by(2).copied().collect(),
535 row.iter().skip(1).step_by(2).copied().collect(),
536 )
537 })
538 .collect();
539 let high_rows: Vec<(Vec<i32>, Vec<i32>)> = (0..high_height)
540 .into_par_iter()
541 .map(|output_y| {
542 let mut row = Vec::with_capacity(width);
543 for x in 0..width {
544 row.push(vertical_high_53_i32_at(
545 block_samples,
546 block_cols,
547 width,
548 height,
549 x,
550 output_y,
551 ));
552 }
553 reversible_lift_53_i32(&mut row);
554 (
555 row.iter().step_by(2).copied().collect(),
556 row.iter().skip(1).step_by(2).copied().collect(),
557 )
558 })
559 .collect();
560
561 let mut ll = Vec::with_capacity(low_width * low_height);
562 let mut hl = Vec::with_capacity(high_width * low_height);
563 for (low, high) in low_rows {
564 ll.extend(low);
565 hl.extend(high);
566 }
567
568 let mut lh = Vec::with_capacity(low_width * high_height);
569 let mut hh = Vec::with_capacity(high_width * high_height);
570 for (low, high) in high_rows {
571 lh.extend(low);
572 hh.extend(high);
573 }
574
575 Ok(ReversibleDwt53FirstLevel {
576 ll,
577 hl,
578 lh,
579 hh,
580 low_width,
581 low_height,
582 high_width,
583 high_height,
584 })
585}
586
587fn reversible_dwt53_first_level_rayon(
588 job: DctGridToReversibleDwt53Job<'_>,
589) -> Result<ReversibleDwt53FirstLevel, &'static str> {
590 validate_reversible_grid(
591 job.dequantized_blocks.len(),
592 job.block_cols,
593 job.block_rows,
594 job.width,
595 job.height,
596 )?;
597 let block_samples = idct_blocks_to_signed_samples_rayon(job.dequantized_blocks);
598 reversible_dwt53_first_level_from_block_samples(
599 &block_samples,
600 job.block_cols,
601 job.block_rows,
602 job.width,
603 job.height,
604 )
605}
606
607fn validate_reversible_grid(
608 block_count: usize,
609 block_cols: usize,
610 block_rows: usize,
611 width: usize,
612 height: usize,
613) -> Result<(), &'static str> {
614 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
615 .map_err(|_| REVERSIBLE_DWT53_UNSUPPORTED_GRID)
616}
617
618fn vertical_low_53_i32_at(
619 block_samples: &[[i32; 64]],
620 block_cols: usize,
621 width: usize,
622 height: usize,
623 x: usize,
624 low_idx: usize,
625) -> i32 {
626 reversible_lift_53_low_at(height, low_idx, |y| {
627 component_sample_i32(block_samples, block_cols, width, height, x, y)
628 })
629}
630
631fn vertical_high_53_i32_at(
632 block_samples: &[[i32; 64]],
633 block_cols: usize,
634 width: usize,
635 height: usize,
636 x: usize,
637 high_idx: usize,
638) -> i32 {
639 reversible_lift_53_high_at(height, high_idx, |y| {
640 component_sample_i32(block_samples, block_cols, width, height, x, y)
641 })
642}
643
644fn component_sample_i32(
645 block_samples: &[[i32; 64]],
646 block_cols: usize,
647 width: usize,
648 height: usize,
649 x: usize,
650 y: usize,
651) -> i32 {
652 debug_assert!(x < width);
653 debug_assert!(y < height);
654 let block_x = x / 8;
655 let block_y = y / 8;
656 let block_idx = block_y * block_cols + block_x;
657 let local_idx = (y % 8) * 8 + (x % 8);
658 block_samples[block_idx][local_idx]
659}
660
661#[cfg(test)]
662mod ground_truth_tests {
663 use super::{
673 reversible_dwt53_first_level_from_block_samples, reversible_lift_53_i32,
674 ReversibleDwt53FirstLevel,
675 };
676
677 fn floor2(a: i32, b: i32) -> i32 {
678 a.div_euclid(b)
679 }
680
681 fn ws_reflect(i: isize, n: usize) -> usize {
684 if n == 1 {
685 return 0;
686 }
687 let n = isize::try_from(n).unwrap();
688 let period = 2 * (n - 1);
689 let mut k = i.rem_euclid(period);
690 if k >= n {
691 k = period - k;
692 }
693 usize::try_from(k).unwrap()
694 }
695
696 fn ref_53_forward(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
701 let n = signal.len();
702 if n < 2 {
703 return (signal.to_vec(), Vec::new());
704 }
705 let sig = |i: isize| signal[ws_reflect(i, n)];
706 let detail = |m: isize| {
707 let c = 2 * m + 1;
708 sig(c) - floor2(sig(c - 1) + sig(c + 1), 2)
709 };
710 let low: Vec<i32> = (0..n.div_ceil(2))
711 .map(|m| {
712 let mi = isize::try_from(m).unwrap();
713 sig(2 * mi) + floor2(detail(mi - 1) + detail(mi) + 2, 4)
714 })
715 .collect();
716 let high: Vec<i32> = (0..n / 2)
717 .map(|m| detail(isize::try_from(m).unwrap()))
718 .collect();
719 (low, high)
720 }
721
722 fn ref_53_2d(plane: &[i32], width: usize, height: usize) -> ReversibleDwt53FirstLevel {
725 let low_width = width.div_ceil(2);
726 let high_width = width / 2;
727 let low_height = height.div_ceil(2);
728 let high_height = height / 2;
729
730 let mut v_low = vec![0i32; width * low_height];
731 let mut v_high = vec![0i32; width * high_height];
732 for x in 0..width {
733 let column: Vec<i32> = (0..height).map(|y| plane[y * width + x]).collect();
734 let (lo, hi) = ref_53_forward(&column);
735 for (oy, &value) in lo.iter().enumerate() {
736 v_low[oy * width + x] = value;
737 }
738 for (oy, &value) in hi.iter().enumerate() {
739 v_high[oy * width + x] = value;
740 }
741 }
742
743 let horizontal = |source: &[i32], rows: usize| -> (Vec<i32>, Vec<i32>) {
744 let mut low = vec![0i32; low_width * rows];
745 let mut high = vec![0i32; high_width * rows];
746 for oy in 0..rows {
747 let (lo, hi) = ref_53_forward(&source[oy * width..oy * width + width]);
748 low[oy * low_width..oy * low_width + low_width].copy_from_slice(&lo);
749 high[oy * high_width..oy * high_width + high_width].copy_from_slice(&hi);
750 }
751 (low, high)
752 };
753
754 let (ll, hl) = horizontal(&v_low, low_height);
755 let (lh, hh) = horizontal(&v_high, high_height);
756
757 ReversibleDwt53FirstLevel {
758 ll,
759 hl,
760 lh,
761 hh,
762 low_width,
763 low_height,
764 high_width,
765 high_height,
766 }
767 }
768
769 fn pack_plane(plane: &[i32], width: usize, height: usize) -> (Vec<[i32; 64]>, usize, usize) {
773 let block_cols = width.div_ceil(8);
774 let block_rows = height.div_ceil(8);
775 let mut blocks = vec![[0i32; 64]; block_cols * block_rows];
776 for y in 0..height {
777 for x in 0..width {
778 let block = (y / 8) * block_cols + (x / 8);
779 blocks[block][(y % 8) * 8 + (x % 8)] = plane[y * width + x];
780 }
781 }
782 (blocks, block_cols, block_rows)
783 }
784
785 fn next_sample(state: &mut u64) -> i32 {
786 *state = state
787 .wrapping_mul(6_364_136_223_846_793_005)
788 .wrapping_add(1_442_695_040_888_963_407);
789 ((*state >> 40) & 0x1ff) as i32 - 256
790 }
791
792 #[test]
793 fn reversible_lift_53_matches_canonical_formula_1d() {
794 let mut state = 0x0a11_ce5e_ed00_d001u64;
795 for n in [2usize, 3, 4, 5, 8, 9, 12, 15, 16, 23, 32, 33, 64, 65] {
796 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
797 let mut lifted = signal.clone();
798 reversible_lift_53_i32(&mut lifted);
799 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
800 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
801 let (low, high) = ref_53_forward(&signal);
802 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
803 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
804 }
805 }
806
807 #[test]
808 fn reversible_lift_53_shared_helper_matches_canonical_formula_1d() {
809 let mut state = 0x5a53_5a53_5a53_5a53u64;
810 for n in [2usize, 3, 4, 5, 8, 9, 16, 17, 31, 32, 65] {
811 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
812 let mut lifted = signal.clone();
813 crate::reversible53::reversible_lift_53_i32(&mut lifted);
814 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
815 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
816 let (low, high) = ref_53_forward(&signal);
817 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
818 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
819 }
820 }
821
822 #[test]
823 fn reversible_dwt53_2d_matches_canonical_separable() {
824 let mut state = 0xfeed_5eed_d00d_face_u64;
825 for (width, height) in [
826 (8usize, 8usize),
827 (16, 16),
828 (24, 16),
829 (15, 13),
830 (16, 23),
831 (9, 7),
832 (32, 32),
833 ] {
834 let plane: Vec<i32> = (0..width * height)
835 .map(|_| next_sample(&mut state))
836 .collect();
837 let (blocks, block_cols, block_rows) = pack_plane(&plane, width, height);
838 let got = reversible_dwt53_first_level_from_block_samples(
839 &blocks, block_cols, block_rows, width, height,
840 )
841 .expect("oracle accepts the packed grid");
842 let want = ref_53_2d(&plane, width, height);
843 assert_eq!(
844 (
845 got.low_width,
846 got.low_height,
847 got.high_width,
848 got.high_height
849 ),
850 (
851 want.low_width,
852 want.low_height,
853 want.high_width,
854 want.high_height
855 ),
856 "band dimensions for {width}x{height}"
857 );
858 assert_eq!(got.ll, want.ll, "LL mismatch for {width}x{height}");
859 assert_eq!(got.hl, want.hl, "HL mismatch for {width}x{height}");
860 assert_eq!(got.lh, want.lh, "LH mismatch for {width}x{height}");
861 assert_eq!(got.hh, want.hh, "HH mismatch for {width}x{height}");
862 }
863 }
864
865 #[test]
866 fn reversible_lift_53_kills_dc_and_linear_detail() {
867 let mut constant = vec![7i32; 32];
869 reversible_lift_53_i32(&mut constant);
870 assert!(
871 constant.iter().skip(1).step_by(2).all(|&v| v == 0),
872 "constant produced nonzero detail"
873 );
874 assert!(
875 constant.iter().step_by(2).all(|&v| v == 7),
876 "constant low band drifted from 7"
877 );
878
879 let ramp: Vec<i32> = (0..40_i32).map(|k| 3 * k - 5).collect();
881 let mut lifted = ramp;
882 reversible_lift_53_i32(&mut lifted);
883 let detail: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
884 for &value in &detail[1..detail.len() - 1] {
885 assert_eq!(value, 0, "linear ramp produced interior detail {value}");
886 }
887 }
888
889 #[test]
890 fn reversible_dwt53_2d_separates_horizontal_and_vertical_detail() {
891 let (width, height) = (16usize, 16usize);
893 let varies_in_x: Vec<i32> = (0..width * height)
894 .map(|i| 3 * i32::try_from(i % width).unwrap() - 7)
895 .collect();
896 let (blocks, bc, br) = pack_plane(&varies_in_x, width, height);
897 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
898 .expect("oracle accepts grid");
899 assert!(
900 t.lh.iter().all(|&v| v == 0),
901 "x-only plane produced LH detail"
902 );
903 assert!(
904 t.hh.iter().all(|&v| v == 0),
905 "x-only plane produced HH detail"
906 );
907
908 let varies_in_y: Vec<i32> = (0..width * height)
910 .map(|i| 3 * i32::try_from(i / width).unwrap() - 7)
911 .collect();
912 let (blocks, bc, br) = pack_plane(&varies_in_y, width, height);
913 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
914 .expect("oracle accepts grid");
915 assert!(
916 t.hl.iter().all(|&v| v == 0),
917 "y-only plane produced HL detail"
918 );
919 assert!(
920 t.hh.iter().all(|&v| v == 0),
921 "y-only plane produced HH detail"
922 );
923 }
924}