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#[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 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 #[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 " # ", "# #", " # ", ]);
341 }
342
343 #[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 " # ", "###", " # ", ]);
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]
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}