blob: 67a3a96006b8b62110334545e1a9aa0a66e004c2 [file] [log] [blame]
// 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;
/// A mask.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mask {
data: Arc<Vec<u8>>,
width: u16,
height: u16,
}
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)
}
fn new_with(pixmap: &Pixmap, alpha_mask: bool) -> Self {
let mut data = Vec::with_capacity(pixmap.width() as usize * pixmap.height() as usize);
for pixel in pixmap.data() {
if alpha_mask {
data.push(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")]
data.push((luma * 255.0 + 0.5) as u8);
}
}
Self {
data: Arc::new(data),
width: pixmap.width(),
height: pixmap.height(),
}
}
/// Return the width of the mask.
pub fn width(&self) -> u16 {
self.width
}
/// Return the height of the mask.
pub fn height(&self) -> u16 {
self.height
}
/// Sample the value at a specific location.
///
/// This function might panic or yield a wrong result if the location
/// is out-of-bounds.
pub fn sample(&self, x: u16, y: u16) -> u8 {
debug_assert!(
x < self.width && y < self.height,
"cannot sample mask outside of its range"
);
self.data[y as usize * self.width as usize + x as usize]
}
}