#![allow(dead_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::too_many_arguments)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MotionVector {
pub x: i16,
pub y: i16,
}
impl MotionVector {
#[must_use]
pub const fn new(x: i16, y: i16) -> Self {
Self { x, y }
}
#[must_use]
pub const fn x_int(self) -> i32 {
(self.x >> 2) as i32
}
#[must_use]
pub const fn y_int(self) -> i32 {
(self.y >> 2) as i32
}
#[must_use]
pub const fn x_frac(self) -> u8 {
(self.x & 3) as u8
}
#[must_use]
pub const fn y_frac(self) -> u8 {
(self.y & 3) as u8
}
#[must_use]
pub const fn is_integer(self) -> bool {
self.x & 3 == 0 && self.y & 3 == 0
}
}
const SUBPEL_FILTERS: [[i32; 6]; 8] = [
[0, 0, 128, 0, 0, 0], [0, -6, 123, 12, -1, 0], [2, -11, 108, 36, -8, 1], [0, -9, 93, 50, -6, 0], [3, -16, 77, 77, -16, 3], [0, -6, 50, 93, -9, 0], [1, -8, 36, 108, -11, 2], [0, -1, 12, 123, -6, 0], ];
#[allow(clippy::similar_names)]
pub fn motion_compensate(
dst: &mut [u8],
dst_stride: usize,
ref_frame: &[u8],
ref_stride: usize,
mv: MotionVector,
block_w: usize,
block_h: usize,
ref_x: usize,
ref_y: usize,
) {
let x_int = mv.x_int();
let y_int = mv.y_int();
let x_frac = mv.x_frac();
let y_frac = mv.y_frac();
let ref_x = (ref_x as i32 + x_int) as usize;
let ref_y = (ref_y as i32 + y_int) as usize;
if x_frac == 0 && y_frac == 0 {
copy_block(
dst, dst_stride, ref_frame, ref_stride, ref_x, ref_y, block_w, block_h,
);
} else if y_frac == 0 {
filter_horizontal(
dst, dst_stride, ref_frame, ref_stride, ref_x, ref_y, block_w, block_h, x_frac,
);
} else if x_frac == 0 {
filter_vertical(
dst, dst_stride, ref_frame, ref_stride, ref_x, ref_y, block_w, block_h, y_frac,
);
} else {
filter_2d(
dst, dst_stride, ref_frame, ref_stride, ref_x, ref_y, block_w, block_h, x_frac, y_frac,
);
}
}
fn copy_block(
dst: &mut [u8],
dst_stride: usize,
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
) {
for row in 0..height {
let dst_offset = row * dst_stride;
let src_offset = (src_y + row) * src_stride + src_x;
if src_offset + width <= src.len() && dst_offset + width <= dst.len() {
dst[dst_offset..dst_offset + width]
.copy_from_slice(&src[src_offset..src_offset + width]);
}
}
}
fn filter_horizontal(
dst: &mut [u8],
dst_stride: usize,
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
frac: u8,
) {
let filter = &SUBPEL_FILTERS[frac as usize];
for row in 0..height {
let dst_offset = row * dst_stride;
let src_offset = (src_y + row) * src_stride + src_x;
for col in 0..width {
let mut sum = 0i32;
for (i, &tap) in filter.iter().enumerate() {
let idx = src_offset + col + i;
if idx < 2 || idx + 3 >= src.len() {
continue;
}
sum += tap * i32::from(src[idx - 2 + i]);
}
let pixel = ((sum + 64) >> 7).clamp(0, 255) as u8;
if dst_offset + col < dst.len() {
dst[dst_offset + col] = pixel;
}
}
}
}
fn filter_vertical(
dst: &mut [u8],
dst_stride: usize,
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
frac: u8,
) {
let filter = &SUBPEL_FILTERS[frac as usize];
for row in 0..height {
let dst_offset = row * dst_stride;
for col in 0..width {
let mut sum = 0i32;
for (i, &tap) in filter.iter().enumerate() {
let src_row = src_y + row + i;
if src_row < 2 || src_row + 3 >= src.len() / src_stride {
continue;
}
let idx = (src_row - 2 + i) * src_stride + src_x + col;
if idx < src.len() {
sum += tap * i32::from(src[idx]);
}
}
let pixel = ((sum + 64) >> 7).clamp(0, 255) as u8;
if dst_offset + col < dst.len() {
dst[dst_offset + col] = pixel;
}
}
}
}
#[allow(clippy::similar_names)]
fn filter_2d(
dst: &mut [u8],
dst_stride: usize,
src: &[u8],
src_stride: usize,
src_x: usize,
src_y: usize,
width: usize,
height: usize,
x_frac: u8,
y_frac: u8,
) {
let mut temp = vec![0u8; (height + 5) * width];
filter_horizontal(
&mut temp,
width,
src,
src_stride,
src_x,
src_y.saturating_sub(2),
width,
height + 5,
x_frac,
);
filter_vertical(dst, dst_stride, &temp, width, 0, 2, width, height, y_frac);
}
#[must_use]
pub fn clamp_mv(
mv: MotionVector,
x: usize,
y: usize,
width: usize,
height: usize,
frame_w: usize,
frame_h: usize,
) -> MotionVector {
let x = x as i32;
let y = y as i32;
let width = width as i32;
let height = height as i32;
let frame_w = frame_w as i32;
let frame_h = frame_h as i32;
let ref_x = x + mv.x_int();
let ref_y = y + mv.y_int();
let clamped_x = ref_x.clamp(0, frame_w - width);
let clamped_y = ref_y.clamp(0, frame_h - height);
let new_x = ((clamped_x - x) << 2) as i16 + (mv.x & 3) as i16;
let new_y = ((clamped_y - y) << 2) as i16 + (mv.y & 3) as i16;
MotionVector::new(new_x, new_y)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_motion_vector() {
let mv = MotionVector::new(10, 6);
assert_eq!(mv.x_int(), 2);
assert_eq!(mv.y_int(), 1);
assert_eq!(mv.x_frac(), 2);
assert_eq!(mv.y_frac(), 2);
assert!(!mv.is_integer());
let int_mv = MotionVector::new(8, 12); assert_eq!(int_mv.x_int(), 2);
assert_eq!(int_mv.y_int(), 3);
assert!(int_mv.is_integer());
}
#[test]
fn test_subpel_filters() {
assert_eq!(SUBPEL_FILTERS[0], [0, 0, 128, 0, 0, 0]);
assert_eq!(SUBPEL_FILTERS[4], [3, -16, 77, 77, -16, 3]);
for filter in &SUBPEL_FILTERS {
let sum: i32 = filter.iter().sum();
assert!((sum - 128).abs() <= 2);
}
}
#[test]
fn test_copy_block() {
let src = vec![100u8; 64]; let mut dst = vec![0u8; 64];
copy_block(&mut dst, 8, &src, 8, 0, 0, 4, 4);
for row in 0..4 {
for col in 0..4 {
assert_eq!(dst[row * 8 + col], 100);
}
}
}
#[test]
fn test_motion_compensate_integer() {
let ref_frame = vec![50u8; 256]; let mut dst = vec![0u8; 64];
let mv = MotionVector::new(0, 0);
motion_compensate(&mut dst, 8, &ref_frame, 16, mv, 8, 8, 0, 0);
assert!(dst.iter().all(|&p| p == 50));
}
#[test]
fn test_clamp_mv() {
let mv = MotionVector::new(100, 100);
let clamped = clamp_mv(mv, 10, 10, 4, 4, 32, 32);
let ref_x = 10 + clamped.x_int();
let ref_y = 10 + clamped.y_int();
assert!(ref_x >= 0);
assert!(ref_y >= 0);
assert!(ref_x + 4 <= 32);
assert!(ref_y + 4 <= 32);
}
#[test]
fn test_clamp_mv_negative() {
let mv = MotionVector::new(-100, -100);
let clamped = clamp_mv(mv, 10, 10, 4, 4, 32, 32);
let ref_x = 10 + clamped.x_int();
let ref_y = 10 + clamped.y_int();
assert!(ref_x >= 0);
assert!(ref_y >= 0);
}
#[test]
fn test_filter_horizontal() {
let src = vec![100u8; 256];
let mut dst = vec![0u8; 64];
filter_horizontal(&mut dst, 8, &src, 16, 0, 0, 8, 8, 2);
assert!(dst.iter().any(|&p| p > 0));
}
#[test]
fn test_filter_vertical() {
let src = vec![100u8; 256];
let mut dst = vec![0u8; 64];
filter_vertical(&mut dst, 8, &src, 16, 0, 0, 8, 8, 2);
assert!(dst.iter().any(|&p| p > 0));
}
#[test]
fn test_filter_2d() {
let src = vec![100u8; 256];
let mut dst = vec![0u8; 64];
filter_2d(&mut dst, 8, &src, 16, 0, 0, 8, 8, 2, 2);
assert!(dst.iter().any(|&p| p > 0));
}
#[test]
fn test_motion_vector_zero() {
let mv = MotionVector::new(0, 0);
assert_eq!(mv.x_int(), 0);
assert_eq!(mv.y_int(), 0);
assert_eq!(mv.x_frac(), 0);
assert_eq!(mv.y_frac(), 0);
assert!(mv.is_integer());
}
}