use crate::common::types::MAX_SEGMENTS;
const VP8_RANDOM_TABLE_SIZE: usize = 55;
const VP8_RANDOM_DITHER_FIX: u32 = 8;
const VP8_DITHER_AMP_BITS: u32 = 7;
const VP8_DITHER_AMP_CENTER: i32 = 1 << VP8_DITHER_AMP_BITS;
const VP8_DITHER_DESCALE: u32 = 4;
const VP8_DITHER_DESCALE_ROUNDER: i32 = 1 << (VP8_DITHER_DESCALE - 1);
const MIN_DITHER_AMP: i32 = 4;
const DITHER_AMP_TAB: [u8; 12] = [8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1];
const RANDOM_TABLE: [u32; VP8_RANDOM_TABLE_SIZE] = [
0x0de15230, 0x03b31886, 0x775faccb, 0x1c88626a, 0x68385c55, 0x14b3b828, 0x4a85fef8, 0x49ddb84b,
0x64fcf397, 0x5c550289, 0x4a290000, 0x0d7ec1da, 0x5940b7ab, 0x5492577d, 0x4e19ca72, 0x38d38c69,
0x0c01ee65, 0x32a1755f, 0x5437f652, 0x5abb2c32, 0x0faa57b1, 0x73f533e7, 0x685feeda, 0x7563cce2,
0x6e990e83, 0x4730a7ed, 0x4fc0d9c6, 0x496b153c, 0x4f1403fa, 0x541afb0c, 0x73990b32, 0x26d7cb1c,
0x6fcc3706, 0x2cbb77d8, 0x75762f2a, 0x6425ccdd, 0x24b35461, 0x0a7d8715, 0x220414a8, 0x141ebf67,
0x56b41583, 0x73e502e3, 0x44cab16f, 0x28264d42, 0x73baaefb, 0x0a50ebed, 0x1d6ab6fb, 0x0d3ad40b,
0x35db3b68, 0x2b081e83, 0x77ce6b95, 0x5181e5f0, 0x78853bbc, 0x009f9494, 0x27e5ed3c,
];
pub(crate) struct VP8Random {
index1: usize,
index2: usize,
tab: [u32; VP8_RANDOM_TABLE_SIZE],
}
impl VP8Random {
pub(crate) fn new() -> Self {
Self {
index1: 0,
index2: 31,
tab: RANDOM_TABLE,
}
}
fn random_bits2(&mut self, num_bits: u32, amp: i32) -> i32 {
let diff = self.tab[self.index1].wrapping_sub(self.tab[self.index2]);
let diff = diff & 0x7FFF_FFFF;
self.tab[self.index1] = diff;
self.index1 += 1;
if self.index1 == VP8_RANDOM_TABLE_SIZE {
self.index1 = 0;
}
self.index2 += 1;
if self.index2 == VP8_RANDOM_TABLE_SIZE {
self.index2 = 0;
}
let diff = ((diff << 1) as i32) >> (32 - num_bits as i32);
let diff = (diff * amp) >> VP8_RANDOM_DITHER_FIX;
diff + (1 << (num_bits - 1))
}
}
pub(crate) fn init_dither_amplitudes(
uv_quant_indices: &[i32; MAX_SEGMENTS],
strength: u8,
) -> (bool, [i32; MAX_SEGMENTS]) {
let max_amp = (1i32 << VP8_RANDOM_DITHER_FIX) - 1;
let f = if strength == 0 {
0
} else {
(i32::from(strength.min(100)) * max_amp) / 100
};
let mut amps = [0i32; MAX_SEGMENTS];
let mut any_nonzero = false;
if f > 0 {
for (s, &uv_quant) in uv_quant_indices.iter().enumerate() {
let idx = uv_quant.clamp(0, DITHER_AMP_TAB.len() as i32 - 1) as usize;
if uv_quant < DITHER_AMP_TAB.len() as i32 {
amps[s] = (f * i32::from(DITHER_AMP_TAB[idx])) >> 3;
}
if amps[s] >= MIN_DITHER_AMP {
any_nonzero = true;
} else {
amps[s] = 0; }
}
}
(any_nonzero, amps)
}
fn dither_8x8(rg: &mut VP8Random, dst: &mut [u8], stride: usize, amp: i32) {
for j in 0..8 {
let row_start = j * stride;
for i in 0..8 {
let dither_val = rg.random_bits2(VP8_DITHER_AMP_BITS + 1, amp);
let delta0 = dither_val - VP8_DITHER_AMP_CENTER;
let delta1 = (delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE;
let val = i32::from(dst[row_start + i]) + delta1;
dst[row_start + i] = val.clamp(0, 255) as u8;
}
}
}
pub(crate) struct DitherRowParams<'a> {
pub cache_u: &'a mut [u8],
pub cache_v: &'a mut [u8],
pub cache_uv_stride: usize,
pub extra_uv_rows: usize,
pub mb_dither_amps: &'a [i32],
}
pub(crate) fn dither_row(rg: &mut VP8Random, params: DitherRowParams<'_>) {
let cache_offset = params.extra_uv_rows * params.cache_uv_stride;
for (mbx, &) in params.mb_dither_amps.iter().enumerate() {
if amp >= MIN_DITHER_AMP {
let offset = cache_offset + mbx * 8;
dither_8x8(
rg,
&mut params.cache_u[offset..],
params.cache_uv_stride,
amp,
);
dither_8x8(
rg,
&mut params.cache_v[offset..],
params.cache_uv_stride,
amp,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn test_random_deterministic() {
let mut rg = VP8Random::new();
let v1 = rg.random_bits2(VP8_DITHER_AMP_BITS + 1, 100);
let v2 = rg.random_bits2(VP8_DITHER_AMP_BITS + 1, 100);
assert_ne!(v1, v2, "consecutive values should differ");
let mut rg2 = VP8Random::new();
let v1b = rg2.random_bits2(VP8_DITHER_AMP_BITS + 1, 100);
let v2b = rg2.random_bits2(VP8_DITHER_AMP_BITS + 1, 100);
assert_eq!(v1, v1b, "same seed should produce same sequence");
assert_eq!(v2, v2b);
}
#[test]
fn test_random_range() {
let mut rg = VP8Random::new();
for _ in 0..1000 {
let val = rg.random_bits2(VP8_DITHER_AMP_BITS + 1, 255);
let delta0 = val - VP8_DITHER_AMP_CENTER;
let delta1 = (delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE;
assert!(
delta1.abs() <= 16,
"delta {delta1} out of expected range for amp=255"
);
}
}
#[test]
fn test_dither_amplitudes_zero_strength() {
let indices = [0; MAX_SEGMENTS];
let (enabled, amps) = init_dither_amplitudes(&indices, 0);
assert!(!enabled);
assert_eq!(amps, [0; MAX_SEGMENTS]);
}
#[test]
fn test_dither_amplitudes_low_quant() {
let indices = [0, 1, 2, 3];
let (enabled, amps) = init_dither_amplitudes(&indices, 50);
assert!(enabled);
for & in &s {
assert!(amp > 0, "low quant index should produce non-zero amp");
}
}
#[test]
fn test_dither_amplitudes_high_quant() {
let indices = [100, 100, 100, 100];
let (enabled, amps) = init_dither_amplitudes(&indices, 50);
assert!(!enabled);
assert_eq!(amps, [0; MAX_SEGMENTS]);
}
#[test]
fn test_dither_8x8_modifies_pixels() {
let mut rg = VP8Random::new();
let stride = 16;
let mut buf = vec![128u8; stride * 8];
let original = buf.clone();
dither_8x8(&mut rg, &mut buf, stride, 100);
assert_ne!(buf, original, "dithering should modify pixel values");
}
#[test]
fn test_dither_8x8_stays_in_bounds() {
let mut rg = VP8Random::new();
let stride = 8;
let mut buf_low = vec![0u8; stride * 8];
dither_8x8(&mut rg, &mut buf_low, stride, 255);
let mut rg2 = VP8Random::new();
let mut buf_high = vec![255u8; stride * 8];
dither_8x8(&mut rg2, &mut buf_high, stride, 255);
}
}