embedded_graphics/primitives/arc/
styled.rs1use crate::{
2 draw_target::DrawTarget,
3 geometry::Dimensions,
4 pixelcolor::PixelColor,
5 primitives::{
6 arc::Arc,
7 common::{DistanceIterator, PlaneSector},
8 styled::{StyledDimensions, StyledDrawable, StyledPixels},
9 OffsetOutline, PrimitiveStyle, Rectangle,
10 },
11 Pixel,
12};
13
14use az::SaturatingAs;
15
16#[derive(Clone, PartialEq, Debug)]
18#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
19pub struct StyledPixelsIterator<C> {
20 iter: DistanceIterator,
21
22 plane_sector: PlaneSector,
23
24 outer_threshold: u32,
25 inner_threshold: u32,
26
27 stroke_color: Option<C>,
28}
29
30impl<C: PixelColor> StyledPixelsIterator<C> {
31 fn new(primitive: &Arc, style: &PrimitiveStyle<C>) -> Self {
32 let circle = primitive.to_circle();
33
34 let outside_edge = circle.offset(style.outside_stroke_width().saturating_as());
35 let inside_edge = circle.offset(-style.inside_stroke_width().saturating_as::<i32>());
36
37 let iter = if !style.is_transparent() {
38 outside_edge.distances()
40 } else {
41 DistanceIterator::empty()
42 };
43
44 let plane_sector = PlaneSector::new(primitive.angle_start, primitive.angle_sweep);
45
46 Self {
47 iter,
48 plane_sector,
49 outer_threshold: outside_edge.threshold(),
50 inner_threshold: inside_edge.threshold(),
51 stroke_color: style.stroke_color,
52 }
53 }
54}
55
56impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
57 type Item = Pixel<C>;
58
59 fn next(&mut self) -> Option<Self::Item> {
60 let stroke_color = self.stroke_color?;
61
62 self.iter
63 .find(|(_, delta, distance)| {
64 *distance < self.outer_threshold
65 && *distance >= self.inner_threshold
66 && self.plane_sector.contains(*delta)
67 })
68 .map(|(point, ..)| Pixel(point, stroke_color))
69 }
70}
71
72impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Arc {
73 type Color = C;
74 type Output = ();
75
76 fn draw_styled<D>(
77 &self,
78 style: &PrimitiveStyle<C>,
79 target: &mut D,
80 ) -> Result<Self::Output, D::Error>
81 where
82 D: DrawTarget<Color = C>,
83 {
84 target.draw_iter(StyledPixelsIterator::new(self, style))
85 }
86}
87
88impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Arc {
89 type Iter = StyledPixelsIterator<C>;
90
91 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
92 StyledPixelsIterator::new(self, style)
93 }
94}
95
96impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Arc {
97 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
99 let offset = style.outside_stroke_width().saturating_as();
100
101 self.bounding_box().offset(offset)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::{
109 draw_target::DrawTargetExt,
110 geometry::{AnchorPoint, AngleUnit, Point, Size},
111 mock_display::MockDisplay,
112 pixelcolor::BinaryColor,
113 primitives::{Circle, Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
114 Drawable,
115 };
116
117 #[test]
119 fn tiny_arc() -> Result<(), core::convert::Infallible> {
120 let mut display = MockDisplay::new();
121
122 Arc::new(Point::zero(), 7, 210.0.deg(), 120.0.deg())
123 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
124 .draw(&mut display)?;
125
126 display.assert_pattern(&[
127 " ### ", " # # ", ]);
130
131 Ok(())
132 }
133
134 #[test]
136 fn quadrant_arcs() {
137 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 2);
138
139 for &diameter in &[11, 12] {
140 for &(angle_start, angle_sweep, anchor_point) in &[
141 (0.0.deg(), 90.0.deg(), AnchorPoint::BottomRight),
142 (90.0.deg(), 90.0.deg(), AnchorPoint::BottomLeft),
143 (180.0.deg(), 90.0.deg(), AnchorPoint::TopLeft),
144 (270.0.deg(), 90.0.deg(), AnchorPoint::TopRight),
145 (0.0.deg(), -90.0.deg(), AnchorPoint::TopRight),
146 (90.0.deg(), -90.0.deg(), AnchorPoint::BottomRight),
147 (180.0.deg(), -90.0.deg(), AnchorPoint::BottomLeft),
148 (270.0.deg(), -90.0.deg(), AnchorPoint::TopLeft),
149 ] {
150 let circle = Circle::new(Point::new(1, 1), diameter).into_styled(style);
151
152 let bounding_box = circle.bounding_box();
154 let clip_rect = bounding_box
155 .resized((bounding_box.size + Size::new_equal(1)) / 2, anchor_point);
156
157 let mut expected = MockDisplay::new();
159 circle.draw(&mut expected.clipped(&clip_rect)).unwrap();
160
161 let mut display = MockDisplay::new();
163 Arc::new(Point::new(1, 1), diameter, angle_start, angle_sweep)
164 .into_styled(style)
165 .draw(&mut display)
166 .unwrap();
167
168 display.assert_eq_with_message(&expected, |f| {
169 write!(
170 f,
171 "diameter: {}, angle_start: {}, angle_sweep: {}",
172 diameter,
173 angle_start.to_degrees(),
174 angle_sweep.to_degrees()
175 )
176 });
177 }
178 }
179 }
180
181 #[test]
182 fn stroke_alignment() {
183 const CENTER: Point = Point::new(15, 15);
184 const SIZE: u32 = 10;
185
186 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
187
188 let mut display_center = MockDisplay::new();
189 Arc::with_center(CENTER, SIZE, 0.0.deg(), 90.0.deg())
190 .into_styled(style)
191 .draw(&mut display_center)
192 .unwrap();
193
194 let mut display_inside = MockDisplay::new();
195 Arc::with_center(CENTER, SIZE + 2, 0.0.deg(), 90.0.deg())
196 .into_styled(
197 PrimitiveStyleBuilder::from(&style)
198 .stroke_alignment(StrokeAlignment::Inside)
199 .build(),
200 )
201 .draw(&mut display_inside)
202 .unwrap();
203
204 let mut display_outside = MockDisplay::new();
205 Arc::with_center(CENTER, SIZE - 4, 0.0.deg(), 90.0.deg())
206 .into_styled(
207 PrimitiveStyleBuilder::from(&style)
208 .stroke_alignment(StrokeAlignment::Outside)
209 .build(),
210 )
211 .draw(&mut display_outside)
212 .unwrap();
213
214 display_inside.assert_eq(&display_center);
215 display_outside.assert_eq(&display_center);
216 }
217
218 #[test]
219 fn bounding_boxes() {
220 const CENTER: Point = Point::new(15, 15);
221 const SIZE: u32 = 10;
222
223 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
224
225 let center = Arc::with_center(CENTER, SIZE, 0.0.deg(), 90.0.deg()).into_styled(style);
226 let inside = Arc::with_center(CENTER, SIZE + 2, 0.0.deg(), 90.0.deg()).into_styled(
227 PrimitiveStyleBuilder::from(&style)
228 .stroke_alignment(StrokeAlignment::Inside)
229 .build(),
230 );
231 let outside = Arc::with_center(CENTER, SIZE - 4, 0.0.deg(), 90.0.deg()).into_styled(
232 PrimitiveStyleBuilder::from(&style)
233 .stroke_alignment(StrokeAlignment::Outside)
234 .build(),
235 );
236
237 assert_eq!(center.bounding_box(), inside.bounding_box());
238 assert_eq!(outside.bounding_box(), inside.bounding_box());
239
240 }
245
246 #[test]
247 fn bounding_box_is_independent_of_colors() {
248 const CENTER: Point = Point::new(15, 15);
249 const SIZE: u32 = 10;
250
251 let arc = Arc::with_center(CENTER, SIZE, 0.0.deg(), 90.0.deg());
252
253 let transparent_arc = arc.into_styled(
254 PrimitiveStyleBuilder::<BinaryColor>::new()
255 .stroke_width(5)
256 .build(),
257 );
258 let stroked_arc = arc.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 5));
259
260 assert_eq!(transparent_arc.bounding_box(), stroked_arc.bounding_box(),);
261 }
262}