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