embedded_graphics/primitives/ellipse/
styled.rs

1use crate::{
2    draw_target::DrawTarget,
3    geometry::{Dimensions, Point},
4    pixelcolor::PixelColor,
5    primitives::{
6        common::{Scanline, StyledScanline},
7        ellipse::{points::Scanlines, Ellipse, EllipseContains},
8        styled::{StyledDimensions, StyledDrawable, StyledPixels},
9        PrimitiveStyle, Rectangle,
10    },
11    Pixel,
12};
13use az::SaturatingAs;
14
15/// Pixel iterator for each pixel in the ellipse border
16#[derive(Clone, Eq, PartialEq, Hash, Debug)]
17#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
18pub struct StyledPixelsIterator<C> {
19    styled_scanlines: StyledScanlines,
20
21    stroke_left: Scanline,
22    fill: Scanline,
23    stroke_right: Scanline,
24
25    stroke_color: Option<C>,
26    fill_color: Option<C>,
27}
28
29impl<C: PixelColor> StyledPixelsIterator<C> {
30    pub(in crate::primitives) fn new(primitive: &Ellipse, style: &PrimitiveStyle<C>) -> Self {
31        let stroke_area = style.stroke_area(primitive);
32        let fill_area = style.fill_area(primitive);
33
34        Self {
35            styled_scanlines: StyledScanlines::new(&stroke_area, &fill_area),
36            stroke_left: Scanline::new_empty(0),
37            fill: Scanline::new_empty(0),
38            stroke_right: Scanline::new_empty(0),
39            stroke_color: style.stroke_color,
40            fill_color: style.fill_color,
41        }
42    }
43}
44
45impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
46    type Item = Pixel<C>;
47
48    fn next(&mut self) -> Option<Self::Item> {
49        match (self.stroke_color, self.fill_color) {
50            (Some(stroke_color), None) => loop {
51                if let Some(pixel) = self
52                    .stroke_left
53                    .next()
54                    .or_else(|| self.stroke_right.next())
55                    .map(|p| Pixel(p, stroke_color))
56                {
57                    return Some(pixel);
58                }
59
60                let scanline = self.styled_scanlines.next()?;
61                self.stroke_left = scanline.stroke_left();
62                self.stroke_right = scanline.stroke_right();
63            },
64            (Some(stroke_color), Some(fill_color)) => loop {
65                if let Some(pixel) = self
66                    .stroke_left
67                    .next()
68                    .map(|p| Pixel(p, stroke_color))
69                    .or_else(|| self.fill.next().map(|p| Pixel(p, fill_color)))
70                    .or_else(|| self.stroke_right.next().map(|p| Pixel(p, stroke_color)))
71                {
72                    return Some(pixel);
73                }
74
75                let scanline = self.styled_scanlines.next()?;
76                self.stroke_left = scanline.stroke_left();
77                self.fill = scanline.fill();
78                self.stroke_right = scanline.stroke_right();
79            },
80            (None, Some(fill_color)) => loop {
81                if let Some(pixel) = self.fill.next().map(|p| Pixel(p, fill_color)) {
82                    return Some(pixel);
83                }
84
85                let scanline = self.styled_scanlines.next()?;
86                self.fill = scanline.fill();
87            },
88            (None, None) => None,
89        }
90    }
91}
92
93impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Ellipse {
94    type Iter = StyledPixelsIterator<C>;
95
96    fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
97        StyledPixelsIterator::new(self, style)
98    }
99}
100
101impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Ellipse {
102    type Color = C;
103    type Output = ();
104
105    fn draw_styled<D>(
106        &self,
107        style: &PrimitiveStyle<C>,
108        target: &mut D,
109    ) -> Result<Self::Output, D::Error>
110    where
111        D: DrawTarget<Color = C>,
112    {
113        match (style.effective_stroke_color(), style.fill_color) {
114            (Some(stroke_color), None) => {
115                for scanline in
116                    StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
117                {
118                    scanline.draw_stroke(target, stroke_color)?;
119                }
120            }
121            (Some(stroke_color), Some(fill_color)) => {
122                for scanline in
123                    StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
124                {
125                    scanline.draw_stroke_and_fill(target, stroke_color, fill_color)?;
126                }
127            }
128            (None, Some(fill_color)) => {
129                for scanline in Scanlines::new(&style.fill_area(self)) {
130                    scanline.draw(target, fill_color)?;
131                }
132            }
133            (None, None) => {}
134        }
135
136        Ok(())
137    }
138}
139
140impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Ellipse {
141    fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
142        let offset = style.outside_stroke_width().saturating_as();
143
144        self.bounding_box().offset(offset)
145    }
146}
147
148#[derive(Clone, Eq, PartialEq, Hash, Debug)]
149#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
150struct StyledScanlines {
151    scanlines: Scanlines,
152    fill_area: EllipseContains,
153}
154
155impl StyledScanlines {
156    pub fn new(stroke_area: &Ellipse, fill_area: &Ellipse) -> Self {
157        Self {
158            scanlines: Scanlines::new(stroke_area),
159            fill_area: EllipseContains::new(fill_area.size),
160        }
161    }
162}
163
164impl Iterator for StyledScanlines {
165    type Item = StyledScanline;
166
167    fn next(&mut self) -> Option<Self::Item> {
168        self.scanlines.next().map(|scanline| {
169            let scaled_y = scanline.y * 2 - self.scanlines.center_2x.y;
170
171            let fill_range = scanline
172                .x
173                .clone()
174                .find(|x| {
175                    self.fill_area
176                        .contains(Point::new(*x * 2 - self.scanlines.center_2x.x, scaled_y))
177                })
178                .map(|x| x..scanline.x.end - (x - scanline.x.start));
179
180            StyledScanline::new(scanline.y, scanline.x, fill_range)
181        })
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::{
189        geometry::{Point, Size},
190        iterator::PixelIteratorExt,
191        mock_display::MockDisplay,
192        pixelcolor::BinaryColor,
193        primitives::{Circle, Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
194        Drawable,
195    };
196
197    fn test_circles(style: PrimitiveStyle<BinaryColor>) {
198        for diameter in 0..50 {
199            let top_left = Point::new_equal(style.stroke_width.saturating_as());
200
201            let mut expected = MockDisplay::new();
202            Circle::new(top_left, diameter)
203                .into_styled(style)
204                .draw(&mut expected)
205                .unwrap();
206
207            let mut display = MockDisplay::new();
208            Ellipse::new(top_left, Size::new(diameter, diameter))
209                .into_styled(style)
210                .draw(&mut display)
211                .unwrap();
212
213            display.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}", diameter));
214        }
215    }
216
217    fn test_ellipse(size: Size, style: PrimitiveStyle<BinaryColor>, pattern: &[&str]) {
218        let ellipse = Ellipse::new(Point::new(0, 0), size).into_styled(style);
219
220        let mut drawable = MockDisplay::new();
221        ellipse.draw(&mut drawable).unwrap();
222        drawable.assert_pattern(pattern);
223
224        let mut pixels = MockDisplay::new();
225        ellipse.pixels().draw(&mut pixels).unwrap();
226        pixels.assert_pattern(pattern);
227    }
228
229    #[test]
230    fn ellipse_equals_circle_fill() {
231        test_circles(PrimitiveStyle::with_fill(BinaryColor::On));
232    }
233
234    #[test]
235    fn ellipse_equals_circle_stroke_1px() {
236        test_circles(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
237    }
238
239    #[test]
240    fn ellipse_equals_circle_stroke_10px() {
241        test_circles(PrimitiveStyle::with_stroke(BinaryColor::On, 10));
242    }
243
244    #[test]
245    fn filled_ellipse() {
246        #[rustfmt::skip]
247        test_ellipse(Size::new(20, 10), PrimitiveStyle::with_fill(BinaryColor::On), &[
248            "      ########      ",
249            "   ##############   ",
250            " ################## ",
251            "####################",
252            "####################",
253            "####################",
254            "####################",
255            " ################## ",
256            "   ##############   ",
257            "      ########      ",
258        ],);
259    }
260
261    #[test]
262    fn thick_stroke_glitch() {
263        test_ellipse(
264            Size::new(11, 21),
265            PrimitiveStyleBuilder::new()
266                .stroke_width(10)
267                .stroke_color(BinaryColor::On)
268                .stroke_alignment(StrokeAlignment::Inside)
269                .fill_color(BinaryColor::Off)
270                .build(),
271            &[
272                "    ###    ",
273                "   #####   ",
274                "  #######  ",
275                " ######### ",
276                " ######### ",
277                " ######### ",
278                "###########",
279                "###########",
280                "###########",
281                "###########",
282                "###########",
283                "###########",
284                "###########",
285                "###########",
286                "###########",
287                " ######### ",
288                " ######### ",
289                " ######### ",
290                "  #######  ",
291                "   #####   ",
292                "    ###    ",
293            ],
294        );
295    }
296
297    #[test]
298    fn thin_stroked_ellipse() {
299        #[rustfmt::skip]
300        test_ellipse(Size::new(20, 10), PrimitiveStyle::with_stroke(BinaryColor::On, 1), &[
301            "      ########      ",
302            "   ###        ###   ",
303            " ##              ## ",
304            "##                ##",
305            "#                  #",
306            "#                  #",
307            "##                ##",
308            " ##              ## ",
309            "   ###        ###   ",
310            "      ########      ",
311        ],);
312    }
313
314    #[test]
315    fn fill_and_stroke() {
316        test_ellipse(
317            Size::new(20, 10),
318            PrimitiveStyleBuilder::new()
319                .stroke_width(3)
320                .stroke_color(BinaryColor::Off)
321                .stroke_alignment(StrokeAlignment::Inside)
322                .fill_color(BinaryColor::On)
323                .build(),
324            &[
325                "      ........      ",
326                "   ..............   ",
327                " .................. ",
328                ".....##########.....",
329                "...##############...",
330                "...##############...",
331                ".....##########.....",
332                " .................. ",
333                "   ..............   ",
334                "      ........      ",
335            ],
336        );
337    }
338
339    #[test]
340    fn stroke_alignment() {
341        const CENTER: Point = Point::new(15, 15);
342        const SIZE: Size = Size::new(10, 5);
343
344        let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
345
346        let mut display_center = MockDisplay::new();
347        Ellipse::with_center(CENTER, SIZE)
348            .into_styled(style)
349            .draw(&mut display_center)
350            .unwrap();
351
352        let mut display_inside = MockDisplay::new();
353        Ellipse::with_center(CENTER, SIZE + Size::new(2, 2))
354            .into_styled(
355                PrimitiveStyleBuilder::from(&style)
356                    .stroke_alignment(StrokeAlignment::Inside)
357                    .build(),
358            )
359            .draw(&mut display_inside)
360            .unwrap();
361
362        let mut display_outside = MockDisplay::new();
363        Ellipse::with_center(CENTER, SIZE - Size::new(4, 4))
364            .into_styled(
365                PrimitiveStyleBuilder::from(&style)
366                    .stroke_alignment(StrokeAlignment::Outside)
367                    .build(),
368            )
369            .draw(&mut display_outside)
370            .unwrap();
371
372        display_inside.assert_eq(&display_center);
373        display_outside.assert_eq(&display_center);
374    }
375
376    #[test]
377    fn bounding_boxes() {
378        const CENTER: Point = Point::new(15, 15);
379        const SIZE: Size = Size::new(15, 25);
380
381        let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
382
383        let center = Ellipse::with_center(CENTER, SIZE).into_styled(style);
384        let inside = Ellipse::with_center(CENTER, SIZE).into_styled(
385            PrimitiveStyleBuilder::from(&style)
386                .stroke_alignment(StrokeAlignment::Inside)
387                .build(),
388        );
389        let outside = Ellipse::with_center(CENTER, SIZE).into_styled(
390            PrimitiveStyleBuilder::from(&style)
391                .stroke_alignment(StrokeAlignment::Outside)
392                .build(),
393        );
394
395        let mut display = MockDisplay::new();
396        center.draw(&mut display).unwrap();
397        assert_eq!(display.affected_area(), center.bounding_box());
398
399        let mut display = MockDisplay::new();
400        outside.draw(&mut display).unwrap();
401        assert_eq!(display.affected_area(), outside.bounding_box());
402
403        let mut display = MockDisplay::new();
404        inside.draw(&mut display).unwrap();
405        assert_eq!(display.affected_area(), inside.bounding_box());
406    }
407
408    #[test]
409    fn bounding_box_is_independent_of_colors() {
410        let transparent_ellipse = Ellipse::new(Point::new(5, 5), Size::new(11, 14))
411            .into_styled(PrimitiveStyle::<BinaryColor>::new());
412        let filled_ellipse = Ellipse::new(Point::new(5, 5), Size::new(11, 14))
413            .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
414
415        assert_eq!(
416            transparent_ellipse.bounding_box(),
417            filled_ellipse.bounding_box(),
418        );
419    }
420}