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