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}