ruisa/
mask.rs

1// Copyright 2020 Yevhenii Reizner
2//
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5
6#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
7use ruisa_path::NoStdFloat;
8
9use alloc::vec;
10use alloc::vec::Vec;
11
12use ruisa_path::{IntRect, IntSize, Path, Scalar, Transform};
13
14use crate::painter::DrawTiler;
15use crate::pipeline::RasterPipelineBlitter;
16use crate::canvas::SubCanvasMut;
17use crate::scan;
18use crate::{FillRule, CanvasRef};
19
20/// A mask type.
21#[derive(Clone, Copy, PartialEq, Debug)]
22pub enum MaskType {
23    /// Transfers only the Alpha channel from `Canvas` to `Mask`.
24    Alpha,
25    /// Transfers RGB channels as luminance from `Canvas` to `Mask`.
26    ///
27    /// Formula: `Y = 0.2126 * R + 0.7152 * G + 0.0722 * B`
28    Luminance,
29}
30
31/// A mask.
32///
33/// During drawing over `Canvas`, mask's black (0) "pixels" would block rendering
34/// and white (255) will allow it.
35/// Anything in between is used for gradual masking and anti-aliasing.
36///
37/// Unlike Skia, we're using just a simple 8bit alpha mask.
38/// It's way slower, but easier to implement.
39#[derive(Clone, PartialEq)]
40pub struct Mask {
41    data: Vec<u8>,
42    size: IntSize,
43}
44
45impl Mask {
46    /// Creates a new mask by taking ownership over a mask buffer.
47    ///
48    /// The size needs to match the data provided.
49    pub fn new(width: u32, height: u32) -> Option<Self> {
50        let size = IntSize::from_wh(width, height)?;
51        Some(Mask {
52            data: vec![0; width as usize * height as usize],
53            size,
54        })
55    }
56
57    /// Creates a new mask from a `CanvasRef`.
58    pub fn from_canvas(canvas: CanvasRef, mask_type: MaskType) -> Self {
59        let data_len = canvas.width() as usize * canvas.height() as usize;
60        let mut mask = Mask {
61            data: vec![0; data_len],
62            size: canvas.size(),
63        };
64
65        // TODO: optimize
66        match mask_type {
67            MaskType::Alpha => {
68                for (p, a) in canvas.pixels().iter().zip(mask.data.as_mut_slice()) {
69                    *a = p.alpha();
70                }
71            }
72            MaskType::Luminance => {
73                for (p, ma) in canvas.pixels().iter().zip(mask.data.as_mut_slice()) {
74                    // Normalize.
75                    let mut r = f32::from(p.red()) / 255.0;
76                    let mut g = f32::from(p.green()) / 255.0;
77                    let mut b = f32::from(p.blue()) / 255.0;
78                    let a = f32::from(p.alpha()) / 255.0;
79
80                    // Demultiply.
81                    if p.alpha() != 0 {
82                        r /= a;
83                        g /= a;
84                        b /= a;
85                    }
86
87                    let luma = r * 0.2126 + g * 0.7152 + b * 0.0722;
88                    *ma = ((luma * a) * 255.0).clamp(0.0, 255.0).ceil() as u8;
89                }
90            }
91        }
92
93        mask
94    }
95
96    /// Creates a new mask by taking ownership over a mask buffer.
97    ///
98    /// The size needs to match the data provided.
99    pub fn from_vec(data: Vec<u8>, size: IntSize) -> Option<Self> {
100        let data_len = size.width() as usize * size.height() as usize;
101        if data.len() != data_len {
102            return None;
103        }
104
105        Some(Mask { data, size })
106    }
107
108    /// Returns mask's width.
109    #[inline]
110    pub fn width(&self) -> u32 {
111        self.size.width()
112    }
113
114    /// Returns mask's height.
115    #[inline]
116    pub fn height(&self) -> u32 {
117        self.size.height()
118    }
119
120    /// Returns mask's size.
121    #[allow(dead_code)]
122    pub(crate) fn size(&self) -> IntSize {
123        self.size
124    }
125
126    /// Returns the internal data.
127    pub fn data(&self) -> &[u8] {
128        self.data.as_slice()
129    }
130
131    /// Returns the mutable internal data.
132    pub fn data_mut(&mut self) -> &mut [u8] {
133        self.data.as_mut_slice()
134    }
135
136    pub(crate) fn as_submask<'a>(&'a self) -> SubMaskRef<'a> {
137        SubMaskRef {
138            size: self.size,
139            real_width: self.size.width(),
140            data: &self.data,
141        }
142    }
143
144    pub(crate) fn submask<'a>(&'a self, rect: IntRect) -> Option<SubMaskRef<'a>> {
145        let rect = self.size.to_int_rect(0, 0).intersect(&rect)?;
146        let row_bytes = self.width() as usize;
147        let offset = rect.top() as usize * row_bytes + rect.left() as usize;
148
149        Some(SubMaskRef {
150            size: rect.size(),
151            real_width: self.size.width(),
152            data: &self.data[offset..],
153        })
154    }
155
156    pub(crate) fn as_subcanvas<'a>(&'a mut self) -> SubCanvasMut<'a> {
157        SubCanvasMut {
158            size: self.size,
159            real_width: self.size.width() as usize,
160            data: &mut self.data,
161        }
162    }
163
164    pub(crate) fn subcanvas<'a>(&'a mut self, rect: IntRect) -> Option<SubCanvasMut<'a>> {
165        let rect = self.size.to_int_rect(0, 0).intersect(&rect)?;
166        let row_bytes = self.width() as usize;
167        let offset = rect.top() as usize * row_bytes + rect.left() as usize;
168
169        Some(SubCanvasMut {
170            size: rect.size(),
171            real_width: self.size.width() as usize,
172            data: &mut self.data[offset..],
173        })
174    }
175
176    /// Loads a PNG file into a `Mask`.
177    ///
178    /// Only grayscale images are supported.
179    pub fn decode_png(data: &[u8]) -> Result<Self, png::DecodingError> {
180        fn make_custom_png_error(msg: &str) -> png::DecodingError {
181            std::io::Error::new(std::io::ErrorKind::Other, msg).into()
182        }
183
184        let mut decoder = png::Decoder::new(data);
185        decoder.set_transformations(png::Transformations::normalize_to_color8());
186        let mut reader = decoder.read_info()?;
187        let mut img_data = vec![0; reader.output_buffer_size()];
188        let info = reader.next_frame(&mut img_data)?;
189
190        if info.bit_depth != png::BitDepth::Eight {
191            return Err(make_custom_png_error("unsupported bit depth"));
192        }
193
194        if info.color_type != png::ColorType::Grayscale {
195            return Err(make_custom_png_error("only grayscale masks are supported"));
196        }
197
198        let size = IntSize::from_wh(info.width, info.height)
199            .ok_or_else(|| make_custom_png_error("invalid image size"))?;
200
201        Mask::from_vec(img_data, size)
202            .ok_or_else(|| make_custom_png_error("failed to create a mask"))
203    }
204
205    /// Loads a PNG file into a `Mask`.
206    ///
207    /// Only grayscale images are supported.
208    pub fn from_image_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, png::DecodingError> {
209        // `png::Decoder` is generic over input, which means that it will instance
210        // two copies: one for `&[]` and one for `File`. Which will simply bloat the code.
211        // Therefore we're using only one type for input.
212        let data = std::fs::read(path)?;
213        Self::decode_png(&data)
214    }
215
216    /// Encodes mask into a PNG data.
217    pub fn to_png(&self) -> Result<Vec<u8>, png::EncodingError> {
218        let mut data = Vec::new();
219        {
220            let mut encoder = png::Encoder::new(&mut data, self.width(), self.height());
221            encoder.set_color(png::ColorType::Grayscale);
222            encoder.set_depth(png::BitDepth::Eight);
223            let mut writer = encoder.write_header()?;
224            writer.write_image_data(&self.data)?;
225        }
226
227        Ok(data)
228    }
229
230    /// Saves mask as a PNG file.
231    pub fn save_png<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), png::EncodingError> {
232        let data = self.to_png()?;
233        std::fs::write(path, data)?;
234        Ok(())
235    }
236
237    // Almost a direct copy of CanvasMut::fill_path
238    /// Draws a filled path onto the mask.
239    ///
240    /// In terms of RGB (no alpha) image, draws a white path on top of black mask.
241    ///
242    /// Doesn't reset the existing mask content and draws the path on top of existing data.
243    ///
244    /// If the above behavior is undesired, [`clear()`] should be called first.
245    ///
246    /// This method is intended to be used for simple cases. For more complex masks
247    /// prefer [`from_canvas()`].
248    pub fn fill_path(
249        &mut self,
250        path: &Path,
251        fill_rule: FillRule,
252        anti_alias: bool,
253        transform: Transform,
254    ) {
255        if transform.is_identity() {
256            // This is sort of similar to SkDraw::drawPath
257
258            // Skip empty paths and horizontal/vertical lines.
259            let path_bounds = path.bounds();
260            if path_bounds.width().is_nearly_zero() || path_bounds.height().is_nearly_zero() {
261                log::warn!("empty paths and horizontal/vertical lines cannot be filled");
262                return;
263            }
264
265            if crate::painter::is_too_big_for_math(path) {
266                log::warn!("path coordinates are too big");
267                return;
268            }
269
270            // TODO: ignore paths outside the canvas
271
272            if let Some(tiler) = DrawTiler::new(self.width(), self.height()) {
273                let mut path = path.clone(); // TODO: avoid cloning
274
275                for tile in tiler {
276                    let ts = Transform::from_translate(-(tile.x() as f32), -(tile.y() as f32));
277                    path = match path.transform(ts) {
278                        Some(v) => v,
279                        None => {
280                            log::warn!("path transformation failed");
281                            return;
282                        }
283                    };
284
285                    let clip_rect = tile.size().to_screen_int_rect(0, 0);
286                    let mut subpix = match self.subcanvas(tile.to_int_rect()) {
287                        Some(v) => v,
288                        None => continue, // technically unreachable
289                    };
290
291                    let mut blitter = match RasterPipelineBlitter::new_mask(&mut subpix) {
292                        Some(v) => v,
293                        None => continue, // nothing to do, all good
294                    };
295
296                    // We're ignoring "errors" here, because `fill_path` will return `None`
297                    // when rendering a tile that doesn't have a path on it.
298                    // Which is not an error in this case.
299                    if anti_alias {
300                        scan::path_aa::fill_path(&path, fill_rule, &clip_rect, &mut blitter);
301                    } else {
302                        scan::path::fill_path(&path, fill_rule, &clip_rect, &mut blitter);
303                    }
304
305                    let ts = Transform::from_translate(tile.x() as f32, tile.y() as f32);
306                    path = match path.transform(ts) {
307                        Some(v) => v,
308                        None => return, // technically unreachable
309                    };
310                }
311            } else {
312                let clip_rect = self.size().to_screen_int_rect(0, 0);
313                let mut subpix = self.as_subcanvas();
314                let mut blitter = match RasterPipelineBlitter::new_mask(&mut subpix) {
315                    Some(v) => v,
316                    None => return, // nothing to do, all good
317                };
318
319                if anti_alias {
320                    scan::path_aa::fill_path(path, fill_rule, &clip_rect, &mut blitter);
321                } else {
322                    scan::path::fill_path(path, fill_rule, &clip_rect, &mut blitter);
323                }
324            }
325        } else {
326            let path = match path.clone().transform(transform) {
327                Some(v) => v,
328                None => {
329                    log::warn!("path transformation failed");
330                    return;
331                }
332            };
333
334            self.fill_path(&path, fill_rule, anti_alias, Transform::identity());
335        }
336    }
337
338    /// Intersects the provided path with the current clipping path.
339    ///
340    /// A temporary mask with the same size as the current one will be created.
341    pub fn intersect_path(
342        &mut self,
343        path: &Path,
344        fill_rule: FillRule,
345        anti_alias: bool,
346        transform: Transform,
347    ) {
348        let mut submask = Mask::new(self.width(), self.height()).unwrap();
349        submask.fill_path(path, fill_rule, anti_alias, transform);
350
351        for (a, b) in self.data.iter_mut().zip(submask.data.iter()) {
352            *a = crate::color::premultiply_u8(*a, *b);
353        }
354    }
355
356    /// Inverts the mask.
357    pub fn invert(&mut self) {
358        self.data.iter_mut().for_each(|a| *a = 255 - *a);
359    }
360
361    /// Clears the mask.
362    ///
363    /// Zero-fills the internal data buffer.
364    pub fn clear(&mut self) {
365        self.data.fill(0);
366    }
367}
368
369impl core::fmt::Debug for Mask {
370    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371        f.debug_struct("Mask")
372            .field("data", &"...")
373            .field("width", &self.size.width())
374            .field("height", &self.size.height())
375            .finish()
376    }
377}
378
379#[derive(Clone, Copy)]
380pub struct SubMaskRef<'a> {
381    pub data: &'a [u8],
382    pub size: IntSize,
383    pub real_width: u32,
384}
385
386impl<'a> SubMaskRef<'a> {
387    pub(crate) fn mask_ctx(&self) -> crate::pipeline::MaskCtx<'a> {
388        crate::pipeline::MaskCtx {
389            data: &self.data,
390            real_width: self.real_width,
391        }
392    }
393}