Skip to main content

ferroui_base/rendering/composition/effects/
graphics_effects.rs

1//! The effect descriptions an effect factory is created from: the FerroUI
2//! form of the Win2D effects (`Microsoft.Graphics.Canvas.Effects`) that
3//! WinUI's `Compositor.CreateEffectFactory` takes as an `IGraphicsEffect`.
4//!
5//! Deviation (winui-composition.md): WinUI takes any `IGraphicsEffect`
6//! (an object that names a Direct2D effect and its properties); here an
7//! effect is one of the descriptions of this module, plain values with the
8//! members of the Win2D classes, and the factory accepts the ones the
9//! drawing contexts can express.
10
11use crate::media::{Color, ColorMatrix, ColorMatrixAlphaMode, Colors};
12
13/// A named leaf of an effect graph that a brush is bound to (WinUI
14/// `Microsoft.UI.Composition.CompositionEffectSourceParameter`): the input
15/// of the effect that
16/// [`CompositionEffectBrush::set_source_parameter`](crate::rendering::composition::CompositionEffectBrush::set_source_parameter)
17/// fills with a composition brush, or the composed children of the layer
18/// visual whose effect the brush is.
19#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
20pub struct CompositionEffectSourceParameter {
21    name: String,
22}
23
24impl CompositionEffectSourceParameter {
25    /// `CompositionEffectSourceParameter(name)`.
26    pub fn new(name: impl Into<String>) -> Self {
27        Self { name: name.into() }
28    }
29
30    /// The name the brush is bound by.
31    pub fn name(&self) -> &str {
32        &self.name
33    }
34}
35
36/// How a blur treats the edges of its input (Win2D `EffectBorderMode`):
37/// `Soft` fades the edges (the blur reads transparent pixels beyond its
38/// area), `Hard` keeps them (the blur reads its source beyond the area and
39/// its output is cut to the area).
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
41#[repr(i32)]
42pub enum EffectBorderMode {
43    /// The edges fade to transparent (the Win2D default).
44    #[default]
45    Soft = 0,
46    /// The edge pixels are repeated.
47    Hard = 1,
48}
49
50/// The speed and quality trade-off of an effect (Win2D
51/// `EffectOptimization`).
52///
53/// Deviation (winui-materials.md): the blurs of the drawing contexts are
54/// exact at every setting; `Speed` and `Balanced` draw the output of
55/// `Quality` (Direct2D's approximations are not reproduced).
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
57#[repr(i32)]
58pub enum EffectOptimization {
59    /// The fastest form.
60    Speed = 0,
61    /// A balance of speed and quality (the Win2D default).
62    #[default]
63    Balanced = 1,
64    /// The best quality.
65    Quality = 2,
66}
67
68/// How a composite effect combines its sources (Win2D `CanvasComposite`):
69/// each source is composed onto what the sources before it made.
70///
71/// Every mode is drawn: the Porter-Duff modes with the blend layers of the
72/// drawing contexts, `MaskInvert` from the pixels of the sources drawn
73/// apart (winui-materials.md).
74#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
75#[repr(i32)]
76pub enum CanvasComposite {
77    /// Each source drawn over the previous ones (the Win2D default).
78    #[default]
79    SourceOver = 0,
80    DestinationOver = 1,
81    SourceIn = 2,
82    DestinationIn = 3,
83    SourceOut = 4,
84    DestinationOut = 5,
85    SourceAtop = 6,
86    DestinationAtop = 7,
87    Xor = 8,
88    Add = 9,
89    Copy = 10,
90    BoundedCopy = 11,
91    MaskInvert = 12,
92}
93
94/// An input of an effect (WinUI `IGraphicsEffectSource`): a source
95/// parameter that a brush is bound to, or another effect.
96#[derive(Clone, Debug, PartialEq)]
97pub enum GraphicsEffectSource {
98    /// A named leaf bound to a brush.
99    Parameter(CompositionEffectSourceParameter),
100    /// The output of another effect.
101    Effect(Box<GraphicsEffect>),
102}
103
104impl From<CompositionEffectSourceParameter> for GraphicsEffectSource {
105    fn from(parameter: CompositionEffectSourceParameter) -> Self {
106        GraphicsEffectSource::Parameter(parameter)
107    }
108}
109
110impl From<GraphicsEffect> for GraphicsEffectSource {
111    fn from(effect: GraphicsEffect) -> Self {
112        GraphicsEffectSource::Effect(Box::new(effect))
113    }
114}
115
116/// An effect description (WinUI `IGraphicsEffect`): one of the Win2D
117/// effects of this module.
118#[derive(Clone, Debug, PartialEq)]
119pub enum GraphicsEffect {
120    GaussianBlur(GaussianBlurEffect),
121    Opacity(OpacityEffect),
122    CrossFade(CrossFadeEffect),
123    Composite(CompositeEffect),
124    ColorSource(ColorSourceEffect),
125    Saturation(SaturationEffect),
126    Blend(BlendEffect),
127    ColorMatrix(ColorMatrixEffect),
128    Tint(TintEffect),
129    Invert(InvertEffect),
130    Grayscale(GrayscaleEffect),
131    LuminanceToAlpha(LuminanceToAlphaEffect),
132    ArithmeticComposite(ArithmeticCompositeEffect),
133    AlphaMask(AlphaMaskEffect),
134    Border(BorderEffect),
135    SceneLighting(SceneLightingEffect),
136    Shadow(ShadowEffect),
137}
138
139impl GraphicsEffect {
140    /// The name of the effect (`IGraphicsEffect.Name`): the first part of
141    /// the animatable property names of the effect (`Blur.BlurAmount`).
142    pub fn name(&self) -> &str {
143        match self {
144            GraphicsEffect::GaussianBlur(effect) => &effect.name,
145            GraphicsEffect::Opacity(effect) => &effect.name,
146            GraphicsEffect::CrossFade(effect) => &effect.name,
147            GraphicsEffect::Composite(effect) => &effect.name,
148            GraphicsEffect::ColorSource(effect) => &effect.name,
149            GraphicsEffect::Saturation(effect) => &effect.name,
150            GraphicsEffect::Blend(effect) => &effect.name,
151            GraphicsEffect::ColorMatrix(effect) => &effect.name,
152            GraphicsEffect::Tint(effect) => &effect.name,
153            GraphicsEffect::Invert(effect) => &effect.name,
154            GraphicsEffect::Grayscale(effect) => &effect.name,
155            GraphicsEffect::LuminanceToAlpha(effect) => &effect.name,
156            GraphicsEffect::ArithmeticComposite(effect) => &effect.name,
157            GraphicsEffect::AlphaMask(effect) => &effect.name,
158            GraphicsEffect::Border(effect) => &effect.name,
159            GraphicsEffect::SceneLighting(effect) => &effect.name,
160            GraphicsEffect::Shadow(effect) => &effect.name,
161        }
162    }
163
164    /// The name of the Win2D class of the effect, for messages.
165    pub fn type_name(&self) -> &'static str {
166        match self {
167            GraphicsEffect::GaussianBlur(_) => "GaussianBlurEffect",
168            GraphicsEffect::Opacity(_) => "OpacityEffect",
169            GraphicsEffect::CrossFade(_) => "CrossFadeEffect",
170            GraphicsEffect::Composite(_) => "CompositeEffect",
171            GraphicsEffect::ColorSource(_) => "ColorSourceEffect",
172            GraphicsEffect::Saturation(_) => "SaturationEffect",
173            GraphicsEffect::Blend(_) => "BlendEffect",
174            GraphicsEffect::ColorMatrix(_) => "ColorMatrixEffect",
175            GraphicsEffect::Tint(_) => "TintEffect",
176            GraphicsEffect::Invert(_) => "InvertEffect",
177            GraphicsEffect::Grayscale(_) => "GrayscaleEffect",
178            GraphicsEffect::LuminanceToAlpha(_) => "LuminanceToAlphaEffect",
179            GraphicsEffect::ArithmeticComposite(_) => "ArithmeticCompositeEffect",
180            GraphicsEffect::AlphaMask(_) => "AlphaMaskEffect",
181            GraphicsEffect::Border(_) => "BorderEffect",
182            GraphicsEffect::SceneLighting(_) => "SceneLightingEffect",
183            GraphicsEffect::Shadow(_) => "ShadowEffect",
184        }
185    }
186}
187
188macro_rules! graphics_effect_conversions {
189    ($($ty:ident => $variant:ident),* $(,)?) => {$(
190        impl From<$ty> for GraphicsEffect {
191            fn from(effect: $ty) -> Self {
192                GraphicsEffect::$variant(effect)
193            }
194        }
195
196        impl From<$ty> for GraphicsEffectSource {
197            fn from(effect: $ty) -> Self {
198                GraphicsEffectSource::Effect(Box::new(GraphicsEffect::$variant(effect)))
199            }
200        }
201    )*};
202}
203
204graphics_effect_conversions! {
205    GaussianBlurEffect => GaussianBlur,
206    OpacityEffect => Opacity,
207    CrossFadeEffect => CrossFade,
208    CompositeEffect => Composite,
209    ColorSourceEffect => ColorSource,
210    SaturationEffect => Saturation,
211    BlendEffect => Blend,
212    ColorMatrixEffect => ColorMatrix,
213    TintEffect => Tint,
214    InvertEffect => Invert,
215    GrayscaleEffect => Grayscale,
216    LuminanceToAlphaEffect => LuminanceToAlpha,
217    ArithmeticCompositeEffect => ArithmeticComposite,
218    AlphaMaskEffect => AlphaMask,
219    BorderEffect => Border,
220    ShadowEffect => Shadow,
221    SceneLightingEffect => SceneLighting,
222}
223
224/// Blurs its source (Win2D `GaussianBlurEffect`). Animatable:
225/// `BlurAmount`.
226#[derive(Clone, Debug, PartialEq)]
227pub struct GaussianBlurEffect {
228    /// The name of the effect.
229    pub name: String,
230    /// The standard deviation of the blur, in device independent pixels
231    /// (default 3).
232    pub blur_amount: f32,
233    /// How the edges of the source are treated.
234    pub border_mode: EffectBorderMode,
235    /// The speed and quality trade-off (every setting draws the exact blur).
236    pub optimization: EffectOptimization,
237    /// The input.
238    pub source: Option<GraphicsEffectSource>,
239}
240
241impl Default for GaussianBlurEffect {
242    fn default() -> Self {
243        Self {
244            name: String::new(),
245            blur_amount: 3.0,
246            border_mode: EffectBorderMode::Soft,
247            optimization: EffectOptimization::Balanced,
248            source: None,
249        }
250    }
251}
252
253/// Multiplies the alpha of its source (Win2D `OpacityEffect`). Animatable:
254/// `Opacity`.
255#[derive(Clone, Debug, PartialEq)]
256pub struct OpacityEffect {
257    /// The name of the effect.
258    pub name: String,
259    /// The opacity, 0 to 1 (default 1).
260    pub opacity: f32,
261    /// The input.
262    pub source: Option<GraphicsEffectSource>,
263}
264
265impl Default for OpacityEffect {
266    fn default() -> Self {
267        Self { name: String::new(), opacity: 1.0, source: None }
268    }
269}
270
271/// Blends two sources by a weight (Win2D `CrossFadeEffect`): `source1` at
272/// weight 0, `source2` at weight 1. Animatable: `Weight` (also as
273/// `CrossFade`, the name of the Win2D member).
274#[derive(Clone, Debug, PartialEq)]
275pub struct CrossFadeEffect {
276    /// The name of the effect.
277    pub name: String,
278    /// The weight of `source2`, 0 to 1 (default 0.5).
279    pub weight: f32,
280    /// The input shown at weight 0.
281    pub source1: Option<GraphicsEffectSource>,
282    /// The input shown at weight 1.
283    pub source2: Option<GraphicsEffectSource>,
284}
285
286impl Default for CrossFadeEffect {
287    fn default() -> Self {
288        Self { name: String::new(), weight: 0.5, source1: None, source2: None }
289    }
290}
291
292/// Draws its sources over each other, in order (Win2D `CompositeEffect`).
293#[derive(Clone, Debug, Default, PartialEq)]
294pub struct CompositeEffect {
295    /// The name of the effect.
296    pub name: String,
297    /// How the sources are combined.
298    pub mode: CanvasComposite,
299    /// The inputs, the first at the bottom.
300    pub sources: Vec<GraphicsEffectSource>,
301}
302
303/// Fills the area with a colour (Win2D `ColorSourceEffect`). Animatable:
304/// `Color`.
305#[derive(Clone, Debug, PartialEq)]
306pub struct ColorSourceEffect {
307    /// The name of the effect.
308    pub name: String,
309    /// The colour (default opaque black).
310    pub color: Color,
311}
312
313impl Default for ColorSourceEffect {
314    fn default() -> Self {
315        Self { name: String::new(), color: Colors::BLACK }
316    }
317}
318
319/// Changes the saturation of its source (Win2D `SaturationEffect`): a
320/// colour matrix (`ColorMatrix::saturation`). Animatable: `Saturation`.
321#[derive(Clone, Debug, PartialEq)]
322pub struct SaturationEffect {
323    /// The name of the effect.
324    pub name: String,
325    /// The saturation, 0 (grey) to 1 (unchanged), up to 2 (more saturated)
326    /// (default 0.5).
327    pub saturation: f32,
328    /// The input.
329    pub source: Option<GraphicsEffectSource>,
330}
331
332impl Default for SaturationEffect {
333    fn default() -> Self {
334        Self { name: String::new(), saturation: 0.5, source: None }
335    }
336}
337
338/// How a blend effect mixes its foreground into its background (Win2D
339/// `BlendEffectMode`, the modes of Direct2D's blend effect).
340///
341/// The modes with a counterpart among the blend modes of the drawing
342/// contexts are drawn: `Multiply`, `Screen`, `Darken`, `Lighten`,
343/// `ColorBurn`, `ColorDodge`, `LinearDodge` (an addition), `Overlay`,
344/// `SoftLight`, `HardLight`, `Difference`, `Exclusion`, `Hue`, `Saturation`,
345/// `Color` and `Luminosity`. The other modes are drawn from the pixels of the
346/// two sources (docs/porting/winui-materials.md).
347///
348/// `Color` keeps the hue and saturation of the foreground and the
349/// luminosity of the background; `Luminosity` the other way round, as Win2D
350/// documents them.
351#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
352#[repr(i32)]
353pub enum BlendEffectMode {
354    /// The default of Win2D.
355    #[default]
356    Multiply = 0,
357    Screen = 1,
358    Darken = 2,
359    Lighten = 3,
360    Dissolve = 4,
361    ColorBurn = 5,
362    LinearBurn = 6,
363    DarkerColor = 7,
364    LighterColor = 8,
365    ColorDodge = 9,
366    LinearDodge = 10,
367    Overlay = 11,
368    SoftLight = 12,
369    HardLight = 13,
370    VividLight = 14,
371    LinearLight = 15,
372    PinLight = 16,
373    HardMix = 17,
374    Difference = 18,
375    Exclusion = 19,
376    Hue = 20,
377    Saturation = 21,
378    Color = 22,
379    Luminosity = 23,
380    Subtract = 24,
381    Division = 25,
382}
383
384/// Mixes a foreground into a background (Win2D `BlendEffect`).
385#[derive(Clone, Debug, Default, PartialEq)]
386pub struct BlendEffect {
387    /// The name of the effect.
388    pub name: String,
389    /// How the two are mixed (default `Multiply`).
390    pub mode: BlendEffectMode,
391    /// The input under the foreground (Direct2D's destination).
392    pub background: Option<GraphicsEffectSource>,
393    /// The input mixed into the background (Direct2D's source).
394    pub foreground: Option<GraphicsEffectSource>,
395}
396
397/// Transforms the colours of its source by a matrix (Win2D
398/// `ColorMatrixEffect`).
399#[derive(Clone, Debug, PartialEq)]
400pub struct ColorMatrixEffect {
401    /// The name of the effect.
402    pub name: String,
403    /// The matrix (default the identity).
404    pub color_matrix: ColorMatrix,
405    /// Whether the matrix applies to premultiplied or straight colours
406    /// (default premultiplied).
407    pub alpha_mode: ColorMatrixAlphaMode,
408    /// Whether the output is clamped to 0..1 (default `false`; the drawing
409    /// contexts always clamp).
410    pub clamp_output: bool,
411    /// The input.
412    pub source: Option<GraphicsEffectSource>,
413}
414
415impl Default for ColorMatrixEffect {
416    fn default() -> Self {
417        Self {
418            name: String::new(),
419            color_matrix: ColorMatrix::IDENTITY,
420            alpha_mode: ColorMatrixAlphaMode::Premultiplied,
421            clamp_output: false,
422            source: None,
423        }
424    }
425}
426
427/// Multiplies the channels of its source by a colour (Win2D `TintEffect`).
428/// Animatable: `Color`.
429#[derive(Clone, Debug, PartialEq)]
430pub struct TintEffect {
431    /// The name of the effect.
432    pub name: String,
433    /// The colour (default opaque white).
434    pub color: Color,
435    /// Whether the output is clamped (stored; the drawing contexts clamp).
436    pub clamp_output: bool,
437    /// The input.
438    pub source: Option<GraphicsEffectSource>,
439}
440
441impl Default for TintEffect {
442    fn default() -> Self {
443        Self { name: String::new(), color: Colors::WHITE, clamp_output: false, source: None }
444    }
445}
446
447/// Inverts the colours of its source (Win2D `InvertEffect`).
448#[derive(Clone, Debug, Default, PartialEq)]
449pub struct InvertEffect {
450    /// The name of the effect.
451    pub name: String,
452    /// The input.
453    pub source: Option<GraphicsEffectSource>,
454}
455
456/// Turns its source grey (Win2D `GrayscaleEffect`).
457#[derive(Clone, Debug, Default, PartialEq)]
458pub struct GrayscaleEffect {
459    /// The name of the effect.
460    pub name: String,
461    /// The input.
462    pub source: Option<GraphicsEffectSource>,
463}
464
465/// Turns the luminance of its source into alpha (Win2D
466/// `LuminanceToAlphaEffect`): black with the luminance as its alpha.
467#[derive(Clone, Debug, Default, PartialEq)]
468pub struct LuminanceToAlphaEffect {
469    /// The name of the effect.
470    pub name: String,
471    /// The input.
472    pub source: Option<GraphicsEffectSource>,
473}
474
475/// Combines two sources channel by channel as
476/// `MultiplyAmount * s1 * s2 + Source1Amount * s1 + Source2Amount * s2 + Offset`
477/// on premultiplied colours (Win2D `ArithmeticCompositeEffect`).
478/// Animatable: `MultiplyAmount`, `Source1Amount`, `Source2Amount`, `Offset`.
479///
480/// Drawn when one of the sources is a colour source: the combination is
481/// then a colour matrix of the other source. A factory made from one whose
482/// sources are both images reports
483/// [`CompositionEffectFactoryError::EffectTypeNotSupported`](super::CompositionEffectFactoryError::EffectTypeNotSupported).
484#[derive(Clone, Debug, PartialEq)]
485pub struct ArithmeticCompositeEffect {
486    /// The name of the effect.
487    pub name: String,
488    /// The weight of the product of the sources (default 1).
489    pub multiply_amount: f32,
490    /// The weight of the first source (default 0).
491    pub source1_amount: f32,
492    /// The weight of the second source (default 0).
493    pub source2_amount: f32,
494    /// The constant added (default 0).
495    pub offset: f32,
496    /// Whether the output is clamped (stored; the drawing contexts clamp).
497    pub clamp_output: bool,
498    /// The first input.
499    pub source1: Option<GraphicsEffectSource>,
500    /// The second input.
501    pub source2: Option<GraphicsEffectSource>,
502}
503
504impl Default for ArithmeticCompositeEffect {
505    fn default() -> Self {
506        Self {
507            name: String::new(),
508            multiply_amount: 1.0,
509            source1_amount: 0.0,
510            source2_amount: 0.0,
511            offset: 0.0,
512            clamp_output: false,
513            source1: None,
514            source2: None,
515        }
516    }
517}
518
519/// Keeps its source where its mask is opaque (Win2D `AlphaMaskEffect`):
520/// the source multiplied by the alpha of the mask.
521#[derive(Clone, Debug, Default, PartialEq)]
522pub struct AlphaMaskEffect {
523    /// The name of the effect.
524    pub name: String,
525    /// The input.
526    pub source: Option<GraphicsEffectSource>,
527    /// The mask.
528    pub alpha_mask: Option<GraphicsEffectSource>,
529}
530
531/// How a border effect extends its source beyond its edges (Win2D
532/// `CanvasEdgeBehavior`).
533#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
534#[repr(i32)]
535pub enum CanvasEdgeBehavior {
536    /// The edge pixels repeat (the Win2D default).
537    #[default]
538    Clamp = 0,
539    /// The source tiles.
540    Wrap = 1,
541    /// The source tiles, every other tile mirrored.
542    Mirror = 2,
543}
544
545/// Extends its source beyond its edges (Win2D `BorderEffect`): a surface
546/// brush source tiles over the area of the brush when an edge behaviour is
547/// `Wrap` (or `Mirror`, drawn as `Wrap`); any other source, or `Clamp` on
548/// both sides, is drawn as it is.
549#[derive(Clone, Debug, Default, PartialEq)]
550pub struct BorderEffect {
551    /// The name of the effect.
552    pub name: String,
553    /// The horizontal behaviour.
554    pub extend_x: CanvasEdgeBehavior,
555    /// The vertical behaviour.
556    pub extend_y: CanvasEdgeBehavior,
557    /// The input.
558    pub source: Option<GraphicsEffectSource>,
559}
560
561/// The lighting of the lights that reach the visual that paints the brush
562/// (WinUI `Microsoft.UI.Composition.Effects.SceneLightingEffect`): the sum,
563/// for every point of the brush, of the ambient lights times `AmbientAmount`
564/// and of the diffuse light of the other lights times `DiffuseAmount`,
565/// opaque. Animatable: `AmbientAmount`, `DiffuseAmount`, `SpecularAmount`,
566/// `SpecularShine`.
567///
568/// The lights are the composition lights that target the visual (or one of
569/// its ancestors), and, for a XAML brush, the XAML lights that target the
570/// brush in the subtree of the element that holds them.
571#[derive(Clone, Debug, PartialEq)]
572pub struct SceneLightingEffect {
573    /// The name of the effect.
574    pub name: String,
575    /// The weight of the ambient lights (default 1).
576    pub ambient_amount: f32,
577    /// The weight of the diffuse light (default 1).
578    pub diffuse_amount: f32,
579    /// The weight of the specular light (default 0; stored, flat visuals
580    /// have no specular highlight here).
581    pub specular_amount: f32,
582    /// The shininess of the specular light (default 16; stored).
583    pub specular_shine: f32,
584    /// The normal map (stored; the lit surface is flat).
585    pub normal_map_source: Option<GraphicsEffectSource>,
586}
587
588impl Default for SceneLightingEffect {
589    fn default() -> Self {
590        Self {
591            name: String::new(),
592            ambient_amount: 1.0,
593            diffuse_amount: 1.0,
594            specular_amount: 0.0,
595            specular_shine: 16.0,
596            normal_map_source: None,
597        }
598    }
599}
600
601/// The shadow of its source (Win2D `ShadowEffect`): the alpha of the source
602/// in the shadow colour, blurred. Animatable: `BlurAmount`, `ShadowColor`.
603#[derive(Clone, Debug, PartialEq)]
604pub struct ShadowEffect {
605    /// The name of the effect.
606    pub name: String,
607    /// The standard deviation of the blur, in device independent pixels
608    /// (default 1).
609    pub blur_amount: f32,
610    /// The colour of the shadow (default opaque black); its alpha scales the
611    /// alpha of the source.
612    pub shadow_color: Color,
613    /// The speed and quality trade-off (every setting draws the exact blur).
614    pub optimization: EffectOptimization,
615    /// The input.
616    pub source: Option<GraphicsEffectSource>,
617}
618
619impl Default for ShadowEffect {
620    fn default() -> Self {
621        Self {
622            name: String::new(),
623            blur_amount: 1.0,
624            shadow_color: Color::from_argb(255, 0, 0, 0),
625            optimization: EffectOptimization::Balanced,
626            source: None,
627        }
628    }
629}