embedded_graphics/primitives/ellipse/
mod.rs

1//! The ellipse primitive
2
3use crate::{
4    geometry::{Dimensions, Point, Size},
5    primitives::{circle, ContainsPoint, OffsetOutline, PointsIter, Primitive, Rectangle},
6    transform::Transform,
7};
8
9mod points;
10mod styled;
11
12pub use points::Points;
13pub use styled::StyledPixelsIterator;
14
15/// Ellipse primitive
16///
17/// # Examples
18///
19/// ## Create some ellipses with different styles
20///
21/// ```rust
22/// use embedded_graphics::{
23///     pixelcolor::Rgb565,
24///     prelude::*,
25///     primitives::{Ellipse, PrimitiveStyle, PrimitiveStyleBuilder},
26/// };
27/// # use embedded_graphics::mock_display::MockDisplay;
28///
29/// // Ellipse with 1 pixel wide white stroke with top-left point at (10, 20) with a size of (30, 40)
30/// # let mut display = MockDisplay::default();
31/// Ellipse::new(Point::new(10, 20), Size::new(30, 40))
32///     .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1))
33///     .draw(&mut display)?;
34///
35/// // Ellipse with styled stroke and fill with top-left point at (20, 30) with a size of (40, 30)
36/// let style = PrimitiveStyleBuilder::new()
37///     .stroke_color(Rgb565::RED)
38///     .stroke_width(3)
39///     .fill_color(Rgb565::GREEN)
40///     .build();
41///
42/// # let mut display = MockDisplay::default();
43/// Ellipse::new(Point::new(20, 30), Size::new(40, 30))
44///     .into_styled(style)
45///     .draw(&mut display)?;
46///
47/// // Ellipse with blue fill and no stroke with a translation applied
48/// # let mut display = MockDisplay::default();
49/// Ellipse::new(Point::new(10, 20), Size::new(20, 40))
50///     .translate(Point::new(10, -15))
51///     .into_styled(PrimitiveStyle::with_fill(Rgb565::BLUE))
52///     .draw(&mut display)?;
53/// # Ok::<(), core::convert::Infallible>(())
54/// ```
55#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
56#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
57pub struct Ellipse {
58    /// Top-left point of ellipse's bounding box
59    pub top_left: Point,
60
61    /// Size of the ellipse
62    pub size: Size,
63}
64
65impl Ellipse {
66    /// Create a new ellipse delimited with a top-left point with a specific size
67    pub const fn new(top_left: Point, size: Size) -> Self {
68        Ellipse { top_left, size }
69    }
70
71    /// Create a new ellipse centered around a given point with a specific size
72    pub const fn with_center(center: Point, size: Size) -> Self {
73        let top_left = Rectangle::with_center(center, size).top_left;
74
75        Ellipse { top_left, size }
76    }
77
78    /// Return the center point of the ellipse
79    pub fn center(&self) -> Point {
80        self.bounding_box().center()
81    }
82
83    /// Return the center point of the ellipse scaled by a factor of 2
84    ///
85    /// This method is used to accurately calculate the outside edge of the ellipse.
86    /// The result is not equivalent to `self.center() * 2` because of rounding.
87    fn center_2x(&self) -> Point {
88        center_2x(self.top_left, self.size)
89    }
90}
91
92impl OffsetOutline for Ellipse {
93    fn offset(&self, offset: i32) -> Self {
94        let size = if offset >= 0 {
95            self.size.saturating_add(Size::new_equal(2 * offset as u32))
96        } else {
97            self.size
98                .saturating_sub(Size::new_equal(2 * (-offset) as u32))
99        };
100
101        Self::with_center(self.center(), size)
102    }
103}
104
105/// Return the center point of the ellipse scaled by a factor of 2
106///
107/// This method is used to accurately calculate the outside edge of the ellipse.
108/// The result is not equivalent to `Ellipse::center() * 2` because of rounding.
109pub(in crate::primitives) fn center_2x(top_left: Point, size: Size) -> Point {
110    let radius = size.saturating_sub(Size::new(1, 1));
111
112    top_left * 2 + radius
113}
114
115impl Primitive for Ellipse {}
116
117impl PointsIter for Ellipse {
118    type Iter = Points;
119
120    fn points(&self) -> Self::Iter {
121        Points::new(self)
122    }
123}
124
125impl ContainsPoint for Ellipse {
126    fn contains(&self, point: Point) -> bool {
127        let ellipse_contains = EllipseContains::new(self.size);
128        ellipse_contains.contains(point * 2 - self.center_2x())
129    }
130}
131
132impl Dimensions for Ellipse {
133    fn bounding_box(&self) -> Rectangle {
134        Rectangle::new(self.top_left, self.size)
135    }
136}
137
138impl Transform for Ellipse {
139    /// Translate the ellipse from its current position to a new position by (x, y) pixels,
140    /// returning a new `Ellipse`. For a mutating transform, see `translate_mut`.
141    ///
142    /// ```
143    /// # use embedded_graphics::primitives::Ellipse;
144    /// # use embedded_graphics::prelude::*;
145    /// let ellipse = Ellipse::new(Point::new(5, 10), Size::new(10, 15));
146    /// let moved = ellipse.translate(Point::new(10, 10));
147    ///
148    /// assert_eq!(moved.top_left, Point::new(15, 20));
149    /// ```
150    fn translate(&self, by: Point) -> Self {
151        Self {
152            top_left: self.top_left + by,
153            ..*self
154        }
155    }
156
157    /// Translate the ellipse from its current position to a new position by (x, y) pixels.
158    ///
159    /// ```
160    /// # use embedded_graphics::primitives::Ellipse;
161    /// # use embedded_graphics::prelude::*;
162    /// let mut ellipse = Ellipse::new(Point::new(5, 10), Size::new(10, 15));
163    /// ellipse.translate_mut(Point::new(10, 10));
164    ///
165    /// assert_eq!(ellipse.top_left, Point::new(15, 20));
166    /// ```
167    fn translate_mut(&mut self, by: Point) -> &mut Self {
168        self.top_left += by;
169
170        self
171    }
172}
173
174/// Determines if a point is inside an ellipse.
175// TODO: Make this available to the user as part of #343
176#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)]
177#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
178pub(in crate::primitives) struct EllipseContains {
179    a: u32,
180    b: u32,
181    threshold: u32,
182}
183
184impl EllipseContains {
185    /// Creates an object to determine if a point is inside an ellipse.
186    ///
187    /// The ellipse is always located in the origin.
188    pub const fn new(size: Size) -> Self {
189        let Size { width, height } = size;
190
191        let a = width.pow(2);
192        let b = height.pow(2);
193
194        // Special case for circles, where width and height are equal
195        let threshold = if width == height {
196            circle::diameter_to_threshold(width)
197        } else {
198            b * a
199        };
200
201        Self { a, b, threshold }
202    }
203
204    /// Returns `true` if the point is inside the ellipse.
205    pub const fn contains(&self, point: Point) -> bool {
206        let x = point.x.pow(2) as u32;
207        let y = point.y.pow(2) as u32;
208
209        // Special case for circles, where width and height are equal
210        if self.a == self.b {
211            x + y < self.threshold
212        } else {
213            self.b * x + self.a * y < self.threshold
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221    use crate::{
222        geometry::{Point, Size},
223        mock_display::MockDisplay,
224        pixelcolor::BinaryColor,
225        primitives::ContainsPoint,
226    };
227
228    #[test]
229    fn contains() {
230        let ellipse = Ellipse::new(Point::zero(), Size::new(40, 20));
231
232        let display = MockDisplay::from_points(
233            ellipse
234                .bounding_box()
235                .points()
236                .filter(|p| ellipse.contains(*p)),
237            BinaryColor::On,
238        );
239
240        let expected = MockDisplay::from_points(ellipse.points(), BinaryColor::On);
241
242        display.assert_eq(&expected);
243    }
244
245    #[test]
246    fn translate() {
247        let moved = Ellipse::new(Point::new(4, 6), Size::new(5, 8)).translate(Point::new(3, 5));
248
249        assert_eq!(moved, Ellipse::new(Point::new(7, 11), Size::new(5, 8)));
250    }
251
252    #[test]
253    fn offset() {
254        let center = Point::new(5, 6);
255        let ellipse = Ellipse::with_center(center, Size::new(3, 4));
256
257        assert_eq!(ellipse.offset(0), ellipse);
258
259        assert_eq!(
260            ellipse.offset(1),
261            Ellipse::with_center(center, Size::new(5, 6))
262        );
263        assert_eq!(
264            ellipse.offset(2),
265            Ellipse::with_center(center, Size::new(7, 8))
266        );
267
268        assert_eq!(
269            ellipse.offset(-1),
270            Ellipse::with_center(center, Size::new(1, 2))
271        );
272        assert_eq!(
273            ellipse.offset(-2),
274            Ellipse::with_center(center, Size::new(0, 0))
275        );
276        assert_eq!(
277            ellipse.offset(-3),
278            Ellipse::with_center(center, Size::new(0, 0))
279        );
280    }
281}