Skip to main content

phasm_core/codec/jpeg/
dct.rs

1// Copyright (c) 2026 Christoph Gaffga
2// SPDX-License-Identifier: GPL-3.0-only
3// https://github.com/cgaffga/phasmcore
4
5//! DCT coefficient storage and quantization tables.
6//!
7//! Provides [`DctGrid`] for storing quantized DCT coefficients in block-raster
8//! order, and [`QuantTable`] for the 64-entry quantization matrices.
9
10/// Quantization table: 64 values in natural (row-major) order.
11#[derive(Debug, Clone)]
12pub struct QuantTable {
13    /// Quantization values, indexed by row * 8 + col.
14    pub values: [u16; 64],
15}
16
17impl QuantTable {
18    pub fn new(values: [u16; 64]) -> Self {
19        Self { values }
20    }
21}
22
23/// Grid of quantized DCT coefficients for one image component.
24///
25/// Coefficients are stored in block-raster order. Within each block,
26/// the 64 coefficients are in natural (row-major) order, i.e. index = row * 8 + col.
27#[derive(Debug, Clone)]
28pub struct DctGrid {
29    /// Number of 8×8 blocks horizontally.
30    blocks_wide: usize,
31    /// Number of 8×8 blocks vertically.
32    blocks_tall: usize,
33    /// Flat storage: blocks_tall * blocks_wide * 64 coefficients.
34    coeffs: Vec<i16>,
35}
36
37impl DctGrid {
38    /// Create a new grid initialized to zero.
39    pub fn new(blocks_wide: usize, blocks_tall: usize) -> Self {
40        Self {
41            blocks_wide,
42            blocks_tall,
43            coeffs: vec![0i16; blocks_wide * blocks_tall * 64],
44        }
45    }
46
47    pub fn blocks_wide(&self) -> usize {
48        self.blocks_wide
49    }
50
51    pub fn blocks_tall(&self) -> usize {
52        self.blocks_tall
53    }
54
55    /// Get a coefficient value.
56    /// - `br`, `bc`: block row and column (0-based)
57    /// - `i`, `j`: frequency row and column within the block (0–7)
58    pub fn get(&self, br: usize, bc: usize, i: usize, j: usize) -> i16 {
59        self.coeffs[self.index(br, bc, i, j)]
60    }
61
62    /// Set a coefficient value.
63    pub fn set(&mut self, br: usize, bc: usize, i: usize, j: usize, val: i16) {
64        let idx = self.index(br, bc, i, j);
65        self.coeffs[idx] = val;
66    }
67
68    /// Get a mutable reference to the 64-coefficient block at (br, bc).
69    pub fn block_mut(&mut self, br: usize, bc: usize) -> &mut [i16] {
70        let start = (br * self.blocks_wide + bc) * 64;
71        &mut self.coeffs[start..start + 64]
72    }
73
74    /// Get a reference to the 64-coefficient block at (br, bc).
75    pub fn block(&self, br: usize, bc: usize) -> &[i16] {
76        let start = (br * self.blocks_wide + bc) * 64;
77        &self.coeffs[start..start + 64]
78    }
79
80    /// Total number of blocks.
81    pub fn total_blocks(&self) -> usize {
82        self.blocks_wide * self.blocks_tall
83    }
84
85    /// Raw mutable access to all coefficients.
86    ///
87    /// Layout: `blocks_tall * blocks_wide * 64` contiguous i16 values in
88    /// block-raster order. Each 64-element chunk is one 8×8 block.
89    /// Used by parallel processing (Rayon `par_chunks_mut`).
90    pub fn coeffs_mut(&mut self) -> &mut [i16] {
91        &mut self.coeffs
92    }
93
94    /// Raw read-only access to all coefficients.
95    pub fn coeffs(&self) -> &[i16] {
96        &self.coeffs
97    }
98
99    fn index(&self, br: usize, bc: usize, i: usize, j: usize) -> usize {
100        debug_assert!(br < self.blocks_tall, "block row {br} >= {}", self.blocks_tall);
101        debug_assert!(bc < self.blocks_wide, "block col {bc} >= {}", self.blocks_wide);
102        debug_assert!(i < 8 && j < 8);
103        (br * self.blocks_wide + bc) * 64 + i * 8 + j
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn grid_get_set() {
113        let mut grid = DctGrid::new(2, 3);
114        assert_eq!(grid.blocks_wide(), 2);
115        assert_eq!(grid.blocks_tall(), 3);
116        assert_eq!(grid.total_blocks(), 6);
117
118        // All initialized to zero
119        assert_eq!(grid.get(0, 0, 0, 0), 0);
120        assert_eq!(grid.get(2, 1, 7, 7), 0);
121
122        grid.set(1, 0, 3, 4, 42);
123        assert_eq!(grid.get(1, 0, 3, 4), 42);
124
125        // Other positions unchanged
126        assert_eq!(grid.get(1, 0, 3, 3), 0);
127        assert_eq!(grid.get(0, 0, 3, 4), 0);
128    }
129
130    #[test]
131    fn block_slice_access() {
132        let mut grid = DctGrid::new(1, 1);
133        grid.set(0, 0, 0, 0, 100); // DC
134        grid.set(0, 0, 7, 7, -50);
135
136        let blk = grid.block(0, 0);
137        assert_eq!(blk[0], 100);
138        assert_eq!(blk[63], -50);
139        assert_eq!(blk.len(), 64);
140    }
141
142    #[test]
143    fn block_mut_access() {
144        let mut grid = DctGrid::new(2, 2);
145        let blk = grid.block_mut(1, 1);
146        for (i, v) in blk.iter_mut().enumerate() {
147            *v = i as i16;
148        }
149        assert_eq!(grid.get(1, 1, 0, 0), 0);
150        assert_eq!(grid.get(1, 1, 0, 1), 1);
151        assert_eq!(grid.get(1, 1, 7, 7), 63);
152        // Other block untouched
153        assert_eq!(grid.get(0, 0, 0, 0), 0);
154    }
155
156    #[test]
157    fn quant_table() {
158        let mut vals = [0u16; 64];
159        vals[0] = 16;
160        vals[63] = 99;
161        let qt = QuantTable::new(vals);
162        assert_eq!(qt.values[0], 16);
163        assert_eq!(qt.values[63], 99);
164    }
165}