embedded_graphics/primitives/ellipse/
points.rs

1use core::ops::Range;
2
3use crate::{
4    geometry::{Dimensions, Point},
5    primitives::{
6        common::Scanline,
7        ellipse::{Ellipse, EllipseContains},
8    },
9};
10
11/// Iterator over all points inside the ellipse
12#[derive(Clone, Eq, PartialEq, Hash, Debug)]
13#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
14pub struct Points {
15    scanlines: Scanlines,
16    current_scanline: Scanline,
17}
18
19impl Points {
20    pub(in crate::primitives) fn new(ellipse: &Ellipse) -> Self {
21        Self {
22            scanlines: Scanlines::new(ellipse),
23            current_scanline: Scanline::new_empty(0),
24        }
25    }
26}
27
28impl Iterator for Points {
29    type Item = Point;
30
31    fn next(&mut self) -> Option<Self::Item> {
32        self.current_scanline.next().or_else(|| {
33            self.current_scanline = self.scanlines.next()?;
34            self.current_scanline.next()
35        })
36    }
37}
38
39#[derive(Clone, Eq, PartialEq, Hash, Debug)]
40#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
41pub struct Scanlines {
42    rows: Range<i32>,
43    columns: Range<i32>,
44    pub(super) center_2x: Point,
45    ellipse_contains: EllipseContains,
46}
47
48impl Scanlines {
49    pub fn new(ellipse: &Ellipse) -> Self {
50        let bounding_box = ellipse.bounding_box();
51
52        Self {
53            rows: bounding_box.rows(),
54            columns: bounding_box.columns(),
55            center_2x: ellipse.center_2x(),
56            ellipse_contains: EllipseContains::new(ellipse.size),
57        }
58    }
59}
60
61impl Iterator for Scanlines {
62    type Item = Scanline;
63
64    fn next(&mut self) -> Option<Self::Item> {
65        let y = self.rows.next()?;
66
67        let scaled_y = y * 2 - self.center_2x.y;
68
69        self.columns
70            .clone()
71            // Find the first pixel that is inside the ellipse.
72            .find(|x| {
73                self.ellipse_contains
74                    .contains(Point::new(*x * 2 - self.center_2x.x, scaled_y))
75            })
76            // Shorten the right side of the scanline by the same amount as the left side.
77            .map(|x| Scanline::new(y, x..self.columns.end - (x - self.columns.start)))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::{
85        geometry::Size,
86        primitives::{Circle, PointsIter},
87    };
88
89    #[test]
90    fn matches_circles_points() {
91        for diameter in 0..50 {
92            let circle_points = Circle::new(Point::new(0, 0), diameter).points();
93
94            let ellipse_points =
95                Ellipse::new(Point::new(0, 0), Size::new(diameter, diameter)).points();
96
97            assert!(circle_points.eq(ellipse_points), "diameter = {}", diameter);
98        }
99    }
100}