Skip to main content

alpha2_model/
mask.rs

1//! Shape masks and track mattes: what limits where a clip is visible.
2
3use crate::{Animated, ClipId, FillRule, ShapeGeometry, ShapeIssue, ShapeProblem};
4use serde::{Deserialize, Serialize};
5
6/// Most shape masks on one clip.
7pub const MAX_CLIP_MASKS: usize = 8;
8
9/// A shape that limits where a clip is visible.
10///
11/// Geometry is in the clip's source space before its transform, so the mask moves with the
12/// clip: `(0, 0)` is the source's top-left, `(1, 1)` its bottom-right. Distances (`feather`,
13/// `corner_radius`, polystar radii) are fractions of that space's shorter side.
14#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[serde(deny_unknown_fields)]
17pub struct ShapeMask {
18    /// The shape's outline, as a shapes generator's geometry.
19    pub shape: ShapeGeometry,
20    /// Edge softness, non-negative; zero is the outline's antialiased edge. Coverage is blurred
21    /// by a Gaussian with standard deviation `0.3125 * feather`, a 10-90% ramp of `0.8 * feather`.
22    #[serde(default = "zero")]
23    pub feather: Animated<f32>,
24    /// Whether the mask keeps the outside of the shape instead of its inside.
25    #[serde(default)]
26    pub invert: bool,
27    /// How this mask combines with the ones before it.
28    #[serde(default)]
29    pub mode: MaskMode,
30    /// Which regions of the outline are inside it.
31    #[serde(default)]
32    pub fill_rule: FillRule,
33}
34
35fn zero() -> Animated<f32> {
36    Animated::Constant { value: 0.0 }
37}
38
39/// How a mask combines with the coverage of the masks before it.
40///
41/// Coverage starts empty when the first mask adds and full otherwise, so a clip whose only
42/// mask subtracts is visible outside the shape.
43#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
44#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
45#[serde(rename_all = "snake_case")]
46pub enum MaskMode {
47    /// Union: `c + m - c * m`.
48    #[default]
49    Add,
50    /// Difference: `c * (1 - m)`.
51    Subtract,
52    /// Intersection: `c * m`.
53    Intersect,
54}
55
56/// Another visual clip whose picture sets where this clip is visible.
57///
58/// The matte clip is drawn, with its own transform and effects, only into the matte and is
59/// never composited itself. Where it is not active, coverage is zero.
60#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62#[serde(deny_unknown_fields)]
63pub struct Matte {
64    /// The clip whose picture is the matte.
65    pub source: ClipId,
66    /// What of that picture is the coverage.
67    pub mode: MatteMode,
68}
69
70/// Which property of the matte clip's picture is coverage.
71#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
72#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
73#[serde(rename_all = "snake_case")]
74pub enum MatteMode {
75    /// Its alpha.
76    Alpha,
77    /// One minus its alpha.
78    AlphaInverted,
79    /// Its luminance over black: BT.709 weights on linear light, times alpha.
80    Luma,
81    /// One minus that luminance.
82    LumaInverted,
83}
84
85impl ShapeMask {
86    /// Checks the outline and feather; `at` names the mask in the issue.
87    pub(crate) fn validate(&self, at: &str) -> Result<(), ShapeIssue> {
88        self.shape.validate(&format!("{at}.shape"))?;
89        if crate::animation::valid_scalar_values(&self.feather, |value| value >= 0.0) {
90            Ok(())
91        } else {
92            Err(ShapeIssue {
93                at: format!("{at}.feather"),
94                problem: ShapeProblem::OutOfRange,
95            })
96        }
97    }
98}
99
100/// Checks each of `masks`, naming the first problem by its index.
101pub(crate) fn validate_masks(masks: &[ShapeMask]) -> Result<(), ShapeIssue> {
102    for (index, mask) in masks.iter().enumerate() {
103        mask.validate(&format!("masks[{index}]"))?;
104    }
105    Ok(())
106}