1use crate::frame::glyph_buffer::PxRect;
29
30fn spans(clip: PxRect) -> ((i64, i64), (i64, i64)) {
32 let (x, y) = (i64::from(clip.x), i64::from(clip.y));
33 (
34 (x, x + i64::from(clip.w.max(0))),
35 (y, y + i64::from(clip.h.max(0))),
36 )
37}
38
39pub(crate) fn clip_run(clip: PxRect, y: i64, x0: i64, x1: i64, f: &mut impl FnMut(i32, i32, i32)) {
42 let ((left, right), (top, bottom)) = spans(clip);
43 let (x0, x1) = (x0.max(left), x1.min(right));
44 if x0 >= x1 || y < top || y >= bottom {
45 return;
46 }
47 if let (Ok(y), Ok(x0), Ok(x1)) = (i32::try_from(y), i32::try_from(x0), i32::try_from(x1)) {
48 f(y, x0, x1);
49 }
50}
51
52pub fn line_runs(from: (i32, i32), to: (i32, i32), clip: PxRect, mut f: impl FnMut(i32, i32, i32)) {
56 let (x0, y0) = (i64::from(from.0), i64::from(from.1));
57 let (x1, y1) = (i64::from(to.0), i64::from(to.1));
58 let (dx, dy) = ((x1 - x0).abs(), (y1 - y0).abs());
59 let (sx, sy) = ((x1 - x0).signum(), (y1 - y0).signum());
60 let x_major = dx >= dy;
61 let ((major0, major_step, major_len), (minor0, minor_step, minor_len)) = if x_major {
62 ((x0, sx, dx), (y0, sy, dy))
63 } else {
64 ((y0, sy, dy), (x0, sx, dx))
65 };
66 let (columns, rows) = spans(clip);
67 let (major_span, minor_span) = if x_major {
68 (columns, rows)
69 } else {
70 (rows, columns)
71 };
72 if clip.is_empty() || major_len == 0 && !(major_span.0..major_span.1).contains(&major0) {
75 return;
76 }
77 let (near, far) = (
80 major_step * (major_span.0 - major0),
81 major_step * (major_span.1 - 1 - major0),
82 );
83 let (first, end) = (near.min(far).max(0), (near.max(far) + 1).min(major_len + 1));
84 let mut run: Option<(i64, i64, i64)> = None;
87 for k in first..end {
88 let major = major0 + major_step * k;
89 let minor = minor0 + minor_step * minor_offset(k, major_len, minor_len);
90 if !(minor_span.0..minor_span.1).contains(&minor) {
91 continue;
92 }
93 if !x_major {
94 emit(major, minor, minor + 1, &mut f);
95 continue;
96 }
97 let (column, line) = (major, minor);
98 run = match run {
99 Some((row, first, last))
100 if row == line && (column == first - 1 || column == last + 1) =>
101 {
102 Some((row, first.min(column), last.max(column)))
103 }
104 Some((row, first, last)) => {
105 emit(row, first, last + 1, &mut f);
106 Some((line, column, column))
107 }
108 None => Some((line, column, column)),
109 };
110 }
111 if let Some((row, first, last)) = run {
112 emit(row, first, last + 1, &mut f);
113 }
114}
115
116fn emit(y: i64, x0: i64, x1: i64, f: &mut impl FnMut(i32, i32, i32)) {
118 if let (Ok(y), Ok(x0), Ok(x1)) = (i32::try_from(y), i32::try_from(x0), i32::try_from(x1)) {
119 f(y, x0, x1);
120 }
121}
122
123fn minor_offset(k: i64, major: i64, minor: i64) -> i64 {
127 if major == 0 {
128 return 0;
129 }
130 let (k, major, minor) = (i128::from(k), i128::from(major), i128::from(minor));
131 let offset = (2 * k * minor + major).div_euclid(2 * major);
132 i64::try_from(offset).unwrap_or(0)
134}
135
136pub fn circle_outline_runs(
140 center: (i32, i32),
141 radius: u16,
142 clip: PxRect,
143 mut f: impl FnMut(i32, i32, i32),
144) {
145 let (cx, cy) = (i64::from(center.0), i64::from(center.1));
146 let (mut x, mut y) = (i64::from(radius), 0_i64);
147 let mut d = 1 - x;
148 let mut dot = |px: i64, py: i64| clip_run(clip, py, px, px + 1, &mut f);
149 for _ in 0..=radius {
152 if x < y {
153 break;
154 }
155 for (px, py) in [
156 (x, y),
157 (y, x),
158 (-y, x),
159 (-x, y),
160 (-x, -y),
161 (-y, -x),
162 (y, -x),
163 (x, -y),
164 ] {
165 dot(cx + px, cy + py);
166 }
167 y += 1;
168 if d < 0 {
169 d += 2 * y + 1;
170 } else {
171 x -= 1;
172 d += 2 * (y - x) + 1;
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use alloc::collections::BTreeSet;
180
181 use proptest::prelude::*;
182
183 use super::*;
184 use crate::frame::glyph_buffer::Rect;
185 use crate::prelude::*;
186
187 fn pixels(runs: impl FnOnce(&mut dyn FnMut(i32, i32, i32))) -> BTreeSet<(i32, i32)> {
189 let mut out = BTreeSet::new();
190 runs(&mut |y, x0, x1| {
191 assert!(x0 < x1, "empty run {y} {x0}..{x1}");
192 out.extend((x0..x1).map(|x| (x, y)));
193 });
194 out
195 }
196
197 fn line(from: (i32, i32), to: (i32, i32), clip: PxRect) -> BTreeSet<(i32, i32)> {
198 pixels(|f| line_runs(from, to, clip, f))
199 }
200
201 fn walked(from: (i32, i32), to: (i32, i32)) -> BTreeSet<(i32, i32)> {
203 let (mut x, mut y) = (i64::from(from.0), i64::from(from.1));
204 let (x1, y1) = (i64::from(to.0), i64::from(to.1));
205 let (dx, dy) = ((x1 - x).abs(), -(y1 - y).abs());
206 let (sx, sy) = (if x < x1 { 1 } else { -1 }, if y < y1 { 1 } else { -1 });
207 let mut err = dx + dy;
208 let mut out = BTreeSet::new();
209 loop {
210 out.insert((i32::try_from(x).unwrap(), i32::try_from(y).unwrap()));
211 if x == x1 && y == y1 {
212 return out;
213 }
214 let e2 = 2 * err;
215 if e2 >= dy {
216 err += dy;
217 x += sx;
218 }
219 if e2 <= dx {
220 err += dx;
221 y += sy;
222 }
223 }
224 }
225
226 const WIDE: PxRect = Rect::new(-100, -100, 200, 200);
227
228 #[test]
229 fn a_line_in_each_octant_is_bresenhams() {
230 let ends = [
233 (7, 2),
234 (2, 7),
235 (-2, 7),
236 (-7, 2),
237 (-7, -2),
238 (-2, -7),
239 (2, -7),
240 (7, -2),
241 (5, 0),
242 (0, 5),
243 (-5, 0),
244 (0, -5),
245 (4, 4),
246 (-4, 4),
247 (-4, -4),
248 (4, -4),
249 (0, 0),
250 ];
251 for to in ends {
252 assert_eq!(line((0, 0), to, WIDE), walked((0, 0), to), "to {to:?}");
253 assert_eq!(line(to, (0, 0), WIDE), walked(to, (0, 0)), "from {to:?}");
254 }
255 let shallow: Vec<_> = line((0, 0), (4, 1), WIDE).into_iter().collect();
257 assert_eq!(shallow, [(0, 0), (1, 0), (2, 1), (3, 1), (4, 1)]);
258 }
259
260 #[test]
261 fn a_line_is_one_run_a_row_inside_the_clip() {
262 let mut runs = Vec::new();
263 line_runs((0, 0), (9, 2), WIDE, |y, x0, x1| runs.push((y, x0, x1)));
264 assert_eq!(runs, [(0, 0, 3), (1, 3, 7), (2, 7, 10)]);
265 let mut back = Vec::new();
267 line_runs((9, 2), (0, 0), WIDE, |y, x0, x1| back.push((y, x0, x1)));
268 assert_eq!(back.len(), 3);
269 let mut cut = Vec::new();
271 line_runs((0, 0), (9, 2), Rect::new(2, 1, 3, 5), |y, x0, x1| {
272 cut.push((y, x0, x1));
273 });
274 assert_eq!(cut, [(1, 3, 5)]);
275 assert!(line((0, 0), (9, 2), Rect::new(0, 0, 0, 5)).is_empty());
277 }
278
279 #[test]
280 fn a_line_far_longer_than_its_clip_takes_only_the_clips_steps() {
281 let clip = Rect::new(0, 0, 16, 16);
282 let far = line((i32::MIN, i32::MIN), (i32::MAX, i32::MAX), clip);
283 let near: BTreeSet<_> = (0..16).map(|i| (i, i)).collect();
284 assert_eq!(far, near);
285 let flat = line((i32::MIN, 3), (i32::MAX, 3), clip);
286 assert_eq!(flat, (0..16).map(|x| (x, 3)).collect());
287 }
288
289 #[test]
290 fn a_line_of_one_pixel_is_drawn_only_inside_its_clip() {
291 let clip = Rect::new(0, 0, 4, 4);
292 assert_eq!(line((2, 3), (2, 3), clip), BTreeSet::from([(2, 3)]));
293 for out in [(4, 3), (-1, 3), (2, 4), (2, -1)] {
294 assert!(line(out, out, clip).is_empty(), "{out:?}");
295 }
296 assert_eq!(
298 line((-5, 1), (9, 1), clip),
299 (0..4).map(|x| (x, 1)).collect()
300 );
301 assert_eq!(
302 line((1, 9), (1, -5), clip),
303 (0..4).map(|y| (1, y)).collect()
304 );
305 }
306
307 fn octant(radius: u16) -> Vec<(i32, i32)> {
309 let mut steps = Vec::new();
310 circle_outline_runs((0, 0), radius, WIDE, |y, x, _| {
311 if x >= y && y >= 0 {
312 steps.push((x, y));
313 }
314 });
315 steps.dedup();
316 steps
317 }
318
319 #[test]
320 fn the_midpoint_walk_takes_the_textbooks_steps_in_its_order() {
321 let mut runs = Vec::new();
323 circle_outline_runs((10, 20), 3, WIDE, |y, x0, x1| runs.push((x0, y, x1 - x0)));
324 let points: Vec<(i32, i32)> = runs.iter().map(|&(x, y, _)| (x, y)).collect();
325 assert!(runs.iter().all(|r| r.2 == 1));
326 assert_eq!(
327 points,
328 [
329 (13, 20),
330 (10, 23),
331 (10, 23),
332 (7, 20),
333 (7, 20),
334 (10, 17),
335 (10, 17),
336 (13, 20),
337 (13, 21),
338 (11, 23),
339 (9, 23),
340 (7, 21),
341 (7, 19),
342 (9, 17),
343 (11, 17),
344 (13, 19),
345 (12, 22),
346 (12, 22),
347 (8, 22),
348 (8, 22),
349 (8, 18),
350 (8, 18),
351 (12, 18),
352 (12, 18),
353 ]
354 );
355 assert_eq!(octant(5), [(5, 0), (5, 1), (5, 2), (4, 3)]);
356 assert_eq!(
357 octant(10),
358 [
359 (10, 0),
360 (10, 1),
361 (10, 2),
362 (10, 3),
363 (9, 4),
364 (9, 5),
365 (8, 6),
366 (7, 7)
367 ]
368 );
369 let seventeen = [
370 (17, 0),
371 (17, 1),
372 (17, 2),
373 (17, 3),
374 (17, 4),
375 (16, 5),
376 (16, 6),
377 (15, 7),
378 (15, 8),
379 (14, 9),
380 (14, 10),
381 (13, 11),
382 (12, 12),
383 ];
384 assert_eq!(octant(17), seventeen);
385 }
386
387 #[test]
388 fn the_midpoint_circle_starts_from_one_less_the_radius() {
389 let circle = |r| pixels(|f| circle_outline_runs((0, 0), r, WIDE, f));
390 assert_eq!(circle(0), BTreeSet::from([(0, 0)]));
391 assert_eq!(
393 circle(1),
394 BTreeSet::from([(1, 0), (0, 1), (-1, 0), (0, -1)])
395 );
396 let three = circle(3);
398 for p in [(3, 0), (3, 1), (2, 2), (1, 3), (0, 3), (-3, -1), (-2, -2)] {
399 assert!(three.contains(&p), "{p:?}");
400 }
401 assert!(!three.contains(&(3, 2)));
402 assert_eq!(three.len(), 16);
403 let five = circle(5);
405 let xs: BTreeSet<_> = five.iter().map(|p| p.0).collect();
406 assert_eq!(xs.len(), 11);
407 assert!(five.iter().all(|&(x, y)| five.contains(&(y, x))));
408 let quarter = pixels(|f| circle_outline_runs((0, 0), 5, Rect::new(0, 0, 10, 10), f));
410 assert!(quarter.iter().all(|&(x, y)| x >= 0 && y >= 0));
411 assert!(quarter.is_subset(&five));
412 }
413
414 proptest! {
415 #[test]
416 fn the_closed_form_is_the_walk(
417 x0 in -40..40, y0 in -40..40, x1 in -40..40, y1 in -40..40,
418 cx in -50..50, cy in -50..50, cw in 0..100, ch in 0..100,
419 ) {
420 let clip = Rect::new(cx, cy, cw, ch);
421 let want: BTreeSet<_> = walked((x0, y0), (x1, y1))
422 .into_iter()
423 .filter(|&(x, y)| clip.contains(x, y))
424 .collect();
425 prop_assert_eq!(line((x0, y0), (x1, y1), clip), want);
426 }
427
428 #[test]
429 fn a_circle_never_leaves_its_clip(
430 r in 0_u16..200, cx in -300..300, cy in -300..300,
431 x in -50..50, y in -50..50, w in 0..100, h in 0..100,
432 ) {
433 let clip = Rect::new(x, y, w, h);
434 let got = pixels(|f| circle_outline_runs((cx, cy), r, clip, f));
435 prop_assert!(got.iter().all(|&(px, py)| clip.contains(px, py)));
436 }
437 }
438}