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 pack_upload_transfers: usize,
190 pub pack_upload_bytes: u64,
192 pub resident_dct_handoff_count: usize,
194 pub idct_row_lift_us: u128,
196 pub column_lift_us: u128,
198 pub resident_dwt_handoff_count: usize,
200 pub quantize_codeblock_us: u128,
202 pub ht_encode_us: u128,
204 pub ht_kernel_us: u128,
206 pub ht_status_readback_us: u128,
208 pub ht_status_readback_transfers: usize,
210 pub ht_status_readback_bytes: u64,
212 pub ht_compact_us: u128,
214 pub ht_output_readback_us: u128,
216 pub ht_output_readback_transfers: usize,
218 pub ht_output_readback_bytes: u64,
220 pub ht_codeblock_dispatches: usize,
222 pub readback_us: u128,
224 pub readback_transfers: usize,
226 pub readback_bytes: u64,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
232pub enum TranscodeStageError {
233 Unsupported(&'static str),
236 Backend(String),
238 DeviceUnavailable,
240}
241
242impl fmt::Display for TranscodeStageError {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Unsupported(reason) => f.write_str(reason),
246 Self::Backend(reason) => f.write_str(reason),
247 Self::DeviceUnavailable => f.write_str("accelerator device is unavailable"),
248 }
249 }
250}
251
252impl std::error::Error for TranscodeStageError {}
253
254impl From<&'static str> for TranscodeStageError {
255 fn from(reason: &'static str) -> Self {
256 Self::Unsupported(reason)
257 }
258}
259
260pub trait DctToWaveletStageAccelerator {
262 fn supports_dwt97_batch(&self) -> bool {
268 false
269 }
270
271 fn supports_htj2k97_codeblock_batch(&self) -> bool {
274 false
275 }
276
277 fn supports_htj2k97_i16_preencoded_batch(&self) -> bool {
281 false
282 }
283
284 fn supports_htj2k97_compact_preencoded_batch(&self) -> bool {
287 self.supports_htj2k97_i16_preencoded_batch()
288 }
289
290 fn dct_grid_to_reversible_dwt53(
297 &mut self,
298 _job: DctGridToReversibleDwt53Job<'_>,
299 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
300 Ok(None)
301 }
302
303 fn dct_grid_to_reversible_dwt53_batch(
309 &mut self,
310 _jobs: &[DctGridToReversibleDwt53Job<'_>],
311 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
312 Ok(None)
313 }
314
315 fn dct_grid_to_dwt53(
320 &mut self,
321 _job: DctGridToDwt53Job<'_>,
322 ) -> Result<Option<Dwt53TwoDimensional<f64>>, TranscodeStageError> {
323 Ok(None)
324 }
325
326 fn dct_grid_to_dwt97(
331 &mut self,
332 _job: DctGridToDwt97Job<'_>,
333 ) -> Result<Option<Dwt97TwoDimensional<f64>>, TranscodeStageError> {
334 Ok(None)
335 }
336
337 fn dct_grid_to_dwt97_batch(
343 &mut self,
344 _jobs: &[DctGridToDwt97Job<'_>],
345 ) -> Result<Option<Vec<Dwt97TwoDimensional<f64>>>, TranscodeStageError> {
346 Ok(None)
347 }
348
349 fn dct_grid_to_htj2k97_codeblock_batch(
355 &mut self,
356 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
357 _options: Htj2k97CodeBlockOptions,
358 ) -> Result<Option<Vec<PrequantizedHtj2k97Component>>, TranscodeStageError> {
359 Ok(None)
360 }
361
362 fn dct_grid_to_htj2k97_preencoded_batch(
368 &mut self,
369 _jobs: &[DctGridToHtj2k97CodeBlockJob<'_>],
370 _options: Htj2k97CodeBlockOptions,
371 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
372 Ok(None)
373 }
374
375 fn dct_grid_i16_to_htj2k97_preencoded_batch(
381 &mut self,
382 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
383 _options: Htj2k97CodeBlockOptions,
384 ) -> Result<Option<Vec<PreencodedHtj2k97Component>>, TranscodeStageError> {
385 Ok(None)
386 }
387
388 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch(
395 &mut self,
396 _jobs: &[DctGridI16ToHtj2k97CodeBlockJob<'_>],
397 _options: Htj2k97CodeBlockOptions,
398 ) -> Result<Option<PreencodedHtj2k97CompactBatch>, TranscodeStageError> {
399 Ok(None)
400 }
401
402 fn dct_grid_i16_to_htj2k97_preencoded_batch_groups(
410 &mut self,
411 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
412 _options: Htj2k97CodeBlockOptions,
413 ) -> Result<Option<Vec<Vec<PreencodedHtj2k97Component>>>, TranscodeStageError> {
414 Ok(None)
415 }
416
417 fn dct_grid_i16_to_htj2k97_compact_preencoded_batch_groups(
424 &mut self,
425 _groups: &[DctGridI16ToHtj2k97CodeBlockBatch<'_, '_>],
426 _options: Htj2k97CodeBlockOptions,
427 ) -> Result<Option<PreencodedHtj2k97CompactBatchGroups>, TranscodeStageError> {
428 Ok(None)
429 }
430
431 fn last_dwt97_batch_stage_timings(&self) -> Option<Dwt97BatchStageTimings> {
433 None
434 }
435}
436
437#[derive(Debug, Default, Clone, Copy)]
439pub struct CpuOnlyDctToWaveletStageAccelerator;
440
441impl DctToWaveletStageAccelerator for CpuOnlyDctToWaveletStageAccelerator {}
442
443#[derive(Debug, Default, Clone)]
449pub struct RayonReversibleDwt53Accelerator {
450 attempts: usize,
451 dispatches: usize,
452 batch_attempts: usize,
453 batch_dispatches: usize,
454}
455
456impl RayonReversibleDwt53Accelerator {
457 #[must_use]
459 pub const fn reversible_dwt53_attempts(&self) -> usize {
460 self.attempts
461 }
462
463 #[must_use]
465 pub const fn reversible_dwt53_dispatches(&self) -> usize {
466 self.dispatches
467 }
468
469 #[must_use]
471 pub const fn reversible_dwt53_batch_attempts(&self) -> usize {
472 self.batch_attempts
473 }
474
475 #[must_use]
477 pub const fn reversible_dwt53_batch_dispatches(&self) -> usize {
478 self.batch_dispatches
479 }
480}
481
482impl DctToWaveletStageAccelerator for RayonReversibleDwt53Accelerator {
483 fn dct_grid_to_reversible_dwt53(
484 &mut self,
485 job: DctGridToReversibleDwt53Job<'_>,
486 ) -> Result<Option<ReversibleDwt53FirstLevel>, TranscodeStageError> {
487 self.attempts = self.attempts.saturating_add(1);
488 let output = reversible_dwt53_first_level_rayon(job)?;
489 self.dispatches = self.dispatches.saturating_add(1);
490 Ok(Some(output))
491 }
492
493 fn dct_grid_to_reversible_dwt53_batch(
494 &mut self,
495 jobs: &[DctGridToReversibleDwt53Job<'_>],
496 ) -> Result<Option<Vec<ReversibleDwt53FirstLevel>>, TranscodeStageError> {
497 self.batch_attempts = self.batch_attempts.saturating_add(1);
498 let mut output = Vec::with_capacity(jobs.len());
499 for job in jobs {
500 output.push(reversible_dwt53_first_level_rayon(*job)?);
501 }
502 self.batch_dispatches = self.batch_dispatches.saturating_add(1);
503 Ok(Some(output))
504 }
505}
506
507pub fn idct_blocks_to_signed_samples_rayon(blocks: &[[i16; 64]]) -> Vec<[i32; 64]> {
513 blocks
514 .par_iter()
515 .map(|block| {
516 let decoded = idct_islow_block(block);
517 decoded.map(|sample| i32::from(sample) - 128)
518 })
519 .collect()
520}
521
522pub fn reversible_dwt53_first_level_from_block_samples(
525 block_samples: &[[i32; 64]],
526 block_cols: usize,
527 block_rows: usize,
528 width: usize,
529 height: usize,
530) -> Result<ReversibleDwt53FirstLevel, &'static str> {
531 validate_reversible_grid(block_samples.len(), block_cols, block_rows, width, height)?;
532
533 let low_width = width.div_ceil(2);
534 let low_height = height.div_ceil(2);
535 let high_width = width / 2;
536 let high_height = height / 2;
537
538 let low_rows: Vec<(Vec<i32>, Vec<i32>)> = (0..low_height)
539 .into_par_iter()
540 .map(|output_y| {
541 let mut row = Vec::with_capacity(width);
542 for x in 0..width {
543 row.push(vertical_low_53_i32_at(
544 block_samples,
545 block_cols,
546 width,
547 height,
548 x,
549 output_y,
550 ));
551 }
552 reversible_lift_53_i32(&mut row);
553 (
554 row.iter().step_by(2).copied().collect(),
555 row.iter().skip(1).step_by(2).copied().collect(),
556 )
557 })
558 .collect();
559 let high_rows: Vec<(Vec<i32>, Vec<i32>)> = (0..high_height)
560 .into_par_iter()
561 .map(|output_y| {
562 let mut row = Vec::with_capacity(width);
563 for x in 0..width {
564 row.push(vertical_high_53_i32_at(
565 block_samples,
566 block_cols,
567 width,
568 height,
569 x,
570 output_y,
571 ));
572 }
573 reversible_lift_53_i32(&mut row);
574 (
575 row.iter().step_by(2).copied().collect(),
576 row.iter().skip(1).step_by(2).copied().collect(),
577 )
578 })
579 .collect();
580
581 let mut ll = Vec::with_capacity(low_width * low_height);
582 let mut hl = Vec::with_capacity(high_width * low_height);
583 for (low, high) in low_rows {
584 ll.extend(low);
585 hl.extend(high);
586 }
587
588 let mut lh = Vec::with_capacity(low_width * high_height);
589 let mut hh = Vec::with_capacity(high_width * high_height);
590 for (low, high) in high_rows {
591 lh.extend(low);
592 hh.extend(high);
593 }
594
595 Ok(ReversibleDwt53FirstLevel {
596 ll,
597 hl,
598 lh,
599 hh,
600 low_width,
601 low_height,
602 high_width,
603 high_height,
604 })
605}
606
607fn reversible_dwt53_first_level_rayon(
608 job: DctGridToReversibleDwt53Job<'_>,
609) -> Result<ReversibleDwt53FirstLevel, &'static str> {
610 validate_reversible_grid(
611 job.dequantized_blocks.len(),
612 job.block_cols,
613 job.block_rows,
614 job.width,
615 job.height,
616 )?;
617 let block_samples = idct_blocks_to_signed_samples_rayon(job.dequantized_blocks);
618 reversible_dwt53_first_level_from_block_samples(
619 &block_samples,
620 job.block_cols,
621 job.block_rows,
622 job.width,
623 job.height,
624 )
625}
626
627fn validate_reversible_grid(
628 block_count: usize,
629 block_cols: usize,
630 block_rows: usize,
631 width: usize,
632 height: usize,
633) -> Result<(), &'static str> {
634 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
635 .map_err(|_| REVERSIBLE_DWT53_UNSUPPORTED_GRID)
636}
637
638fn vertical_low_53_i32_at(
639 block_samples: &[[i32; 64]],
640 block_cols: usize,
641 width: usize,
642 height: usize,
643 x: usize,
644 low_idx: usize,
645) -> i32 {
646 reversible_lift_53_low_at(height, low_idx, |y| {
647 component_sample_i32(block_samples, block_cols, width, height, x, y)
648 })
649}
650
651fn vertical_high_53_i32_at(
652 block_samples: &[[i32; 64]],
653 block_cols: usize,
654 width: usize,
655 height: usize,
656 x: usize,
657 high_idx: usize,
658) -> i32 {
659 reversible_lift_53_high_at(height, high_idx, |y| {
660 component_sample_i32(block_samples, block_cols, width, height, x, y)
661 })
662}
663
664fn component_sample_i32(
665 block_samples: &[[i32; 64]],
666 block_cols: usize,
667 width: usize,
668 height: usize,
669 x: usize,
670 y: usize,
671) -> i32 {
672 debug_assert!(x < width);
673 debug_assert!(y < height);
674 let block_x = x / 8;
675 let block_y = y / 8;
676 let block_idx = block_y * block_cols + block_x;
677 let local_idx = (y % 8) * 8 + (x % 8);
678 block_samples[block_idx][local_idx]
679}
680
681#[cfg(test)]
682mod ground_truth_tests {
683 use super::{
693 reversible_dwt53_first_level_from_block_samples, reversible_lift_53_i32,
694 ReversibleDwt53FirstLevel,
695 };
696
697 fn floor2(a: i32, b: i32) -> i32 {
698 a.div_euclid(b)
699 }
700
701 fn ws_reflect(i: isize, n: usize) -> usize {
704 if n == 1 {
705 return 0;
706 }
707 let n = isize::try_from(n).unwrap();
708 let period = 2 * (n - 1);
709 let mut k = i.rem_euclid(period);
710 if k >= n {
711 k = period - k;
712 }
713 usize::try_from(k).unwrap()
714 }
715
716 fn ref_53_forward(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
721 let n = signal.len();
722 if n < 2 {
723 return (signal.to_vec(), Vec::new());
724 }
725 let sig = |i: isize| signal[ws_reflect(i, n)];
726 let detail = |m: isize| {
727 let c = 2 * m + 1;
728 sig(c) - floor2(sig(c - 1) + sig(c + 1), 2)
729 };
730 let low: Vec<i32> = (0..n.div_ceil(2))
731 .map(|m| {
732 let mi = isize::try_from(m).unwrap();
733 sig(2 * mi) + floor2(detail(mi - 1) + detail(mi) + 2, 4)
734 })
735 .collect();
736 let high: Vec<i32> = (0..n / 2)
737 .map(|m| detail(isize::try_from(m).unwrap()))
738 .collect();
739 (low, high)
740 }
741
742 fn ref_53_2d(plane: &[i32], width: usize, height: usize) -> ReversibleDwt53FirstLevel {
745 let low_width = width.div_ceil(2);
746 let high_width = width / 2;
747 let low_height = height.div_ceil(2);
748 let high_height = height / 2;
749
750 let mut v_low = vec![0i32; width * low_height];
751 let mut v_high = vec![0i32; width * high_height];
752 for x in 0..width {
753 let column: Vec<i32> = (0..height).map(|y| plane[y * width + x]).collect();
754 let (lo, hi) = ref_53_forward(&column);
755 for (oy, &value) in lo.iter().enumerate() {
756 v_low[oy * width + x] = value;
757 }
758 for (oy, &value) in hi.iter().enumerate() {
759 v_high[oy * width + x] = value;
760 }
761 }
762
763 let horizontal = |source: &[i32], rows: usize| -> (Vec<i32>, Vec<i32>) {
764 let mut low = vec![0i32; low_width * rows];
765 let mut high = vec![0i32; high_width * rows];
766 for oy in 0..rows {
767 let (lo, hi) = ref_53_forward(&source[oy * width..oy * width + width]);
768 low[oy * low_width..oy * low_width + low_width].copy_from_slice(&lo);
769 high[oy * high_width..oy * high_width + high_width].copy_from_slice(&hi);
770 }
771 (low, high)
772 };
773
774 let (ll, hl) = horizontal(&v_low, low_height);
775 let (lh, hh) = horizontal(&v_high, high_height);
776
777 ReversibleDwt53FirstLevel {
778 ll,
779 hl,
780 lh,
781 hh,
782 low_width,
783 low_height,
784 high_width,
785 high_height,
786 }
787 }
788
789 fn pack_plane(plane: &[i32], width: usize, height: usize) -> (Vec<[i32; 64]>, usize, usize) {
793 let block_cols = width.div_ceil(8);
794 let block_rows = height.div_ceil(8);
795 let mut blocks = vec![[0i32; 64]; block_cols * block_rows];
796 for y in 0..height {
797 for x in 0..width {
798 let block = (y / 8) * block_cols + (x / 8);
799 blocks[block][(y % 8) * 8 + (x % 8)] = plane[y * width + x];
800 }
801 }
802 (blocks, block_cols, block_rows)
803 }
804
805 fn next_sample(state: &mut u64) -> i32 {
806 *state = state
807 .wrapping_mul(6_364_136_223_846_793_005)
808 .wrapping_add(1_442_695_040_888_963_407);
809 ((*state >> 40) & 0x1ff) as i32 - 256
810 }
811
812 #[test]
813 fn reversible_lift_53_matches_canonical_formula_1d() {
814 let mut state = 0x0a11_ce5e_ed00_d001u64;
815 for n in [2usize, 3, 4, 5, 8, 9, 12, 15, 16, 23, 32, 33, 64, 65] {
816 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
817 let mut lifted = signal.clone();
818 reversible_lift_53_i32(&mut lifted);
819 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
820 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
821 let (low, high) = ref_53_forward(&signal);
822 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
823 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
824 }
825 }
826
827 #[test]
828 fn reversible_lift_53_shared_helper_matches_canonical_formula_1d() {
829 let mut state = 0x5a53_5a53_5a53_5a53u64;
830 for n in [2usize, 3, 4, 5, 8, 9, 16, 17, 31, 32, 65] {
831 let signal: Vec<i32> = (0..n).map(|_| next_sample(&mut state)).collect();
832 let mut lifted = signal.clone();
833 crate::reversible53::reversible_lift_53_i32(&mut lifted);
834 let lifted_low: Vec<i32> = lifted.iter().step_by(2).copied().collect();
835 let lifted_high: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
836 let (low, high) = ref_53_forward(&signal);
837 assert_eq!(lifted_low, low, "low band mismatch for n={n}");
838 assert_eq!(lifted_high, high, "high band mismatch for n={n}");
839 }
840 }
841
842 #[test]
843 fn reversible_dwt53_2d_matches_canonical_separable() {
844 let mut state = 0xfeed_5eed_d00d_face_u64;
845 for (width, height) in [
846 (8usize, 8usize),
847 (16, 16),
848 (24, 16),
849 (15, 13),
850 (16, 23),
851 (9, 7),
852 (32, 32),
853 ] {
854 let plane: Vec<i32> = (0..width * height)
855 .map(|_| next_sample(&mut state))
856 .collect();
857 let (blocks, block_cols, block_rows) = pack_plane(&plane, width, height);
858 let got = reversible_dwt53_first_level_from_block_samples(
859 &blocks, block_cols, block_rows, width, height,
860 )
861 .expect("oracle accepts the packed grid");
862 let want = ref_53_2d(&plane, width, height);
863 assert_eq!(
864 (
865 got.low_width,
866 got.low_height,
867 got.high_width,
868 got.high_height
869 ),
870 (
871 want.low_width,
872 want.low_height,
873 want.high_width,
874 want.high_height
875 ),
876 "band dimensions for {width}x{height}"
877 );
878 assert_eq!(got.ll, want.ll, "LL mismatch for {width}x{height}");
879 assert_eq!(got.hl, want.hl, "HL mismatch for {width}x{height}");
880 assert_eq!(got.lh, want.lh, "LH mismatch for {width}x{height}");
881 assert_eq!(got.hh, want.hh, "HH mismatch for {width}x{height}");
882 }
883 }
884
885 #[test]
886 fn reversible_lift_53_kills_dc_and_linear_detail() {
887 let mut constant = vec![7i32; 32];
889 reversible_lift_53_i32(&mut constant);
890 assert!(
891 constant.iter().skip(1).step_by(2).all(|&v| v == 0),
892 "constant produced nonzero detail"
893 );
894 assert!(
895 constant.iter().step_by(2).all(|&v| v == 7),
896 "constant low band drifted from 7"
897 );
898
899 let ramp: Vec<i32> = (0..40_i32).map(|k| 3 * k - 5).collect();
901 let mut lifted = ramp;
902 reversible_lift_53_i32(&mut lifted);
903 let detail: Vec<i32> = lifted.iter().skip(1).step_by(2).copied().collect();
904 for &value in &detail[1..detail.len() - 1] {
905 assert_eq!(value, 0, "linear ramp produced interior detail {value}");
906 }
907 }
908
909 #[test]
910 fn reversible_dwt53_2d_separates_horizontal_and_vertical_detail() {
911 let (width, height) = (16usize, 16usize);
913 let varies_in_x: Vec<i32> = (0..width * height)
914 .map(|i| 3 * i32::try_from(i % width).unwrap() - 7)
915 .collect();
916 let (blocks, bc, br) = pack_plane(&varies_in_x, width, height);
917 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
918 .expect("oracle accepts grid");
919 assert!(
920 t.lh.iter().all(|&v| v == 0),
921 "x-only plane produced LH detail"
922 );
923 assert!(
924 t.hh.iter().all(|&v| v == 0),
925 "x-only plane produced HH detail"
926 );
927
928 let varies_in_y: Vec<i32> = (0..width * height)
930 .map(|i| 3 * i32::try_from(i / width).unwrap() - 7)
931 .collect();
932 let (blocks, bc, br) = pack_plane(&varies_in_y, width, height);
933 let t = reversible_dwt53_first_level_from_block_samples(&blocks, bc, br, width, height)
934 .expect("oracle accepts grid");
935 assert!(
936 t.hl.iter().all(|&v| v == 0),
937 "y-only plane produced HL detail"
938 );
939 assert!(
940 t.hh.iter().all(|&v| v == 0),
941 "y-only plane produced HH detail"
942 );
943 }
944}