use super::tables::ZIGZAG_4X4;
const LEVEL_SCALE_4X4: [[i32; 3]; 6] = [
[160, 256, 208],
[176, 288, 224],
[208, 320, 256],
[224, 368, 288],
[256, 400, 320],
[288, 464, 368],
];
#[inline]
const fn norm_adjust_class(i: usize, j: usize) -> usize {
let even_i = i & 1 == 0;
let even_j = j & 1 == 0;
if even_i && even_j {
0
} else if !even_i && !even_j {
1
} else {
2
}
}
pub fn unzigzag_4x4(scan_coeffs: &[i32; 16]) -> [[i32; 4]; 4] {
let mut out = [[0i32; 4]; 4];
for scan_idx in 0..16 {
let raster = ZIGZAG_4X4[scan_idx] as usize;
let i = raster / 4;
let j = raster % 4;
out[i][j] = scan_coeffs[scan_idx];
}
out
}
pub fn dequant_4x4(
raster: &[[i32; 4]; 4],
qp: i32,
has_dc_separate: bool,
) -> [[i32; 4]; 4] {
debug_assert!((0..52).contains(&qp), "luma QP must be in [0,51], got {qp}");
let q_mod = (qp.rem_euclid(6)) as usize;
let q_bits = qp / 6;
let mut d = [[0i32; 4]; 4];
for i in 0..4 {
for j in 0..4 {
if has_dc_separate && i == 0 && j == 0 {
continue;
}
let c = raster[i][j];
let scale = LEVEL_SCALE_4X4[q_mod][norm_adjust_class(i, j)];
d[i][j] = if q_bits >= 4 {
(c * scale) << (q_bits - 4)
} else {
let shift = 4 - q_bits;
let rnd = 1 << (shift - 1);
(c * scale + rnd) >> shift
};
}
}
d
}
pub fn inverse_4x4_integer(d: &[[i32; 4]; 4]) -> [[i32; 4]; 4] {
let mut g = [[0i32; 4]; 4];
for j in 0..4 {
let e0 = d[0][j] + d[2][j];
let e1 = d[0][j] - d[2][j];
let e2 = (d[1][j] >> 1) - d[3][j];
let e3 = d[1][j] + (d[3][j] >> 1);
g[0][j] = e0 + e3;
g[1][j] = e1 + e2;
g[2][j] = e1 - e2;
g[3][j] = e0 - e3;
}
let mut h = [[0i32; 4]; 4];
for i in 0..4 {
let e0 = g[i][0] + g[i][2];
let e1 = g[i][0] - g[i][2];
let e2 = (g[i][1] >> 1) - g[i][3];
let e3 = g[i][1] + (g[i][3] >> 1);
h[i][0] = e0 + e3;
h[i][1] = e1 + e2;
h[i][2] = e1 - e2;
h[i][3] = e0 - e3;
}
let mut r = [[0i32; 4]; 4];
for i in 0..4 {
for j in 0..4 {
r[i][j] = (h[i][j] + 32) >> 6;
}
}
r
}
pub fn inverse_16x16_dc_hadamard(c: &[[i32; 4]; 4], qp: i32) -> [[i32; 4]; 4] {
debug_assert!((0..52).contains(&qp), "luma QP must be in [0,51], got {qp}");
let mut f = [[0i32; 4]; 4];
for i in 0..4 {
let a0 = c[i][0] + c[i][2];
let a1 = c[i][1] + c[i][3];
let a2 = c[i][0] - c[i][2];
let a3 = c[i][1] - c[i][3];
f[i][0] = a0 + a1;
f[i][1] = a2 + a3;
f[i][2] = a2 - a3;
f[i][3] = a0 - a1;
}
let mut g = [[0i32; 4]; 4];
for j in 0..4 {
let b0 = f[0][j] + f[2][j];
let b1 = f[1][j] + f[3][j];
let b2 = f[0][j] - f[2][j];
let b3 = f[1][j] - f[3][j];
g[0][j] = b0 + b1;
g[1][j] = b2 + b3;
g[2][j] = b2 - b3;
g[3][j] = b0 - b1;
}
let q_mod = qp.rem_euclid(6) as usize;
let q_bits = qp / 6;
let scale = LEVEL_SCALE_4X4[q_mod][0];
let mut out = [[0i32; 4]; 4];
if q_bits >= 6 {
for i in 0..4 {
for j in 0..4 {
out[i][j] = (g[i][j] * scale) << (q_bits - 6);
}
}
} else {
let shift = 6 - q_bits;
let rnd = 1 << (shift - 1);
for i in 0..4 {
for j in 0..4 {
out[i][j] = (g[i][j] * scale + rnd) >> shift;
}
}
}
out
}
const CHROMA_QP_TABLE_8BIT: [u8; 52] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39,
39, 39,
];
pub fn derive_chroma_qp(qp_y: i32, chroma_qp_offset: i32) -> i32 {
let qpi = (qp_y + chroma_qp_offset).clamp(0, 51);
CHROMA_QP_TABLE_8BIT[qpi as usize] as i32
}
pub fn inverse_chroma_dc_2x2_hadamard(c: &[[i32; 2]; 2], qp_c: i32) -> [[i32; 2]; 2] {
debug_assert!((0..52).contains(&qp_c), "chroma QP must be in [0,51], got {qp_c}");
let f00 = c[0][0] + c[0][1];
let f01 = c[0][0] - c[0][1];
let f10 = c[1][0] + c[1][1];
let f11 = c[1][0] - c[1][1];
let g00 = f00 + f10;
let g01 = f01 + f11;
let g10 = f00 - f10;
let g11 = f01 - f11;
let q_mod = qp_c.rem_euclid(6) as usize;
let q_bits = qp_c / 6;
let scale = LEVEL_SCALE_4X4[q_mod][0];
let raw = [[g00, g01], [g10, g11]];
let mut out = [[0i32; 2]; 2];
if q_bits >= 5 {
for i in 0..2 {
for j in 0..2 {
out[i][j] = (raw[i][j] * scale) << (q_bits - 5);
}
}
} else {
let shift = 5 - q_bits;
for i in 0..2 {
for j in 0..2 {
out[i][j] = (raw[i][j] * scale) >> shift;
}
}
}
out
}
pub fn reconstruct_residual_4x4(scan_coeffs: &[i32; 16], qp: i32) -> [[i32; 4]; 4] {
let raster = unzigzag_4x4(scan_coeffs);
let dequant = dequant_4x4(&raster, qp, false);
inverse_4x4_integer(&dequant)
}
pub fn reconstruct_residual_4x4_with_dc(
ac_scan_coeffs: &[i32; 16],
dc_value: i32,
qp: i32,
) -> [[i32; 4]; 4] {
let raster = unzigzag_4x4(ac_scan_coeffs);
let mut dequant = dequant_4x4(&raster, qp, true);
dequant[0][0] = dc_value;
inverse_4x4_integer(&dequant)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn norm_adjust_class_covers_all_positions() {
assert_eq!(norm_adjust_class(0, 0), 0);
assert_eq!(norm_adjust_class(0, 2), 0);
assert_eq!(norm_adjust_class(2, 0), 0);
assert_eq!(norm_adjust_class(2, 2), 0);
assert_eq!(norm_adjust_class(1, 1), 1);
assert_eq!(norm_adjust_class(1, 3), 1);
assert_eq!(norm_adjust_class(3, 1), 1);
assert_eq!(norm_adjust_class(3, 3), 1);
assert_eq!(norm_adjust_class(0, 1), 2);
assert_eq!(norm_adjust_class(1, 0), 2);
assert_eq!(norm_adjust_class(2, 1), 2);
assert_eq!(norm_adjust_class(3, 2), 2);
}
#[test]
fn zero_block_reconstructs_to_zero_residual() {
let zero = [0i32; 16];
for qp in [0, 12, 24, 36, 51] {
let r = reconstruct_residual_4x4(&zero, qp);
for row in &r {
for &v in row {
assert_eq!(v, 0, "qp={qp} residual must be zero");
}
}
}
}
#[test]
fn dc_only_block_reconstructs_to_flat_residual() {
let mut coeffs = [0i32; 16];
coeffs[0] = 4; for qp in [24, 30, 36, 40] {
let r = reconstruct_residual_4x4(&coeffs, qp);
let v00 = r[0][0];
assert!(v00 != 0, "qp={qp} dc-only residual should be non-zero");
for row in &r {
for &v in row {
assert_eq!(v, v00, "qp={qp} flat DC residual must be uniform");
}
}
}
}
#[test]
fn inverse_transform_is_linear() {
let a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]];
let b = [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]];
let mut sum = [[0i32; 4]; 4];
for i in 0..4 {
for j in 0..4 {
sum[i][j] = a[i][j] + b[i][j];
}
}
let ta = inverse_4x4_integer(&a);
let tb = inverse_4x4_integer(&b);
let tsum = inverse_4x4_integer(&sum);
for i in 0..4 {
for j in 0..4 {
let combined = ta[i][j] + tb[i][j];
let diff = (tsum[i][j] - combined).abs();
assert!(
diff <= 1,
"linearity violated at ({i},{j}): sum={} a+b={} diff={}",
tsum[i][j],
combined,
diff
);
}
}
}
#[test]
fn unzigzag_roundtrips_through_known_scan() {
let scan: [i32; 16] = [
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
];
let raster = unzigzag_4x4(&scan);
assert_eq!(raster[0][0], 10); assert_eq!(raster[0][1], 11); assert_eq!(raster[1][0], 12); assert_eq!(raster[2][0], 13); assert_eq!(raster[1][1], 14); assert_eq!(raster[3][3], 25); }
#[test]
fn chroma_qp_table_matches_spec_fixed_points() {
assert_eq!(derive_chroma_qp(0, 0), 0);
assert_eq!(derive_chroma_qp(29, 0), 29);
assert_eq!(derive_chroma_qp(30, 0), 29);
assert_eq!(derive_chroma_qp(31, 0), 30);
assert_eq!(derive_chroma_qp(34, 0), 32);
assert_eq!(derive_chroma_qp(51, 0), 39);
assert_eq!(derive_chroma_qp(50, 5), 39); assert_eq!(derive_chroma_qp(5, -10), 0); }
#[test]
fn chroma_dc_hadamard_preserves_flat_signal() {
let c = [[3, 3], [3, 3]];
let out = inverse_chroma_dc_2x2_hadamard(&c, 26);
assert_ne!(out[0][0], 0, "sum-of-sums entry should be non-zero");
assert_eq!(out[0][1], 0, "diff entries should be zero for flat DC");
assert_eq!(out[1][0], 0);
assert_eq!(out[1][1], 0);
}
#[test]
fn chroma_dc_hadamard_zero_input_is_zero() {
let c = [[0; 2]; 2];
for qp in [0, 12, 26, 40, 51] {
let out = inverse_chroma_dc_2x2_hadamard(&c, qp);
for row in &out {
for &v in row {
assert_eq!(v, 0, "qp={qp}");
}
}
}
}
#[test]
fn dequant_respects_qp_shift_regions() {
let mut raster = [[0i32; 4]; 4];
raster[0][0] = 1;
let d = dequant_4x4(&raster, 30, false);
assert_eq!(d[0][0], 320);
let d = dequant_4x4(&raster, 12, false);
assert_eq!(d[0][0], 40);
}
}