1 | //! The circle primitive |
2 | |
3 | use crate::{ |
4 | geometry::{Dimensions, Point, PointExt, Size}, |
5 | primitives::{ |
6 | common::DistanceIterator, ContainsPoint, OffsetOutline, PointsIter, Primitive, Rectangle, |
7 | }, |
8 | transform::Transform, |
9 | }; |
10 | |
11 | mod points; |
12 | mod styled; |
13 | |
14 | pub use points::Points; |
15 | pub 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))] |
59 | pub 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 | |
67 | impl 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 | |
107 | impl 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 | |
119 | impl Primitive for Circle {} |
120 | |
121 | impl PointsIter for Circle { |
122 | type Iter = Points; |
123 | |
124 | fn points(&self) -> Self::Iter { |
125 | Points::new(self) |
126 | } |
127 | } |
128 | |
129 | impl 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 | |
138 | impl Dimensions for Circle { |
139 | fn bounding_box(&self) -> Rectangle { |
140 | Rectangle::new(self.top_left, Size::new_equal(self.diameter)) |
141 | } |
142 | } |
143 | |
144 | impl 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 | |
180 | pub(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)] |
189 | mod 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 | |