#[cfg(test)]
mod tests;
use anyhow::{Result, anyhow, ensure};
use safefma::Fma;
use semisafe::slice::get as semisafe_get;
use semisafe::slice::get_mut as semisafe_get_mut;
use crate::{
util::{Pixel, reduce_fraction},
video::Framerate,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FrameWindow {
pub nleft: usize,
pub nright: usize,
pub time256: i32,
}
#[must_use]
#[inline]
#[cfg(test)]
pub fn output_num_frames(input_frames: usize, fa: u64, fb: u64) -> usize {
1 + (input_frames - 1) * usize::try_from(fb).expect("fps ratio fits usize")
/ usize::try_from(fa).expect("fps ratio fits usize")
}
#[inline]
pub fn resolve_target_fps(source: Framerate, num: i64, den: i64) -> Result<(u64, u64)> {
if num == 0 || den == 0 {
return Ok((source.numerator * 2, source.denominator));
}
let num = u64::try_from(num).map_err(|_| anyhow!("num must be greater than 0."))?;
let den = u64::try_from(den).map_err(|_| anyhow!("den must be greater than 0."))?;
ensure!(num > 0, "num must be greater than 0.");
ensure!(den > 0, "den must be greater than 0.");
Ok(reduce_fraction(num, den))
}
#[must_use]
#[inline]
pub fn frame_window(n: usize, fa: u64, fb: u64, off: usize) -> FrameWindow {
let nleft = (u128::from(n as u64) * u128::from(fa) / u128::from(fb)) as usize;
let scaled = (n as f64) * fa as f64 / fb as f64;
let mut time256 = (scaled - nleft as f64).fma(256.0, 0.5) as i32;
if off > 1 {
time256 /= off as i32;
}
FrameWindow {
nleft,
nright: nleft + off,
time256,
}
}
#[expect(
clippy::too_many_arguments,
reason = "shared FPS blend helper mirrors C usage"
)]
#[inline]
pub fn blend_plane<T: Pixel>(
dst: &mut [T],
src: &[T],
reference: &[T],
dst_stride: usize,
src_stride: usize,
ref_stride: usize,
width: usize,
height: usize,
time256: i32,
) {
for y in 0..height {
for x in 0..width {
let value =
(<T as num_traits::AsPrimitive<u32>>::as_(*semisafe_get(src, y * src_stride + x))
* (256 - time256) as u32
+ <T as num_traits::AsPrimitive<u32>>::as_(*semisafe_get(
reference,
y * ref_stride + x,
)) * time256 as u32)
>> 8;
*semisafe_get_mut(dst, y * dst_stride + x) = T::from_u32_or_max_value(value);
}
}
}