1use core::f64::consts::PI;
4use core::fmt;
5use std::sync::LazyLock;
6
7pub(crate) const fn low_len(sample_len: usize) -> usize {
8 sample_len.div_ceil(2)
9}
10
11pub(crate) const fn high_len(sample_len: usize) -> usize {
12 sample_len / 2
13}
14
15pub(crate) fn idct8_basis(sample_idx: usize, freq: usize) -> f64 {
16 debug_assert!(sample_idx < 8);
17 debug_assert!(freq < 8);
18
19 idct8_basis_table()[sample_idx][freq]
20}
21
22pub(crate) fn idct8_basis_table() -> &'static [[f64; 8]; 8] {
23 static BASIS: LazyLock<[[f64; 8]; 8]> = LazyLock::new(|| {
24 let mut basis = [[0.0; 8]; 8];
25 for (sample_idx, row) in basis.iter_mut().enumerate() {
26 for (freq, value) in row.iter_mut().enumerate() {
27 *value = idct8_basis_uncached(sample_idx, freq);
28 }
29 }
30 basis
31 });
32 &BASIS
33}
34
35fn idct8_basis_uncached(sample_idx: usize, freq: usize) -> f64 {
36 let scale = if freq == 0 {
37 (1.0_f64 / 8.0).sqrt()
38 } else {
39 (2.0_f64 / 8.0).sqrt()
40 };
41 scale * (((sample_idx as f64 + 0.5) * freq as f64 * PI) / 8.0).cos()
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct DctGridError {
47 block_count: usize,
48 block_cols: usize,
49 block_rows: usize,
50 width: usize,
51 height: usize,
52}
53
54impl DctGridError {
55 const fn new(
56 block_count: usize,
57 block_cols: usize,
58 block_rows: usize,
59 width: usize,
60 height: usize,
61 ) -> Self {
62 Self {
63 block_count,
64 block_cols,
65 block_rows,
66 width,
67 height,
68 }
69 }
70
71 #[must_use]
73 pub const fn block_count(self) -> usize {
74 self.block_count
75 }
76
77 #[must_use]
79 pub const fn block_cols(self) -> usize {
80 self.block_cols
81 }
82
83 #[must_use]
85 pub const fn block_rows(self) -> usize {
86 self.block_rows
87 }
88
89 #[must_use]
91 pub const fn width(self) -> usize {
92 self.width
93 }
94
95 #[must_use]
97 pub const fn height(self) -> usize {
98 self.height
99 }
100}
101
102impl fmt::Display for DctGridError {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(
105 f,
106 "DCT grid has {} blocks for {}x{} grid covering requested {}x{} samples",
107 self.block_count, self.block_cols, self.block_rows, self.width, self.height
108 )
109 }
110}
111
112impl std::error::Error for DctGridError {}
113
114pub(crate) fn validate_dct_block_grid(
115 block_count: usize,
116 block_cols: usize,
117 block_rows: usize,
118 width: usize,
119 height: usize,
120) -> Result<(), DctGridError> {
121 let expected_blocks = block_cols
122 .checked_mul(block_rows)
123 .ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
124 let covered_width = block_cols
125 .checked_mul(8)
126 .ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
127 let covered_height = block_rows
128 .checked_mul(8)
129 .ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
130
131 if block_count != expected_blocks
132 || width == 0
133 || height == 0
134 || width > covered_width
135 || height > covered_height
136 {
137 return Err(DctGridError::new(
138 block_count,
139 block_cols,
140 block_rows,
141 width,
142 height,
143 ));
144 }
145 Ok(())
146}
147
148#[cfg(test)]
149mod tests {
150 use super::{high_len, idct8_basis, low_len, validate_dct_block_grid};
151
152 #[test]
153 fn band_lengths_split_even_and_odd_samples() {
154 assert_eq!((low_len(0), high_len(0)), (0, 0));
155 assert_eq!((low_len(1), high_len(1)), (1, 0));
156 assert_eq!((low_len(8), high_len(8)), (4, 4));
157 assert_eq!((low_len(9), high_len(9)), (5, 4));
158 }
159
160 #[test]
161 fn idct8_basis_is_orthonormal_for_dc_and_first_ac() {
162 assert!((idct8_basis(0, 0) - (1.0_f64 / 8.0).sqrt()).abs() < 1e-12);
163 assert!((idct8_basis(0, 1) - 0.490_392_640_201_615_2).abs() < 1e-12);
164 }
165
166 #[test]
167 fn validates_non_empty_grid_covered_by_blocks() {
168 assert_eq!(validate_dct_block_grid(6, 3, 2, 24, 16), Ok(()));
169 }
170
171 #[test]
172 fn rejects_overflowing_grid_dimensions() {
173 let err = validate_dct_block_grid(1, usize::MAX, usize::MAX, 8, 8)
174 .expect_err("overflowing dimensions must fail");
175 assert_eq!(err.block_count(), 1);
176 assert_eq!(err.block_cols(), usize::MAX);
177 assert_eq!(err.block_rows(), usize::MAX);
178 }
179
180 #[test]
181 fn rejects_zero_image_extent() {
182 assert!(validate_dct_block_grid(1, 1, 1, 0, 8).is_err());
183 assert!(validate_dct_block_grid(1, 1, 1, 8, 0).is_err());
184 }
185}