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&mut DebugStruct<'_, '_>.debug_struct("Mask")
377 .field("data", &"...")
378 .field("width", &self.size.width())
379 .field(name:"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}
399