1#[derive(Clone, Debug, PartialEq)]
20pub struct Region {
21 shape: Shape,
22 ops: Vec<(Op, Region)>,
23}
24
25#[derive(Clone, Debug, PartialEq)]
26enum Shape {
27 Empty,
28 Rect([i32; 4], [f32; 4]),
30 Ellipse([i32; 4]),
31}
32
33#[derive(Clone, Copy, Debug, PartialEq)]
34enum Op {
35 Add,
36 Subtract,
37 Intersect,
38 Xor,
39}
40
41type Spans = Vec<(i32, i32)>;
43
44impl Region {
45 pub fn empty() -> Region {
47 Region {
48 shape: Shape::Empty,
49 ops: Vec::new(),
50 }
51 }
52
53 pub fn rect(x: i32, y: i32, width: u32, height: u32) -> Region {
55 Region {
56 shape: Shape::Rect(bounds(x, y, width, height), [0.0; 4]),
57 ops: Vec::new(),
58 }
59 }
60
61 pub fn ellipse(x: i32, y: i32, width: u32, height: u32) -> Region {
63 Region {
64 shape: Shape::Ellipse(bounds(x, y, width, height)),
65 ops: Vec::new(),
66 }
67 }
68
69 pub fn radius(self, radius: f32) -> Region {
72 self.radii(radius, radius, radius, radius)
73 }
74
75 pub fn radii(
78 mut self,
79 top_left: f32,
80 top_right: f32,
81 bottom_right: f32,
82 bottom_left: f32,
83 ) -> Region {
84 if let Shape::Rect(_, radii) = &mut self.shape {
85 *radii = [top_left, top_right, bottom_right, bottom_left];
86 }
87 self
88 }
89
90 pub fn union(self, other: Region) -> Region {
92 self.then(Op::Add, other)
93 }
94
95 pub fn subtract(self, other: Region) -> Region {
97 self.then(Op::Subtract, other)
98 }
99
100 pub fn intersect(self, other: Region) -> Region {
102 self.then(Op::Intersect, other)
103 }
104
105 pub fn xor(self, other: Region) -> Region {
107 self.then(Op::Xor, other)
108 }
109
110 fn then(mut self, op: Op, other: Region) -> Region {
111 self.ops.push((op, other));
112 self
113 }
114
115 pub fn rects(&self, width: u32, height: u32) -> Vec<[i32; 4]> {
119 let width = i32::try_from(width).unwrap_or(i32::MAX);
120 let height = i32::try_from(height).unwrap_or(i32::MAX);
121 let mut rects = Vec::new();
122 let mut band: Option<(i32, Spans)> = None;
124 for y in 0..=height {
125 let spans = if y < height {
126 clip(self.spans(y as f32 + 0.5), width)
127 } else {
128 Spans::new()
129 };
130 if band.as_ref().is_some_and(|(_, above)| *above == spans) {
131 continue;
132 }
133 if let Some((top, above)) = band.take() {
134 rects.extend(
135 above
136 .iter()
137 .map(|&(start, end)| [start, top, end - start, y - top]),
138 );
139 }
140 band = Some((y, spans));
141 }
142 rects
143 }
144
145 fn spans(&self, y: f32) -> Spans {
147 let mut spans = match self.shape {
148 Shape::Empty => Spans::new(),
149 Shape::Rect(rect, radii) => rect_spans(rect, radii, y),
150 Shape::Ellipse(rect) => ellipse_spans(rect, y),
151 };
152 for (op, other) in &self.ops {
153 let keep = match op {
154 Op::Add => |a, b| a || b,
155 Op::Subtract => |a, b: bool| a && !b,
156 Op::Intersect => |a, b| a && b,
157 Op::Xor => |a, b| a != b,
158 };
159 spans = combine(&spans, &other.spans(y), keep);
160 }
161 spans
162 }
163}
164
165fn bounds(x: i32, y: i32, width: u32, height: u32) -> [i32; 4] {
167 let side = |side: u32| i32::try_from(side).unwrap_or(i32::MAX);
168 [
169 x,
170 y,
171 x.saturating_add(side(width)),
172 y.saturating_add(side(height)),
173 ]
174}
175
176fn rect_spans([left, top, right, bottom]: [i32; 4], radii: [f32; 4], y: f32) -> Spans {
177 if y < top as f32 || y >= bottom as f32 {
178 return Spans::new();
179 }
180 let most = (right as f32 - left as f32).min(bottom as f32 - top as f32) / 2.0;
182 let [top_left, top_right, bottom_right, bottom_left] = radii.map(|r| r.clamp(0.0, most));
183 let inset = |r: f32, dy: f32| {
185 if dy >= r {
186 0.0
187 } else {
188 r - (r * r - (r - dy) * (r - dy)).sqrt()
189 }
190 };
191 let (from_top, from_bottom) = (y - top as f32, bottom as f32 - y);
192 let left_inset = inset(top_left, from_top).max(inset(bottom_left, from_bottom));
193 let right_inset = inset(top_right, from_top).max(inset(bottom_right, from_bottom));
194 span(left as f32 + left_inset, right as f32 - right_inset)
195}
196
197fn ellipse_spans([left, top, right, bottom]: [i32; 4], y: f32) -> Spans {
198 let rx = (right as f32 - left as f32) / 2.0;
199 let ry = (bottom as f32 - top as f32) / 2.0;
200 if rx <= 0.0 || ry <= 0.0 {
201 return Spans::new();
202 }
203 let t = (y - (top as f32 + ry)) / ry;
204 if t.abs() >= 1.0 {
205 return Spans::new();
206 }
207 let (cx, dx) = (left as f32 + rx, rx * (1.0 - t * t).sqrt());
208 span(cx - dx, cx + dx)
209}
210
211fn span(start: f32, end: f32) -> Spans {
213 let (start, end) = (start.round() as i32, end.round() as i32);
214 if start < end {
215 vec![(start, end)]
216 } else {
217 Spans::new()
218 }
219}
220
221fn clip(spans: Spans, width: i32) -> Spans {
223 spans
224 .into_iter()
225 .map(|(start, end)| (start.max(0), end.min(width)))
226 .filter(|(start, end)| start < end)
227 .collect()
228}
229
230fn combine(a: &[(i32, i32)], b: &[(i32, i32)], keep: fn(bool, bool) -> bool) -> Spans {
232 let mut edges: Vec<i32> = a
233 .iter()
234 .chain(b)
235 .flat_map(|&(start, end)| [start, end])
236 .collect();
237 edges.sort_unstable();
238 edges.dedup();
239 let inside =
240 |spans: &[(i32, i32)], x: i32| spans.iter().any(|&(start, end)| start <= x && x < end);
241 let mut out = Spans::new();
242 for pair in edges.windows(2) {
243 let (start, end) = (pair[0], pair[1]);
244 if !keep(inside(a, start), inside(b, start)) {
245 continue;
246 }
247 match out.last_mut() {
248 Some(last) if last.1 == start => last.1 = end,
249 _ => out.push((start, end)),
250 }
251 }
252 out
253}