Skip to main content

ferroui_base/rendering/composition/brushes/
server_composition_effect_brush.rs

1//! The server-side counterparts of the WinUI effect and backdrop brushes:
2//! the drawing of an effect graph over its sources, as a brush and as the
3//! effect of a layer visual.
4
5use super::{paint_composition_brush, ServerCompositionSurfaceBrush};
6use crate::media::immutable::ImmutableSolidColorBrush;
7use crate::media::imaging::BitmapBlendingMode;
8use crate::media::{BoxShadows, Color, ColorMatrix, ColorMatrixAlphaMode, EffectExtensions, IEffect, ImmutableBlurEffect};
9use crate::platform::{AlphaFormat, IDrawingContextImpl, PixelFormat, PixelFormats};
10use crate::rendering::composition::effects::{
11    blur_amount_to_radius, BlendEffectMode, EffectColor, EffectGraph, EffectMatrix, EffectNode, EffectNodeKind,
12    EffectScalar, PixelOp,
13};
14use crate::rendering::composition::generated::{ServerCompositionBrushHooks, ServerCompositionBrushProps};
15use crate::rendering::composition::server::{
16    impl_server_render_resource, IAnimatedServerObject, IServerObject, IServerRenderResource,
17    IServerRenderResourceObserver, ServerCompositionPropertySet, ServerCompositor, ServerObjectId,
18    ServerRenderResource,
19};
20use crate::rendering::composition::transport::BatchStreamReader;
21use crate::{Matrix, PixelSize, Rect, RoundedRect, Thickness, Vector};
22use std::any::{Any, TypeId};
23use std::cell::{Cell, RefCell};
24use std::rc::{Rc, Weak};
25use std::sync::Arc;
26use std::time::Duration;
27
28/// The changes of an effect brush that one batch carries: the property set
29/// of the brush (with the first batch) and the brushes bound to its source
30/// parameters (when they changed).
31pub(crate) struct EffectBrushChanges {
32    pub(crate) properties: Option<ServerObjectId>,
33    pub(crate) sources: Option<Vec<(String, Option<ServerObjectId>)>>,
34}
35
36/// Which side of the composed layer of a layer visual is drawn.
37#[derive(Clone, Copy, PartialEq, Eq)]
38enum LayerPhase {
39    /// Before the children: what is under the layer, and what wraps it is
40    /// pushed.
41    Before,
42    /// After the children: what wraps the layer is popped, and what is over
43    /// it is drawn.
44    After,
45}
46
47fn is_empty(rect: &Rect) -> bool {
48    rect.width <= 0.0 || rect.height <= 0.0
49}
50
51/// The server-side counterpart of a
52/// [`CompositionEffectBrush`](super::CompositionEffectBrush), painted by
53/// the visuals that show it ([`paint_composition_brush`]) and applied by
54/// the layer visuals whose effect it is.
55///
56/// The animatable properties of the graph are read from the property set
57/// of the brush (its `Properties`), whose changes invalidate the brush.
58pub struct ServerCompositionEffectBrush {
59    this: Weak<ServerCompositionEffectBrush>,
60    base: ServerRenderResource,
61    props: ServerCompositionBrushProps,
62    graph: Arc<EffectGraph>,
63    properties: RefCell<Option<Rc<ServerCompositionPropertySet>>>,
64    sources: RefCell<Vec<(String, Option<Rc<dyn IServerObject>>)>>,
65    /// Set while the brush paints: a source that contains the brush is not
66    /// painted inside itself.
67    painting: Cell<bool>,
68}
69
70impl ServerCompositionEffectBrush {
71    /// Creates the brush for the graph of its factory, active: the UI-thread
72    /// brush activates its server object when it is created.
73    pub(crate) fn new(compositor: &Rc<ServerCompositor>, graph: Arc<EffectGraph>) -> Rc<ServerCompositionEffectBrush> {
74        let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionEffectBrush>| ServerCompositionEffectBrush {
75            this: this.clone(),
76            base: ServerRenderResource::new(
77                compositor,
78                this.clone() as Weak<dyn IAnimatedServerObject>,
79                this.clone() as Weak<dyn IServerRenderResource>,
80            ),
81            props: ServerCompositionBrushProps::new(),
82            graph,
83            properties: RefCell::new(None),
84            sources: RefCell::new(Vec::new()),
85            painting: Cell::new(false),
86        });
87        brush.base.object().activate();
88        brush
89    }
90
91    /// The properties of the generated `ServerCompositionBrush`.
92    pub fn brush_props(&self) -> &ServerCompositionBrushProps {
93        &self.props
94    }
95
96    pub fn is_disposed(&self) -> bool {
97        self.base.is_disposed()
98    }
99
100    /// Whether the factory of the brush loaded its effect (otherwise the
101    /// brush draws nothing).
102    pub fn is_loaded(&self) -> bool {
103        self.graph.is_loaded()
104    }
105
106    /// The property set of the brush, once the first batch arrived.
107    pub fn properties(&self) -> Option<Rc<ServerCompositionPropertySet>> {
108        self.properties.borrow().clone()
109    }
110
111    /// The brush bound to the source parameter `name`.
112    pub fn source(&self, name: &str) -> Option<Rc<dyn IServerObject>> {
113        self.sources.borrow().iter().find(|(existing, _)| existing == name).and_then(|(_, source)| source.clone())
114    }
115
116    fn subscription_key(&self) -> usize {
117        self.this.as_ptr() as *const () as usize
118    }
119
120    fn observer(&self) -> Option<Rc<dyn IServerRenderResourceObserver>> {
121        self.this.upgrade().map(|this| this as Rc<dyn IServerRenderResourceObserver>)
122    }
123
124    /// Follows the changes of the entries of the property set: they are the
125    /// animatable properties of the graph.
126    fn set_properties(&self, properties: Option<Rc<ServerCompositionPropertySet>>) {
127        if let Some(old) = self.properties.borrow().as_ref() {
128            old.changed().remove(self.subscription_key());
129        }
130        if let Some(new) = &properties {
131            let weak = self.this.clone();
132            new.changed().add(
133                self.subscription_key(),
134                Rc::new(move || {
135                    if let Some(brush) = weak.upgrade() {
136                        brush.base.core().invalidated(&*brush);
137                    }
138                }),
139            );
140        }
141        *self.properties.borrow_mut() = properties;
142    }
143
144    /// Whether `source` is this brush.
145    fn is_self(&self, source: &Rc<dyn IServerObject>) -> bool {
146        std::ptr::addr_eq(Rc::as_ptr(source), self.this.as_ptr())
147    }
148
149    /// Binds the source parameters, observing the render resources among the
150    /// bound brushes (a change of a source redraws the effect).
151    fn set_sources(&self, sources: Vec<(String, Option<Rc<dyn IServerObject>>)>) {
152        let old = std::mem::replace(&mut *self.sources.borrow_mut(), sources);
153        let Some(observer) = self.observer() else { return };
154        let resources = |sources: &[(String, Option<Rc<dyn IServerObject>>)]| -> Vec<Rc<dyn IServerRenderResource>> {
155            sources
156                .iter()
157                .filter_map(|(_, source)| source.clone())
158                .filter(|source| !self.is_self(source))
159                .filter_map(|source| source.as_render_resource())
160                .collect()
161        };
162        for resource in resources(old.as_slice()) {
163            resource.remove_observer(&observer);
164        }
165        let new = resources(self.sources.borrow().as_slice());
166        for resource in new {
167            resource.add_observer(&observer);
168        }
169    }
170
171    /// The current value of a scalar member: the entry of the property set
172    /// for an animatable member, the value of the description otherwise.
173    fn scalar(&self, value: &EffectScalar) -> f64 {
174        if let (Some(key), Some(properties)) = (&value.key, self.properties.borrow().as_ref()) {
175            if let Some(current) = properties.get_value(key).try_cast::<f64>() {
176                return current;
177            }
178        }
179        value.value as f64
180    }
181
182    /// The current value of a colour member; see [`scalar`](Self::scalar).
183    fn color(&self, value: &EffectColor) -> Color {
184        if let (Some(key), Some(properties)) = (&value.key, self.properties.borrow().as_ref()) {
185            if let Some(current) = properties.get_value(key).try_cast::<Color>() {
186                return current;
187            }
188        }
189        value.value
190    }
191
192    /// The blur radius of the drawing contexts for a blur node, 0 for none.
193    fn blur_radius(&self, amount: &EffectScalar) -> f64 {
194        blur_amount_to_radius(self.scalar(amount))
195    }
196
197    /// A weight or opacity, clamped to 0..1.
198    fn unit(&self, value: &EffectScalar) -> f64 {
199        let value = self.scalar(value);
200        if value.is_nan() {
201            return 0.0;
202        }
203        value.clamp(0.0, 1.0)
204    }
205
206    /// Pushes a blur of `radius` when the canvas has effects; returns
207    /// whether it was pushed (the matching `pop_blur` is called with the
208    /// same answer).
209    fn push_blur(canvas: &mut dyn IDrawingContextImpl, rect: Rect, radius: f64) -> bool {
210        if radius <= 0.0 {
211            return false;
212        }
213        match canvas.as_drawing_context_impl_with_effects() {
214            Some(effects) => {
215                effects.push_effect(Some(rect), &ImmutableBlurEffect::new(radius));
216                true
217            }
218            // Deviation (winui-composition.md): a drawing context without
219            // effects draws the source unblurred.
220            None => false,
221        }
222    }
223
224    fn pop_blur(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
225        if pushed {
226            if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
227                effects.pop_effect();
228            }
229        }
230    }
231
232    /// Whether a blur of `radius` is pushed on `canvas` (the same answer as
233    /// `push_blur` gives).
234    fn blur_is_pushed(canvas: &mut dyn IDrawingContextImpl, radius: f64) -> bool {
235        radius > 0.0 && canvas.as_drawing_context_impl_with_effects().is_some()
236    }
237
238    /// Paints `node` over `rect` with `opacity` (nothing at 0).
239    fn paint_node_with_opacity(&self, node: &EffectNode, canvas: &mut dyn IDrawingContextImpl, rect: Rect, opacity: f64) {
240        if opacity <= 0.0 {
241            return;
242        }
243        if opacity < 1.0 {
244            canvas.push_opacity(opacity, Some(rect));
245        }
246        self.paint_node(node, canvas, rect);
247        if opacity < 1.0 {
248            canvas.pop_opacity();
249        }
250    }
251
252    /// The reach of a blur of `radius` beyond its area: how far a blur that
253    /// reads its source beyond the area (the `Hard` border mode) reads it.
254    fn blur_reach(radius: f64) -> f64 {
255        if radius <= 0.0 {
256            return 0.0;
257        }
258        let blur = ImmutableBlurEffect::new(radius);
259        let padding = EffectExtensions::get_effect_output_padding(Some(&blur as &dyn IEffect));
260        padding.left.max(padding.top).max(padding.right).max(padding.bottom)
261    }
262
263    /// The current matrix of a colour matrix node.
264    fn matrix(&self, matrix: &EffectMatrix) -> ColorMatrix {
265        match matrix {
266            EffectMatrix::Fixed(matrix) => *matrix,
267            EffectMatrix::Saturation(saturation) => ColorMatrix::saturation(self.scalar(saturation) as f32),
268            EffectMatrix::Tint(color) => {
269                let color = self.color(color);
270                ColorMatrix::tint([color.r, color.g, color.b, color.a].map(|channel| channel as f32 / 255.0))
271            }
272        }
273    }
274
275    /// The colour of a colour source node (premultiplied channels, 0 to 1).
276    fn node_color(&self, node: &EffectNode) -> [f32; 4] {
277        let EffectNodeKind::ColorSource(color) = &node.kind else { return [0.0; 4] };
278        let color = self.color(color);
279        let alpha = color.a as f32 / 255.0;
280        [color.r as f32 / 255.0 * alpha, color.g as f32 / 255.0 * alpha, color.b as f32 / 255.0 * alpha, alpha]
281    }
282
283    /// The colour matrix (applied to premultiplied colours as they are) an arithmetic composite
284    /// of an image and a colour is: `image * (k1 c + k_image) + (k_color c + k4)`.
285    fn arithmetic_matrix(
286        &self,
287        multiply: &EffectScalar,
288        source1: &EffectScalar,
289        source2: &EffectScalar,
290        offset: &EffectScalar,
291        image_is_source1: bool,
292        color: &EffectNode,
293    ) -> ColorMatrix {
294        let c = self.node_color(color);
295        let k1 = self.scalar(multiply) as f32;
296        let (k_image, k_color) = if image_is_source1 {
297            (self.scalar(source1) as f32, self.scalar(source2) as f32)
298        } else {
299            (self.scalar(source2) as f32, self.scalar(source1) as f32)
300        };
301        let k4 = self.scalar(offset) as f32;
302        ColorMatrix::scale_and_offset(c.map(|c| k1 * c + k_image), c.map(|c| k_color * c + k4))
303    }
304
305    /// Pushes a blend layer when the canvas has them; returns whether it did.
306    fn push_blend(canvas: &mut dyn IDrawingContextImpl, rect: Rect, mode: BitmapBlendingMode) -> bool {
307        match canvas.as_drawing_context_impl_with_layer_effects() {
308            Some(layers) => {
309                layers.push_blend_layer(rect, mode);
310                true
311            }
312            // Deviation (winui-materials.md): without the layers of the
313            // drawing context the foreground is drawn source-over.
314            None => false,
315        }
316    }
317
318    fn pop_blend(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
319        if pushed {
320            if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
321                layers.pop_blend_layer();
322            }
323        }
324    }
325
326    /// Pushes a colour matrix layer when the canvas has them and the matrix
327    /// changes something; returns whether it did.
328    fn push_matrix(canvas: &mut dyn IDrawingContextImpl, rect: Rect, matrix: &ColorMatrix, alpha_mode: ColorMatrixAlphaMode) -> bool {
329        if matrix.is_identity() {
330            return false;
331        }
332        match canvas.as_drawing_context_impl_with_layer_effects() {
333            Some(layers) => {
334                layers.push_color_matrix_layer(rect, matrix, alpha_mode);
335                true
336            }
337            // Deviation (winui-materials.md): without the layers of the
338            // drawing context the source is drawn unchanged.
339            None => false,
340        }
341    }
342
343    fn pop_matrix(canvas: &mut dyn IDrawingContextImpl, pushed: bool) {
344        if pushed {
345            if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
346                layers.pop_color_matrix_layer();
347            }
348        }
349    }
350
351    /// Paints `node` over `rect`.
352    fn paint_node(&self, node: &EffectNode, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
353        match &node.kind {
354            EffectNodeKind::Parameter(name) => {
355                if let Some(source) = self.source(name).filter(|source| !self.is_self(source)) {
356                    paint_composition_brush(canvas, &source, rect);
357                }
358            }
359            EffectNodeKind::GaussianBlur { amount, hard, source } => {
360                let radius = self.blur_radius(amount);
361                if *hard && Self::blur_is_pushed(canvas, radius) {
362                    // The `Hard` border mode: the blur reads its source beyond
363                    // the area, so its edges do not fade; its output is cut
364                    // to the area.
365                    let reach = Self::blur_reach(radius);
366                    let wide = rect.inflate(reach);
367                    canvas.push_clip(rect);
368                    let pushed = Self::push_blur(canvas, wide, radius);
369                    self.paint_node(source, canvas, wide);
370                    Self::pop_blur(canvas, pushed);
371                    canvas.pop_clip();
372                } else {
373                    let pushed = Self::push_blur(canvas, rect, radius);
374                    self.paint_node(source, canvas, rect);
375                    Self::pop_blur(canvas, pushed);
376                }
377            }
378            EffectNodeKind::Opacity { opacity, source } => {
379                self.paint_node_with_opacity(source, canvas, rect, self.unit(opacity));
380            }
381            EffectNodeKind::CrossFade { weight, source1, source2 } => {
382                // `source1 * (1 - weight) + source2 * weight` (premultiplied):
383                // the second source is added to the first in a layer of their
384                // own. Without blend layers the second is drawn over the first.
385                let weight = self.unit(weight);
386                if weight <= 0.0 || weight >= 1.0 {
387                    self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
388                    self.paint_node_with_opacity(source2, canvas, rect, weight);
389                    return;
390                }
391                canvas.push_layer(rect);
392                self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
393                let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::Plus);
394                self.paint_node_with_opacity(source2, canvas, rect, weight);
395                Self::pop_blend(canvas, pushed);
396                canvas.pop_layer();
397            }
398            EffectNodeKind::Composite { mode, sources } => {
399                if *mode == BitmapBlendingMode::SourceOver {
400                    for source in sources {
401                        self.paint_node(source, canvas, rect);
402                    }
403                    return;
404                }
405                // Each source composed onto what the sources before it made,
406                // in a layer of their own.
407                canvas.push_layer(rect);
408                for (index, source) in sources.iter().enumerate() {
409                    let pushed = index > 0 && Self::push_blend(canvas, rect, *mode);
410                    self.paint_node(source, canvas, rect);
411                    Self::pop_blend(canvas, pushed);
412                }
413                canvas.pop_layer();
414            }
415            EffectNodeKind::ColorSource(color) => {
416                let color = self.color(color);
417                if color.a > 0 {
418                    let brush = ImmutableSolidColorBrush::new(color);
419                    canvas.draw_rectangle(Some(&brush), None, RoundedRect::from_rect(rect), &BoxShadows::default());
420                }
421            }
422            EffectNodeKind::Blend { mode, background, foreground } => {
423                // The background and the foreground mixed in a layer of their
424                // own, which is drawn source-over.
425                canvas.push_layer(rect);
426                self.paint_node(background, canvas, rect);
427                let pushed = Self::push_blend(canvas, rect, *mode);
428                self.paint_node(foreground, canvas, rect);
429                Self::pop_blend(canvas, pushed);
430                canvas.pop_layer();
431            }
432            EffectNodeKind::ColorMatrix { matrix, alpha_mode, source } => {
433                let matrix = self.matrix(matrix);
434                let pushed = Self::push_matrix(canvas, rect, &matrix, *alpha_mode);
435                self.paint_node(source, canvas, rect);
436                Self::pop_matrix(canvas, pushed);
437            }
438            EffectNodeKind::AlphaMask { source, mask } => {
439                canvas.push_layer(rect);
440                self.paint_node(source, canvas, rect);
441                let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
442                if pushed {
443                    self.paint_node(mask, canvas, rect);
444                }
445                Self::pop_blend(canvas, pushed);
446                canvas.pop_layer();
447            }
448            EffectNodeKind::Border { tile, source } => {
449                let surface = match &source.kind {
450                    EffectNodeKind::Parameter(name) if *tile => self.source(name),
451                    _ => None,
452                };
453                match surface.as_ref().and_then(|surface| surface.as_any().downcast_ref::<ServerCompositionSurfaceBrush>()) {
454                    Some(surface) => surface.paint_tiled(canvas, rect),
455                    None => self.paint_node(source, canvas, rect),
456                }
457            }
458            EffectNodeKind::ArithmeticWithColor { multiply, source1, source2, offset, image_is_source1, image, color } => {
459                let matrix = self.arithmetic_matrix(multiply, source1, source2, offset, *image_is_source1, color);
460                let pushed = Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Straight);
461                self.paint_node(image, canvas, rect);
462                Self::pop_matrix(canvas, pushed);
463            }
464            EffectNodeKind::Shadow { amount, color, source } => {
465                let radius = self.blur_radius(amount);
466                let matrix = shadow_matrix(self.color(color));
467                let blurred = Self::push_blur(canvas, rect, radius);
468                let colored = Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Premultiplied);
469                self.paint_node(source, canvas, rect);
470                Self::pop_matrix(canvas, colored);
471                Self::pop_blur(canvas, blurred);
472            }
473            EffectNodeKind::Pixels { op, sources } => self.paint_pixels(op, sources, canvas, rect),
474            EffectNodeKind::SceneLighting { ambient, diffuse, specular, shine } => {
475                let material = crate::rendering::composition::server::LightingMaterial {
476                    ambient: self.scalar(ambient),
477                    diffuse: self.scalar(diffuse),
478                    specular: self.scalar(specular),
479                    shine: self.scalar(shine),
480                };
481                crate::rendering::composition::server::paint_scene_lighting(canvas, rect, &material);
482            }
483        }
484    }
485
486    /// Draws `node` over `rect` into a bitmap of its own at the resolution of
487    /// `canvas` and returns its premultiplied RGBA pixels, or `None` when the
488    /// compositor has no render interface.
489    fn render_node_pixels(&self, node: &EffectNode, canvas: &dyn IDrawingContextImpl, rect: Rect, size: PixelSize) -> Option<Vec<u8>> {
490        let compositor = self.base.compositor()?;
491        let transform = Matrix::create_translation(-rect.x, -rect.y)
492            * Matrix::create_scale(size.width as f64 / rect.width, size.height as f64 / rect.height);
493        let _ = canvas;
494        compositor.render_interface().with_platform_render_interface(|render_interface| {
495            let bitmap = render_interface.create_render_target_bitmap(size, Vector::new(96.0, 96.0));
496            {
497                let mut context = bitmap.create_drawing_context();
498                context.set_transform(transform);
499                self.paint_node(node, &mut *context, rect);
500                context.dispose();
501            }
502            let framebuffer = bitmap.lock();
503            let (width, height) = (size.width as usize, size.height as usize);
504            let row_bytes = framebuffer.row_bytes() as usize;
505            let bgra = framebuffer.format() == PixelFormat::BGRA8888;
506            let premultiplied = framebuffer.alpha_format() != AlphaFormat::Unpremul;
507            let mut rgba = vec![0u8; width * height * 4];
508            framebuffer.with_data(&mut |data| {
509                for y in 0..height {
510                    for x in 0..width {
511                        let at = y * row_bytes + x * 4;
512                        let Some(pixel) = data.get(at..at + 4) else { continue };
513                        let (mut r, mut g, mut b, a) =
514                            if bgra { (pixel[2], pixel[1], pixel[0], pixel[3]) } else { (pixel[0], pixel[1], pixel[2], pixel[3]) };
515                        if !premultiplied {
516                            let scale = |c: u8| ((u32::from(c) * u32::from(a) + 127) / 255) as u8;
517                            (r, g, b) = (scale(r), scale(g), scale(b));
518                        }
519                        rgba[(y * width + x) * 4..(y * width + x) * 4 + 4].copy_from_slice(&[r, g, b, a]);
520                    }
521                }
522            });
523            framebuffer.dispose();
524            rgba
525        })
526    }
527
528    /// Paints an operation on the pixels of `sources`: each source is drawn
529    /// apart into a bitmap at the resolution of `canvas` (at most 4096
530    /// pixels a side), the pixels are combined, and the result is drawn over
531    /// `rect`. Nothing is drawn without a render interface.
532    fn paint_pixels(&self, op: &PixelOp, sources: &[EffectNode], canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
533        if sources.is_empty() || is_empty(&rect) {
534            return;
535        }
536        let transform = canvas.transform();
537        let scale_x = (transform.m11 * transform.m11 + transform.m12 * transform.m12).sqrt().max(1e-6);
538        let scale_y = (transform.m21 * transform.m21 + transform.m22 * transform.m22).sqrt().max(1e-6);
539        let size = PixelSize::new(
540            ((rect.width * scale_x).ceil() as i32).clamp(1, 4096),
541            ((rect.height * scale_y).ceil() as i32).clamp(1, 4096),
542        );
543        let mut images = Vec::with_capacity(sources.len());
544        for source in sources {
545            match self.render_node_pixels(source, canvas, rect, size) {
546                Some(pixels) => images.push(pixels),
547                None => return,
548            }
549        }
550        let mut images = images.into_iter();
551        let Some(mut result) = images.next() else { return };
552        let width = size.width as usize;
553        for (index, other) in images.enumerate() {
554            match op {
555                PixelOp::Blend(mode) => blend_pixels(*mode, &mut result, &other, width),
556                PixelOp::Arithmetic { multiply, source1, source2, offset } => {
557                    let k = [self.scalar(multiply), self.scalar(source1), self.scalar(source2), self.scalar(offset)];
558                    arithmetic_pixels(k.map(|k| k as f32), &mut result, &other);
559                }
560                PixelOp::MaskInvert => mask_invert_pixels(&mut result, &other),
561            }
562            // A blend and an arithmetic composite have two sources.
563            if !matches!(op, PixelOp::MaskInvert) && index == 0 {
564                break;
565            }
566        }
567        let Some(compositor) = self.base.compositor() else { return };
568        let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
569            render_interface.load_bitmap_from_pixels(
570                PixelFormats::RGBA8888,
571                AlphaFormat::Premul,
572                &result,
573                size,
574                Vector::new(96.0, 96.0),
575                size.width * 4,
576            )
577        });
578        if let Some(bitmap) = bitmap {
579            let source = Rect::new(0.0, 0.0, size.width as f64, size.height as f64);
580            canvas.draw_bitmap(&*bitmap, 1.0, source, rect);
581        }
582    }
583
584    /// Paints the brush over `rect`, in the coordinates of `canvas`: the
585    /// graph over its bound sources. A brush whose factory did not load its
586    /// effect paints nothing.
587    pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
588        let Some(root) = self.graph.root.as_ref() else { return };
589        let opacity = self.props.opacity();
590        if opacity <= 0.0 || is_empty(&rect) || self.painting.replace(true) {
591            return;
592        }
593        if opacity != 1.0 {
594            canvas.push_opacity(opacity, Some(rect));
595        }
596        self.paint_node(root, canvas, rect);
597        if opacity != 1.0 {
598            canvas.pop_opacity();
599        }
600        self.painting.set(false);
601    }
602
603    /// One side of the drawing of `node` around the composed layer, which is
604    /// at `path` (the indices of the inputs) below `node`.
605    fn paint_around_layer(
606        &self,
607        node: &EffectNode,
608        path: &[usize],
609        canvas: &mut dyn IDrawingContextImpl,
610        rect: Rect,
611        phase: LayerPhase,
612    ) {
613        let Some((&index, rest)) = path.split_first() else {
614            // The source parameter of the layer: the children of the visual
615            // are drawn here by the render walk.
616            return;
617        };
618        let before = phase == LayerPhase::Before;
619        let has_layers = canvas.as_drawing_context_impl_with_layer_effects().is_some();
620        match &node.kind {
621            EffectNodeKind::Parameter(_) | EffectNodeKind::ColorSource(_) | EffectNodeKind::SceneLighting { .. } => {}
622            EffectNodeKind::GaussianBlur { amount, source, .. } => {
623                let radius = self.blur_radius(amount);
624                match phase {
625                    LayerPhase::Before => {
626                        Self::push_blur(canvas, rect, radius);
627                        self.paint_around_layer(source, rest, canvas, rect, phase);
628                    }
629                    LayerPhase::After => {
630                        self.paint_around_layer(source, rest, canvas, rect, phase);
631                        let pushed = Self::blur_is_pushed(canvas, radius);
632                        Self::pop_blur(canvas, pushed);
633                    }
634                }
635            }
636            EffectNodeKind::Opacity { opacity, source } => {
637                let opacity = self.unit(opacity);
638                self.wrap_layer_in_opacity(source, rest, canvas, rect, phase, opacity);
639            }
640            EffectNodeKind::CrossFade { weight, source1, source2 } => {
641                let weight = self.unit(weight);
642                if index == 0 {
643                    self.wrap_layer_in_opacity(source1, rest, canvas, rect, phase, 1.0 - weight);
644                    if phase == LayerPhase::After {
645                        self.paint_node_with_opacity(source2, canvas, rect, weight);
646                    }
647                } else {
648                    if phase == LayerPhase::Before {
649                        self.paint_node_with_opacity(source1, canvas, rect, 1.0 - weight);
650                    }
651                    self.wrap_layer_in_opacity(source2, rest, canvas, rect, phase, weight);
652                }
653            }
654            EffectNodeKind::Composite { mode, sources } => {
655                let Some(layer) = sources.get(index) else { return };
656                let blended = *mode != BitmapBlendingMode::SourceOver && has_layers;
657                if before {
658                    if blended {
659                        canvas.push_layer(rect);
660                    }
661                    for (position, source) in sources[..index].iter().enumerate() {
662                        let pushed = blended && position > 0 && Self::push_blend(canvas, rect, *mode);
663                        self.paint_node(source, canvas, rect);
664                        Self::pop_blend(canvas, pushed);
665                    }
666                    if blended && index > 0 {
667                        Self::push_blend(canvas, rect, *mode);
668                    }
669                    self.paint_around_layer(layer, rest, canvas, rect, phase);
670                } else {
671                    self.paint_around_layer(layer, rest, canvas, rect, phase);
672                    Self::pop_blend(canvas, blended && index > 0);
673                    for source in &sources[index + 1..] {
674                        let pushed = blended && Self::push_blend(canvas, rect, *mode);
675                        self.paint_node(source, canvas, rect);
676                        Self::pop_blend(canvas, pushed);
677                    }
678                    if blended {
679                        canvas.pop_layer();
680                    }
681                }
682            }
683            EffectNodeKind::Blend { mode, background, foreground } => {
684                if index == 0 {
685                    // The layer is the background.
686                    if before {
687                        canvas.push_layer(rect);
688                        self.paint_around_layer(background, rest, canvas, rect, phase);
689                    } else {
690                        self.paint_around_layer(background, rest, canvas, rect, phase);
691                        let pushed = Self::push_blend(canvas, rect, *mode);
692                        self.paint_node(foreground, canvas, rect);
693                        Self::pop_blend(canvas, pushed);
694                        canvas.pop_layer();
695                    }
696                } else if before {
697                    // The layer is the foreground.
698                    canvas.push_layer(rect);
699                    self.paint_node(background, canvas, rect);
700                    Self::push_blend(canvas, rect, *mode);
701                    self.paint_around_layer(foreground, rest, canvas, rect, phase);
702                } else {
703                    self.paint_around_layer(foreground, rest, canvas, rect, phase);
704                    Self::pop_blend(canvas, has_layers);
705                    canvas.pop_layer();
706                }
707            }
708            EffectNodeKind::ColorMatrix { matrix, alpha_mode, source } => {
709                let matrix = self.matrix(matrix);
710                if before {
711                    Self::push_matrix(canvas, rect, &matrix, *alpha_mode);
712                    self.paint_around_layer(source, rest, canvas, rect, phase);
713                } else {
714                    self.paint_around_layer(source, rest, canvas, rect, phase);
715                    Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
716                }
717            }
718            EffectNodeKind::ArithmeticWithColor { multiply, source1, source2, offset, image_is_source1, image, color } => {
719                let image_index = if *image_is_source1 { 0 } else { 1 };
720                if index != image_index {
721                    // The layer is the colour: not an image.
722                    return;
723                }
724                let matrix = self.arithmetic_matrix(multiply, source1, source2, offset, *image_is_source1, color);
725                if before {
726                    Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Straight);
727                    self.paint_around_layer(image, rest, canvas, rect, phase);
728                } else {
729                    self.paint_around_layer(image, rest, canvas, rect, phase);
730                    Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
731                }
732            }
733            EffectNodeKind::AlphaMask { source, mask } => {
734                if index == 0 {
735                    // The layer is the source.
736                    if before {
737                        canvas.push_layer(rect);
738                        self.paint_around_layer(source, rest, canvas, rect, phase);
739                    } else {
740                        self.paint_around_layer(source, rest, canvas, rect, phase);
741                        let pushed = Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
742                        if pushed {
743                            self.paint_node(mask, canvas, rect);
744                        }
745                        Self::pop_blend(canvas, pushed);
746                        canvas.pop_layer();
747                    }
748                } else if before {
749                    // The layer is the mask.
750                    canvas.push_layer(rect);
751                    self.paint_node(source, canvas, rect);
752                    Self::push_blend(canvas, rect, BitmapBlendingMode::DestinationIn);
753                    self.paint_around_layer(mask, rest, canvas, rect, phase);
754                } else {
755                    self.paint_around_layer(mask, rest, canvas, rect, phase);
756                    Self::pop_blend(canvas, has_layers);
757                    canvas.pop_layer();
758                }
759            }
760            EffectNodeKind::Border { source, .. } => {
761                self.paint_around_layer(source, rest, canvas, rect, phase);
762            }
763            EffectNodeKind::Shadow { amount, color, source } => {
764                let radius = self.blur_radius(amount);
765                let matrix = shadow_matrix(self.color(color));
766                if before {
767                    Self::push_blur(canvas, rect, radius);
768                    Self::push_matrix(canvas, rect, &matrix, ColorMatrixAlphaMode::Premultiplied);
769                    self.paint_around_layer(source, rest, canvas, rect, phase);
770                } else {
771                    self.paint_around_layer(source, rest, canvas, rect, phase);
772                    Self::pop_matrix(canvas, has_layers && !matrix.is_identity());
773                    let pushed = Self::blur_is_pushed(canvas, radius);
774                    Self::pop_blur(canvas, pushed);
775                }
776            }
777            // Deviation (winui-materials.md): the children of a layer visual
778            // are drawn by the render walk, not into a bitmap of their own,
779            // so an operation on their pixels draws them unchanged.
780            EffectNodeKind::Pixels { sources, .. } => {
781                if let Some(layer) = sources.get(index) {
782                    self.paint_around_layer(layer, rest, canvas, rect, phase);
783                }
784            }
785        }
786    }
787
788    /// One side of an opacity around the input of a node that leads to the
789    /// layer.
790    fn wrap_layer_in_opacity(
791        &self,
792        source: &EffectNode,
793        rest: &[usize],
794        canvas: &mut dyn IDrawingContextImpl,
795        rect: Rect,
796        phase: LayerPhase,
797        opacity: f64,
798    ) {
799        match phase {
800            LayerPhase::Before => {
801                if opacity < 1.0 {
802                    canvas.push_opacity(opacity.max(0.0), Some(rect));
803                }
804                self.paint_around_layer(source, rest, canvas, rect, phase);
805            }
806            LayerPhase::After => {
807                self.paint_around_layer(source, rest, canvas, rect, phase);
808                if opacity < 1.0 {
809                    canvas.pop_opacity();
810                }
811            }
812        }
813    }
814
815    /// What a layer visual whose effect is the brush draws before its
816    /// children, over `rect` (the bounds of the composed layer): the parts
817    /// of the graph under the layer, and what wraps the layer (blurs,
818    /// opacities) pushed. The children are the first source parameter of the
819    /// graph; [`pop_for_layer`](Self::pop_for_layer) is called after them
820    /// with the same rectangle.
821    ///
822    /// A graph without a source parameter is drawn whole and the children
823    /// are not shown; a brush whose factory did not load its effect shows
824    /// nothing.
825    pub fn push_for_layer(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
826        let opacity = self.props.opacity();
827        if opacity != 1.0 {
828            canvas.push_opacity(opacity.max(0.0), Some(rect));
829        }
830        match (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) {
831            (Some(root), Some(path)) => self.paint_around_layer(root, path, canvas, rect, LayerPhase::Before),
832            (root, _) => {
833                if let Some(root) = root {
834                    self.paint_node(root, canvas, rect);
835                }
836                // Nothing of the children is shown.
837                canvas.push_clip(Rect::new(rect.x, rect.y, 0.0, 0.0));
838            }
839        }
840    }
841
842    /// What a layer visual whose effect is the brush draws after its
843    /// children: see [`push_for_layer`](Self::push_for_layer).
844    pub fn pop_for_layer(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
845        match (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) {
846            (Some(root), Some(path)) => self.paint_around_layer(root, path, canvas, rect, LayerPhase::After),
847            _ => canvas.pop_clip(),
848        }
849        if self.props.opacity() != 1.0 {
850            canvas.pop_opacity();
851        }
852    }
853
854    /// How far the output of the brush as the effect of a layer extends
855    /// beyond the composed layer: the padding of the blurs around it.
856    pub fn layer_padding(&self) -> Thickness {
857        let mut padding = Thickness::default();
858        let (Some(mut node), Some(path)) = (self.graph.root.as_ref(), self.graph.layer_path.as_deref()) else {
859            return padding;
860        };
861        for &index in path {
862            if let EffectNodeKind::GaussianBlur { amount, .. } = &node.kind {
863                let radius = self.blur_radius(amount);
864                if radius > 0.0 {
865                    let blur = ImmutableBlurEffect::new(radius);
866                    padding = padding + EffectExtensions::get_effect_output_padding(Some(&blur as &dyn IEffect));
867                }
868            }
869            match node.sources().get(index).copied() {
870                Some(source) => node = source,
871                None => break,
872            }
873        }
874        padding
875    }
876}
877
878impl_server_render_resource!(
879    ServerCompositionEffectBrush,
880    base,
881    composition_property: ServerCompositionBrushProps::get_composition_property
882);
883
884impl ServerCompositionBrushHooks for ServerCompositionEffectBrush {}
885
886impl IServerObject for ServerCompositionEffectBrush {
887    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
888        self.props.deserialize_changes_core(self, reader, committed_at);
889        let Some(changes) = reader.read_value::<EffectBrushChanges>() else { return };
890        let compositor = self.base.compositor();
891        let resolve = |id: ServerObjectId| compositor.as_ref().and_then(|compositor| compositor.get_object(id));
892        if let Some(id) = changes.properties {
893            let properties = resolve(id).and_then(|set| set.into_any_rc().downcast::<ServerCompositionPropertySet>().ok());
894            self.set_properties(properties);
895        }
896        if let Some(sources) = changes.sources {
897            self.set_sources(sources.into_iter().map(|(name, id)| (name, id.and_then(resolve))).collect());
898        }
899    }
900
901    fn values_invalidated(&self) {
902        self.base.core().invalidated(self);
903    }
904
905    fn dispose(&self) {
906        self.set_properties(None);
907        self.set_sources(Vec::new());
908        self.base.core().dispose();
909    }
910
911    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
912        Some(self)
913    }
914
915    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
916        Some(self)
917    }
918
919    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
920        self.props.find_props(type_id)
921    }
922
923    fn as_any(&self) -> &dyn Any {
924        self
925    }
926
927    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
928        self
929    }
930}
931
932/// The server-side counterpart of a
933/// [`CompositionBackdropBrush`](super::CompositionBackdropBrush): it paints
934/// what was drawn under the visual that paints it, drawn again by the
935/// compositor ([`draw_backdrop`](crate::rendering::composition::server::draw_backdrop),
936/// winui-materials.md).
937pub struct ServerCompositionBackdropBrush {
938    base: ServerRenderResource,
939    props: ServerCompositionBrushProps,
940}
941
942impl ServerCompositionBackdropBrush {
943    /// Creates the brush, active.
944    pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionBackdropBrush> {
945        let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionBackdropBrush>| ServerCompositionBackdropBrush {
946            base: ServerRenderResource::new(
947                compositor,
948                this.clone() as Weak<dyn IAnimatedServerObject>,
949                this.clone() as Weak<dyn IServerRenderResource>,
950            ),
951            props: ServerCompositionBrushProps::new(),
952        });
953        brush.base.object().activate();
954        brush
955    }
956
957    /// The properties of the generated `ServerCompositionBrush`.
958    pub fn brush_props(&self) -> &ServerCompositionBrushProps {
959        &self.props
960    }
961
962    /// Paints the backdrop of the visual being drawn over `rect`, with the
963    /// opacity of the brush.
964    pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
965        let opacity = self.props.opacity();
966        if opacity <= 0.0 {
967            return;
968        }
969        if opacity < 1.0 {
970            canvas.push_opacity(opacity, Some(rect));
971        }
972        crate::rendering::composition::server::draw_backdrop(canvas, rect);
973        if opacity < 1.0 {
974            canvas.pop_opacity();
975        }
976    }
977}
978
979impl_server_render_resource!(
980    ServerCompositionBackdropBrush,
981    base,
982    composition_property: ServerCompositionBrushProps::get_composition_property
983);
984
985impl ServerCompositionBrushHooks for ServerCompositionBackdropBrush {}
986
987impl IServerObject for ServerCompositionBackdropBrush {
988    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
989        self.props.deserialize_changes_core(self, reader, committed_at);
990    }
991
992    fn values_invalidated(&self) {
993        self.base.core().invalidated(self);
994    }
995
996    fn dispose(&self) {
997        self.base.core().dispose();
998    }
999
1000    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
1001        Some(self)
1002    }
1003
1004    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
1005        Some(self)
1006    }
1007
1008    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
1009        self.props.find_props(type_id)
1010    }
1011
1012    fn as_any(&self) -> &dyn Any {
1013        self
1014    }
1015
1016    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
1017        self
1018    }
1019}
1020
1021/// The server-side counterpart of a
1022/// [`CompositionHostBackdropBrush`](super::CompositionHostBackdropBrush):
1023/// what is behind the window. Over a window that the platform composes over
1024/// what is behind it (a transparency level other than none), the area of
1025/// the brush is made transparent, so that shows through; over an opaque
1026/// window the brush paints nothing.
1027pub struct ServerCompositionHostBackdropBrush {
1028    base: ServerRenderResource,
1029    props: ServerCompositionBrushProps,
1030}
1031
1032impl ServerCompositionHostBackdropBrush {
1033    /// Creates the brush, active.
1034    pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionHostBackdropBrush> {
1035        let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionHostBackdropBrush>| ServerCompositionHostBackdropBrush {
1036            base: ServerRenderResource::new(
1037                compositor,
1038                this.clone() as Weak<dyn IAnimatedServerObject>,
1039                this.clone() as Weak<dyn IServerRenderResource>,
1040            ),
1041            props: ServerCompositionBrushProps::new(),
1042        });
1043        brush.base.object().activate();
1044        brush
1045    }
1046
1047    /// The properties of the generated `ServerCompositionBrush`.
1048    pub fn brush_props(&self) -> &ServerCompositionBrushProps {
1049        &self.props
1050    }
1051
1052    /// Makes `rect` transparent in a transparent window.
1053    pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
1054        let transparent = crate::rendering::composition::server::painting_visual()
1055            .and_then(|visual| visual.root())
1056            .is_some_and(|target| target.transparency_level() != crate::rendering::composition::CompositionTransparencyLevel::None);
1057        if !transparent || self.props.opacity() <= 0.0 {
1058            return;
1059        }
1060        canvas.push_clip(rect);
1061        if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
1062            // A layer composed with `Source`: what it holds (nothing)
1063            // replaces what is under it.
1064            layers.push_blend_layer(rect, BitmapBlendingMode::Source);
1065            layers.pop_blend_layer();
1066        }
1067        canvas.pop_clip();
1068    }
1069}
1070
1071impl_server_render_resource!(
1072    ServerCompositionHostBackdropBrush,
1073    base,
1074    composition_property: ServerCompositionBrushProps::get_composition_property
1075);
1076
1077impl ServerCompositionBrushHooks for ServerCompositionHostBackdropBrush {}
1078
1079impl IServerObject for ServerCompositionHostBackdropBrush {
1080    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
1081        self.props.deserialize_changes_core(self, reader, committed_at);
1082    }
1083
1084    fn values_invalidated(&self) {
1085        self.base.core().invalidated(self);
1086    }
1087
1088    fn dispose(&self) {
1089        self.base.core().dispose();
1090    }
1091
1092    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
1093        Some(self)
1094    }
1095
1096    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
1097        Some(self)
1098    }
1099
1100    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
1101        self.props.find_props(type_id)
1102    }
1103
1104    fn as_any(&self) -> &dyn Any {
1105        self
1106    }
1107
1108    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
1109        self
1110    }
1111}
1112
1113/// The colour matrix of a shadow (on un-premultiplied colours): the colour of the
1114/// shadow, with the alpha of the source times the alpha of the colour.
1115fn shadow_matrix(color: Color) -> ColorMatrix {
1116    let channel = |value: u8| f32::from(value) / 255.0;
1117    ColorMatrix::new([
1118        [0.0, 0.0, 0.0, 0.0],
1119        [0.0, 0.0, 0.0, 0.0],
1120        [0.0, 0.0, 0.0, 0.0],
1121        [0.0, 0.0, 0.0, channel(color.a)],
1122        [channel(color.r), channel(color.g), channel(color.b), 0.0],
1123    ])
1124}
1125
1126/// A premultiplied RGBA pixel as straight colour channels and alpha, 0 to 1.
1127fn unpremultiply(pixel: &[u8]) -> ([f32; 3], f32) {
1128    let alpha = f32::from(pixel[3]) / 255.0;
1129    if alpha <= 0.0 {
1130        return ([0.0; 3], 0.0);
1131    }
1132    let channel = |value: u8| (f32::from(value) / 255.0 / alpha).min(1.0);
1133    ([channel(pixel[0]), channel(pixel[1]), channel(pixel[2])], alpha)
1134}
1135
1136fn store(pixel: &mut [u8], color: [f32; 3], alpha: f32) {
1137    let alpha = alpha.clamp(0.0, 1.0);
1138    for channel in 0..3 {
1139        pixel[channel] = (color[channel].clamp(0.0, 1.0) * alpha * 255.0).round() as u8;
1140    }
1141    pixel[3] = (alpha * 255.0).round() as u8;
1142}
1143
1144fn luminosity(color: [f32; 3]) -> f32 {
1145    0.3 * color[0] + 0.59 * color[1] + 0.11 * color[2]
1146}
1147
1148fn color_dodge(b: f32, s: f32) -> f32 {
1149    if b <= 0.0 {
1150        0.0
1151    } else if s >= 1.0 {
1152        1.0
1153    } else {
1154        (b / (1.0 - s)).min(1.0)
1155    }
1156}
1157
1158fn color_burn(b: f32, s: f32) -> f32 {
1159    if b >= 1.0 {
1160        1.0
1161    } else if s <= 0.0 {
1162        0.0
1163    } else {
1164        1.0 - ((1.0 - b) / s).min(1.0)
1165    }
1166}
1167
1168/// A small hash of a pixel position, 0 to 1: the random choice of
1169/// `Dissolve`, the same for a pixel in every frame.
1170fn dissolve_noise(index: usize) -> f32 {
1171    let mut x = index as u32 ^ 0x9E37_79B9;
1172    x = x.wrapping_mul(0x85EB_CA6B);
1173    x ^= x >> 13;
1174    x = x.wrapping_mul(0xC2B2_AE35);
1175    x ^= x >> 16;
1176    (x & 0xFFFF) as f32 / 65535.0
1177}
1178
1179/// Blends the premultiplied pixels of `foreground` into `background` with
1180/// one of the modes of Direct2D's blend effect that the blend layers do not
1181/// have; the result is composed as a separable blend is:
1182/// `(1 - as) ab Cb + (1 - ab) as Cs + as ab B(Cb, Cs)`.
1183fn blend_pixels(mode: BlendEffectMode, background: &mut [u8], foreground: &[u8], width: usize) {
1184    let _ = width;
1185    for (index, (back, fore)) in background.chunks_exact_mut(4).zip(foreground.chunks_exact(4)).enumerate() {
1186        let (cb, ab) = unpremultiply(back);
1187        let (cs, as_) = unpremultiply(fore);
1188        if mode == BlendEffectMode::Dissolve {
1189            // The foreground where a random number falls under its alpha,
1190            // opaque; the background elsewhere.
1191            if as_ > 0.0 && dissolve_noise(index) < as_ {
1192                store(back, cs, 1.0);
1193            }
1194            continue;
1195        }
1196        let blended: [f32; 3] = match mode {
1197            BlendEffectMode::DarkerColor => {
1198                if luminosity(cs) < luminosity(cb) {
1199                    cs
1200                } else {
1201                    cb
1202                }
1203            }
1204            BlendEffectMode::LighterColor => {
1205                if luminosity(cs) > luminosity(cb) {
1206                    cs
1207                } else {
1208                    cb
1209                }
1210            }
1211            _ => std::array::from_fn(|channel| {
1212                let (b, s) = (cb[channel], cs[channel]);
1213                match mode {
1214                    BlendEffectMode::LinearBurn => (b + s - 1.0).max(0.0),
1215                    BlendEffectMode::VividLight => {
1216                        if s <= 0.5 {
1217                            color_burn(b, 2.0 * s)
1218                        } else {
1219                            color_dodge(b, 2.0 * (s - 0.5))
1220                        }
1221                    }
1222                    BlendEffectMode::LinearLight => (b + 2.0 * s - 1.0).clamp(0.0, 1.0),
1223                    BlendEffectMode::PinLight => {
1224                        if s <= 0.5 {
1225                            b.min(2.0 * s)
1226                        } else {
1227                            b.max(2.0 * s - 1.0)
1228                        }
1229                    }
1230                    BlendEffectMode::HardMix => {
1231                        if b + s >= 1.0 {
1232                            1.0
1233                        } else {
1234                            0.0
1235                        }
1236                    }
1237                    BlendEffectMode::Subtract => (b - s).max(0.0),
1238                    BlendEffectMode::Division => {
1239                        if s <= 0.0 {
1240                            if b <= 0.0 {
1241                                0.0
1242                            } else {
1243                                1.0
1244                            }
1245                        } else {
1246                            (b / s).min(1.0)
1247                        }
1248                    }
1249                    // The other modes are drawn by the blend layers.
1250                    _ => s,
1251                }
1252            }),
1253        };
1254        let alpha = as_ + ab - as_ * ab;
1255        if alpha <= 0.0 {
1256            store(back, [0.0; 3], 0.0);
1257            continue;
1258        }
1259        let premultiplied: [f32; 3] = std::array::from_fn(|channel| {
1260            (1.0 - as_) * ab * cb[channel] + (1.0 - ab) * as_ * cs[channel] + as_ * ab * blended[channel]
1261        });
1262        store(back, premultiplied.map(|value| value / alpha), alpha);
1263    }
1264}
1265
1266/// `k1 * s1 * s2 + k2 * s1 + k3 * s2 + k4` of premultiplied pixels, per
1267/// channel, clamped to 0..1 and the colour to the alpha (Direct2D's
1268/// arithmetic composite).
1269fn arithmetic_pixels(k: [f32; 4], first: &mut [u8], second: &[u8]) {
1270    for (one, two) in first.chunks_exact_mut(4).zip(second.chunks_exact(4)) {
1271        let mut out = [0.0f32; 4];
1272        for channel in 0..4 {
1273            let (a, b) = (f32::from(one[channel]) / 255.0, f32::from(two[channel]) / 255.0);
1274            out[channel] = (k[0] * a * b + k[1] * a + k[2] * b + k[3]).clamp(0.0, 1.0);
1275        }
1276        for channel in 0..3 {
1277            out[channel] = out[channel].min(out[3]);
1278        }
1279        for channel in 0..4 {
1280            one[channel] = (out[channel] * 255.0).round() as u8;
1281        }
1282    }
1283}
1284
1285/// The `MaskInvert` composite of the premultiplied pixels of `source` onto
1286/// `destination`: the colour of the destination inverted where the source
1287/// covers it (`as (ad - Cd) + (1 - as) Cd`), the alpha of the destination
1288/// kept.
1289fn mask_invert_pixels(destination: &mut [u8], source: &[u8]) {
1290    for (dest, src) in destination.chunks_exact_mut(4).zip(source.chunks_exact(4)) {
1291        let source_alpha = f32::from(src[3]) / 255.0;
1292        let dest_alpha = f32::from(dest[3]) / 255.0;
1293        for channel in 0..3 {
1294            let d = f32::from(dest[channel]) / 255.0;
1295            let out = source_alpha * (dest_alpha - d) + (1.0 - source_alpha) * d;
1296            dest[channel] = (out.clamp(0.0, dest_alpha) * 255.0).round() as u8;
1297        }
1298    }
1299}
1300
1301#[cfg(test)]
1302mod pixel_tests {
1303    use super::*;
1304
1305    #[test]
1306    fn the_extra_blend_modes_follow_their_formulas() {
1307        let opaque = |r: u8, g: u8, b: u8| vec![r, g, b, 255];
1308        let mut back = opaque(200, 100, 50);
1309        blend_pixels(BlendEffectMode::Subtract, &mut back, &opaque(100, 150, 25), 1);
1310        assert_eq!(opaque(100, 0, 25), back);
1311        let mut back = opaque(200, 100, 50);
1312        blend_pixels(BlendEffectMode::LinearBurn, &mut back, &opaque(100, 255, 0), 1);
1313        assert_eq!(opaque(45, 100, 0), back);
1314        let mut back = opaque(100, 100, 100);
1315        blend_pixels(BlendEffectMode::HardMix, &mut back, &opaque(200, 100, 0), 1);
1316        assert_eq!(opaque(255, 0, 0), back);
1317        let mut back = opaque(10, 10, 10);
1318        blend_pixels(BlendEffectMode::LighterColor, &mut back, &opaque(200, 0, 0), 1);
1319        assert_eq!(opaque(200, 0, 0), back);
1320    }
1321
1322    #[test]
1323    fn a_blend_over_transparency_is_the_foreground() {
1324        let mut back = vec![0, 0, 0, 0];
1325        blend_pixels(BlendEffectMode::Division, &mut back, &[100, 50, 0, 255], 1);
1326        assert_eq!(vec![100, 50, 0, 255], back);
1327    }
1328
1329    #[test]
1330    fn the_arithmetic_composite_of_two_images() {
1331        let mut first = vec![255, 0, 128, 255];
1332        arithmetic_pixels([0.0, 0.5, 0.5, 0.0], &mut first, &[0, 255, 128, 255]);
1333        assert_eq!(vec![128, 128, 128, 255], first);
1334    }
1335
1336    #[test]
1337    fn mask_invert_inverts_the_destination_under_the_source() {
1338        let mut destination = vec![255, 0, 64, 255, 255, 0, 64, 255];
1339        mask_invert_pixels(&mut destination, &[0, 0, 0, 255, 0, 0, 0, 0]);
1340        assert_eq!(vec![0, 255, 191, 255, 255, 0, 64, 255], destination);
1341    }
1342}