1use std::sync::OnceLock;
13
14use crate::codec::jpeg::JpegImage;
15
16static COSINE: OnceLock<[[f64; 8]; 8]> = OnceLock::new();
19
20static NORM: OnceLock<[f64; 8]> = OnceLock::new();
22
23fn cosine_table() -> &'static [[f64; 8]; 8] {
24 COSINE.get_or_init(|| {
25 let mut table = [[0.0f64; 8]; 8];
26 for u in 0..8 {
27 for x in 0..8 {
28 table[u][x] = crate::det_math::det_cos((2 * x + 1) as f64 * u as f64 * std::f64::consts::PI / 16.0);
29 }
30 }
31 table
32 })
33}
34
35fn norm_table() -> &'static [f64; 8] {
36 NORM.get_or_init(|| {
37 let mut n = [0.5f64; 8];
38 n[0] = 1.0 / (8.0f64).sqrt();
39 n
40 })
41}
42
43pub fn idct_block(quantized: &[i16; 64], qt: &[u16; 64]) -> [f64; 64] {
48 let cos = cosine_table();
49 let c = norm_table();
50
51 let mut f = [0.0f64; 64];
53 for i in 0..64 {
54 f[i] = quantized[i] as f64 * qt[i] as f64;
55 }
56
57 let mut temp = [0.0f64; 64];
60 for col in 0..8 {
61 for y in 0..8 {
62 let mut sum = 0.0;
63 for v in 0..8 {
64 sum += c[v] * f[v * 8 + col] * cos[v][y];
65 }
66 temp[y * 8 + col] = sum;
67 }
68 }
69
70 let mut pixels = [0.0f64; 64];
72 for row in 0..8 {
73 for x in 0..8 {
74 let mut sum = 0.0;
75 for u in 0..8 {
76 sum += c[u] * temp[row * 8 + u] * cos[u][x];
77 }
78 pixels[row * 8 + x] = sum + 128.0;
79 }
80 }
81
82 pixels
83}
84
85pub fn dct_block(pixels: &[f64; 64], qt: &[u16; 64]) -> [i16; 64] {
90 let cos = cosine_table();
91 let c = norm_table();
92
93 let mut shifted = [0.0f64; 64];
95 for i in 0..64 {
96 shifted[i] = pixels[i] - 128.0;
97 }
98
99 let mut temp = [0.0f64; 64];
102 for row in 0..8 {
103 for u in 0..8 {
104 let mut sum = 0.0;
105 for x in 0..8 {
106 sum += shifted[row * 8 + x] * cos[u][x];
107 }
108 temp[row * 8 + u] = c[u] * sum;
109 }
110 }
111
112 let mut coeffs = [0.0f64; 64];
114 for col in 0..8 {
115 for v in 0..8 {
116 let mut sum = 0.0;
117 for y in 0..8 {
118 sum += temp[y * 8 + col] * cos[v][y];
119 }
120 coeffs[v * 8 + col] = c[v] * sum;
121 }
122 }
123
124 let mut quantized = [0i16; 64];
126 for i in 0..64 {
127 quantized[i] = (coeffs[i] / qt[i] as f64).round() as i16;
128 }
129 quantized
130}
131
132pub fn dct_block_unquantized(pixels: &[f64; 64], qt: &[u16; 64]) -> [f64; 64] {
142 let cos = cosine_table();
143 let c = norm_table();
144
145 let mut shifted = [0.0f64; 64];
147 for i in 0..64 {
148 shifted[i] = pixels[i] - 128.0;
149 }
150
151 let mut temp = [0.0f64; 64];
153 for row in 0..8 {
154 for u in 0..8 {
155 let mut sum = 0.0;
156 for x in 0..8 {
157 sum += shifted[row * 8 + x] * cos[u][x];
158 }
159 temp[row * 8 + u] = c[u] * sum;
160 }
161 }
162
163 let mut coeffs = [0.0f64; 64];
164 for col in 0..8 {
165 for v in 0..8 {
166 let mut sum = 0.0;
167 for y in 0..8 {
168 sum += temp[y * 8 + col] * cos[v][y];
169 }
170 coeffs[v * 8 + col] = c[v] * sum;
171 }
172 }
173
174 let mut unquantized = [0.0f64; 64];
176 for i in 0..64 {
177 unquantized[i] = coeffs[i] / qt[i] as f64;
178 }
179 unquantized
180}
181
182pub fn jpeg_to_luma_f64(img: &JpegImage) -> Option<(Vec<f64>, usize, usize)> {
189 let grid = img.dct_grid(0);
190 let qt_id = img.frame_info().components[0].quant_table_id as usize;
191 let qt = img.quant_table(qt_id)?;
192
193 let bw = grid.blocks_wide();
194 let bh = grid.blocks_tall();
195 let width = bw * 8;
196 let height = bh * 8;
197 let mut pixels = vec![0.0f64; width * height];
198
199 for br in 0..bh {
200 for bc in 0..bw {
201 let block = grid.block(br, bc);
202 let quantized: [i16; 64] = block.try_into().unwrap();
203 let block_pixels = idct_block(&quantized, &qt.values);
204
205 for row in 0..8 {
206 for col in 0..8 {
207 let py = br * 8 + row;
208 let px = bc * 8 + col;
209 pixels[py * width + px] = block_pixels[row * 8 + col];
210 }
211 }
212 }
213 }
214
215 Some((pixels, width, height))
216}
217
218pub fn rgb_to_luma_blocks(rgb: &[u8], width: u32, height: u32) -> Vec<[f64; 64]> {
233 let w = width as usize;
234 let h = height as usize;
235 let bw = w.div_ceil(8);
236 let bh = h.div_ceil(8);
237
238 let mut blocks = Vec::with_capacity(bw * bh);
239
240 for br in 0..bh {
241 for bc in 0..bw {
242 let mut block = [0.0f64; 64];
243 for row in 0..8 {
244 for col in 0..8 {
245 let py = (br * 8 + row).min(h - 1);
247 let px = (bc * 8 + col).min(w - 1);
248 let idx = (py * w + px) * 3;
249 let r = rgb[idx] as f64;
250 let g = rgb[idx + 1] as f64;
251 let b = rgb[idx + 2] as f64;
252 block[row * 8 + col] = 0.299 * r + 0.587 * g + 0.114 * b;
253 }
254 }
255 blocks.push(block);
256 }
257 }
258
259 blocks
260}
261
262pub fn luma_f64_to_jpeg(pixels: &[f64], width: usize, height: usize, img: &mut JpegImage) -> Option<()> {
268 let qt_id = img.frame_info().components[0].quant_table_id as usize;
269 let qt_values = img.quant_table(qt_id)?.values;
270 let grid = img.dct_grid_mut(0);
271 let bw = grid.blocks_wide();
272 let bh = grid.blocks_tall();
273
274 assert_eq!(width, bw * 8);
275 assert_eq!(height, bh * 8);
276
277 for br in 0..bh {
278 for bc in 0..bw {
279 let mut block_pixels = [0.0f64; 64];
280 for row in 0..8 {
281 for col in 0..8 {
282 let py = br * 8 + row;
283 let px = bc * 8 + col;
284 block_pixels[row * 8 + col] = pixels[py * width + px];
285 }
286 }
287
288 let quantized = dct_block(&block_pixels, &qt_values);
289 let block = grid.block_mut(br, bc);
290 block.copy_from_slice(&quantized);
291 }
292 }
293 Some(())
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn idct_dct_roundtrip() {
302 let mut quantized = [0i16; 64];
304 quantized[0] = 100; quantized[1] = 10;
306 quantized[8] = -5;
307 quantized[9] = 3;
308
309 let qt = [1u16; 64];
311
312 let pixels = idct_block(&quantized, &qt);
313 let recovered = dct_block(&pixels, &qt);
314
315 for i in 0..64 {
316 assert!(
317 (quantized[i] - recovered[i]).abs() <= 1,
318 "Mismatch at index {i}: expected {}, got {}",
319 quantized[i],
320 recovered[i]
321 );
322 }
323 }
324
325 #[test]
326 fn dc_only_block_produces_flat_pixels() {
327 let mut quantized = [0i16; 64];
328 quantized[0] = 16; let qt = [1u16; 64];
330
331 let pixels = idct_block(&quantized, &qt);
332
333 let expected = 128.0 + 16.0 / 8.0;
336 let dc_val = pixels[0];
337 for i in 0..64 {
338 assert!(
339 (pixels[i] - dc_val).abs() < 1e-10,
340 "Pixel {i} = {}, expected uniform {}",
341 pixels[i],
342 dc_val
343 );
344 }
345 assert!((dc_val - expected).abs() < 1e-10);
346 }
347
348 #[test]
349 fn idct_dct_roundtrip_with_quant() {
350 let qt = [
352 16, 11, 10, 16, 24, 40, 51, 61,
353 12, 12, 14, 19, 26, 58, 60, 55,
354 14, 13, 16, 24, 40, 57, 69, 56,
355 14, 17, 22, 29, 51, 87, 80, 62,
356 18, 22, 37, 56, 68, 109, 103, 77,
357 24, 35, 55, 64, 81, 104, 113, 92,
358 49, 64, 78, 87, 103, 121, 120, 101,
359 72, 92, 95, 98, 112, 100, 103, 99u16,
360 ];
361
362 let mut quantized = [0i16; 64];
363 quantized[0] = 50;
364 quantized[1] = -3;
365 quantized[8] = 2;
366
367 let pixels = idct_block(&quantized, &qt);
368 let recovered = dct_block(&pixels, &qt);
369
370 for i in 0..64 {
371 assert!(
372 (quantized[i] - recovered[i]).abs() <= 1,
373 "Mismatch at index {i}: expected {}, got {}",
374 quantized[i],
375 recovered[i]
376 );
377 }
378 }
379}