pub fn forward_wht4(t: &mut [i64], shift: u32) {
let out_a = t[0];
let out_b = t[1];
let out_c = t[2];
let out_d = t[3];
let big_a = out_a + out_b;
let big_d = out_d - out_c;
let e = (big_a - big_d) >> 1;
let b0 = e - out_b;
let c0 = e - out_c;
let a0 = big_a - c0;
let d0 = big_d + b0;
let scale = 1i64 << shift;
t[0] = a0 * scale;
t[1] = c0 * scale;
t[2] = d0 * scale;
t[3] = b0 * scale;
}
pub fn forward_wht_4x4(input: &[i64]) -> [i64; 16] {
assert_eq!(
input.len(),
16,
"oxideav-av1 forward_wht_4x4 expects 4 * 4 = 16 spatial samples"
);
let mut work = [0i64; 16];
work.copy_from_slice(input);
let mut col_buf = [0i64; 4];
for j in 0..4 {
for i in 0..4 {
col_buf[i] = work[i * 4 + j];
}
forward_wht4(&mut col_buf, 0);
for i in 0..4 {
work[i * 4 + j] = col_buf[i];
}
}
let mut row_buf = [0i64; 4];
for i in 0..4 {
row_buf.copy_from_slice(&work[i * 4..i * 4 + 4]);
forward_wht4(&mut row_buf, 2);
work[i * 4..i * 4 + 4].copy_from_slice(&row_buf);
}
work
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::{DCT_DCT, TX_4X4};
use crate::transform::{inverse_transform_2d, inverse_wht4};
#[test]
fn wht_4_roundtrip_bit_exact_shift0() {
for k in 0..4 {
for v in (-256i64..=255).step_by(7) {
let mut t = [0i64; 4];
t[k] = v;
let original = t;
forward_wht4(&mut t, 0);
inverse_wht4(&mut t, 0);
assert_eq!(
t, original,
"shift = 0, slot {k}, value {v}: round-trip diverged",
);
}
}
}
#[test]
fn wht_4_roundtrip_bit_exact_shift2() {
for k in 0..4 {
for v in (-256i64..=255).step_by(11) {
let mut t = [0i64; 4];
t[k] = v;
let original = t;
forward_wht4(&mut t, 2);
inverse_wht4(&mut t, 2);
assert_eq!(
t, original,
"shift = 2, slot {k}, value {v}: round-trip diverged",
);
}
}
}
#[test]
fn wht_4_roundtrip_mixed_slots_shift0() {
let cases: &[[i64; 4]] = &[
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[1, 1, 1, 1],
[1, -1, 1, -1],
[-128, 127, -128, 127],
[255, -255, 255, -255],
[3, 5, 7, 11],
[-7, 13, -19, 23],
];
for case in cases {
let mut t = *case;
forward_wht4(&mut t, 0);
inverse_wht4(&mut t, 0);
assert_eq!(&t, case, "mixed-slot shift=0 round-trip diverged");
}
}
#[test]
fn forward_wht4_zero_input_yields_zero() {
let mut t = [0i64; 4];
forward_wht4(&mut t, 0);
assert_eq!(t, [0; 4]);
let mut t = [0i64; 4];
forward_wht4(&mut t, 2);
assert_eq!(t, [0; 4]);
}
#[test]
fn forward_wht_4x4_zero_input_yields_zero() {
let input = [0i64; 16];
let out = forward_wht_4x4(&input);
assert_eq!(out, [0; 16]);
let back = inverse_transform_2d(&out, TX_4X4, DCT_DCT, 8, true);
assert_eq!(back, vec![0; 16]);
}
#[test]
fn forward_wht_4x4_flat_dc_roundtrips() {
for v in (-128i64..=127).step_by(3) {
let input = [v; 16];
let coeffs = forward_wht_4x4(&input);
let back = inverse_transform_2d(&coeffs, TX_4X4, DCT_DCT, 8, true);
assert_eq!(
back,
input.to_vec(),
"flat v = {v}: lossless round-trip diverged"
);
}
}
#[test]
fn forward_wht_4x4_unit_impulses_roundtrip() {
for k in 0..16 {
for & in &[1i64, -1, 64, -64, 127, -128] {
let mut input = [0i64; 16];
input[k] = amp;
let coeffs = forward_wht_4x4(&input);
let back = inverse_transform_2d(&coeffs, TX_4X4, DCT_DCT, 8, true);
assert_eq!(
back,
input.to_vec(),
"impulse k = {k}, amp = {amp}: lossless round-trip diverged",
);
}
}
}
#[test]
fn forward_wht_4x4_lcg_sweep_roundtrip() {
let mut state: u64 = 0xDEAD_BEEF_CAFE_BABE;
for trial in 0..64 {
let mut input = [0i64; 16];
for cell in input.iter_mut() {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
let v = ((state >> 23) as u32 & 0x1FF) as i64 - 256;
*cell = v;
}
let coeffs = forward_wht_4x4(&input);
let back = inverse_transform_2d(&coeffs, TX_4X4, DCT_DCT, 8, true);
assert_eq!(
back,
input.to_vec(),
"trial {trial}: input = {input:?}, coeffs = {coeffs:?}",
);
}
}
#[test]
fn forward_wht_4x4_linearity_at_doubled_input() {
let mut a = [0i64; 16];
for (k, slot) in a.iter_mut().enumerate() {
*slot = ((k as i64) - 8) * 4;
}
let mut b = a;
for slot in b.iter_mut() {
*slot *= 2;
}
let fa = forward_wht_4x4(&a);
let fb = forward_wht_4x4(&b);
for k in 0..16 {
assert_eq!(fb[k], 2 * fa[k], "linearity slot {k}");
}
}
#[test]
#[should_panic(expected = "forward_wht_4x4 expects")]
fn forward_wht_4x4_rejects_short_buffer() {
let _ = forward_wht_4x4(&[0i64; 15]);
}
}