motion_core/drawing/blend.rs
1//! Blending, fill and mask interpretation.
2
3/// Linear-light layer blending, with explicit compositing space.
4///
5/// Separable modes evaluate straight linear channels with source-over alpha.
6/// Extended RGB is not gamut-clipped. Plus instead adds premultiplied RGB and
7/// clamps the summed alpha to one. Dodge/Burn retain their formula's intrinsic
8/// endpoints and saturation; other modes extend their piecewise formulas without
9/// input clipping. Nonseparable modes use hue/saturation/luminosity replacement.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11#[cfg_attr(
12 feature = "serde",
13 derive(serde::Serialize, serde::Deserialize),
14 serde(rename_all = "snake_case")
15)]
16#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17pub enum BlendMode {
18 /// Premultiplied source-over; the default.
19 #[default]
20 SourceOver,
21 /// Unclipped additive RGB with saturated additive alpha.
22 Plus,
23 /// Product of straight source and backdrop channels.
24 Multiply,
25 /// Source plus backdrop minus their product.
26 Screen,
27 /// Minimum of straight source and backdrop channels.
28 Darken,
29 /// Maximum of straight source and backdrop channels.
30 Lighten,
31 /// Absolute difference of straight channels.
32 Difference,
33 /// Source plus backdrop minus twice their product.
34 Exclusion,
35 /// Multiply or screen, selected by the backdrop channel at one half.
36 Overlay,
37 /// Multiply or screen, selected by the source channel at one half.
38 HardLight,
39 /// Piecewise soft lighting, with the low-backdrop cubic and upper square root.
40 SoftLight,
41 /// Backdrop divided by one minus source, saturated above at one.
42 ///
43 /// Nonpositive backdrop returns zero before source at or above one returns one.
44 ColorDodge,
45 /// One minus the saturated ratio of one minus backdrop to source.
46 ///
47 /// Backdrop at or above one returns one before nonpositive source returns zero.
48 ColorBurn,
49 /// Source hue with backdrop saturation and luminosity.
50 Hue,
51 /// Source saturation with backdrop hue and luminosity.
52 Saturation,
53 /// Source hue/saturation with backdrop luminosity.
54 Color,
55 /// Source luminosity with backdrop hue/saturation.
56 Luminosity,
57 /// Internal linear-only additive capture used by sample accumulation.
58 #[doc(hidden)]
59 LinearPlus,
60}
61
62/// Color space used for compositing, independent of stored linear pixels.
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
64pub enum BlendSpace {
65 /// Composite straight linear RGB, retaining the default.
66 #[default]
67 LinearRgb,
68 /// Composite encoded sRGB, then return to linear-premultiplied storage.
69 Srgb,
70}
71
72/// How closed contours determine the filled interior.
73///
74/// Recording retains both rules without silently substituting one for
75/// the default NonZero rule.
76///
77/// ```
78/// use motion_core::prelude::*;
79/// use motion_core::{
80/// DisplayList, RasterLimits, ResourceStore, ValidatedDisplayList, MAX_PATH_VERBS,
81/// MAX_STROKE_VERBS, MAX_SVG_PATH_BYTES, sample_offsets,
82/// };
83/// let path = Path2D::from_rect(Rect::new(0.0, 0.0, 10.0, 20.0))?;
84/// let fill = path.fill(Color::WHITE);
85/// let native = DrawContext::default().record(&fill, ValidationLimits::default())?;
86/// assert!(matches!(
87/// native[0],
88/// DrawCommand::Fill {
89/// rule: FillRule::NonZero,
90/// ..
91/// }
92/// ));
93/// let even = DrawContext::default().record(
94/// &fill.fill_rule(FillRule::EvenOdd),
95/// ValidationLimits::default(),
96/// )?;
97/// assert!(matches!(
98/// even[0],
99/// DrawCommand::Fill {
100/// rule: FillRule::EvenOdd,
101/// ..
102/// }
103/// ));
104/// # Ok::<(), motion_core::ValidationError>(())
105/// ```
106#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
107#[cfg_attr(
108 feature = "serde",
109 derive(serde::Serialize, serde::Deserialize),
110 serde(rename_all = "snake_case")
111)]
112#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
113pub enum FillRule {
114 /// Nonzero signed winding count; the default.
115 #[default]
116 NonZero,
117 /// Odd crossing count.
118 EvenOdd,
119}
120
121/// How linear-premultiplied content becomes a layer's mask coverage.
122///
123/// Coverage is computed in the compositing color space and clamped
124/// to 0..=1 before optional inversion, without altering working colors.
125/// Alpha-zero pixels have zero ordinary coverage, even with hidden extended
126/// RGB; inverted modes therefore retain source content at those pixels.
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
128#[cfg_attr(
129 feature = "serde",
130 derive(serde::Serialize, serde::Deserialize),
131 serde(rename_all = "snake_case")
132)]
133#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
134pub enum MaskMode {
135 /// Use only the completed mask's alpha.
136 #[default]
137 Alpha,
138 /// One minus the completed mask's alpha, including transparent artwork.
139 InvertedAlpha,
140 /// Use Rec.709 luminance of linear premultiplied RGB, already alpha-weighted.
141 Luminance,
142 /// One minus clamped linear-premultiplied luminance; alpha is not inverted first.
143 InvertedLuminance,
144}