embedded_graphics/primitives/circle/
mod.rs

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