Skip to main content

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