Skip to main content

ferroui_base/platform/
i_drawing_context_impl_with_layer_effects.rs

1use super::IDrawingContextImpl;
2use crate::media::imaging::BitmapBlendingMode;
3use crate::media::{ColorMatrix, ColorMatrixAlphaMode};
4use crate::{PixelSize, Rect};
5use std::sync::Arc;
6
7/// A drawing context implementation that can compose a layer onto what is
8/// under it with a blend mode or through a colour matrix: what the effects
9/// of WinUI's composition (`BlendEffect`, `ColorMatrixEffect`,
10/// `SaturationEffect`, the composite modes) are drawn with.
11///
12/// Every push has its pop; the pushes nest with the other pushes of the
13/// context (clips, layers, opacities, effects).
14pub trait IDrawingContextImplWithLayerEffects: IDrawingContextImpl {
15    /// Pushes a layer: what is drawn until the matching
16    /// [`pop_blend_layer`](Self::pop_blend_layer) is composed onto what is
17    /// under it with `mode` (the Porter-Duff modes compose, the others mix
18    /// and compose source-over). `bounds` is the area drawn into, in the
19    /// coordinates of the context.
20    fn push_blend_layer(&mut self, bounds: Rect, mode: BitmapBlendingMode);
21
22    /// Pops the latest blend layer.
23    fn pop_blend_layer(&mut self);
24
25    /// Pushes a layer: what is drawn until the matching
26    /// [`pop_color_matrix_layer`](Self::pop_color_matrix_layer) is
27    /// transformed by `matrix` (applied to premultiplied or straight colours,
28    /// as `alpha_mode` says) and composed source-over onto what is under it.
29    /// `bounds` is the area drawn into, in the coordinates of the context;
30    /// nothing outside it is kept.
31    fn push_color_matrix_layer(&mut self, bounds: Rect, matrix: &ColorMatrix, alpha_mode: ColorMatrixAlphaMode);
32
33    /// Pops the latest colour matrix layer.
34    fn pop_color_matrix_layer(&mut self);
35
36    /// Pushes a layer: each colour channel of what is drawn until the
37    /// matching [`pop_multiply_layer`](Self::pop_multiply_layer) is
38    /// multiplied by the factor `factors` has at that point (stretched over
39    /// `bounds`, sampled bilinearly), the alpha is kept, and the result is
40    /// composed source-over onto what is under it. A factor can exceed 1
41    /// (up to [`MultiplyLayerFactors::scale`]); the result is clamped to the
42    /// alpha. `bounds` is the area drawn into, in the coordinates of the
43    /// context; nothing outside it is kept. What the lights of WinUI's
44    /// composition are drawn with (winui-materials.md).
45    fn push_multiply_layer(&mut self, bounds: Rect, factors: &MultiplyLayerFactors);
46
47    /// Pops the latest multiply layer.
48    fn pop_multiply_layer(&mut self);
49}
50
51/// The per-channel factors of a multiply layer: an opaque RGBA image whose
52/// channels, divided by 255 and times [`scale`](Self::scale), are the
53/// factors of the red, green and blue channels.
54#[derive(Clone, Debug, PartialEq)]
55pub struct MultiplyLayerFactors {
56    /// The size of the image in samples.
57    pub size: PixelSize,
58    /// The samples, row by row from the top, 4 bytes each (the fourth 255).
59    pub rgba: Arc<Vec<u8>>,
60    /// What a sample of 255 stands for (1 or more).
61    pub scale: f32,
62}
63
64impl MultiplyLayerFactors {
65    /// The factors of the red, green and blue channels at `(u, v)` of the
66    /// unit square of the image (0 to 1 across it), sampled bilinearly
67    /// between the centres of the samples, as the backends sample a bitmap.
68    pub fn factor_at(&self, u: f64, v: f64) -> [f32; 3] {
69        let (width, height) = (self.size.width.max(1) as usize, self.size.height.max(1) as usize);
70        if self.rgba.len() < width * height * 4 {
71            return [1.0; 3];
72        }
73        let x = (u * width as f64 - 0.5).clamp(0.0, (width - 1) as f64);
74        let y = (v * height as f64 - 0.5).clamp(0.0, (height - 1) as f64);
75        let (x0, y0) = (x.floor() as usize, y.floor() as usize);
76        let (x1, y1) = ((x0 + 1).min(width - 1), (y0 + 1).min(height - 1));
77        let (fx, fy) = ((x - x0 as f64) as f32, (y - y0 as f64) as f32);
78        let sample = |x: usize, y: usize, channel: usize| f32::from(self.rgba[(y * width + x) * 4 + channel]) / 255.0;
79        let mut factors = [0.0; 3];
80        for (channel, factor) in factors.iter_mut().enumerate() {
81            let top = sample(x0, y0, channel) * (1.0 - fx) + sample(x1, y0, channel) * fx;
82            let bottom = sample(x0, y1, channel) * (1.0 - fx) + sample(x1, y1, channel) * fx;
83            *factor = (top * (1.0 - fy) + bottom * fy) * self.scale;
84        }
85        factors
86    }
87
88    /// Multiplies the premultiplied RGBA pixels `rgba` (row by row, `width`
89    /// pixels a row) by the factors; `to_unit` maps the centre of a pixel to
90    /// the unit square of the factors (`None` outside them: the pixel is
91    /// cleared, as nothing outside the bounds of the layer is kept).
92    pub fn apply_to_premultiplied_rgba8(&self, rgba: &mut [u8], width: usize, to_unit: impl Fn(f64, f64) -> Option<(f64, f64)>) {
93        if width == 0 {
94            return;
95        }
96        for (index, pixel) in rgba.chunks_exact_mut(4).enumerate() {
97            if pixel[3] == 0 {
98                continue;
99            }
100            let (x, y) = ((index % width) as f64 + 0.5, (index / width) as f64 + 0.5);
101            let Some((u, v)) = to_unit(x, y) else {
102                pixel.copy_from_slice(&[0, 0, 0, 0]);
103                continue;
104            };
105            let factors = self.factor_at(u, v);
106            let alpha = f32::from(pixel[3]);
107            for channel in 0..3 {
108                pixel[channel] = (f32::from(pixel[channel]) * factors[channel]).round().clamp(0.0, alpha) as u8;
109            }
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    fn factors(rgba: Vec<u8>, width: i32, height: i32, scale: f32) -> MultiplyLayerFactors {
119        MultiplyLayerFactors { size: PixelSize::new(width, height), rgba: Arc::new(rgba), scale }
120    }
121
122    #[test]
123    fn the_factors_are_sampled_between_the_centres_of_the_samples() {
124        let map = factors(vec![0, 0, 0, 255, 255, 255, 255, 255], 2, 1, 1.0);
125        assert_eq!([0.0; 3], map.factor_at(0.0, 0.5));
126        assert_eq!([1.0; 3], map.factor_at(1.0, 0.5));
127        let middle = map.factor_at(0.5, 0.5);
128        assert!((middle[0] - 0.5).abs() < 1e-6);
129    }
130
131    #[test]
132    fn a_scale_brightens_and_the_result_is_clamped_to_the_alpha() {
133        let map = factors(vec![255, 128, 0, 255], 1, 1, 2.0);
134        let mut pixels = vec![100, 100, 100, 200, 50, 50, 50, 50];
135        map.apply_to_premultiplied_rgba8(&mut pixels, 2, |x, y| Some((x / 2.0, y)));
136        assert_eq!(vec![200, 100, 0, 200, 50, 50, 0, 50], pixels);
137    }
138
139    #[test]
140    fn pixels_outside_the_factors_are_cleared() {
141        let map = factors(vec![255, 255, 255, 255], 1, 1, 1.0);
142        let mut pixels = vec![10, 10, 10, 10];
143        map.apply_to_premultiplied_rgba8(&mut pixels, 1, |_, _| None);
144        assert_eq!(vec![0, 0, 0, 0], pixels);
145    }
146}