#[derive(Clone, Debug, PartialEq)]
pub struct Region {
shape: Shape,
ops: Vec<(Op, Region)>,
}
#[derive(Clone, Debug, PartialEq)]
enum Shape {
Empty,
Rect([i32; 4], [f32; 4]),
Ellipse([i32; 4]),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Op {
Add,
Subtract,
Intersect,
Xor,
}
type Spans = Vec<(i32, i32)>;
impl Region {
pub fn empty() -> Region {
Region {
shape: Shape::Empty,
ops: Vec::new(),
}
}
pub fn rect(x: i32, y: i32, width: u32, height: u32) -> Region {
Region {
shape: Shape::Rect(bounds(x, y, width, height), [0.0; 4]),
ops: Vec::new(),
}
}
pub fn ellipse(x: i32, y: i32, width: u32, height: u32) -> Region {
Region {
shape: Shape::Ellipse(bounds(x, y, width, height)),
ops: Vec::new(),
}
}
pub fn radius(self, radius: f32) -> Region {
self.radii(radius, radius, radius, radius)
}
pub fn radii(
mut self,
top_left: f32,
top_right: f32,
bottom_right: f32,
bottom_left: f32,
) -> Region {
if let Shape::Rect(_, radii) = &mut self.shape {
*radii = [top_left, top_right, bottom_right, bottom_left];
}
self
}
pub fn union(self, other: Region) -> Region {
self.then(Op::Add, other)
}
pub fn subtract(self, other: Region) -> Region {
self.then(Op::Subtract, other)
}
pub fn intersect(self, other: Region) -> Region {
self.then(Op::Intersect, other)
}
pub fn xor(self, other: Region) -> Region {
self.then(Op::Xor, other)
}
fn then(mut self, op: Op, other: Region) -> Region {
self.ops.push((op, other));
self
}
pub fn rects(&self, width: u32, height: u32) -> Vec<[i32; 4]> {
let width = i32::try_from(width).unwrap_or(i32::MAX);
let height = i32::try_from(height).unwrap_or(i32::MAX);
let mut rects = Vec::new();
let mut band: Option<(i32, Spans)> = None;
for y in 0..=height {
let spans = if y < height {
clip(self.spans(y as f32 + 0.5), width)
} else {
Spans::new()
};
if band.as_ref().is_some_and(|(_, above)| *above == spans) {
continue;
}
if let Some((top, above)) = band.take() {
rects.extend(
above
.iter()
.map(|&(start, end)| [start, top, end - start, y - top]),
);
}
band = Some((y, spans));
}
rects
}
fn spans(&self, y: f32) -> Spans {
let mut spans = match self.shape {
Shape::Empty => Spans::new(),
Shape::Rect(rect, radii) => rect_spans(rect, radii, y),
Shape::Ellipse(rect) => ellipse_spans(rect, y),
};
for (op, other) in &self.ops {
let keep = match op {
Op::Add => |a, b| a || b,
Op::Subtract => |a, b: bool| a && !b,
Op::Intersect => |a, b| a && b,
Op::Xor => |a, b| a != b,
};
spans = combine(&spans, &other.spans(y), keep);
}
spans
}
}
fn bounds(x: i32, y: i32, width: u32, height: u32) -> [i32; 4] {
let side = |side: u32| i32::try_from(side).unwrap_or(i32::MAX);
[
x,
y,
x.saturating_add(side(width)),
y.saturating_add(side(height)),
]
}
fn rect_spans([left, top, right, bottom]: [i32; 4], radii: [f32; 4], y: f32) -> Spans {
if y < top as f32 || y >= bottom as f32 {
return Spans::new();
}
let most = (right as f32 - left as f32).min(bottom as f32 - top as f32) / 2.0;
let [top_left, top_right, bottom_right, bottom_left] = radii.map(|r| r.clamp(0.0, most));
let inset = |r: f32, dy: f32| {
if dy >= r {
0.0
} else {
r - (r * r - (r - dy) * (r - dy)).sqrt()
}
};
let (from_top, from_bottom) = (y - top as f32, bottom as f32 - y);
let left_inset = inset(top_left, from_top).max(inset(bottom_left, from_bottom));
let right_inset = inset(top_right, from_top).max(inset(bottom_right, from_bottom));
span(left as f32 + left_inset, right as f32 - right_inset)
}
fn ellipse_spans([left, top, right, bottom]: [i32; 4], y: f32) -> Spans {
let rx = (right as f32 - left as f32) / 2.0;
let ry = (bottom as f32 - top as f32) / 2.0;
if rx <= 0.0 || ry <= 0.0 {
return Spans::new();
}
let t = (y - (top as f32 + ry)) / ry;
if t.abs() >= 1.0 {
return Spans::new();
}
let (cx, dx) = (left as f32 + rx, rx * (1.0 - t * t).sqrt());
span(cx - dx, cx + dx)
}
fn span(start: f32, end: f32) -> Spans {
let (start, end) = (start.round() as i32, end.round() as i32);
if start < end {
vec![(start, end)]
} else {
Spans::new()
}
}
fn clip(spans: Spans, width: i32) -> Spans {
spans
.into_iter()
.map(|(start, end)| (start.max(0), end.min(width)))
.filter(|(start, end)| start < end)
.collect()
}
fn combine(a: &[(i32, i32)], b: &[(i32, i32)], keep: fn(bool, bool) -> bool) -> Spans {
let mut edges: Vec<i32> = a
.iter()
.chain(b)
.flat_map(|&(start, end)| [start, end])
.collect();
edges.sort_unstable();
edges.dedup();
let inside =
|spans: &[(i32, i32)], x: i32| spans.iter().any(|&(start, end)| start <= x && x < end);
let mut out = Spans::new();
for pair in edges.windows(2) {
let (start, end) = (pair[0], pair[1]);
if !keep(inside(a, start), inside(b, start)) {
continue;
}
match out.last_mut() {
Some(last) if last.1 == start => last.1 = end,
_ => out.push((start, end)),
}
}
out
}