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