Skip to main content

yog_graphics/
mask.rs

1//! One-color pictures, a bit a pixel: a pixel font's glyph is one — a
2//! thirty-second of the same picture as RGBA.
3
4use yog_data::Schema;
5use yog_data::with::Raw;
6
7use crate::color::Color;
8
9use crate::image::{BYTES_PER_PIXEL, Image};
10
11/// `width × height` pixels, a bit each: rows top to bottom, a row's
12/// pixels from its first byte's highest bit on, each row whole bytes.
13#[derive(Schema, Debug, Clone, PartialEq, Default)]
14pub struct Mask {
15    pub width: u32,
16    pub height: u32,
17    #[data(with = "Raw")]
18    pub bits: Vec<u8>,
19}
20
21impl Mask {
22    /// The bytes a row takes.
23    pub fn row_bytes(width: u32) -> usize {
24        (width as usize).div_ceil(u8::BITS as usize)
25    }
26
27    /// Whether pixel (`x`, `y`) is set.
28    pub fn get(&self, x: u32, y: u32) -> bool {
29        let at = y as usize * Self::row_bytes(self.width) + x as usize / u8::BITS as usize;
30        let bit = u8::BITS - 1 - x % u8::BITS;
31        self.bits.get(at).is_some_and(|b| b >> bit & 1 == 1)
32    }
33
34    /// `image` as a mask and its one color, when it is one: every pixel
35    /// clear or opaque, all opaque ones one color (else its colors and
36    /// edges keep it an image).
37    pub fn of(image: &Image) -> Option<(Mask, Color)> {
38        let mut color = None;
39        let row = Self::row_bytes(image.width);
40        let mut bits = vec![0u8; row * image.height as usize];
41        for (i, p) in image.rgba.as_chunks::<BYTES_PER_PIXEL>().0.iter().enumerate() {
42            let alpha = p[BYTES_PER_PIXEL - 1];
43            if alpha == 0 {
44                continue;
45            }
46            if alpha != u8::MAX || *color.get_or_insert([p[0], p[1], p[2]]) != [p[0], p[1], p[2]] {
47                return None;
48            }
49            let (x, y) = (i % image.width as usize, i / image.width as usize);
50            bits[y * row + x / u8::BITS as usize] |=
51                1 << (u8::BITS as usize - 1 - x % u8::BITS as usize);
52        }
53        let [r, g, b] = color.unwrap_or_default();
54        let mask = Mask {
55            width: image.width,
56            height: image.height,
57            bits,
58        };
59        Some((mask, Color::from_rgba8([r, g, b, u8::MAX])))
60    }
61
62    /// As an image: set pixels `color`, the rest clear.
63    pub fn image(&self, color: Color) -> Image {
64        let mut out = Image::new(self.width, self.height);
65        let px = color.to_rgba8();
66        for y in 0..self.height {
67            for x in 0..self.width {
68                if self.get(x, y) {
69                    let at = (y * self.width + x) as usize * BYTES_PER_PIXEL;
70                    out.rgba[at..at + BYTES_PER_PIXEL].copy_from_slice(&px);
71                }
72            }
73        }
74        out
75    }
76}