vello_common/
mask.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Alpha and luminance masks.
5
6use crate::pixmap::Pixmap;
7use alloc::sync::Arc;
8
9/// A mask.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Mask {
12    data: Arc<[u8]>,
13    width: u16,
14    height: u16,
15}
16
17impl Mask {
18    /// Create a new alpha mask from the pixmap.
19    pub fn new_alpha(pixmap: &Pixmap) -> Self {
20        Self::new_with(pixmap, true)
21    }
22
23    /// Create a new luminance mask from the pixmap.
24    pub fn new_luminance(pixmap: &Pixmap) -> Self {
25        Self::new_with(pixmap, false)
26    }
27
28    fn new_with(pixmap: &Pixmap, alpha_mask: bool) -> Self {
29        let data = Arc::from_iter(pixmap.data().iter().map(|pixel| {
30            if alpha_mask {
31                pixel.a
32            } else {
33                let r = f32::from(pixel.r) / 255.;
34                let g = f32::from(pixel.g) / 255.;
35                let b = f32::from(pixel.b) / 255.;
36
37                // See CSS Masking Module Level 1 § 7.10.1
38                // <https://www.w3.org/TR/css-masking-1/#MaskValues>
39                // and Filter Effects Module Level 1 § 9.6
40                // <https://www.w3.org/TR/filter-effects-1/#elementdef-fecolormatrix>.
41                // Note r, g and b are premultiplied by alpha.
42                let luma = r * 0.2126 + g * 0.7152 + b * 0.0722;
43                #[expect(clippy::cast_possible_truncation, reason = "This cannot overflow")]
44                {
45                    (luma * 255.0 + 0.5) as u8
46                }
47            }
48        }));
49
50        Self {
51            data,
52            width: pixmap.width(),
53            height: pixmap.height(),
54        }
55    }
56
57    /// Return the width of the mask.
58    pub fn width(&self) -> u16 {
59        self.width
60    }
61
62    /// Return the height of the mask.
63    pub fn height(&self) -> u16 {
64        self.height
65    }
66
67    /// Sample the value at a specific location.
68    ///
69    /// This function might panic or yield a wrong result if the location
70    /// is out-of-bounds.
71    pub fn sample(&self, x: u16, y: u16) -> u8 {
72        debug_assert!(
73            x < self.width && y < self.height,
74            "cannot sample mask outside of its range"
75        );
76
77        self.data[y as usize * self.width as usize + x as usize]
78    }
79}