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