use crate::geometry::Transform;
use crate::path::{FillRule, Path};
use crate::pixmap::FLATTEN_TOLERANCE;
use crate::raster::Rasterizer;
#[derive(Clone)]
pub struct Mask {
width: u32,
height: u32,
data: Vec<u8>,
}
impl Mask {
pub fn new(width: u32, height: u32) -> Option<Mask> {
if width == 0 || height == 0 {
return None;
}
let len = (width as usize).checked_mul(height as usize)?;
Some(Mask { width, height, data: vec![0; len] })
}
pub fn from_path(
width: u32,
height: u32,
path: &Path,
fill_rule: FillRule,
anti_alias: bool,
transform: Transform,
) -> Option<Mask> {
let mut mask = Mask::new(width, height)?;
mask.fill_path(path, fill_rule, anti_alias, transform);
Some(mask)
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
#[inline]
pub(crate) fn coverage_at(&self, x: usize, y: usize) -> f32 {
if x >= self.width as usize || y >= self.height as usize {
return 0.0;
}
self.data[y * self.width as usize + x] as f32 / 255.0
}
pub fn fill_path(
&mut self,
path: &Path,
fill_rule: FillRule,
anti_alias: bool,
transform: Transform,
) {
self.rasterize(path, fill_rule, anti_alias, transform, |old, cov| {
old.max(cov)
});
}
pub fn intersect_path(
&mut self,
path: &Path,
fill_rule: FillRule,
anti_alias: bool,
transform: Transform,
) {
let coverage = match Mask::from_path(self.width, self.height, path, fill_rule, anti_alias, transform)
{
Some(m) => m,
None => return,
};
for (dst, &src) in self.data.iter_mut().zip(coverage.data.iter()) {
*dst = ((*dst as u16 * src as u16 + 127) / 255) as u8;
}
}
fn rasterize<F: Fn(f32, f32) -> f32>(
&mut self,
path: &Path,
fill_rule: FillRule,
anti_alias: bool,
transform: Transform,
combine: F,
) {
let tol = FLATTEN_TOLERANCE / transform.max_scale().max(1e-3);
let contours = path.to_contours(transform, tol);
if contours.is_empty() {
return;
}
let (mut min_x, mut min_y) = (f32::INFINITY, f32::INFINITY);
let (mut max_x, mut max_y) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
for c in contours.iter() {
for p in &c.points {
min_x = min_x.min(p.x);
min_y = min_y.min(p.y);
max_x = max_x.max(p.x);
max_y = max_y.max(p.y);
}
}
if !(min_x <= max_x && min_y <= max_y) {
return;
}
let mw = self.width as i32;
let mh = self.height as i32;
let x0 = (min_x.floor() as i32 - 1).clamp(0, mw);
let y0 = (min_y.floor() as i32 - 1).clamp(0, mh);
let x1 = (max_x.ceil() as i32 + 1).clamp(0, mw);
let y1 = (max_y.ceil() as i32 + 1).clamp(0, mh);
let bw = (x1 - x0) as usize;
let bh = (y1 - y0) as usize;
if bw == 0 || bh == 0 {
return;
}
let mut rast = Rasterizer::new(x0, y0, bw, bh);
for c in contours.iter() {
let n = c.points.len();
if n < 2 {
continue;
}
for i in 0..n {
rast.add_line(c.points[i], c.points[(i + 1) % n]);
}
}
let width = self.width as usize;
let data = &mut self.data;
rast.for_each_pixel(fill_rule, |x, y, coverage| {
let cov = if anti_alias {
coverage
} else if coverage >= 0.5 {
1.0
} else {
0.0
};
let idx = y * width + x;
let old = data[idx] as f32 / 255.0;
let new = combine(old, cov).clamp(0.0, 1.0);
data[idx] = (new * 255.0 + 0.5) as u8;
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Rect;
use crate::path::PathBuilder;
#[test]
fn rect_mask_covers_interior_only() {
let path = PathBuilder::from_rect(Rect::from_xywh(2.0, 2.0, 6.0, 6.0).unwrap());
let mask = Mask::from_path(10, 10, &path, FillRule::NonZero, true, Transform::identity())
.unwrap();
assert!(mask.coverage_at(5, 5) > 0.99, "inside");
assert!(mask.coverage_at(0, 0) < 0.01, "outside");
}
#[test]
fn intersect_keeps_overlap_only() {
let left = PathBuilder::from_rect(Rect::from_xywh(0.0, 0.0, 6.0, 10.0).unwrap());
let mut mask =
Mask::from_path(10, 10, &left, FillRule::NonZero, true, Transform::identity()).unwrap();
let right = PathBuilder::from_rect(Rect::from_xywh(4.0, 0.0, 6.0, 10.0).unwrap());
mask.intersect_path(&right, FillRule::NonZero, true, Transform::identity());
assert!(mask.coverage_at(5, 5) > 0.99, "intersection");
assert!(mask.coverage_at(1, 5) < 0.01, "outside the intersection");
}
}