| // 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] | |
| } | |
| } |