#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub struct MotionInterpolator;
impl MotionInterpolator {
#[must_use]
pub fn new() -> Self {
Self
}
#[must_use]
pub fn interpolate(
&self,
prev: &[u8],
next: &[u8],
flow: &[(f32, f32)],
t: f32,
w: u32,
h: u32,
) -> Vec<u8> {
interpolate(prev, next, flow, t, w, h)
}
}
impl Default for MotionInterpolator {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn interpolate(
prev: &[u8],
next: &[u8],
flow: &[(f32, f32)],
t: f32,
w: u32,
h: u32,
) -> Vec<u8> {
let n = (w as usize) * (h as usize);
if prev.len() != n || next.len() != n || flow.len() != n {
return Vec::new();
}
let t = t.clamp(0.0, 1.0);
let one_minus_t = 1.0 - t;
let w_usize = w as usize;
let h_usize = h as usize;
let mut out = vec![0u8; n];
for y in 0..h_usize {
for x in 0..w_usize {
let idx = y * w_usize + x;
let (fx, fy) = flow[idx];
let prev_x = x as f32 - t * fx;
let prev_y = y as f32 - t * fy;
let prev_val = sample_nearest(prev, w_usize, h_usize, prev_x, prev_y);
let next_x = x as f32 + one_minus_t * fx;
let next_y = y as f32 + one_minus_t * fy;
let next_val = sample_nearest(next, w_usize, h_usize, next_x, next_y);
let blended = one_minus_t * prev_val as f32 + t * next_val as f32;
out[idx] = blended.round().clamp(0.0, 255.0) as u8;
}
}
out
}
fn sample_nearest(frame: &[u8], w: usize, h: usize, fx: f32, fy: f32) -> u8 {
let xi = (fx.round() as isize).clamp(0, w as isize - 1) as usize;
let yi = (fy.round() as isize).clamp(0, h as isize - 1) as usize;
frame[yi * w + xi]
}
#[cfg(test)]
mod tests {
use super::*;
fn make_frame(w: usize, h: usize, value: u8) -> Vec<u8> {
vec![value; w * h]
}
fn zero_flow(n: usize) -> Vec<(f32, f32)> {
vec![(0.0, 0.0); n]
}
#[test]
fn test_interpolate_t0_returns_prev() {
let prev = make_frame(4, 4, 50);
let next = make_frame(4, 4, 200);
let flow = zero_flow(16);
let out = interpolate(&prev, &next, &flow, 0.0, 4, 4);
assert_eq!(out.len(), 16);
assert!(out.iter().all(|&v| v == 50), "t=0 should return prev");
}
#[test]
fn test_interpolate_t1_returns_next() {
let prev = make_frame(4, 4, 0);
let next = make_frame(4, 4, 200);
let flow = zero_flow(16);
let out = interpolate(&prev, &next, &flow, 1.0, 4, 4);
assert_eq!(out.len(), 16);
assert!(out.iter().all(|&v| v == 200), "t=1 should return next");
}
#[test]
fn test_interpolate_midpoint() {
let prev = make_frame(4, 4, 100);
let next = make_frame(4, 4, 200);
let flow = zero_flow(16);
let out = interpolate(&prev, &next, &flow, 0.5, 4, 4);
for &v in &out {
assert!(
(v as i32 - 150).abs() <= 1,
"midpoint blend should be ~150, got {v}"
);
}
}
#[test]
fn test_interpolate_wrong_size_returns_empty() {
let prev = vec![0u8; 10];
let next = vec![0u8; 16]; let flow = zero_flow(16);
let out = interpolate(&prev, &next, &flow, 0.5, 4, 4);
assert!(out.is_empty());
}
#[test]
fn test_motion_interpolator_struct() {
let interp = MotionInterpolator::new();
let prev = make_frame(4, 4, 60);
let next = make_frame(4, 4, 120);
let flow = zero_flow(16);
let out = interp.interpolate(&prev, &next, &flow, 0.5, 4, 4);
assert_eq!(out.len(), 16);
}
#[test]
fn test_interpolate_with_flow_no_panic() {
let w = 8usize;
let h = 8usize;
let prev: Vec<u8> = (0..w * h).map(|i| (i % 256) as u8).collect();
let next: Vec<u8> = (0..w * h).map(|i| ((i + 128) % 256) as u8).collect();
let flow: Vec<(f32, f32)> = (0..w * h)
.map(|i| (((i % 5) as f32) - 2.0, ((i % 3) as f32) - 1.0))
.collect();
let out = interpolate(&prev, &next, &flow, 0.3, w as u32, h as u32);
assert_eq!(out.len(), w * h);
}
}