1use crate::dct_grid::{high_len, idct8_basis, low_len, validate_dct_block_grid};
10pub use crate::DctGridError as Dct53GridError;
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct Dwt53TwoDimensional<T> {
15 pub ll: Vec<T>,
17 pub hl: Vec<T>,
19 pub lh: Vec<T>,
21 pub hh: Vec<T>,
23 pub low_width: usize,
25 pub low_height: usize,
27 pub high_width: usize,
29 pub high_height: usize,
31}
32
33impl Dwt53TwoDimensional<f64> {
34 #[must_use]
36 pub fn max_abs_diff(&self, other: &Self) -> f64 {
37 assert_eq!(self.low_width, other.low_width);
38 assert_eq!(self.low_height, other.low_height);
39 assert_eq!(self.high_width, other.high_width);
40 assert_eq!(self.high_height, other.high_height);
41
42 self.ll
43 .iter()
44 .zip(other.ll.iter())
45 .chain(self.hl.iter().zip(other.hl.iter()))
46 .chain(self.lh.iter().zip(other.lh.iter()))
47 .chain(self.hh.iter().zip(other.hh.iter()))
48 .map(|(actual, expected)| (actual - expected).abs())
49 .fold(0.0, f64::max)
50 }
51}
52
53#[derive(Debug, Default)]
59pub struct Dct53GridScratch {
60 x_weights: Dwt53WeightRows,
61 y_weights: Dwt53WeightRows,
62}
63
64impl Dct53GridScratch {
65 #[must_use]
69 pub fn weight_row_capacity(&self) -> usize {
70 self.x_weights.weight_capacity() + self.y_weights.weight_capacity()
71 }
72}
73
74#[must_use]
76pub fn dct8x8_to_dwt53_float_linear(block: [[f64; 8]; 8]) -> Dwt53TwoDimensional<f64> {
77 let width = 8;
78 let height = 8;
79 let low_width = low_len(width);
80 let low_height = low_len(height);
81 let high_width = high_len(width);
82 let high_height = high_len(height);
83
84 let mut ll = Vec::with_capacity(low_width * low_height);
85 let mut hl = Vec::with_capacity(high_width * low_height);
86 let mut lh = Vec::with_capacity(low_width * high_height);
87 let mut hh = Vec::with_capacity(high_width * high_height);
88
89 for y in 0..low_height {
90 for x in 0..low_width {
91 ll.push(project_dct_block(&block, true, y, true, x));
92 }
93 for x in 0..high_width {
94 hl.push(project_dct_block(&block, true, y, false, x));
95 }
96 }
97
98 for y in 0..high_height {
99 for x in 0..low_width {
100 lh.push(project_dct_block(&block, false, y, true, x));
101 }
102 for x in 0..high_width {
103 hh.push(project_dct_block(&block, false, y, false, x));
104 }
105 }
106
107 Dwt53TwoDimensional {
108 ll,
109 hl,
110 lh,
111 hh,
112 low_width,
113 low_height,
114 high_width,
115 high_height,
116 }
117}
118
119pub fn dct8x8_blocks_to_dwt53_float_linear(
124 blocks: &[[[f64; 8]; 8]],
125 block_cols: usize,
126 block_rows: usize,
127 width: usize,
128 height: usize,
129) -> Result<Dwt53TwoDimensional<f64>, Dct53GridError> {
130 let mut scratch = Dct53GridScratch::default();
131 dct8x8_blocks_to_dwt53_float_linear_with_scratch(
132 blocks,
133 block_cols,
134 block_rows,
135 width,
136 height,
137 &mut scratch,
138 )
139}
140
141pub fn dct8x8_blocks_to_dwt53_float_linear_with_scratch(
144 blocks: &[[[f64; 8]; 8]],
145 block_cols: usize,
146 block_rows: usize,
147 width: usize,
148 height: usize,
149 scratch: &mut Dct53GridScratch,
150) -> Result<Dwt53TwoDimensional<f64>, Dct53GridError> {
151 validate_grid(blocks.len(), block_cols, block_rows, width, height)?;
152
153 let low_width = low_len(width);
154 let low_height = low_len(height);
155 let high_width = high_len(width);
156 let high_height = high_len(height);
157 scratch.x_weights.ensure_sample_len(width);
158 scratch.y_weights.ensure_sample_len(height);
159 let x_weights = &scratch.x_weights;
160 let y_weights = &scratch.y_weights;
161
162 let mut ll = Vec::with_capacity(low_width * low_height);
163 let mut hl = Vec::with_capacity(high_width * low_height);
164 let mut lh = Vec::with_capacity(low_width * high_height);
165 let mut hh = Vec::with_capacity(high_width * high_height);
166
167 for y in 0..low_height {
168 for x in 0..low_width {
169 ll.push(project_dct_grid(
170 blocks,
171 block_cols,
172 &y_weights.low[y].taps,
173 &x_weights.low[x].taps,
174 ));
175 }
176 for x in 0..high_width {
177 hl.push(project_dct_grid(
178 blocks,
179 block_cols,
180 &y_weights.low[y].taps,
181 &x_weights.high[x].taps,
182 ));
183 }
184 }
185
186 for y in 0..high_height {
187 for x in 0..low_width {
188 lh.push(project_dct_grid(
189 blocks,
190 block_cols,
191 &y_weights.high[y].taps,
192 &x_weights.low[x].taps,
193 ));
194 }
195 for x in 0..high_width {
196 hh.push(project_dct_grid(
197 blocks,
198 block_cols,
199 &y_weights.high[y].taps,
200 &x_weights.high[x].taps,
201 ));
202 }
203 }
204
205 Ok(Dwt53TwoDimensional {
206 ll,
207 hl,
208 lh,
209 hh,
210 low_width,
211 low_height,
212 high_width,
213 high_height,
214 })
215}
216
217#[must_use]
220pub fn idct8x8_then_dwt53_float(block: [[f64; 8]; 8]) -> Dwt53TwoDimensional<f64> {
221 let mut samples = Vec::with_capacity(64);
222 for y in 0..8 {
223 for x in 0..8 {
224 samples.push(idct8x8_sample(&block, x, y));
225 }
226 }
227
228 linearized_53_2d_from_plane(&samples, 8, 8)
229}
230
231pub fn dct8x8_blocks_then_dwt53_float(
234 blocks: &[[[f64; 8]; 8]],
235 block_cols: usize,
236 block_rows: usize,
237 width: usize,
238 height: usize,
239) -> Result<Dwt53TwoDimensional<f64>, Dct53GridError> {
240 validate_grid(blocks.len(), block_cols, block_rows, width, height)?;
241
242 let mut samples = Vec::with_capacity(width * height);
243 for y in 0..height {
244 let block_y = y / 8;
245 let local_y = y % 8;
246 for x in 0..width {
247 let block_x = x / 8;
248 let local_x = x % 8;
249 let block = &blocks[block_y * block_cols + block_x];
250 samples.push(idct8x8_sample(block, local_x, local_y));
251 }
252 }
253
254 Ok(linearized_53_2d_from_plane(&samples, width, height))
255}
256
257fn project_dct_block(
258 block: &[[f64; 8]; 8],
259 vertical_low: bool,
260 output_y: usize,
261 horizontal_low: bool,
262 output_x: usize,
263) -> f64 {
264 let mut output = 0.0;
265
266 for sample_y in 0..8 {
267 let y_weight = linearized_53_sample_weight(8, vertical_low, output_y, sample_y);
268 if y_weight == 0.0 {
269 continue;
270 }
271
272 for sample_x in 0..8 {
273 let x_weight = linearized_53_sample_weight(8, horizontal_low, output_x, sample_x);
274 if x_weight == 0.0 {
275 continue;
276 }
277
278 let sample_weight = y_weight * x_weight;
279 for (freq_y, coefficient_row) in block.iter().enumerate() {
280 let y_basis = idct8_basis(sample_y, freq_y);
281 for (freq_x, coefficient) in coefficient_row.iter().copied().enumerate() {
282 output += sample_weight * y_basis * idct8_basis(sample_x, freq_x) * coefficient;
283 }
284 }
285 }
286 }
287
288 output
289}
290
291fn project_dct_grid(
292 blocks: &[[[f64; 8]; 8]],
293 block_cols: usize,
294 y_weights: &[SparseWeightTap],
295 x_weights: &[SparseWeightTap],
296) -> f64 {
297 let mut output = 0.0;
298
299 for &SparseWeightTap {
300 sample_idx: sample_y,
301 weight: y_weight,
302 } in y_weights
303 {
304 let block_y = sample_y / 8;
305 let local_y = sample_y % 8;
306
307 for &SparseWeightTap {
308 sample_idx: sample_x,
309 weight: x_weight,
310 } in x_weights
311 {
312 let block_x = sample_x / 8;
313 let local_x = sample_x % 8;
314 let block = &blocks[block_y * block_cols + block_x];
315 let sample_weight = y_weight * x_weight;
316
317 for (freq_y, coefficient_row) in block.iter().enumerate() {
318 let y_basis = idct8_basis(local_y, freq_y);
319 for (freq_x, coefficient) in coefficient_row.iter().copied().enumerate() {
320 output += sample_weight * y_basis * idct8_basis(local_x, freq_x) * coefficient;
321 }
322 }
323 }
324 }
325
326 output
327}
328
329fn idct8x8_sample(block: &[[f64; 8]; 8], x: usize, y: usize) -> f64 {
330 let mut sample = 0.0;
331 for (freq_y, row) in block.iter().enumerate() {
332 let y_basis = idct8_basis(y, freq_y);
333 for (freq_x, coefficient) in row.iter().copied().enumerate() {
334 sample += coefficient * y_basis * idct8_basis(x, freq_x);
335 }
336 }
337 sample
338}
339
340pub(crate) fn linearized_53_2d_from_plane(
341 samples: &[f64],
342 width: usize,
343 height: usize,
344) -> Dwt53TwoDimensional<f64> {
345 debug_assert_eq!(samples.len(), width * height);
346
347 let low_width = low_len(width);
348 let low_height = low_len(height);
349 let high_width = high_len(width);
350 let high_height = high_len(height);
351
352 let mut row_low = Vec::with_capacity(height * low_width);
353 let mut row_high = Vec::with_capacity(height * high_width);
354 for y in 0..height {
355 let start = y * width;
356 let row = &samples[start..start + width];
357 let transformed = linearized_53_from_sample_slice(row);
358 row_low.extend(transformed.low);
359 row_high.extend(transformed.high);
360 }
361
362 let mut ll = Vec::with_capacity(low_width * low_height);
363 let mut lh = Vec::with_capacity(low_width * high_height);
364 for x in 0..low_width {
365 let column = column_from_rows(&row_low, low_width, x, height);
366 let transformed = linearized_53_from_sample_slice(&column);
367 ll.extend(transformed.low);
368 lh.extend(transformed.high);
369 }
370
371 let mut hl = Vec::with_capacity(high_width * low_height);
372 let mut hh = Vec::with_capacity(high_width * high_height);
373 for x in 0..high_width {
374 let column = column_from_rows(&row_high, high_width, x, height);
375 let transformed = linearized_53_from_sample_slice(&column);
376 hl.extend(transformed.low);
377 hh.extend(transformed.high);
378 }
379
380 Dwt53TwoDimensional {
381 ll: transpose_band(&ll, low_height, low_width),
382 hl: transpose_band(&hl, low_height, high_width),
383 lh: transpose_band(&lh, high_height, low_width),
384 hh: transpose_band(&hh, high_height, high_width),
385 low_width,
386 low_height,
387 high_width,
388 high_height,
389 }
390}
391
392fn column_from_rows(rows: &[f64], stride: usize, x: usize, height: usize) -> Vec<f64> {
393 (0..height).map(|y| rows[y * stride + x]).collect()
394}
395
396fn transpose_band(column_major: &[f64], height: usize, width: usize) -> Vec<f64> {
397 let mut row_major = Vec::with_capacity(width * height);
398 for y in 0..height {
399 for x in 0..width {
400 row_major.push(column_major[x * height + y]);
401 }
402 }
403 row_major
404}
405
406fn linearized_53_sample_weight(
407 sample_len: usize,
408 is_low: bool,
409 output_idx: usize,
410 sample_idx: usize,
411) -> f64 {
412 let mut basis = vec![0.0; sample_len];
413 basis[sample_idx] = 1.0;
414 let row = linearized_53_from_sample_slice(&basis);
415 if is_low {
416 row.low[output_idx]
417 } else {
418 row.high[output_idx]
419 }
420}
421
422fn linearized_53_from_sample_slice(samples: &[f64]) -> Dwt53OneDimensional {
423 let mut high = Vec::with_capacity(high_len(samples.len()));
424 for odd_idx in (1..samples.len()).step_by(2) {
425 let left = samples[odd_idx - 1];
426 let right = samples.get(odd_idx + 1).copied().unwrap_or(left);
427 high.push(samples[odd_idx] - ((left + right) * 0.5));
428 }
429
430 let mut low = Vec::with_capacity(low_len(samples.len()));
431 for even_idx in (0..samples.len()).step_by(2) {
432 let current = samples[even_idx];
433 let even_output_idx = even_idx / 2;
434 let left_high = even_output_idx.checked_sub(1).and_then(|idx| high.get(idx));
435 let right_high = high.get(even_output_idx);
436 let update = match (left_high, right_high) {
437 (Some(left), Some(right)) => (*left + *right) * 0.25,
438 (None, Some(right)) => *right * 0.5,
439 (Some(left), None) => *left * 0.5,
440 (None, None) => 0.0,
441 };
442 low.push(current + update);
443 }
444
445 Dwt53OneDimensional { low, high }
446}
447
448fn validate_grid(
449 block_count: usize,
450 block_cols: usize,
451 block_rows: usize,
452 width: usize,
453 height: usize,
454) -> Result<(), Dct53GridError> {
455 validate_dct_block_grid(block_count, block_cols, block_rows, width, height)
456}
457
458#[derive(Debug, Default)]
459struct Dwt53WeightRows {
460 sample_len: Option<usize>,
461 low: Vec<SparseWeightRow>,
462 high: Vec<SparseWeightRow>,
463}
464
465impl Dwt53WeightRows {
466 fn ensure_sample_len(&mut self, sample_len: usize) {
467 if self.sample_len == Some(sample_len) {
468 return;
469 }
470
471 resize_weight_rows(&mut self.low, low_len(sample_len), 5);
472 resize_weight_rows(&mut self.high, high_len(sample_len), 3);
473
474 for sample_idx in 0..sample_len {
475 let mut basis = vec![0.0; sample_len];
476 basis[sample_idx] = 1.0;
477 let transformed = linearized_53_from_sample_slice(&basis);
478 for (row, &weight) in self.low.iter_mut().zip(transformed.low.iter()) {
479 if weight != 0.0 {
480 row.taps.push(SparseWeightTap { sample_idx, weight });
481 }
482 }
483 for (row, &weight) in self.high.iter_mut().zip(transformed.high.iter()) {
484 if weight != 0.0 {
485 row.taps.push(SparseWeightTap { sample_idx, weight });
486 }
487 }
488 }
489
490 self.sample_len = Some(sample_len);
491 }
492
493 fn weight_capacity(&self) -> usize {
494 self.low
495 .iter()
496 .map(|row| row.taps.capacity())
497 .sum::<usize>()
498 + self
499 .high
500 .iter()
501 .map(|row| row.taps.capacity())
502 .sum::<usize>()
503 }
504}
505
506fn resize_weight_rows(rows: &mut Vec<SparseWeightRow>, row_count: usize, max_taps: usize) {
507 if rows.len() < row_count {
508 rows.resize_with(row_count, SparseWeightRow::default);
509 }
510 for row in rows.iter_mut().take(row_count) {
511 row.taps.clear();
512 if row.taps.capacity() < max_taps {
513 row.taps.reserve_exact(max_taps - row.taps.capacity());
514 }
515 }
516 rows.truncate(row_count);
517}
518
519#[derive(Debug, Default)]
520struct SparseWeightRow {
521 taps: Vec<SparseWeightTap>,
522}
523
524#[derive(Debug, Clone, Copy)]
525struct SparseWeightTap {
526 sample_idx: usize,
527 weight: f64,
528}
529
530struct Dwt53OneDimensional {
531 low: Vec<f64>,
532 high: Vec<f64>,
533}