#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "reproduces the C uint8_t/int16_t wrapping and clip semantics of the \
reference decoder; every AVG/DC value is provably in 0..=255 before the cast"
)]
#![allow(
clippy::many_single_char_names,
reason = "A..L and X are the canonical RFC 6386 / libwebp neighbor-sample labels; \
preserving them keeps this transcription auditable against dsp/dec.c"
)]
use crate::lossy::constants::{
B_DC_PRED, B_HD_PRED, B_HE_PRED, B_HU_PRED, B_LD_PRED, B_RD_PRED, B_TM_PRED, B_VE_PRED,
B_VL_PRED, B_VR_PRED, DC_PRED, H_PRED, TM_PRED, V_PRED,
};
use crate::lossy::work::work;
pub(crate) fn clip8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
pub(crate) const fn avg3(a: i32, b: i32, c: i32) -> u8 {
((a + 2 * b + c + 2) >> 2) as u8
}
pub(crate) const fn avg2(a: i32, b: i32) -> u8 {
((a + b + 1) >> 1) as u8
}
pub(crate) fn put(plane: &mut [u8], off: usize, stride: usize, x: usize, y: usize, v: u8) {
plane[off + x + y * stride] = v;
}
pub(crate) fn fill_square(plane: &mut [u8], off: usize, stride: usize, size: usize, v: u8) {
for j in 0..size {
let base = off + j * stride;
plane[base..base + size].fill(v);
}
}
pub(crate) fn true_motion(plane: &mut [u8], off: usize, stride: usize, size: usize) {
let top_left = i32::from(plane[off - stride - 1]);
let mut top = [0u8; 16];
top[..size].copy_from_slice(&plane[off - stride..off - stride + size]);
for y in 0..size {
let left = i32::from(plane[off + y * stride - 1]);
let base = off + y * stride;
for (out, &t) in plane[base..base + size].iter_mut().zip(&top[..size]) {
*out = clip8(i32::from(t) + left - top_left);
}
}
}
#[cfg(any(test, feature = "bench"))]
pub(crate) fn true_motion_reference(plane: &mut [u8], off: usize, stride: usize, size: usize) {
let top_left = i32::from(plane[off - stride - 1]);
for y in 0..size {
let left = i32::from(plane[off + y * stride - 1]);
let base = off + y * stride;
for x in 0..size {
let top = i32::from(plane[off - stride + x]);
plane[base + x] = clip8(top + left - top_left);
}
}
}
pub(crate) fn ve16(plane: &mut [u8], off: usize, stride: usize) {
let mut top = [0u8; 16];
top.copy_from_slice(&plane[off - stride..off - stride + 16]);
for j in 0..16 {
let base = off + j * stride;
plane[base..base + 16].copy_from_slice(&top);
}
}
pub(crate) fn he16(plane: &mut [u8], off: usize, stride: usize) {
for j in 0..16 {
let base = off + j * stride;
let v = plane[base - 1];
plane[base..base + 16].fill(v);
}
}
pub(crate) fn dc16_both(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 16i32;
for j in 0..16 {
dc += i32::from(plane[off - stride + j]) + i32::from(plane[off + j * stride - 1]);
}
(dc >> 5) as u8
}
pub(crate) fn dc16_top(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 8i32;
for &t in &plane[off - stride..off - stride + 16] {
dc += i32::from(t);
}
(dc >> 4) as u8
}
pub(crate) fn dc16_left(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 8i32;
for j in 0..16 {
dc += i32::from(plane[off + j * stride - 1]);
}
(dc >> 4) as u8
}
pub(crate) fn dc16(plane: &mut [u8], off: usize, stride: usize, has_top: bool, has_left: bool) {
let v = match (has_top, has_left) {
(true, true) => dc16_both(plane, off, stride),
(true, false) => dc16_top(plane, off, stride),
(false, true) => dc16_left(plane, off, stride),
(false, false) => 0x80,
};
fill_square(plane, off, stride, 16, v);
}
pub(crate) fn ve8(plane: &mut [u8], off: usize, stride: usize) {
let mut top = [0u8; 8];
top.copy_from_slice(&plane[off - stride..off - stride + 8]);
for j in 0..8 {
let base = off + j * stride;
plane[base..base + 8].copy_from_slice(&top);
}
}
pub(crate) fn he8(plane: &mut [u8], off: usize, stride: usize) {
for j in 0..8 {
let base = off + j * stride;
let v = plane[base - 1];
plane[base..base + 8].fill(v);
}
}
pub(crate) fn dc8_both(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 8i32;
for j in 0..8 {
dc += i32::from(plane[off - stride + j]) + i32::from(plane[off + j * stride - 1]);
}
(dc >> 4) as u8
}
pub(crate) fn dc8_top(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 4i32;
for &t in &plane[off - stride..off - stride + 8] {
dc += i32::from(t);
}
(dc >> 3) as u8
}
pub(crate) fn dc8_left(plane: &[u8], off: usize, stride: usize) -> u8 {
let mut dc = 4i32;
for j in 0..8 {
dc += i32::from(plane[off + j * stride - 1]);
}
(dc >> 3) as u8
}
pub(crate) fn dc8(plane: &mut [u8], off: usize, stride: usize, has_top: bool, has_left: bool) {
let v = match (has_top, has_left) {
(true, true) => dc8_both(plane, off, stride),
(true, false) => dc8_top(plane, off, stride),
(false, true) => dc8_left(plane, off, stride),
(false, false) => 0x80,
};
fill_square(plane, off, stride, 8, v);
}
pub(crate) fn ve4(plane: &mut [u8], off: usize, stride: usize) {
let tl = i32::from(plane[off - stride - 1]);
let t0 = i32::from(plane[off - stride]);
let t1 = i32::from(plane[off - stride + 1]);
let t2 = i32::from(plane[off - stride + 2]);
let t3 = i32::from(plane[off - stride + 3]);
let t4 = i32::from(plane[off - stride + 4]);
let vals = [
avg3(tl, t0, t1),
avg3(t0, t1, t2),
avg3(t1, t2, t3),
avg3(t2, t3, t4),
];
for j in 0..4 {
let base = off + j * stride;
plane[base..base + 4].copy_from_slice(&vals);
}
}
pub(crate) fn he4(plane: &mut [u8], off: usize, stride: usize) {
let a = i32::from(plane[off - stride - 1]);
let b = i32::from(plane[off - 1]);
let c = i32::from(plane[off + stride - 1]);
let d = i32::from(plane[off + 2 * stride - 1]);
let e = i32::from(plane[off + 3 * stride - 1]);
fill_square4_row(plane, off, avg3(a, b, c));
fill_square4_row(plane, off + stride, avg3(b, c, d));
fill_square4_row(plane, off + 2 * stride, avg3(c, d, e));
fill_square4_row(plane, off + 3 * stride, avg3(d, e, e));
}
fn fill_square4_row(plane: &mut [u8], base: usize, v: u8) {
plane[base..base + 4].fill(v);
}
pub(crate) fn dc4(plane: &mut [u8], off: usize, stride: usize) {
let mut dc = 4i32;
for j in 0..4 {
dc += i32::from(plane[off - stride + j]) + i32::from(plane[off + j * stride - 1]);
}
fill_square(plane, off, stride, 4, (dc >> 3) as u8);
}
pub(crate) fn rd4(plane: &mut [u8], off: usize, stride: usize) {
let i = i32::from(plane[off - 1]);
let j = i32::from(plane[off + stride - 1]);
let k = i32::from(plane[off + 2 * stride - 1]);
let l = i32::from(plane[off + 3 * stride - 1]);
let x = i32::from(plane[off - stride - 1]);
let a = i32::from(plane[off - stride]);
let b = i32::from(plane[off - stride + 1]);
let c = i32::from(plane[off - stride + 2]);
let d = i32::from(plane[off - stride + 3]);
put(plane, off, stride, 0, 3, avg3(j, k, l));
let v = avg3(i, j, k);
put(plane, off, stride, 1, 3, v);
put(plane, off, stride, 0, 2, v);
let v = avg3(x, i, j);
put(plane, off, stride, 2, 3, v);
put(plane, off, stride, 1, 2, v);
put(plane, off, stride, 0, 1, v);
let v = avg3(a, x, i);
put(plane, off, stride, 3, 3, v);
put(plane, off, stride, 2, 2, v);
put(plane, off, stride, 1, 1, v);
put(plane, off, stride, 0, 0, v);
let v = avg3(b, a, x);
put(plane, off, stride, 3, 2, v);
put(plane, off, stride, 2, 1, v);
put(plane, off, stride, 1, 0, v);
let v = avg3(c, b, a);
put(plane, off, stride, 3, 1, v);
put(plane, off, stride, 2, 0, v);
put(plane, off, stride, 3, 0, avg3(d, c, b));
}
pub(crate) fn ld4(plane: &mut [u8], off: usize, stride: usize) {
let a = i32::from(plane[off - stride]);
let b = i32::from(plane[off - stride + 1]);
let c = i32::from(plane[off - stride + 2]);
let d = i32::from(plane[off - stride + 3]);
let e = i32::from(plane[off - stride + 4]);
let f = i32::from(plane[off - stride + 5]);
let g = i32::from(plane[off - stride + 6]);
let h = i32::from(plane[off - stride + 7]);
put(plane, off, stride, 0, 0, avg3(a, b, c));
let v = avg3(b, c, d);
put(plane, off, stride, 1, 0, v);
put(plane, off, stride, 0, 1, v);
let v = avg3(c, d, e);
put(plane, off, stride, 2, 0, v);
put(plane, off, stride, 1, 1, v);
put(plane, off, stride, 0, 2, v);
let v = avg3(d, e, f);
put(plane, off, stride, 3, 0, v);
put(plane, off, stride, 2, 1, v);
put(plane, off, stride, 1, 2, v);
put(plane, off, stride, 0, 3, v);
let v = avg3(e, f, g);
put(plane, off, stride, 3, 1, v);
put(plane, off, stride, 2, 2, v);
put(plane, off, stride, 1, 3, v);
let v = avg3(f, g, h);
put(plane, off, stride, 3, 2, v);
put(plane, off, stride, 2, 3, v);
put(plane, off, stride, 3, 3, avg3(g, h, h));
}
pub(crate) fn vr4(plane: &mut [u8], off: usize, stride: usize) {
let i = i32::from(plane[off - 1]);
let j = i32::from(plane[off + stride - 1]);
let k = i32::from(plane[off + 2 * stride - 1]);
let x = i32::from(plane[off - stride - 1]);
let a = i32::from(plane[off - stride]);
let b = i32::from(plane[off - stride + 1]);
let c = i32::from(plane[off - stride + 2]);
let d = i32::from(plane[off - stride + 3]);
let v = avg2(x, a);
put(plane, off, stride, 0, 0, v);
put(plane, off, stride, 1, 2, v);
let v = avg2(a, b);
put(plane, off, stride, 1, 0, v);
put(plane, off, stride, 2, 2, v);
let v = avg2(b, c);
put(plane, off, stride, 2, 0, v);
put(plane, off, stride, 3, 2, v);
put(plane, off, stride, 3, 0, avg2(c, d));
put(plane, off, stride, 0, 3, avg3(k, j, i));
put(plane, off, stride, 0, 2, avg3(j, i, x));
let v = avg3(i, x, a);
put(plane, off, stride, 0, 1, v);
put(plane, off, stride, 1, 3, v);
let v = avg3(x, a, b);
put(plane, off, stride, 1, 1, v);
put(plane, off, stride, 2, 3, v);
let v = avg3(a, b, c);
put(plane, off, stride, 2, 1, v);
put(plane, off, stride, 3, 3, v);
put(plane, off, stride, 3, 1, avg3(b, c, d));
}
pub(crate) fn vl4(plane: &mut [u8], off: usize, stride: usize) {
let a = i32::from(plane[off - stride]);
let b = i32::from(plane[off - stride + 1]);
let c = i32::from(plane[off - stride + 2]);
let d = i32::from(plane[off - stride + 3]);
let e = i32::from(plane[off - stride + 4]);
let f = i32::from(plane[off - stride + 5]);
let g = i32::from(plane[off - stride + 6]);
let h = i32::from(plane[off - stride + 7]);
put(plane, off, stride, 0, 0, avg2(a, b));
let v = avg2(b, c);
put(plane, off, stride, 1, 0, v);
put(plane, off, stride, 0, 2, v);
let v = avg2(c, d);
put(plane, off, stride, 2, 0, v);
put(plane, off, stride, 1, 2, v);
let v = avg2(d, e);
put(plane, off, stride, 3, 0, v);
put(plane, off, stride, 2, 2, v);
put(plane, off, stride, 0, 1, avg3(a, b, c));
let v = avg3(b, c, d);
put(plane, off, stride, 1, 1, v);
put(plane, off, stride, 0, 3, v);
let v = avg3(c, d, e);
put(plane, off, stride, 2, 1, v);
put(plane, off, stride, 1, 3, v);
let v = avg3(d, e, f);
put(plane, off, stride, 3, 1, v);
put(plane, off, stride, 2, 3, v);
put(plane, off, stride, 3, 2, avg3(e, f, g));
put(plane, off, stride, 3, 3, avg3(f, g, h));
}
pub(crate) fn hu4(plane: &mut [u8], off: usize, stride: usize) {
let i = i32::from(plane[off - 1]);
let j = i32::from(plane[off + stride - 1]);
let k = i32::from(plane[off + 2 * stride - 1]);
let l = i32::from(plane[off + 3 * stride - 1]);
put(plane, off, stride, 0, 0, avg2(i, j));
let v = avg2(j, k);
put(plane, off, stride, 2, 0, v);
put(plane, off, stride, 0, 1, v);
let v = avg2(k, l);
put(plane, off, stride, 2, 1, v);
put(plane, off, stride, 0, 2, v);
put(plane, off, stride, 1, 0, avg3(i, j, k));
let v = avg3(j, k, l);
put(plane, off, stride, 3, 0, v);
put(plane, off, stride, 1, 1, v);
let v = avg3(k, l, l);
put(plane, off, stride, 3, 1, v);
put(plane, off, stride, 1, 2, v);
let lv = l as u8;
put(plane, off, stride, 3, 2, lv);
put(plane, off, stride, 2, 2, lv);
put(plane, off, stride, 0, 3, lv);
put(plane, off, stride, 1, 3, lv);
put(plane, off, stride, 2, 3, lv);
put(plane, off, stride, 3, 3, lv);
}
pub(crate) fn hd4(plane: &mut [u8], off: usize, stride: usize) {
let i = i32::from(plane[off - 1]);
let j = i32::from(plane[off + stride - 1]);
let k = i32::from(plane[off + 2 * stride - 1]);
let l = i32::from(plane[off + 3 * stride - 1]);
let x = i32::from(plane[off - stride - 1]);
let a = i32::from(plane[off - stride]);
let b = i32::from(plane[off - stride + 1]);
let c = i32::from(plane[off - stride + 2]);
let v = avg2(i, x);
put(plane, off, stride, 0, 0, v);
put(plane, off, stride, 2, 1, v);
let v = avg2(j, i);
put(plane, off, stride, 0, 1, v);
put(plane, off, stride, 2, 2, v);
let v = avg2(k, j);
put(plane, off, stride, 0, 2, v);
put(plane, off, stride, 2, 3, v);
put(plane, off, stride, 0, 3, avg2(l, k));
put(plane, off, stride, 3, 0, avg3(a, b, c));
put(plane, off, stride, 2, 0, avg3(x, a, b));
let v = avg3(i, x, a);
put(plane, off, stride, 1, 0, v);
put(plane, off, stride, 3, 1, v);
let v = avg3(j, i, x);
put(plane, off, stride, 1, 1, v);
put(plane, off, stride, 3, 2, v);
let v = avg3(k, j, i);
put(plane, off, stride, 1, 2, v);
put(plane, off, stride, 3, 3, v);
put(plane, off, stride, 1, 3, avg3(l, k, j));
}
pub(crate) fn predict_luma16(
plane: &mut [u8],
off: usize,
stride: usize,
mode: u8,
has_top: bool,
has_left: bool,
) {
work!(PredictLuma);
match mode {
DC_PRED => dc16(plane, off, stride, has_top, has_left),
TM_PRED => true_motion(plane, off, stride, 16),
V_PRED => ve16(plane, off, stride),
H_PRED => he16(plane, off, stride),
_ => {},
}
}
pub(crate) fn predict_chroma8(
plane: &mut [u8],
off: usize,
stride: usize,
mode: u8,
has_top: bool,
has_left: bool,
) {
work!(PredictChroma);
match mode {
DC_PRED => dc8(plane, off, stride, has_top, has_left),
TM_PRED => true_motion(plane, off, stride, 8),
V_PRED => ve8(plane, off, stride),
H_PRED => he8(plane, off, stride),
_ => {},
}
}
pub(crate) fn predict_luma4(plane: &mut [u8], off: usize, stride: usize, mode: u8) {
work!(PredictLuma);
match mode {
B_DC_PRED => dc4(plane, off, stride),
B_TM_PRED => true_motion(plane, off, stride, 4),
B_VE_PRED => ve4(plane, off, stride),
B_HE_PRED => he4(plane, off, stride),
B_RD_PRED => rd4(plane, off, stride),
B_VR_PRED => vr4(plane, off, stride),
B_LD_PRED => ld4(plane, off, stride),
B_VL_PRED => vl4(plane, off, stride),
B_HD_PRED => hd4(plane, off, stride),
B_HU_PRED => hu4(plane, off, stride),
_ => {},
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::needless_range_loop,
clippy::doc_markdown,
reason = "these KATs compare a predictor's output against an expected \
[y][x] grid, where indexing by (x, y) reads clearest, and cite \
informal RFC 6386 / libwebp neighbor labels in comments"
)]
use super::{predict_chroma8, predict_luma4, predict_luma16};
use crate::lossy::constants::{
B_DC_PRED, B_HD_PRED, B_HE_PRED, B_HU_PRED, B_LD_PRED, B_RD_PRED, B_TM_PRED, B_VE_PRED,
B_VL_PRED, B_VR_PRED, DC_PRED, H_PRED, TM_PRED, V_PRED,
};
const STRIDE: usize = 24;
const ROWS: usize = 20;
const OFF: usize = STRIDE + 1;
const TL: i32 = 27;
fn top_val(x: usize) -> u8 {
u8::try_from(10 + 8 * x).unwrap()
}
fn left_val(y: usize) -> u8 {
u8::try_from(200 - 9 * y).unwrap()
}
fn top_i(x: usize) -> i32 {
i32::from(top_val(x))
}
fn left_i(y: usize) -> i32 {
i32::from(left_val(y))
}
fn r_avg2(a: i32, b: i32) -> u8 {
u8::try_from((a + b + 1) >> 1).unwrap()
}
fn r_avg3(a: i32, b: i32, c: i32) -> u8 {
u8::try_from((a + 2 * b + c + 2) >> 2).unwrap()
}
fn r_clip(v: i32) -> u8 {
u8::try_from(v.clamp(0, 255)).unwrap()
}
fn ramp_plane() -> [u8; STRIDE * ROWS] {
let mut p = [0u8; STRIDE * ROWS];
p[OFF - STRIDE - 1] = u8::try_from(TL).unwrap();
for x in 0..20 {
p[OFF - STRIDE + x] = top_val(x);
}
for y in 0..16 {
p[OFF + y * STRIDE - 1] = left_val(y);
}
p
}
fn zero_block(plane: &mut [u8; STRIDE * ROWS], size: usize) {
for y in 0..size {
let base = OFF + y * STRIDE;
plane[base..base + size].fill(0);
}
}
fn at(p: &[u8], x: usize, y: usize) -> u8 {
p[OFF + x + y * STRIDE]
}
fn run16(mode: u8, has_top: bool, has_left: bool) -> [u8; STRIDE * ROWS] {
let mut p = ramp_plane();
zero_block(&mut p, 16);
predict_luma16(&mut p, OFF, STRIDE, mode, has_top, has_left);
p
}
fn run8(mode: u8, has_top: bool, has_left: bool) -> [u8; STRIDE * ROWS] {
let mut p = ramp_plane();
zero_block(&mut p, 8);
predict_chroma8(&mut p, OFF, STRIDE, mode, has_top, has_left);
p
}
fn run4(mode: u8) -> [u8; STRIDE * ROWS] {
let mut p = ramp_plane();
zero_block(&mut p, 4);
predict_luma4(&mut p, OFF, STRIDE, mode);
p
}
fn assert_uniform(p: &[u8], size: usize, expected: u8) {
for y in 0..size {
for x in 0..size {
assert_eq!(at(p, x, y), expected, "({x},{y})");
}
}
}
fn assert_grid4(p: &[u8], expected: [[u8; 4]; 4], name: &str) {
for y in 0..4 {
for x in 0..4 {
assert_eq!(at(p, x, y), expected[y][x], "{name} at ({x},{y})");
}
}
}
#[test]
fn luma16_vertical_copies_top_ramp() {
let p = run16(V_PRED, true, true);
for y in 0..16 {
for x in 0..16 {
assert_eq!(at(&p, x, y), top_val(x), "({x},{y})");
}
}
}
#[test]
fn luma16_horizontal_fills_left_ramp() {
let p = run16(H_PRED, true, true);
for y in 0..16 {
for x in 0..16 {
assert_eq!(at(&p, x, y), left_val(y), "({x},{y})");
}
}
}
#[test]
fn luma16_dc_both_averages_all_32() {
let p = run16(DC_PRED, true, true);
let mut sum = 16i32;
for k in 0..16 {
sum += top_i(k) + left_i(k);
}
let dc = u8::try_from(sum >> 5).unwrap();
assert_eq!(dc, 101);
assert_uniform(&p, 16, dc);
}
#[test]
fn luma16_dc_top_only_averages_16_top() {
let p = run16(DC_PRED, true, false);
let mut sum = 8i32;
for k in 0..16 {
sum += top_i(k);
}
let dc = u8::try_from(sum >> 4).unwrap();
assert_eq!(dc, 70);
assert_uniform(&p, 16, dc);
}
#[test]
fn luma16_dc_left_only_averages_16_left() {
let p = run16(DC_PRED, false, true);
let mut sum = 8i32;
for k in 0..16 {
sum += left_i(k);
}
let dc = u8::try_from(sum >> 4).unwrap();
assert_eq!(dc, 133);
assert_uniform(&p, 16, dc);
}
#[test]
fn luma16_dc_no_top_no_left_is_128() {
let p = run16(DC_PRED, false, false);
assert_uniform(&p, 16, 0x80);
}
#[test]
fn luma16_true_motion_matches_clip_formula() {
let p = run16(TM_PRED, true, true);
for y in 0..16 {
for x in 0..16 {
let e = r_clip(top_i(x) + left_i(y) - TL);
assert_eq!(at(&p, x, y), e, "({x},{y})");
}
}
assert_eq!(at(&p, 0, 0), 183);
assert_eq!(at(&p, 15, 0), 255);
assert_eq!(at(&p, 0, 15), 48);
assert_eq!(at(&p, 15, 15), 168);
}
#[test]
fn chroma8_vertical_copies_top_ramp() {
let p = run8(V_PRED, true, true);
for y in 0..8 {
for x in 0..8 {
assert_eq!(at(&p, x, y), top_val(x), "({x},{y})");
}
}
}
#[test]
fn chroma8_horizontal_fills_left_ramp() {
let p = run8(H_PRED, true, true);
for y in 0..8 {
for x in 0..8 {
assert_eq!(at(&p, x, y), left_val(y), "({x},{y})");
}
}
}
#[test]
fn chroma8_dc_both_averages_all_16() {
let p = run8(DC_PRED, true, true);
let mut sum = 8i32;
for k in 0..8 {
sum += top_i(k) + left_i(k);
}
let dc = u8::try_from(sum >> 4).unwrap();
assert_eq!(dc, 103);
assert_uniform(&p, 8, dc);
}
#[test]
fn chroma8_dc_top_only_averages_8_top() {
let p = run8(DC_PRED, true, false);
let mut sum = 4i32;
for k in 0..8 {
sum += top_i(k);
}
let dc = u8::try_from(sum >> 3).unwrap();
assert_eq!(dc, 38);
assert_uniform(&p, 8, dc);
}
#[test]
fn chroma8_dc_left_only_averages_8_left() {
let p = run8(DC_PRED, false, true);
let mut sum = 4i32;
for k in 0..8 {
sum += left_i(k);
}
let dc = u8::try_from(sum >> 3).unwrap();
assert_eq!(dc, 169);
assert_uniform(&p, 8, dc);
}
#[test]
fn chroma8_dc_no_top_no_left_is_128() {
let p = run8(DC_PRED, false, false);
assert_uniform(&p, 8, 0x80);
}
#[test]
fn chroma8_true_motion_matches_clip_formula() {
let p = run8(TM_PRED, true, true);
for y in 0..8 {
for x in 0..8 {
let e = r_clip(top_i(x) + left_i(y) - TL);
assert_eq!(at(&p, x, y), e, "({x},{y})");
}
}
assert_eq!(at(&p, 0, 0), 183);
assert_eq!(at(&p, 7, 0), 239);
assert_eq!(at(&p, 0, 7), 120);
assert_eq!(at(&p, 7, 7), 176);
}
#[test]
fn luma4_dc_averages_4_top_4_left() {
let p = run4(B_DC_PRED);
let mut sum = 4i32;
for k in 0..4 {
sum += top_i(k) + left_i(k);
}
let dc = u8::try_from(sum >> 3).unwrap();
assert_eq!(dc, 104);
assert_uniform(&p, 4, dc);
}
#[test]
fn luma4_vertical_avg3_of_top_triples() {
let p = run4(B_VE_PRED);
let col = [
r_avg3(TL, top_i(0), top_i(1)),
r_avg3(top_i(0), top_i(1), top_i(2)),
r_avg3(top_i(1), top_i(2), top_i(3)),
r_avg3(top_i(2), top_i(3), top_i(4)),
];
assert_eq!(col, [16, 18, 26, 34]);
for y in 0..4 {
for x in 0..4 {
assert_eq!(at(&p, x, y), col[x], "({x},{y})");
}
}
}
#[test]
fn luma4_horizontal_avg3_of_left_triples() {
let p = run4(B_HE_PRED);
let row = [
r_avg3(TL, left_i(0), left_i(1)),
r_avg3(left_i(0), left_i(1), left_i(2)),
r_avg3(left_i(1), left_i(2), left_i(3)),
r_avg3(left_i(2), left_i(3), left_i(3)),
];
assert_eq!(row, [155, 191, 182, 175]);
for y in 0..4 {
for x in 0..4 {
assert_eq!(at(&p, x, y), row[y], "({x},{y})");
}
}
}
#[test]
fn luma4_true_motion_matches_clip_formula() {
let p = run4(B_TM_PRED);
for y in 0..4 {
for x in 0..4 {
let e = r_clip(top_i(x) + left_i(y) - TL);
assert_eq!(at(&p, x, y), e, "({x},{y})");
}
}
assert_eq!(at(&p, 0, 0), 183);
assert_eq!(at(&p, 3, 0), 207);
assert_eq!(at(&p, 0, 3), 156);
assert_eq!(at(&p, 3, 3), 180);
}
#[test]
fn luma4_down_right_diagonal() {
let p = run4(B_RD_PRED);
let x = TL;
let a = top_i(0);
let b = top_i(1);
let c = top_i(2);
let d = top_i(3);
let i = left_i(0);
let j = left_i(1);
let k = left_i(2);
let l = left_i(3);
let expected = [
[
r_avg3(a, x, i),
r_avg3(b, a, x),
r_avg3(c, b, a),
r_avg3(d, c, b),
],
[
r_avg3(x, i, j),
r_avg3(a, x, i),
r_avg3(b, a, x),
r_avg3(c, b, a),
],
[
r_avg3(i, j, k),
r_avg3(x, i, j),
r_avg3(a, x, i),
r_avg3(b, a, x),
],
[
r_avg3(j, k, l),
r_avg3(i, j, k),
r_avg3(x, i, j),
r_avg3(a, x, i),
],
];
assert_grid4(&p, expected, "RD4");
assert_eq!(at(&p, 0, 0), 66);
assert_eq!(at(&p, 3, 0), 26);
assert_eq!(at(&p, 0, 2), 191);
assert_eq!(at(&p, 0, 3), 182);
}
#[test]
fn luma4_down_left_diagonal() {
let p = run4(B_LD_PRED);
let a = top_i(0);
let b = top_i(1);
let c = top_i(2);
let d = top_i(3);
let e = top_i(4);
let f = top_i(5);
let g = top_i(6);
let h = top_i(7);
let expected = [
[
r_avg3(a, b, c),
r_avg3(b, c, d),
r_avg3(c, d, e),
r_avg3(d, e, f),
],
[
r_avg3(b, c, d),
r_avg3(c, d, e),
r_avg3(d, e, f),
r_avg3(e, f, g),
],
[
r_avg3(c, d, e),
r_avg3(d, e, f),
r_avg3(e, f, g),
r_avg3(f, g, h),
],
[
r_avg3(d, e, f),
r_avg3(e, f, g),
r_avg3(f, g, h),
r_avg3(g, h, h),
],
];
assert_grid4(&p, expected, "LD4");
assert_eq!(at(&p, 0, 0), 18);
assert_eq!(at(&p, 3, 0), 42);
assert_eq!(at(&p, 0, 3), 42);
assert_eq!(at(&p, 3, 3), 64);
}
#[test]
fn luma4_vertical_right_diagonal() {
let p = run4(B_VR_PRED);
let x = TL;
let a = top_i(0);
let b = top_i(1);
let c = top_i(2);
let d = top_i(3);
let i = left_i(0);
let j = left_i(1);
let k = left_i(2);
let expected = [
[r_avg2(x, a), r_avg2(a, b), r_avg2(b, c), r_avg2(c, d)],
[
r_avg3(i, x, a),
r_avg3(x, a, b),
r_avg3(a, b, c),
r_avg3(b, c, d),
],
[r_avg3(j, i, x), r_avg2(x, a), r_avg2(a, b), r_avg2(b, c)],
[
r_avg3(k, j, i),
r_avg3(i, x, a),
r_avg3(x, a, b),
r_avg3(a, b, c),
],
];
assert_grid4(&p, expected, "VR4");
assert_eq!(at(&p, 0, 0), 19);
assert_eq!(at(&p, 3, 0), 30);
assert_eq!(at(&p, 0, 2), 155);
assert_eq!(at(&p, 0, 3), 191);
assert_eq!(at(&p, 1, 1), 16);
}
#[test]
fn luma4_vertical_left_diagonal() {
let p = run4(B_VL_PRED);
let a = top_i(0);
let b = top_i(1);
let c = top_i(2);
let d = top_i(3);
let e = top_i(4);
let f = top_i(5);
let g = top_i(6);
let h = top_i(7);
let expected = [
[r_avg2(a, b), r_avg2(b, c), r_avg2(c, d), r_avg2(d, e)],
[
r_avg3(a, b, c),
r_avg3(b, c, d),
r_avg3(c, d, e),
r_avg3(d, e, f),
],
[r_avg2(b, c), r_avg2(c, d), r_avg2(d, e), r_avg3(e, f, g)],
[
r_avg3(b, c, d),
r_avg3(c, d, e),
r_avg3(d, e, f),
r_avg3(f, g, h),
],
];
assert_grid4(&p, expected, "VL4");
assert_eq!(at(&p, 0, 0), 14);
assert_eq!(at(&p, 3, 0), 38);
assert_eq!(at(&p, 3, 2), 50);
assert_eq!(at(&p, 3, 3), 58);
assert_eq!(at(&p, 0, 3), 26);
}
#[test]
fn luma4_horizontal_up_diagonal() {
let p = run4(B_HU_PRED);
let i = left_i(0);
let j = left_i(1);
let k = left_i(2);
let l = left_i(3);
let l8 = u8::try_from(l).unwrap();
let expected = [
[r_avg2(i, j), r_avg3(i, j, k), r_avg2(j, k), r_avg3(j, k, l)],
[r_avg2(j, k), r_avg3(j, k, l), r_avg2(k, l), r_avg3(k, l, l)],
[r_avg2(k, l), r_avg3(k, l, l), l8, l8],
[l8, l8, l8, l8],
];
assert_grid4(&p, expected, "HU4");
assert_eq!(at(&p, 0, 0), 196);
assert_eq!(at(&p, 1, 0), 191);
assert_eq!(at(&p, 3, 0), 182);
assert_eq!(at(&p, 0, 2), 178);
assert_eq!(at(&p, 2, 2), 173);
}
#[test]
fn luma4_horizontal_down_diagonal() {
let p = run4(B_HD_PRED);
let x = TL;
let a = top_i(0);
let b = top_i(1);
let c = top_i(2);
let i = left_i(0);
let j = left_i(1);
let k = left_i(2);
let l = left_i(3);
let expected = [
[
r_avg2(i, x),
r_avg3(i, x, a),
r_avg3(x, a, b),
r_avg3(a, b, c),
],
[r_avg2(j, i), r_avg3(j, i, x), r_avg2(i, x), r_avg3(i, x, a)],
[r_avg2(k, j), r_avg3(k, j, i), r_avg2(j, i), r_avg3(j, i, x)],
[r_avg2(l, k), r_avg3(l, k, j), r_avg2(k, j), r_avg3(k, j, i)],
];
assert_grid4(&p, expected, "HD4");
assert_eq!(at(&p, 0, 0), 114);
assert_eq!(at(&p, 2, 0), 16);
assert_eq!(at(&p, 3, 0), 18);
assert_eq!(at(&p, 0, 1), 196);
assert_eq!(at(&p, 0, 3), 178);
}
#[test]
fn true_motion_reference_tracks_true_motion_off_stride_geometry() {
const STRIDE: usize = 24;
const ROWS: usize = 20;
const OFF: usize = 3 * STRIDE + 5; let base: Vec<u8> = (0..STRIDE * ROWS)
.map(|i| u8::try_from((i * 97 + 13) % 251).unwrap())
.collect();
for size in [4usize, 8, 16] {
let mut opt = base.clone();
let mut reference = base.clone();
super::true_motion(&mut opt, OFF, STRIDE, size);
super::true_motion_reference(&mut reference, OFF, STRIDE, size);
assert_eq!(opt, reference, "size {size}");
}
}
#[test]
fn dc16_both_averages_the_true_top_row_off_stride_geometry() {
const STRIDE: usize = 24;
const ROWS: usize = 24;
const OFF: usize = 3 * STRIDE + 1; let plane: Vec<u8> = (0..STRIDE * ROWS)
.map(|i| u8::try_from((i * 53 + 7) % 241).unwrap())
.collect();
let mut sum = 16i32;
for j in 0..16 {
sum += i32::from(plane[OFF - STRIDE + j]) + i32::from(plane[OFF + j * STRIDE - 1]);
}
let expected = u8::try_from(sum >> 5).unwrap();
assert_eq!(super::dc16_both(&plane, OFF, STRIDE), expected);
}
proptest::proptest! {
#[test]
fn true_motion_matches_reference(
size_sel in 0usize..3,
seed in proptest::prelude::any::<u64>(),
) {
const STRIDE: usize = 24;
const OFF: usize = STRIDE + 1;
let size = [4usize, 8, 16][size_sel];
let mut st = seed;
let mut next = || {
st = st.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
(z ^ (z >> 31)) as u8
};
let base: Vec<u8> = (0..STRIDE * 20).map(|_| next()).collect();
let mut opt = base.clone();
let mut reference = base;
super::true_motion(&mut opt, OFF, STRIDE, size);
super::true_motion_reference(&mut reference, OFF, STRIDE, size);
proptest::prop_assert_eq!(opt, reference);
}
}
}