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" ))] |
7 | use tiny_skia_path::NoStdFloat; |
8 | |
9 | use alloc::vec; |
10 | use alloc::vec::Vec; |
11 | |
12 | use tiny_skia_path::{IntRect, IntSize, Path, Scalar, Transform}; |
13 | |
14 | use crate::geom::IntSizeExt; |
15 | use crate::painter::DrawTiler; |
16 | use crate::pipeline::RasterPipelineBlitter; |
17 | use crate::pixmap::SubPixmapMut; |
18 | use crate::scan; |
19 | use crate::{FillRule, PixmapRef}; |
20 | |
21 | /// A mask type. |
22 | #[derive (Clone, Copy, PartialEq, Debug)] |
23 | pub 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)] |
41 | pub struct Mask { |
42 | data: Vec<u8>, |
43 | size: IntSize, |
44 | } |
45 | |
46 | impl 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 | |
374 | impl 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)] |
385 | pub struct SubMaskRef<'a> { |
386 | pub data: &'a [u8], |
387 | pub size: IntSize, |
388 | pub real_width: u32, |
389 | } |
390 | |
391 | impl<'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 | |