Skip to main content

ff_filter/
composite.rs

1//! Porter-Duff alpha-compositing operators.
2
3// ── CompositeOp ────────────────────────────────────────────────────────────────
4
5/// Porter-Duff alpha-compositing operator for combining two video layers.
6///
7/// Unlike [`BlendMode`](crate::BlendMode) (which operates on pixel values), these
8/// operators combine layers by their alpha channels. At least the top layer must
9/// carry an alpha channel (e.g. `rgba` or `yuva420p`).
10///
11/// There is no `blend all_mode` token for Porter-Duff compositing, so this type
12/// has no `FfmpegToken` impl; each operator maps to a specific `FFmpeg` construction
13/// (`overlay` or `blend` with a per-channel expression) in the filter graph.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum CompositeOp {
16    /// Top layer rendered over the bottom (standard alpha compositing).
17    ///
18    /// Built via `overlay=format=auto:shortest=1`.
19    #[default]
20    Over,
21
22    /// Bottom layer rendered over the top; `Over` with the inputs swapped.
23    ///
24    /// Built via `overlay` with swapped input order.
25    Under,
26
27    /// Top layer masked by the bottom layer's alpha (intersection).
28    ///
29    /// Built via `blend` with `c0_expr='B*A/255'`.
30    In,
31
32    /// Top layer visible only where the bottom layer is transparent.
33    ///
34    /// Built via `blend` with `c0_expr='B*(255-A)/255'`.
35    Out,
36
37    /// Top layer placed atop the bottom; visible only where the bottom is opaque.
38    ///
39    /// Built via `blend` with `c0_expr='B*A/255 + A*(255-B)/255'`.
40    Atop,
41
42    /// Pixels from exactly one layer (XOR of opaque regions).
43    ///
44    /// Built via `blend` with `c0_expr='B*(255-A)/255 + A*(255-B)/255'`.
45    Xor,
46}
47
48impl CompositeOp {
49    /// Returns the `FFmpeg` `blend` `all_expr` formula for the expression-based
50    /// operators (`In`/`Out`/`Atop`/`Xor`), or `None` for `Over`/`Under` which
51    /// are built with the `overlay` filter rather than `blend`.
52    ///
53    /// In the formula, `A` is the bottom pixel and `B` is the top pixel. This is
54    /// the single source of these formulas, shared by the `Composite` filter step
55    /// and the `MultiTrackComposer` canvas compositing.
56    #[must_use]
57    pub(crate) fn blend_all_expr(self) -> Option<&'static str> {
58        match self {
59            Self::Over | Self::Under => None,
60            Self::In => Some("B*A/255"),
61            Self::Out => Some("B*(255-A)/255"),
62            Self::Atop => Some("B*A/255 + A*(255-B)/255"),
63            Self::Xor => Some("B*(255-A)/255 + A*(255-B)/255"),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::CompositeOp;
71
72    #[test]
73    fn blend_all_expr_should_be_none_for_overlay_built_operators() {
74        assert_eq!(CompositeOp::Over.blend_all_expr(), None);
75        assert_eq!(CompositeOp::Under.blend_all_expr(), None);
76    }
77
78    #[test]
79    fn blend_all_expr_should_return_porter_duff_formula_for_expression_operators() {
80        assert_eq!(CompositeOp::In.blend_all_expr(), Some("B*A/255"));
81        assert_eq!(CompositeOp::Out.blend_all_expr(), Some("B*(255-A)/255"));
82        assert_eq!(
83            CompositeOp::Atop.blend_all_expr(),
84            Some("B*A/255 + A*(255-B)/255")
85        );
86        assert_eq!(
87            CompositeOp::Xor.blend_all_expr(),
88            Some("B*(255-A)/255 + A*(255-B)/255")
89        );
90    }
91
92    #[test]
93    fn composite_op_should_default_to_over() {
94        assert_eq!(CompositeOp::default(), CompositeOp::Over);
95    }
96}