pub mod auto_key;
pub mod composite;
pub mod keyer;
pub mod matte;
pub mod matting;
pub mod spill;
use crate::error::{CvError, CvResult};
use oximedia_codec::VideoFrame;
pub use auto_key::AutoKeyDetector;
pub use composite::{BlendMode, Compositor, LightWrap};
pub use keyer::{ColorKeyer, KeyMethod, KeySpace};
pub use matte::{AlphaMatte, MatteRefiner, RefineOperation};
pub use spill::{DespillAlgorithm, SpillSuppressor};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rgb {
pub r: f32,
pub g: f32,
pub b: f32,
}
impl Rgb {
#[must_use]
pub const fn new(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b }
}
#[must_use]
pub fn from_u8(r: u8, g: u8, b: u8) -> Self {
Self {
r: f32::from(r) / 255.0,
g: f32::from(g) / 255.0,
b: f32::from(b) / 255.0,
}
}
#[must_use]
pub fn to_u8(&self) -> (u8, u8, u8) {
(
(self.r * 255.0).clamp(0.0, 255.0) as u8,
(self.g * 255.0).clamp(0.0, 255.0) as u8,
(self.b * 255.0).clamp(0.0, 255.0) as u8,
)
}
#[must_use]
#[allow(clippy::float_cmp)]
pub fn to_hsv(&self) -> Hsv {
let max = self.r.max(self.g).max(self.b);
let min = self.r.min(self.g).min(self.b);
let delta = max - min;
let v = max;
let s = if max == 0.0 { 0.0 } else { delta / max };
let h = if delta == 0.0 {
0.0
} else if max == self.r {
60.0 * (((self.g - self.b) / delta) % 6.0)
} else if max == self.g {
60.0 * (((self.b - self.r) / delta) + 2.0)
} else {
60.0 * (((self.r - self.g) / delta) + 4.0)
};
let h = if h < 0.0 { h + 360.0 } else { h };
Hsv::new(h, s, v)
}
#[must_use]
pub fn distance(&self, other: &Self) -> f32 {
let dr = self.r - other.r;
let dg = self.g - other.g;
let db = self.b - other.b;
(dr * dr + dg * dg + db * db).sqrt()
}
#[must_use]
pub const fn green_screen() -> Self {
Self {
r: 0.0,
g: 1.0,
b: 0.0,
}
}
#[must_use]
pub const fn blue_screen() -> Self {
Self {
r: 0.0,
g: 0.0,
b: 1.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Hsv {
pub h: f32,
pub s: f32,
pub v: f32,
}
impl Hsv {
#[must_use]
pub const fn new(h: f32, s: f32, v: f32) -> Self {
Self { h, s, v }
}
#[must_use]
pub fn to_rgb(&self) -> Rgb {
let c = self.v * self.s;
let h_prime = self.h / 60.0;
let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
let m = self.v - c;
let (r, g, b) = match h_prime as i32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
5 => (c, 0.0, x),
_ => (0.0, 0.0, 0.0),
};
Rgb::new(r + m, g + m, b + m)
}
#[must_use]
pub fn hue_distance(&self, other: &Self) -> f32 {
let diff = (self.h - other.h).abs();
diff.min(360.0 - diff)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyColor {
GreenScreen,
BlueScreen,
Custom,
}
impl KeyColor {
#[must_use]
pub fn to_rgb(&self) -> Rgb {
match self {
Self::GreenScreen => Rgb::green_screen(),
Self::BlueScreen => Rgb::blue_screen(),
Self::Custom => Rgb::new(0.0, 0.0, 0.0),
}
}
}
#[derive(Debug, Clone)]
pub struct ChromaKeyConfig {
pub key_color: Rgb,
pub threshold: f32,
pub tolerance: f32,
pub softness: f32,
pub spill_suppression: f32,
pub despill_algorithm: DespillAlgorithm,
pub key_space: KeySpace,
pub defringe: bool,
pub defringe_radius: u32,
pub light_wrap: bool,
pub light_wrap_intensity: f32,
pub erosion_iterations: u32,
pub dilation_iterations: u32,
pub matte_blur_radius: f32,
}
impl ChromaKeyConfig {
#[must_use]
pub fn new(key_color: Rgb) -> Self {
Self {
key_color,
threshold: 0.3,
tolerance: 0.1,
softness: 0.05,
spill_suppression: 0.5,
despill_algorithm: DespillAlgorithm::Simple,
key_space: KeySpace::Hsv,
defringe: true,
defringe_radius: 2,
light_wrap: false,
light_wrap_intensity: 0.3,
erosion_iterations: 0,
dilation_iterations: 0,
matte_blur_radius: 1.0,
}
}
#[must_use]
pub fn green_screen() -> Self {
Self::new(Rgb::green_screen())
}
#[must_use]
pub fn blue_screen() -> Self {
Self::new(Rgb::blue_screen())
}
#[must_use]
pub fn with_quality_settings(mut self) -> Self {
self.despill_algorithm = DespillAlgorithm::Advanced;
self.defringe = true;
self.defringe_radius = 3;
self.matte_blur_radius = 1.5;
self
}
#[must_use]
pub fn with_light_wrap(mut self, intensity: f32) -> Self {
self.light_wrap = true;
self.light_wrap_intensity = intensity.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn with_matte_refinement(mut self, erode: u32, dilate: u32, blur: f32) -> Self {
self.erosion_iterations = erode;
self.dilation_iterations = dilate;
self.matte_blur_radius = blur;
self
}
}
impl Default for ChromaKeyConfig {
fn default() -> Self {
Self::green_screen()
}
}
pub struct ChromaKey {
config: ChromaKeyConfig,
keyer: ColorKeyer,
spill_suppressor: SpillSuppressor,
matte_refiner: MatteRefiner,
compositor: Compositor,
}
impl ChromaKey {
#[must_use]
pub fn new(config: ChromaKeyConfig) -> Self {
let keyer = ColorKeyer::new(
config.key_color,
config.threshold,
config.tolerance,
config.key_space,
);
let spill_suppressor = SpillSuppressor::new(
config.key_color,
config.spill_suppression,
config.despill_algorithm,
);
let matte_refiner = MatteRefiner::new();
let compositor = Compositor::new();
Self {
config,
keyer,
spill_suppressor,
matte_refiner,
compositor,
}
}
pub fn process(
&self,
foreground: &VideoFrame,
background: Option<&VideoFrame>,
) -> CvResult<VideoFrame> {
if let Some(bg) = background {
if foreground.width != bg.width || foreground.height != bg.height {
return Err(CvError::invalid_parameter(
"dimensions",
format!(
"foreground {}x{} != background {}x{}",
foreground.width, foreground.height, bg.width, bg.height
),
));
}
}
let mut matte = self.keyer.key_frame(foreground)?;
if self.config.erosion_iterations > 0 {
matte = self
.matte_refiner
.erode(&matte, self.config.erosion_iterations)?;
}
if self.config.dilation_iterations > 0 {
matte = self
.matte_refiner
.dilate(&matte, self.config.dilation_iterations)?;
}
if self.config.matte_blur_radius > 0.0 {
matte = self
.matte_refiner
.blur(&matte, self.config.matte_blur_radius)?;
}
if self.config.softness > 0.0 {
matte = self.matte_refiner.feather(&matte, self.config.softness)?;
}
let mut despilled = foreground.clone();
if self.config.spill_suppression > 0.0 {
self.spill_suppressor.suppress(&mut despilled, &matte)?;
}
if self.config.defringe {
self.compositor
.defringe(&mut despilled, &matte, self.config.defringe_radius)?;
}
if let Some(bg) = background {
let mut result = self.compositor.composite(&despilled, bg, &matte)?;
if self.config.light_wrap {
let light_wrap = LightWrap::new(self.config.light_wrap_intensity);
light_wrap.apply(&mut result, bg, &matte)?;
}
Ok(result)
} else {
self.compositor.apply_matte(&despilled, &matte)
}
}
pub fn generate_matte(&self, frame: &VideoFrame) -> CvResult<AlphaMatte> {
let mut matte = self.keyer.key_frame(frame)?;
if self.config.erosion_iterations > 0 {
matte = self
.matte_refiner
.erode(&matte, self.config.erosion_iterations)?;
}
if self.config.dilation_iterations > 0 {
matte = self
.matte_refiner
.dilate(&matte, self.config.dilation_iterations)?;
}
if self.config.matte_blur_radius > 0.0 {
matte = self
.matte_refiner
.blur(&matte, self.config.matte_blur_radius)?;
}
if self.config.softness > 0.0 {
matte = self.matte_refiner.feather(&matte, self.config.softness)?;
}
Ok(matte)
}
pub fn set_key_color(&mut self, color: Rgb) {
self.config.key_color = color;
self.keyer.set_key_color(color);
self.spill_suppressor.set_key_color(color);
}
pub fn set_thresholds(&mut self, threshold: f32, tolerance: f32) {
self.config.threshold = threshold;
self.config.tolerance = tolerance;
self.keyer.set_thresholds(threshold, tolerance);
}
pub fn set_spill_suppression(&mut self, strength: f32) {
self.config.spill_suppression = strength.clamp(0.0, 1.0);
self.spill_suppressor.set_strength(strength);
}
#[must_use]
pub fn config(&self) -> &ChromaKeyConfig {
&self.config
}
pub fn auto_detect_key_color(
&mut self,
frame: &VideoFrame,
x: u32,
y: u32,
width: u32,
height: u32,
) -> CvResult<Rgb> {
let detector = AutoKeyDetector::new();
let color = detector.detect_from_region(frame, x, y, width, height)?;
self.set_key_color(color);
Ok(color)
}
}