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#[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}