use crate::{Animated, ClipId, FillRule, ShapeGeometry, ShapeIssue, ShapeProblem};
use serde::{Deserialize, Serialize};
pub const MAX_CLIP_MASKS: usize = 8;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ShapeMask {
pub shape: ShapeGeometry,
#[serde(default = "zero")]
pub feather: Animated<f32>,
#[serde(default)]
pub invert: bool,
#[serde(default)]
pub mode: MaskMode,
#[serde(default)]
pub fill_rule: FillRule,
}
fn zero() -> Animated<f32> {
Animated::Constant { value: 0.0 }
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MaskMode {
#[default]
Add,
Subtract,
Intersect,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Matte {
pub source: ClipId,
pub mode: MatteMode,
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MatteMode {
Alpha,
AlphaInverted,
Luma,
LumaInverted,
}
impl ShapeMask {
pub(crate) fn validate(&self, at: &str) -> Result<(), ShapeIssue> {
self.shape.validate(&format!("{at}.shape"))?;
if crate::animation::valid_scalar_values(&self.feather, |value| value >= 0.0) {
Ok(())
} else {
Err(ShapeIssue {
at: format!("{at}.feather"),
problem: ShapeProblem::OutOfRange,
})
}
}
}
pub(crate) fn validate_masks(masks: &[ShapeMask]) -> Result<(), ShapeIssue> {
for (index, mask) in masks.iter().enumerate() {
mask.validate(&format!("masks[{index}]"))?;
}
Ok(())
}