pub mod artifact;
pub mod blend;
pub mod occlusion;
pub mod optical_flow;
pub mod quality;
pub mod warp;
use crate::error::{CvError, CvResult};
use oximedia_codec::VideoFrame;
pub use artifact::{ArtifactReducer, ArtifactReductionConfig, ArtifactType};
pub use blend::{BlendMode, Blender};
pub use occlusion::{OcclusionDetector, OcclusionMap};
pub use optical_flow::{FlowEstimator, FlowMethod, FlowPyramid};
pub use quality::{AdaptiveParameterTuner, InterpolationQualityMetrics, QualityAssessor};
pub use warp::{WarpMode, Warper};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InterpolationQuality {
Fast,
#[default]
Balanced,
HighQuality,
}
#[derive(Debug, Clone, Copy)]
pub struct FrameRateConfig {
pub source_fps: f64,
pub target_fps: f64,
}
impl FrameRateConfig {
#[must_use]
pub const fn new(source_fps: f64, target_fps: f64) -> Self {
Self {
source_fps,
target_fps,
}
}
#[must_use]
pub fn ratio(&self) -> f64 {
self.target_fps / self.source_fps
}
}
#[derive(Debug, Clone)]
pub struct InterpolatorConfig {
pub quality: InterpolationQuality,
pub detect_occlusions: bool,
pub reduce_artifacts: bool,
pub use_pyramid: bool,
pub pyramid_levels: u32,
pub block_size: u32,
pub search_range: i32,
pub blend_mode: BlendMode,
}
impl Default for InterpolatorConfig {
fn default() -> Self {
Self {
quality: InterpolationQuality::Balanced,
detect_occlusions: true,
reduce_artifacts: true,
use_pyramid: true,
pyramid_levels: 3,
block_size: 16,
search_range: 16,
blend_mode: BlendMode::Adaptive,
}
}
}
impl InterpolatorConfig {
#[must_use]
pub fn for_quality(quality: InterpolationQuality) -> Self {
match quality {
InterpolationQuality::Fast => Self {
quality,
detect_occlusions: false,
reduce_artifacts: false,
use_pyramid: false,
blend_mode: BlendMode::Linear,
..Self::default()
},
InterpolationQuality::Balanced => Self {
quality,
detect_occlusions: true,
reduce_artifacts: true,
use_pyramid: true,
pyramid_levels: 2,
blend_mode: BlendMode::Adaptive,
..Self::default()
},
InterpolationQuality::HighQuality => Self {
quality,
detect_occlusions: true,
reduce_artifacts: true,
use_pyramid: true,
pyramid_levels: 3,
blend_mode: BlendMode::MotionWeighted,
..Self::default()
},
}
}
}
pub struct FrameInterpolator {
config: InterpolatorConfig,
flow_estimator: FlowEstimator,
warper: Warper,
blender: Blender,
occlusion_detector: OcclusionDetector,
}
impl FrameInterpolator {
#[must_use]
pub fn new(quality: InterpolationQuality) -> Self {
let config = InterpolatorConfig::for_quality(quality);
Self::with_config(config)
}
#[must_use]
pub fn with_config(config: InterpolatorConfig) -> Self {
let flow_method = match config.quality {
InterpolationQuality::Fast => FlowMethod::BlockMatching,
InterpolationQuality::Balanced => FlowMethod::LucasKanade,
InterpolationQuality::HighQuality => FlowMethod::Farneback,
};
let mut flow_estimator = FlowEstimator::new(flow_method);
if config.use_pyramid {
flow_estimator = flow_estimator.with_pyramid_levels(config.pyramid_levels);
}
if config.quality == InterpolationQuality::Fast {
flow_estimator = flow_estimator
.with_block_size(config.block_size)
.with_search_range(config.search_range);
}
let warper = Warper::new(WarpMode::Bilinear);
let blender = Blender::new(config.blend_mode);
let occlusion_detector = OcclusionDetector::new();
Self {
config,
flow_estimator,
warper,
blender,
occlusion_detector,
}
}
pub fn interpolate_between(
&mut self,
frame1: &VideoFrame,
frame2: &VideoFrame,
t: f32,
) -> CvResult<VideoFrame> {
self.validate_frames(frame1, frame2)?;
self.validate_time_parameter(t)?;
if t <= 0.0 {
return Ok(frame1.clone());
}
if t >= 1.0 {
return Ok(frame2.clone());
}
let (flow_forward, flow_backward) =
self.flow_estimator.estimate_bidirectional(frame1, frame2)?;
let occlusion_map = if self.config.detect_occlusions {
Some(self.occlusion_detector.detect(
&flow_forward,
&flow_backward,
frame1.width,
frame1.height,
)?)
} else {
None
};
let warped_from_1 = self.warper.warp_forward(frame1, &flow_forward, t)?;
let warped_from_2 = self.warper.warp_backward(frame2, &flow_backward, 1.0 - t)?;
let result = self.blender.blend(
&warped_from_1,
&warped_from_2,
t,
&flow_forward,
&flow_backward,
occlusion_map.as_ref(),
)?;
if self.config.reduce_artifacts {
self.reduce_artifacts(&result)
} else {
Ok(result)
}
}
pub fn interpolate_for_rate_conversion(
&mut self,
frame1: &VideoFrame,
frame2: &VideoFrame,
config: &FrameRateConfig,
) -> CvResult<Vec<VideoFrame>> {
let ratio = config.ratio();
let num_frames = ratio.floor() as usize;
if num_frames <= 1 {
return Ok(vec![frame1.clone()]);
}
let mut result = Vec::with_capacity(num_frames);
result.push(frame1.clone());
for i in 1..num_frames {
let t = i as f32 / num_frames as f32;
let interpolated = self.interpolate_between(frame1, frame2, t)?;
result.push(interpolated);
}
Ok(result)
}
#[must_use]
pub const fn config(&self) -> &InterpolatorConfig {
&self.config
}
pub fn set_quality(&mut self, quality: InterpolationQuality) {
self.config.quality = quality;
let new_config = InterpolatorConfig::for_quality(quality);
*self = Self::with_config(new_config);
}
pub fn set_occlusion_detection(&mut self, enabled: bool) {
self.config.detect_occlusions = enabled;
}
pub fn set_artifact_reduction(&mut self, enabled: bool) {
self.config.reduce_artifacts = enabled;
}
pub fn set_blend_mode(&mut self, mode: BlendMode) {
self.config.blend_mode = mode;
self.blender = Blender::new(mode);
}
fn validate_frames(&self, frame1: &VideoFrame, frame2: &VideoFrame) -> CvResult<()> {
if frame1.width != frame2.width || frame1.height != frame2.height {
return Err(CvError::invalid_dimensions(frame1.width, frame1.height));
}
if frame1.format != frame2.format {
return Err(CvError::unsupported_format(format!(
"Frame format mismatch: {:?} vs {:?}",
frame1.format, frame2.format
)));
}
if frame1.width == 0 || frame1.height == 0 {
return Err(CvError::invalid_dimensions(frame1.width, frame1.height));
}
Ok(())
}
fn validate_time_parameter(&self, t: f32) -> CvResult<()> {
if !(0.0..=1.0).contains(&t) {
return Err(CvError::invalid_parameter(
"t",
format!("{t} (must be in range [0.0, 1.0])"),
));
}
Ok(())
}
fn reduce_artifacts(&self, frame: &VideoFrame) -> CvResult<VideoFrame> {
let mut config = ArtifactReductionConfig::default();
match self.config.quality {
InterpolationQuality::Fast => {
config.reduce_halo = false;
config.reduce_ghosting = false;
config.sharpen = false;
}
InterpolationQuality::Balanced => {
config.halo_strength = 0.3;
config.ghosting_strength = 0.2;
config.sharpen_strength = 0.1;
}
InterpolationQuality::HighQuality => {
config.halo_strength = 0.5;
config.ghosting_strength = 0.3;
config.sharpen_strength = 0.2;
}
}
let reducer = ArtifactReducer::with_config(config);
reducer.reduce_artifacts(frame, None, None)
}
}
impl Default for FrameInterpolator {
fn default() -> Self {
Self::new(InterpolationQuality::Balanced)
}
}