use crate::dpb::DecodedPicture;
#[inline]
fn ref_luma(pic: &DecodedPicture, x: i32, y: i32) -> i32 {
let cx = x.clamp(0, pic.width as i32 - 1) as usize;
let cy = y.clamp(0, pic.height as i32 - 1) as usize;
pic.y[cy * pic.width as usize + cx] as i32
}
#[inline]
fn ref_chroma(plane: &[u8], width: usize, height: usize, x: i32, y: i32) -> i32 {
let cx = x.clamp(0, width as i32 - 1) as usize;
let cy = y.clamp(0, height as i32 - 1) as usize;
plane[cy * width + cx] as i32
}
#[inline]
fn clip_u8(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
#[inline]
fn avg(a: u8, b: u8) -> u8 {
((a as u16 + b as u16 + 1) >> 1) as u8
}
fn half_pel_h(pic: &DecodedPicture, x: i32, y: i32) -> u8 {
let s = |dx: i32| ref_luma(pic, x + dx, y);
clip_u8((s(-2) - 5 * s(-1) + 20 * s(0) + 20 * s(1) - 5 * s(2) + s(3) + 16) >> 5)
}
fn half_pel_v(pic: &DecodedPicture, x: i32, y: i32) -> u8 {
let s = |dy: i32| ref_luma(pic, x, y + dy);
clip_u8((s(-2) - 5 * s(-1) + 20 * s(0) + 20 * s(1) - 5 * s(2) + s(3) + 16) >> 5)
}
fn half_pel_hv(pic: &DecodedPicture, x: i32, y: i32) -> u8 {
let mut h = [0i32; 6];
for (i, dy) in (-2..=3).enumerate() {
let s = |dx: i32| ref_luma(pic, x + dx, y + dy);
h[i] = s(-2) - 5 * s(-1) + 20 * s(0) + 20 * s(1) - 5 * s(2) + s(3);
}
let val = h[0] - 5 * h[1] + 20 * h[2] + 20 * h[3] - 5 * h[4] + h[5];
clip_u8((val + 512) >> 10)
}
fn luma_interp(pic: &DecodedPicture, x: i32, y: i32, frac_x: i32, frac_y: i32) -> u8 {
match (frac_x, frac_y) {
(0, 0) => ref_luma(pic, x, y) as u8,
(2, 0) => half_pel_h(pic, x, y),
(0, 2) => half_pel_v(pic, x, y),
(2, 2) => half_pel_hv(pic, x, y),
(1, 0) => avg(ref_luma(pic, x, y) as u8, half_pel_h(pic, x, y)),
(3, 0) => avg(half_pel_h(pic, x, y), ref_luma(pic, x + 1, y) as u8),
(0, 1) => avg(ref_luma(pic, x, y) as u8, half_pel_v(pic, x, y)),
(0, 3) => avg(half_pel_v(pic, x, y), ref_luma(pic, x, y + 1) as u8),
(2, 1) => avg(half_pel_h(pic, x, y), half_pel_hv(pic, x, y)),
(2, 3) => avg(half_pel_hv(pic, x, y), half_pel_h(pic, x, y + 1)),
(1, 2) => avg(half_pel_v(pic, x, y), half_pel_hv(pic, x, y)),
(3, 2) => avg(half_pel_hv(pic, x, y), half_pel_v(pic, x + 1, y)),
(1, 1) => avg(half_pel_h(pic, x, y), half_pel_v(pic, x, y)),
(3, 1) => avg(half_pel_h(pic, x, y), half_pel_v(pic, x + 1, y)),
(1, 3) => avg(half_pel_v(pic, x, y), half_pel_h(pic, x, y + 1)),
(3, 3) => avg(half_pel_v(pic, x + 1, y), half_pel_h(pic, x, y + 1)),
_ => unreachable!(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn luma_mc(
ref_pic: &DecodedPicture,
x: i32,
y: i32,
dx: i32,
dy: i32,
block_w: usize,
block_h: usize,
output: &mut [u8],
) {
let frac_x = dx.rem_euclid(4);
let frac_y = dy.rem_euclid(4);
let x_int = x + (dx >> 2);
let y_int = y + (dy >> 2);
if frac_x == 0 && frac_y == 0 {
let w = ref_pic.width as i32;
let h = ref_pic.height as i32;
if x_int >= 0
&& y_int >= 0
&& x_int + block_w as i32 <= w
&& y_int + block_h as i32 <= h
{
let stride = w as usize;
let mut src_off = y_int as usize * stride + x_int as usize;
for row in 0..block_h {
output[row * block_w..(row + 1) * block_w]
.copy_from_slice(&ref_pic.y[src_off..src_off + block_w]);
src_off += stride;
}
} else {
for row in 0..block_h {
for col in 0..block_w {
output[row * block_w + col] =
ref_luma(ref_pic, x_int + col as i32, y_int + row as i32) as u8;
}
}
}
return;
}
for row in 0..block_h {
for col in 0..block_w {
output[row * block_w + col] = luma_interp(
ref_pic,
x_int + col as i32,
y_int + row as i32,
frac_x,
frac_y,
);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn chroma_mc(
ref_plane: &[u8],
ref_width: usize,
ref_height: usize,
x: i32,
y: i32,
dx: i32,
dy: i32,
block_w: usize,
block_h: usize,
output: &mut [u8],
) {
let frac_x = dx.rem_euclid(8);
let frac_y = dy.rem_euclid(8);
let x_int = x + (dx >> 3);
let y_int = y + (dy >> 3);
if frac_x == 0 && frac_y == 0 {
let w = ref_width as i32;
let h = ref_height as i32;
if x_int >= 0
&& y_int >= 0
&& x_int + block_w as i32 <= w
&& y_int + block_h as i32 <= h
{
let mut src_off = y_int as usize * ref_width + x_int as usize;
for row in 0..block_h {
output[row * block_w..(row + 1) * block_w]
.copy_from_slice(&ref_plane[src_off..src_off + block_w]);
src_off += ref_width;
}
} else {
for row in 0..block_h {
for col in 0..block_w {
output[row * block_w + col] = ref_chroma(
ref_plane,
ref_width,
ref_height,
x_int + col as i32,
y_int + row as i32,
) as u8;
}
}
}
return;
}
for row in 0..block_h {
for col in 0..block_w {
let xf = x_int + col as i32;
let yf = y_int + row as i32;
let a = ref_chroma(ref_plane, ref_width, ref_height, xf, yf);
let b = ref_chroma(ref_plane, ref_width, ref_height, xf + 1, yf);
let c = ref_chroma(ref_plane, ref_width, ref_height, xf, yf + 1);
let d = ref_chroma(ref_plane, ref_width, ref_height, xf + 1, yf + 1);
let val = (8 - frac_x) * (8 - frac_y) * a
+ frac_x * (8 - frac_y) * b
+ (8 - frac_x) * frac_y * c
+ frac_x * frac_y * d;
output[row * block_w + col] = ((val + 32) >> 6) as u8;
}
}
}
pub fn bi_pred_avg(pred_l0: &[u8], pred_l1: &[u8], output: &mut [u8]) {
for (o, (&a, &b)) in output.iter_mut().zip(pred_l0.iter().zip(pred_l1.iter())) {
*o = ((a as u16 + b as u16 + 1) >> 1) as u8;
}
}
pub fn weighted_uni(pred: &mut [u8], log2_denom: u32, weight: i32, offset: i32) {
if log2_denom == 0 {
for p in pred.iter_mut() {
*p = ((*p as i32 * weight + offset).clamp(0, 255)) as u8;
}
} else {
let round = 1i32 << (log2_denom - 1);
for p in pred.iter_mut() {
*p = ((*p as i32 * weight + round) >> log2_denom)
.wrapping_add(offset)
.clamp(0, 255) as u8;
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn weighted_bi(
pred_l0: &[u8],
pred_l1: &[u8],
output: &mut [u8],
log2_denom: u32,
w0: i32,
o0: i32,
w1: i32,
o1: i32,
) {
let round = 1i32 << log2_denom;
let offset = (o0 + o1 + 1) >> 1;
let shift = log2_denom + 1;
for (o, (&a, &b)) in output.iter_mut().zip(pred_l0.iter().zip(pred_l1.iter())) {
*o = ((a as i32 * w0 + b as i32 * w1 + round) >> shift)
.wrapping_add(offset)
.clamp(0, 255) as u8;
}
}
pub fn weighted_bi_implicit(pred_l0: &[u8], pred_l1: &[u8], output: &mut [u8], w0: i32, w1: i32) {
let round = 1i32 << 5; for (o, (&a, &b)) in output.iter_mut().zip(pred_l0.iter().zip(pred_l1.iter())) {
*o = ((a as i32 * w0 + b as i32 * w1 + round) >> 6).clamp(0, 255) as u8;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::rc::Rc;
fn make_ref_pic(width: u32, height: u32, y_data: Vec<u8>) -> Rc<DecodedPicture> {
let uv_size = (width / 2 * height / 2) as usize;
Rc::new(DecodedPicture {
y: y_data,
u: vec![128; uv_size],
v: vec![128; uv_size],
width,
height,
frame_num: 0,
pic_order_cnt: 0,
mv_l0: vec![],
ref_idx_l0: vec![],
ref_poc_l0: vec![],
mv_l1: vec![],
ref_idx_l1: vec![],
mb_width: width / 16,
is_intra: false,
})
}
#[test]
fn test_integer_pel_copy() {
let mut y = vec![0u8; 64];
for r in 0..8 {
for c in 0..8 {
y[r * 8 + c] = (r * 16 + c * 4) as u8;
}
}
let pic = make_ref_pic(8, 8, y.clone());
let mut out = vec![0u8; 16]; luma_mc(&pic, 1, 2, 0, 0, 4, 4, &mut out);
for r in 0..4 {
for c in 0..4 {
assert_eq!(out[r * 4 + c], y[(r + 2) * 8 + (c + 1)]);
}
}
}
#[test]
fn test_integer_pel_with_mv() {
let y = vec![42u8; 64];
let pic = make_ref_pic(8, 8, y);
let mut out = vec![0u8; 16];
luma_mc(&pic, 0, 0, 4, 8, 4, 4, &mut out);
assert!(out.iter().all(|&v| v == 42));
}
#[test]
fn test_half_pel_horizontal() {
let mut y = vec![128u8; 16];
for c in 0..8 {
y[c] = 0;
}
for c in 8..16 {
y[c] = 255;
}
let pic = make_ref_pic(16, 1, y);
let mut out = [0u8; 1];
luma_mc(&pic, 7, 0, 2, 0, 1, 1, &mut out);
assert_eq!(out[0], 128);
}
#[test]
fn test_half_pel_vertical() {
let mut y = vec![0u8; 16];
for r in 8..16 {
y[r] = 255;
}
let pic = make_ref_pic(1, 16, y);
let mut out = [0u8; 1];
luma_mc(&pic, 0, 7, 0, 2, 1, 1, &mut out);
assert_eq!(out[0], 128);
}
#[test]
fn test_uniform_ref_all_frac_positions() {
let y = vec![100u8; 256];
let pic = make_ref_pic(16, 16, y);
for frac_x in 0..4 {
for frac_y in 0..4 {
let mut out = [0u8; 1];
luma_mc(&pic, 4, 4, frac_x, frac_y, 1, 1, &mut out);
assert_eq!(
out[0], 100,
"frac ({},{}) should give 100 for uniform ref",
frac_x, frac_y
);
}
}
}
#[test]
fn test_chroma_mc_integer() {
let plane = vec![200u8; 64]; let mut out = vec![0u8; 16]; chroma_mc(&plane, 8, 8, 0, 0, 0, 0, 4, 4, &mut out);
assert!(out.iter().all(|&v| v == 200));
}
#[test]
fn test_chroma_mc_half_pel() {
let plane = vec![0, 255, 0, 255];
let mut out = [0u8; 1];
chroma_mc(&plane, 4, 1, 0, 0, 4, 0, 1, 1, &mut out);
assert_eq!(out[0], 128);
}
#[test]
fn test_boundary_clipping() {
let y: Vec<u8> = (0..16).collect();
let pic = make_ref_pic(4, 4, y);
let mut out = [0u8; 1];
luma_mc(&pic, 0, 0, -4, -4, 1, 1, &mut out);
assert_eq!(out[0], 0); }
#[test]
fn test_negative_mv_fractional() {
let y = vec![128u8; 256];
let pic = make_ref_pic(16, 16, y);
let mut out = [0u8; 1];
luma_mc(&pic, 8, 8, -1, -1, 1, 1, &mut out);
assert_eq!(out[0], 128);
}
}