#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "VP8 transforms store results into i16 with the C int16_t wrapping \
semantics of the reference decoder, and clip8 narrows an already-\
clamped [0,255] value to u8; the casts reproduce that exactly"
)]
use crate::lossy::work::work;
pub(crate) fn transform_wht(input: [i16; 16], out: &mut [i16; 384]) {
work!(IdctCall);
let mut tmp = [0i32; 16];
for i in 0..4 {
let a0 = i32::from(input[i]) + i32::from(input[12 + i]);
let a1 = i32::from(input[4 + i]) + i32::from(input[8 + i]);
let a2 = i32::from(input[4 + i]) - i32::from(input[8 + i]);
let a3 = i32::from(input[i]) - i32::from(input[12 + i]);
tmp[i] = a0 + a1;
tmp[8 + i] = a0 - a1;
tmp[4 + i] = a3 + a2;
tmp[12 + i] = a3 - a2;
}
for i in 0..4 {
let dc = tmp[i * 4] + 3; let a0 = dc + tmp[3 + i * 4];
let a1 = tmp[1 + i * 4] + tmp[2 + i * 4];
let a2 = tmp[1 + i * 4] - tmp[2 + i * 4];
let a3 = dc - tmp[3 + i * 4];
let base = i * 64;
out[base] = ((a0 + a1) >> 3) as i16;
out[base + 16] = ((a3 + a2) >> 3) as i16;
out[base + 32] = ((a0 - a1) >> 3) as i16;
out[base + 48] = ((a3 - a2) >> 3) as i16;
}
}
pub(crate) fn transform_dc(dc: i16, plane: &mut [u8], off: usize, stride: usize) {
work!(IdctCall);
let d = (i32::from(dc) + 4) >> 3;
for row in 0..4 {
let base = off + row * stride;
for x in 0..4 {
let idx = base + x;
plane[idx] = clip8(i32::from(plane[idx]) + d);
}
}
}
const fn mul1(a: i32) -> i32 {
((a * 20091) >> 16) + a
}
const fn mul2(a: i32) -> i32 {
(a * 35468) >> 16
}
fn clip8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
fn store(plane: &mut [u8], row: usize, x: usize, v: i32) {
let idx = row + x;
plane[idx] = clip8(i32::from(plane[idx]) + (v >> 3));
}
pub(crate) fn transform_one(coeffs: &[i16], plane: &mut [u8], off: usize, stride: usize) {
work!(IdctCall);
let mut tmp = [0i32; 16];
for i in 0..4 {
let in0 = i32::from(coeffs[i]);
let in8 = i32::from(coeffs[8 + i]);
let in4 = i32::from(coeffs[4 + i]);
let in12 = i32::from(coeffs[12 + i]);
let a = in0 + in8;
let b = in0 - in8;
let c = mul2(in4) - mul1(in12);
let d = mul1(in4) + mul2(in12);
let base = i * 4;
tmp[base] = a + d;
tmp[base + 1] = b + c;
tmp[base + 2] = b - c;
tmp[base + 3] = a - d;
}
for i in 0..4 {
let dc = tmp[i] + 4;
let t8 = tmp[8 + i];
let a = dc + t8;
let b = dc - t8;
let c = mul2(tmp[4 + i]) - mul1(tmp[12 + i]);
let d = mul1(tmp[4 + i]) + mul2(tmp[12 + i]);
let row = off + i * stride;
store(plane, row, 0, a + d);
store(plane, row, 1, b + c);
store(plane, row, 2, b - c);
store(plane, row, 3, a - d);
}
}
#[cfg(test)]
mod tests {
use super::{mul1, mul2, transform_dc, transform_one, transform_wht};
#[test]
fn transform_dc_matches_full_transform_on_dc_only_blocks() {
let stride = 6;
for dc in [-2048i16, -600, -100, -8, -1, 0, 1, 7, 8, 100, 600, 2047] {
for bg in [0u8, 1, 5, 100, 128, 250, 255] {
let mut full = [bg; 4 * 6];
let mut fast = [bg; 4 * 6];
let mut coeffs = [0i16; 16];
coeffs[0] = dc;
transform_one(&coeffs, &mut full, 0, stride);
transform_dc(dc, &mut fast, 0, stride);
assert_eq!(fast, full, "dc={dc} bg={bg}");
}
}
}
#[test]
fn all_zero_input_is_all_zero_dc() {
let mut out = [1i16; 384];
transform_wht([0; 16], &mut out);
for b in 0..16 {
assert_eq!(out[b * 16], 0);
}
}
#[test]
fn uniform_dc_propagates_to_every_block() {
let mut input = [0i16; 16];
input[0] = 8;
let mut out = [0i16; 384];
transform_wht(input, &mut out);
for b in 0..16 {
assert_eq!(out[b * 16], 1, "block {b} DC");
}
}
#[test]
fn wht_horizontal_split_from_in0_and_in1() {
const SENT: i16 = 999;
let mut input = [0i16; 16];
input[0] = 40;
input[1] = 80;
let mut out = [SENT; 384];
transform_wht(input, &mut out);
let expected: [i16; 16] = [
15, 15, -5, -5, 15, 15, -5, -5, 15, 15, -5, -5, 15, 15, -5, -5,
];
for (b, &want) in expected.iter().enumerate() {
assert_eq!(out[b * 16], want, "block {b} DC");
}
for (i, &v) in out.iter().enumerate() {
if i % 16 != 0 {
assert_eq!(v, SENT, "non-DC coeff slot {i} was clobbered");
}
}
}
#[test]
fn wht_vertical_split_from_in0_and_in4() {
let mut input = [0i16; 16];
input[0] = 40;
input[4] = 80;
let mut out = [0i16; 384];
transform_wht(input, &mut out);
let expected: [i16; 16] = [
15, 15, 15, 15, 15, 15, 15, 15, -5, -5, -5, -5, -5, -5, -5, -5,
];
for (b, &want) in expected.iter().enumerate() {
assert_eq!(out[b * 16], want, "block {b} DC");
}
}
#[test]
fn mul_constants_match_reference() {
assert_eq!((mul1(0), mul2(0)), (0, 0));
assert_eq!((mul1(100), mul2(100)), (130, 54));
assert_eq!((mul1(-100), mul2(-100)), (-131, -55));
assert_eq!((mul1(200), mul2(200)), (261, 108));
assert_eq!((mul1(-160), mul2(-160)), (-210, -87));
}
#[test]
fn transform_one_dc_only_adds_uniform_offset() {
let stride = 4;
let mut plane = [100u8; 16];
let mut coeffs = [0i16; 16];
coeffs[0] = 28;
transform_one(&coeffs, &mut plane, 0, stride);
assert!(plane.iter().all(|&p| p == 104));
}
#[test]
fn transform_one_respects_stride_and_clips() {
let stride = 6;
let mut plane = [5u8; 4 * 6];
let mut coeffs = [0i16; 16];
coeffs[0] = -100;
transform_one(&coeffs, &mut plane, 0, stride);
for y in 0..4 {
for x in 0..6 {
let expected = if x < 4 { 0 } else { 5 };
assert_eq!(plane[x + y * stride], expected, "x={x} y={y}");
}
}
}
#[test]
fn transform_one_single_ac_coeff1_is_horizontal_ripple() {
let stride = 4;
let mut plane = [128u8; 16];
let mut coeffs = [0i16; 16];
coeffs[1] = 200;
transform_one(&coeffs, &mut plane, 0, stride);
let expected_row = [161u8, 142, 115, 95];
for y in 0..4 {
for (x, &want) in expected_row.iter().enumerate() {
assert_eq!(plane[x + y * stride], want, "x={x} y={y}");
}
}
}
#[test]
fn transform_one_single_ac_coeff4_is_vertical_ripple() {
let stride = 4;
let mut plane = [128u8; 16];
let mut coeffs = [0i16; 16];
coeffs[4] = 200;
transform_one(&coeffs, &mut plane, 0, stride);
let expected_rows = [161u8, 142, 115, 95];
for (y, &want) in expected_rows.iter().enumerate() {
for x in 0..4 {
assert_eq!(plane[x + y * stride], want, "x={x} y={y}");
}
}
}
#[test]
fn transform_one_mixed_dc_and_two_ac_coeffs() {
let stride = 6;
let mut plane = [100u8; 4 * 6];
let mut coeffs = [0i16; 16];
coeffs[0] = 100;
coeffs[1] = 200;
coeffs[4] = -160;
transform_one(&coeffs, &mut plane, 0, stride);
let block: [[u8; 4]; 4] = [
[119, 100, 73, 54],
[134, 115, 88, 69],
[156, 137, 110, 91],
[171, 152, 125, 106],
];
for (y, row) in block.iter().enumerate() {
for x in 0..6 {
let want = if x < 4 { row[x] } else { 100 };
assert_eq!(plane[x + y * stride], want, "x={x} y={y}");
}
}
}
}