Skip to main content

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;
8use alloc::vec::Vec;
9
10#[derive(Debug, PartialEq, Eq)]
11struct MaskRepr {
12    data: Vec<u8>,
13    width: u16,
14    height: u16,
15}
16
17// Note that we are on purpose storing width and height inside the `Arc`
18// to reduce the memory footprint of the struct.
19/// A mask.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Mask(Arc<MaskRepr>);
22
23impl Mask {
24    /// Create a new alpha mask from the pixmap.
25    pub fn new_alpha(pixmap: &Pixmap) -> Self {
26        Self::new_with(pixmap, true)
27    }
28
29    /// Create a new luminance mask from the pixmap.
30    pub fn new_luminance(pixmap: &Pixmap) -> Self {
31        Self::new_with(pixmap, false)
32    }
33
34    /// Create a new mask from the given alpha data.
35    ///
36    /// The `data` vector must be of length `width * height` exactly.
37    ///
38    /// The pixels are in row-major order.
39    ///
40    /// # Panics
41    ///
42    /// Panics if the `data` vector is not of length `width * height`.
43    pub fn from_parts(data: Vec<u8>, width: u16, height: u16) -> Self {
44        assert_eq!(
45            data.len(),
46            usize::from(width) * usize::from(height),
47            "`data` should have `width * height` length"
48        );
49
50        Self(Arc::new(MaskRepr {
51            data,
52            width,
53            height,
54        }))
55    }
56
57    fn new_with(pixmap: &Pixmap, alpha_mask: bool) -> Self {
58        let data = pixmap
59            .data()
60            .iter()
61            .map(|pixel| {
62                if alpha_mask {
63                    pixel.a
64                } else {
65                    let r = f32::from(pixel.r) / 255.;
66                    let g = f32::from(pixel.g) / 255.;
67                    let b = f32::from(pixel.b) / 255.;
68
69                    // See CSS Masking Module Level 1 § 7.10.1
70                    // <https://www.w3.org/TR/css-masking-1/#MaskValues>
71                    // and Filter Effects Module Level 1 § 9.6
72                    // <https://www.w3.org/TR/filter-effects-1/#elementdef-fecolormatrix>.
73                    // Note r, g and b are premultiplied by alpha.
74                    let luma = r * 0.2126 + g * 0.7152 + b * 0.0722;
75                    #[expect(clippy::cast_possible_truncation, reason = "This cannot overflow")]
76                    {
77                        (luma * 255.0 + 0.5) as u8
78                    }
79                }
80            })
81            .collect::<Vec<u8>>();
82
83        Self(Arc::new(MaskRepr {
84            data,
85            width: pixmap.width(),
86            height: pixmap.height(),
87        }))
88    }
89
90    /// Return the width of the mask.
91    #[inline(always)]
92    pub fn width(&self) -> u16 {
93        self.0.width
94    }
95
96    /// Return the height of the mask.
97    #[inline(always)]
98    pub fn height(&self) -> u16 {
99        self.0.height
100    }
101
102    /// Sample the value at a specific location.
103    ///
104    /// This function might panic or yield a wrong result if the location
105    /// is out-of-bounds.
106    #[inline(always)]
107    pub fn sample(&self, x: u16, y: u16) -> u8 {
108        let repr = &*self.0;
109        debug_assert!(
110            x < repr.width && y < repr.height,
111            "cannot sample mask outside of its range"
112        );
113
114        repr.data[y as usize * repr.width as usize + x as usize]
115    }
116}