Skip to main content

zoomvtools/filters/
flow_fps.rs

1#[cfg(test)]
2mod tests;
3
4use anyhow::{Result, ensure};
5
6use crate::{
7    analysis::MVAnalysisData,
8    fake::group_of_planes::FakeGroupOfPlanes,
9    frame::{FramePlanesMut, FrameView, PlaneSizeTuple},
10    video::VideoInfo,
11};
12
13use super::flow_inter::{FlowInterExtraVectors, FlowInterpolationBase, FlowInterpolationMode};
14
15/// Options used to construct [`FlowFPS`].
16#[derive(Debug, Clone, Copy)]
17pub struct FlowFPSOptions {
18    /// Mask mode in the MVTools-compatible `0..=2` range.
19    pub mask_mode: u8,
20    /// Motion-length scaling factor used for occlusion masks.
21    pub ml: f64,
22}
23
24/// Frame-rate conversion filter built on motion interpolation.
25pub struct FlowFPS {
26    base: FlowInterpolationBase,
27    mask_mode: u8,
28}
29
30impl FlowFPS {
31    /// Builds a motion-interpolated FPS filter from validated clip and vector metadata.
32    ///
33    /// # Errors
34    /// Returns an error if the clip format, vector metadata, or options are invalid.
35    #[inline]
36    pub fn new(
37        info: VideoInfo,
38        backward_data: MVAnalysisData,
39        forward_data: MVAnalysisData,
40        options: FlowFPSOptions,
41    ) -> Result<Self> {
42        ensure!(
43            (0..=2).contains(&options.mask_mode),
44            "FlowFPS: mask must be 0, 1, or 2."
45        );
46        ensure!(options.ml > 0.0, "FlowFPS: ml must be greater than 0.");
47
48        Ok(Self {
49            base: FlowInterpolationBase::new(
50                info,
51                backward_data,
52                forward_data,
53                options.ml,
54                "FlowFPS",
55            )?,
56            mask_mode: options.mask_mode,
57        })
58    }
59
60    /// Renders one interpolated frame into `output`.
61    ///
62    /// `time256` uses the MVTools 8.8 fixed-point time domain.
63    ///
64    /// # Errors
65    /// Returns an error if the frames, vectors, optional extra vectors, or output buffers do not
66    /// match the filter configuration.
67    #[inline]
68    pub fn render_frame<T: crate::util::Pixel>(
69        &self,
70        backward: &FrameView<'_, T>,
71        forward: &FrameView<'_, T>,
72        output: &mut FramePlanesMut<'_, T>,
73        output_pitch: PlaneSizeTuple,
74        backward_vectors: &FakeGroupOfPlanes,
75        forward_vectors: &FakeGroupOfPlanes,
76        extra_vectors: Option<FlowInterExtraVectors<'_>>,
77        time256: i32,
78    ) -> Result<()> {
79        ensure!(
80            self.mask_mode == 2 || extra_vectors.is_none(),
81            "FlowFPS: extra vectors require mask mode 2."
82        );
83
84        let mode = match (self.mask_mode, extra_vectors.is_some()) {
85            (0, _) => FlowInterpolationMode::Simple,
86            (1, _) => FlowInterpolationMode::Regular,
87            (2, true) => FlowInterpolationMode::Extra,
88            (2, false) => FlowInterpolationMode::Simple,
89            _ => unreachable!("mask_mode validated in constructor"),
90        };
91
92        self.base.render_frame(
93            mode,
94            time256,
95            backward,
96            forward,
97            output,
98            output_pitch,
99            backward_vectors,
100            forward_vectors,
101            extra_vectors,
102        )
103    }
104
105    /// Linearly blends `current` and `future` using the supplied 8.8 fixed-point time.
106    ///
107    /// # Errors
108    /// Returns an error if the frames or output buffers do not match the filter configuration.
109    #[inline]
110    pub fn blend_frame<T: crate::util::Pixel>(
111        &self,
112        current: &FrameView<'_, T>,
113        future: &FrameView<'_, T>,
114        output: &mut FramePlanesMut<'_, T>,
115        output_pitch: PlaneSizeTuple,
116        time256: i32,
117    ) -> Result<()> {
118        self.base
119            .blend_frame(time256, current, future, output, output_pitch)
120    }
121}