Skip to main content

j2k_transcode/
dct_grid.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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/// Error returned when a DCT block grid cannot cover the requested component.
45#[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    /// Number of supplied 8x8 DCT blocks.
72    #[must_use]
73    pub const fn block_count(self) -> usize {
74        self.block_count
75    }
76
77    /// Declared block columns.
78    #[must_use]
79    pub const fn block_cols(self) -> usize {
80        self.block_cols
81    }
82
83    /// Declared block rows.
84    #[must_use]
85    pub const fn block_rows(self) -> usize {
86        self.block_rows
87    }
88
89    /// Requested component width.
90    #[must_use]
91    pub const fn width(self) -> usize {
92        self.width
93    }
94
95    /// Requested component height.
96    #[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}