| 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 | |