embedded_graphics/primitives/circle/
styled.rs

1use crate::{
2    draw_target::DrawTarget,
3    geometry::{Dimensions, Point, PointExt},
4    pixelcolor::PixelColor,
5    primitives::{
6        circle::{points::Scanlines, Circle},
7        common::{Scanline, StyledScanline},
8        rectangle::Rectangle,
9        styled::{StyledDimensions, StyledDrawable, StyledPixels},
10        PrimitiveStyle,
11    },
12    Pixel,
13};
14use az::SaturatingAs;
15
16/// Pixel iterator for each pixel in the circle border
17#[derive(Clone, Eq, PartialEq, Hash, Debug)]
18#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
19pub struct StyledPixelsIterator<C> {
20    styled_scanlines: StyledScanlines,
21
22    stroke_left: Scanline,
23    fill: Scanline,
24    stroke_right: Scanline,
25
26    stroke_color: Option<C>,
27    fill_color: Option<C>,
28}
29
30impl<C: PixelColor> StyledPixelsIterator<C> {
31    pub(in crate::primitives) fn new(primitive: &Circle, style: &PrimitiveStyle<C>) -> Self {
32        let stroke_area = style.stroke_area(primitive);
33        let fill_area = style.fill_area(primitive);
34
35        Self {
36            styled_scanlines: StyledScanlines::new(&stroke_area, &fill_area),
37            stroke_left: Scanline::new_empty(0),
38            fill: Scanline::new_empty(0),
39            stroke_right: Scanline::new_empty(0),
40            stroke_color: style.stroke_color,
41            fill_color: style.fill_color,
42        }
43    }
44}
45
46impl<C> Iterator for StyledPixelsIterator<C>
47where
48    C: PixelColor,
49{
50    type Item = Pixel<C>;
51
52    fn next(&mut self) -> Option<Self::Item> {
53        match (self.stroke_color, self.fill_color) {
54            (Some(stroke_color), None) => loop {
55                if let Some(pixel) = self
56                    .stroke_left
57                    .next()
58                    .or_else(|| self.stroke_right.next())
59                    .map(|p| Pixel(p, stroke_color))
60                {
61                    return Some(pixel);
62                }
63
64                let scanline = self.styled_scanlines.next()?;
65                self.stroke_left = scanline.stroke_left();
66                self.stroke_right = scanline.stroke_right();
67            },
68            (Some(stroke_color), Some(fill_color)) => loop {
69                if let Some(pixel) = self
70                    .stroke_left
71                    .next()
72                    .map(|p| Pixel(p, stroke_color))
73                    .or_else(|| self.fill.next().map(|p| Pixel(p, fill_color)))
74                    .or_else(|| self.stroke_right.next().map(|p| Pixel(p, stroke_color)))
75                {
76                    return Some(pixel);
77                }
78
79                let scanline = self.styled_scanlines.next()?;
80                self.stroke_left = scanline.stroke_left();
81                self.fill = scanline.fill();
82                self.stroke_right = scanline.stroke_right();
83            },
84            (None, Some(fill_color)) => loop {
85                if let Some(pixel) = self.fill.next().map(|p| Pixel(p, fill_color)) {
86                    return Some(pixel);
87                }
88
89                let scanline = self.styled_scanlines.next()?;
90                self.fill = scanline.fill();
91            },
92            (None, None) => None,
93        }
94    }
95}
96
97impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Circle {
98    type Iter = StyledPixelsIterator<C>;
99
100    fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
101        StyledPixelsIterator::new(self, style)
102    }
103}
104
105impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Circle {
106    type Color = C;
107    type Output = ();
108
109    fn draw_styled<D>(
110        &self,
111        style: &PrimitiveStyle<C>,
112        target: &mut D,
113    ) -> Result<Self::Output, D::Error>
114    where
115        D: DrawTarget<Color = C>,
116    {
117        match (style.effective_stroke_color(), style.fill_color) {
118            (Some(stroke_color), None) => {
119                for scanline in
120                    StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
121                {
122                    scanline.draw_stroke(target, stroke_color)?;
123                }
124            }
125            (Some(stroke_color), Some(fill_color)) => {
126                for scanline in
127                    StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
128                {
129                    scanline.draw_stroke_and_fill(target, stroke_color, fill_color)?;
130                }
131            }
132            (None, Some(fill_color)) => {
133                for scanline in Scanlines::new(&style.fill_area(self)) {
134                    scanline.draw(target, fill_color)?;
135                }
136            }
137            (None, None) => {}
138        }
139
140        Ok(())
141    }
142}
143
144impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Circle {
145    fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
146        let offset = style.outside_stroke_width().saturating_as();
147
148        self.bounding_box().offset(offset)
149    }
150}
151#[derive(Clone, Eq, PartialEq, Hash, Debug)]
152#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
153struct StyledScanlines {
154    scanlines: Scanlines,
155    fill_threshold: u32,
156}
157
158impl StyledScanlines {
159    pub fn new(stroke_area: &Circle, fill_area: &Circle) -> Self {
160        Self {
161            scanlines: Scanlines::new(stroke_area),
162            fill_threshold: fill_area.threshold(),
163        }
164    }
165}
166
167impl Iterator for StyledScanlines {
168    type Item = StyledScanline;
169
170    fn next(&mut self) -> Option<Self::Item> {
171        self.scanlines.next().map(|scanline| {
172            let fill_range = scanline
173                .x
174                .clone()
175                .find(|x| {
176                    let delta = Point::new(*x, scanline.y) * 2 - self.scanlines.center_2x;
177                    (delta.length_squared() as u32) < self.fill_threshold
178                })
179                .map(|x| x..scanline.x.end - (x - scanline.x.start));
180
181            StyledScanline::new(scanline.y, scanline.x, fill_range)
182        })
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use crate::{
190        geometry::{Dimensions, Point},
191        iterator::PixelIteratorExt,
192        mock_display::MockDisplay,
193        pixelcolor::BinaryColor,
194        primitives::{
195            OffsetOutline, PointsIter, Primitive, PrimitiveStyleBuilder, StrokeAlignment, Styled,
196        },
197        Drawable,
198    };
199
200    /// Draws a styled circle by only using the points iterator.
201    fn draw_circle(
202        diameter: u32,
203        stroke_color: Option<BinaryColor>,
204        stroke_width: u32,
205        fill_color: Option<BinaryColor>,
206    ) -> MockDisplay<BinaryColor> {
207        let circle = Circle::with_center(Point::new_equal(10), diameter);
208
209        let mut display = MockDisplay::new();
210        display.set_pixels(circle.points(), stroke_color);
211        display.set_pixels(
212            circle.offset(-stroke_width.saturating_as::<i32>()).points(),
213            fill_color,
214        );
215
216        display
217    }
218
219    #[test]
220    fn fill() {
221        for diameter in 5..=6 {
222            let expected = draw_circle(diameter, None, 0, Some(BinaryColor::On));
223
224            let circle = Circle::with_center(Point::new_equal(10), diameter)
225                .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
226
227            let mut drawable = MockDisplay::new();
228            circle.draw(&mut drawable).unwrap();
229            drawable.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}", diameter));
230
231            let mut pixels = MockDisplay::new();
232            circle.pixels().draw(&mut pixels).unwrap();
233            pixels.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}", diameter));
234        }
235    }
236
237    #[test]
238    fn stroke() {
239        for (diameter, stroke_width) in [(5, 2), (5, 3), (6, 2), (6, 3)].iter().copied() {
240            let expected = draw_circle(diameter, Some(BinaryColor::On), stroke_width, None);
241
242            let style = PrimitiveStyleBuilder::new()
243                .stroke_color(BinaryColor::On)
244                .stroke_width(stroke_width)
245                .stroke_alignment(StrokeAlignment::Inside)
246                .build();
247
248            let circle = Circle::with_center(Point::new_equal(10), diameter).into_styled(style);
249
250            let mut drawable = MockDisplay::new();
251            circle.draw(&mut drawable).unwrap();
252            drawable.assert_eq_with_message(&expected, |f| {
253                write!(
254                    f,
255                    "diameter = {}, stroke_width = {}",
256                    diameter, stroke_width
257                )
258            });
259
260            let mut pixels = MockDisplay::new();
261            circle.pixels().draw(&mut pixels).unwrap();
262            pixels.assert_eq_with_message(&expected, |f| {
263                write!(
264                    f,
265                    "diameter = {}, stroke_width = {}",
266                    diameter, stroke_width
267                )
268            });
269        }
270    }
271
272    #[test]
273    fn stroke_and_fill() {
274        for (diameter, stroke_width) in [(5, 2), (5, 3), (6, 2), (6, 3)].iter().copied() {
275            let expected = draw_circle(
276                diameter,
277                Some(BinaryColor::On),
278                stroke_width,
279                Some(BinaryColor::Off),
280            );
281
282            let style = PrimitiveStyleBuilder::new()
283                .fill_color(BinaryColor::Off)
284                .stroke_color(BinaryColor::On)
285                .stroke_width(stroke_width)
286                .stroke_alignment(StrokeAlignment::Inside)
287                .build();
288
289            let circle = Circle::with_center(Point::new_equal(10), diameter).into_styled(style);
290
291            let mut drawable = MockDisplay::new();
292            circle.draw(&mut drawable).unwrap();
293            drawable.assert_eq_with_message(&expected, |f| {
294                write!(
295                    f,
296                    "diameter = {}, stroke_width = {}",
297                    diameter, stroke_width
298                )
299            });
300
301            let mut pixels = MockDisplay::new();
302            circle.pixels().draw(&mut pixels).unwrap();
303            pixels.assert_eq_with_message(&expected, |f| {
304                write!(
305                    f,
306                    "diameter = {}, stroke_width = {}",
307                    diameter, stroke_width
308                )
309            });
310        }
311    }
312
313    #[test]
314    fn filled_styled_points_matches_points() {
315        let circle = Circle::with_center(Point::new(10, 10), 5);
316
317        let styled_points = circle
318            .clone()
319            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
320            .pixels()
321            .map(|Pixel(p, _)| p);
322
323        assert!(circle.points().eq(styled_points));
324    }
325
326    // Check that tiny circles render as a "+" shape with a hole in the center
327    #[test]
328    fn tiny_circle() {
329        let mut display = MockDisplay::new();
330
331        Circle::new(Point::new(0, 0), 3)
332            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
333            .draw(&mut display)
334            .unwrap();
335
336        display.assert_pattern(&[
337            " # ", //
338            "# #", //
339            " # ", //
340        ]);
341    }
342
343    // Check that tiny filled circle render as a "+" shape with NO hole in the center
344    #[test]
345    fn tiny_circle_filled() {
346        let mut display = MockDisplay::new();
347
348        Circle::new(Point::new(0, 0), 3)
349            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
350            .draw(&mut display)
351            .unwrap();
352
353        display.assert_pattern(&[
354            " # ", //
355            "###", //
356            " # ", //
357        ]);
358    }
359
360    #[test]
361    fn transparent_border() {
362        let circle: Styled<Circle, PrimitiveStyle<BinaryColor>> =
363            Circle::new(Point::new(-5, -5), 21)
364                .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
365
366        assert!(circle.pixels().count() > 0);
367    }
368
369    #[test]
370    fn it_handles_negative_coordinates() {
371        let positive = Circle::new(Point::new(10, 10), 5)
372            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
373            .pixels();
374
375        let negative = Circle::new(Point::new(-10, -10), 5)
376            .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
377            .pixels();
378
379        assert!(negative.eq(positive.map(|Pixel(p, c)| Pixel(p - Point::new(20, 20), c))));
380    }
381
382    #[test]
383    fn stroke_alignment() {
384        const CENTER: Point = Point::new(15, 15);
385        const SIZE: u32 = 10;
386
387        let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
388
389        let mut display_center = MockDisplay::new();
390        Circle::with_center(CENTER, SIZE)
391            .into_styled(style)
392            .draw(&mut display_center)
393            .unwrap();
394
395        let mut display_inside = MockDisplay::new();
396        Circle::with_center(CENTER, SIZE + 2)
397            .into_styled(
398                PrimitiveStyleBuilder::from(&style)
399                    .stroke_alignment(StrokeAlignment::Inside)
400                    .build(),
401            )
402            .draw(&mut display_inside)
403            .unwrap();
404
405        let mut display_outside = MockDisplay::new();
406        Circle::with_center(CENTER, SIZE - 4)
407            .into_styled(
408                PrimitiveStyleBuilder::from(&style)
409                    .stroke_alignment(StrokeAlignment::Outside)
410                    .build(),
411            )
412            .draw(&mut display_outside)
413            .unwrap();
414
415        display_inside.assert_eq(&display_center);
416        display_outside.assert_eq(&display_center);
417    }
418
419    /// Test for issue #143
420    #[test]
421    fn issue_143_stroke_and_fill() {
422        for size in 0..10 {
423            let circle_no_stroke: Styled<Circle, PrimitiveStyle<BinaryColor>> =
424                Circle::new(Point::new(10, 16), size)
425                    .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
426
427            let style = PrimitiveStyleBuilder::new()
428                .fill_color(BinaryColor::On)
429                .stroke_color(BinaryColor::On)
430                .stroke_width(1)
431                .build();
432            let circle_stroke: Styled<Circle, PrimitiveStyle<BinaryColor>> =
433                Circle::new(Point::new(10, 16), size).into_styled(style);
434
435            assert_eq!(
436                circle_stroke.bounding_box(),
437                circle_no_stroke.bounding_box(),
438                "Filled and unfilled circle bounding boxes are unequal for radius {}",
439                size
440            );
441            assert!(
442                circle_no_stroke.pixels().eq(circle_stroke.pixels()),
443                "Filled and unfilled circle iters are unequal for radius {}",
444                size
445            );
446        }
447    }
448
449    #[test]
450    fn bounding_boxes() {
451        const CENTER: Point = Point::new(15, 15);
452        const SIZE: u32 = 10;
453
454        let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
455
456        let center = Circle::with_center(CENTER, SIZE).into_styled(style);
457
458        let inside = Circle::with_center(CENTER, SIZE).into_styled(
459            PrimitiveStyleBuilder::from(&style)
460                .stroke_alignment(StrokeAlignment::Inside)
461                .build(),
462        );
463
464        let outside = Circle::with_center(CENTER, SIZE).into_styled(
465            PrimitiveStyleBuilder::from(&style)
466                .stroke_alignment(StrokeAlignment::Outside)
467                .build(),
468        );
469
470        let mut display = MockDisplay::new();
471        center.draw(&mut display).unwrap();
472        assert_eq!(display.affected_area(), center.bounding_box());
473
474        let mut display = MockDisplay::new();
475        outside.draw(&mut display).unwrap();
476        assert_eq!(display.affected_area(), outside.bounding_box());
477
478        let mut display = MockDisplay::new();
479        inside.draw(&mut display).unwrap();
480        assert_eq!(display.affected_area(), inside.bounding_box());
481    }
482
483    #[test]
484    fn bounding_box_is_independent_of_colors() {
485        let transparent_circle =
486            Circle::new(Point::new(5, 5), 11).into_styled(PrimitiveStyle::<BinaryColor>::new());
487        let filled_circle = Circle::new(Point::new(5, 5), 11)
488            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
489
490        assert_eq!(
491            transparent_circle.bounding_box(),
492            filled_circle.bounding_box(),
493        );
494    }
495}