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: u32 = 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: Point = self.center_2x() - point * 2;
132 let distance: u32 = 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(exp:2) - diameter / 2
183 } else {
184 diameter.pow(exp: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}
261