1use crate::{
2 draw_target::DrawTarget,
3 geometry::angle_consts::ANGLE_90DEG,
4 geometry::{Angle, Dimensions},
5 pixelcolor::PixelColor,
6 primitives::{
7 common::{
8 DistanceIterator, LineSide, LinearEquation, PlaneSector, PointType, NORMAL_VECTOR_SCALE,
9 },
10 styled::{StyledDimensions, StyledDrawable, StyledPixels},
11 PrimitiveStyle, Rectangle, Sector,
12 },
13 Pixel,
14};
15use az::SaturatingAs;
16
17#[derive(Clone, PartialEq, Debug)]
19#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
20pub struct StyledPixelsIterator<C> {
21 iter: DistanceIterator,
22
23 plane_sector: PlaneSector,
24
25 outer_threshold: u32,
26 inner_threshold: u32,
27
28 stroke_threshold_inside: i32,
29 stroke_threshold_outside: i32,
30
31 bevel: Option<(BevelKind, LinearEquation)>,
32
33 stroke_color: Option<C>,
34 fill_color: Option<C>,
35}
36
37impl<C: PixelColor> StyledPixelsIterator<C> {
38 fn new(primitive: &Sector, style: &PrimitiveStyle<C>) -> Self {
39 let stroke_area = style.stroke_area(primitive);
40 let fill_area = style.fill_area(primitive);
41
42 let stroke_area_circle = stroke_area.to_circle();
43
44 let iter = if !style.is_transparent() {
45 stroke_area_circle.distances()
47 } else {
48 DistanceIterator::empty()
49 };
50
51 let outer_threshold = stroke_area_circle.threshold();
52 let inner_threshold = fill_area.to_circle().threshold();
53
54 let plane_sector = PlaneSector::new(stroke_area.angle_start, stroke_area.angle_sweep);
55
56 let inside_stroke_width: i32 = style.inside_stroke_width().saturating_as();
57 let outside_stroke_width: i32 = style.outside_stroke_width().saturating_as();
58
59 let stroke_threshold_inside =
60 inside_stroke_width * NORMAL_VECTOR_SCALE * 2 - NORMAL_VECTOR_SCALE;
61 let stroke_threshold_outside =
62 outside_stroke_width * NORMAL_VECTOR_SCALE * 2 + NORMAL_VECTOR_SCALE;
63
64 let angle_sweep_abs = primitive.angle_sweep.abs();
66 let exterior_bevel = angle_sweep_abs < Angle::from_degrees(55.0);
67 let interior_bevel = angle_sweep_abs > Angle::from_degrees(360.0 - 55.0)
68 && angle_sweep_abs < Angle::from_degrees(360.0);
69
70 let bevel = if exterior_bevel || interior_bevel {
71 let half_sweep = primitive.angle_start
72 + Angle::from_radians(primitive.angle_sweep.to_radians() / 2.0);
73 let threshold = -outside_stroke_width * NORMAL_VECTOR_SCALE * 4;
74
75 if interior_bevel {
76 Some((
77 BevelKind::Interior,
78 LinearEquation::with_angle_and_distance(half_sweep + ANGLE_90DEG, threshold),
79 ))
80 } else {
81 Some((
82 BevelKind::Exterior,
83 LinearEquation::with_angle_and_distance(half_sweep - ANGLE_90DEG, threshold),
84 ))
85 }
86 } else {
87 None
88 };
89
90 Self {
91 iter,
92 plane_sector,
93 outer_threshold,
94 inner_threshold,
95 stroke_threshold_inside,
96 stroke_threshold_outside,
97 bevel,
98 stroke_color: style.stroke_color,
99 fill_color: style.fill_color,
100 }
101 }
102}
103
104impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
105 type Item = Pixel<C>;
106
107 fn next(&mut self) -> Option<Self::Item> {
108 let outer_threshold = self.outer_threshold;
109
110 loop {
111 let (point, delta, distance) = self
112 .iter
113 .find(|(_, _, distance)| *distance < outer_threshold)?;
114
115 let mut point_type = match self.plane_sector.point_type(
117 delta,
118 self.stroke_threshold_inside,
119 self.stroke_threshold_outside,
120 ) {
121 Some(point_type) => point_type,
122 None => continue,
123 };
124
125 if point_type == PointType::Stroke {
127 if let Some((kind, equation)) = self.bevel {
128 if equation.check_side(delta, LineSide::Left) {
129 match kind {
130 BevelKind::Interior => point_type = PointType::Fill,
131 BevelKind::Exterior => continue,
132 }
133 }
134 }
135 }
136
137 if point_type == PointType::Fill && distance >= self.inner_threshold {
139 point_type = PointType::Stroke;
140 }
141
142 let color = match point_type {
143 PointType::Stroke => self.stroke_color,
144 PointType::Fill => self.fill_color,
145 };
146
147 if let Some(color) = color {
148 return Some(Pixel(point, color));
149 }
150 }
151 }
152}
153
154impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Sector {
155 type Iter = StyledPixelsIterator<C>;
156
157 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
158 StyledPixelsIterator::new(self, style)
159 }
160}
161
162impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Sector {
163 type Color = C;
164 type Output = ();
165
166 fn draw_styled<D>(
167 &self,
168 style: &PrimitiveStyle<C>,
169 target: &mut D,
170 ) -> Result<Self::Output, D::Error>
171 where
172 D: DrawTarget<Color = C>,
173 {
174 target.draw_iter(StyledPixelsIterator::new(self, style))
175 }
176}
177
178impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Sector {
179 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
181 let offset = style.outside_stroke_width().saturating_as();
182
183 self.bounding_box().offset(offset)
184 }
185}
186
187#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
188#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
189enum BevelKind {
190 Interior,
191 Exterior,
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::{
198 geometry::{AngleUnit, Point},
199 mock_display::MockDisplay,
200 pixelcolor::{BinaryColor, Rgb888, RgbColor},
201 primitives::{
202 Circle, Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment, Styled,
203 },
204 Drawable,
205 };
206
207 #[test]
209 fn tiny_sector() {
210 let mut display = MockDisplay::new();
211
212 Sector::new(Point::zero(), 9, 210.0.deg(), 120.0.deg())
213 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
214 .draw(&mut display)
215 .unwrap();
216
217 display.assert_pattern(&[
218 " ##### ", " ## ## ", "## ##", " ## ## ", " ### ", ]);
224 }
225
226 #[cfg_attr(not(feature = "fixed_point"), test)]
229 #[cfg_attr(feature = "fixed_point", allow(unused))]
230 fn tiny_sector_filled() {
231 let mut display = MockDisplay::new();
232
233 Sector::new(Point::zero(), 7, -30.0.deg(), -300.0.deg())
234 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
235 .draw(&mut display)
236 .unwrap();
237
238 display.assert_pattern(&[
239 " ### ", " ##### ", "###### ", "##### ", "###### ", " ##### ", " ### ", ]);
247 }
248
249 #[test]
250 fn transparent_border() {
251 let sector: Styled<Sector, PrimitiveStyle<BinaryColor>> =
252 Sector::new(Point::new(-5, -5), 21, 0.0.deg(), 90.0.deg())
253 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
254
255 assert!(sector.pixels().count() > 0);
256 }
257
258 fn test_stroke_alignment(
259 stroke_alignment: StrokeAlignment,
260 diameter: u32,
261 expected_pattern: &[&str],
262 ) {
263 let style = PrimitiveStyleBuilder::new()
264 .stroke_color(BinaryColor::On)
265 .stroke_width(3)
266 .stroke_alignment(stroke_alignment)
267 .build();
268
269 let mut display = MockDisplay::new();
270
271 Sector::with_center(Point::new(3, 10), diameter, 0.0.deg(), -90.0.deg())
272 .into_styled(style)
273 .draw(&mut display)
274 .unwrap();
275
276 display.assert_pattern(expected_pattern);
277 }
278
279 #[test]
280 fn stroke_alignment_inside() {
281 test_stroke_alignment(
282 StrokeAlignment::Inside,
283 19 + 2,
284 &[
285 " #### ",
286 " ###### ",
287 " ####### ",
288 " ######## ",
289 " ### #### ",
290 " ### #### ",
291 " ### ### ",
292 " ### ####",
293 " ###########",
294 " ###########",
295 " ###########",
296 ],
297 );
298 }
299
300 #[test]
301 fn stroke_alignment_center() {
302 test_stroke_alignment(
303 StrokeAlignment::Center,
304 19,
305 &[
306 " ##### ",
307 " ####### ",
308 " ######## ",
309 " ### ##### ",
310 " ### #### ",
311 " ### #### ",
312 " ### ### ",
313 " ### ####",
314 " ### ###",
315 " ############",
316 " ############",
317 " ############",
318 ],
319 );
320 }
321
322 #[test]
323 fn stroke_alignment_outside() {
324 test_stroke_alignment(
325 StrokeAlignment::Outside,
326 19 - 4,
327 &[
328 "####### ",
329 "######### ",
330 "########## ",
331 "### ##### ",
332 "### #### ",
333 "### #### ",
334 "### ### ",
335 "### ####",
336 "### ###",
337 "### ###",
338 "### ###",
339 "##############",
340 "##############",
341 "##############",
342 ],
343 );
344 }
345
346 #[test]
347 fn bounding_boxes() {
348 const CENTER: Point = Point::new(15, 15);
349 const SIZE: u32 = 10;
350
351 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
352
353 let center = Sector::with_center(CENTER, SIZE, 0.0.deg(), 90.0.deg()).into_styled(style);
354 let inside = Sector::with_center(CENTER, SIZE + 2, 0.0.deg(), 90.0.deg()).into_styled(
355 PrimitiveStyleBuilder::from(&style)
356 .stroke_alignment(StrokeAlignment::Inside)
357 .build(),
358 );
359 let outside = Sector::with_center(CENTER, SIZE - 4, 0.0.deg(), 90.0.deg()).into_styled(
360 PrimitiveStyleBuilder::from(&style)
361 .stroke_alignment(StrokeAlignment::Outside)
362 .build(),
363 );
364 let transparent = Sector::with_center(CENTER, SIZE, 0.0.deg(), 90.0.deg()).into_styled(
365 PrimitiveStyleBuilder::<BinaryColor>::new()
366 .stroke_width(3)
367 .build(),
368 );
369
370 assert_eq!(center.bounding_box(), inside.bounding_box());
376 assert_eq!(outside.bounding_box(), inside.bounding_box());
377 assert_eq!(transparent.bounding_box(), inside.bounding_box());
378 }
379
380 #[test]
382 fn issue_484_line_join_90_deg() {
383 let mut display = MockDisplay::<Rgb888>::new();
384
385 Sector::new(Point::new(-6, 1), 15, 0.0.deg(), -90.0.deg())
386 .into_styled(
387 PrimitiveStyleBuilder::new()
388 .stroke_color(Rgb888::RED)
389 .stroke_width(3)
390 .fill_color(Rgb888::GREEN)
391 .build(),
392 )
393 .draw(&mut display)
394 .unwrap();
395
396 display.assert_pattern(&[
397 "RRRR ",
398 "RRRRRR ",
399 "RRRRRRRR ",
400 "RRRGRRRR ",
401 "RRRGGRRRR ",
402 "RRRGGGRRR ",
403 "RRRGGGGRRR",
404 "RRRRRRRRRR",
405 "RRRRRRRRRR",
406 "RRRRRRRRRR",
407 ]);
408 }
409
410 #[test]
412 fn issue_484_line_join_20_deg() {
413 let mut display = MockDisplay::<Rgb888>::new();
414
415 Sector::new(Point::new(-4, -3), 15, 0.0.deg(), -20.0.deg())
416 .into_styled(
417 PrimitiveStyleBuilder::new()
418 .stroke_color(Rgb888::RED)
419 .stroke_width(3)
420 .fill_color(Rgb888::GREEN)
421 .build(),
422 )
423 .draw(&mut display)
424 .unwrap();
425
426 display.assert_pattern(&[
427 " R ",
428 " RRRR ",
429 " RRRRRRR",
430 " RRRRRRRRRR",
431 " RRRRRRRRRRR",
432 " RRRRRRRRRR",
433 ]);
434 }
435
436 #[test]
438 fn issue_484_line_join_340_deg() {
439 let mut display = MockDisplay::<Rgb888>::new();
440
441 Sector::new(Point::new_equal(2), 15, 20.0.deg(), 340.0.deg())
442 .into_styled(
443 PrimitiveStyleBuilder::new()
444 .stroke_color(Rgb888::RED)
445 .stroke_width(3)
446 .fill_color(Rgb888::GREEN)
447 .build(),
448 )
449 .draw(&mut display)
450 .unwrap();
451
452 display.assert_pattern(&[
453 " ",
454 " RRRRR ",
455 " RRRRRRRRR ",
456 " RRRRRRRRRRRRR ",
457 " RRRRGGGGGRRRR ",
458 " RRRRGGGGGGGRRRR ",
459 " RRRGGGGGGGGGRRR ",
460 " RRRGGGGGGGGGGGRRR",
461 " RRRGGGGRRRRRRRRRR",
462 " RRRGGGRRRRRRRRRRR",
463 " RRRGGGGRRRRRRRRRR",
464 " RRRGGGGGGGRRRRRRR",
465 " RRRGGGGGGGGRRRR ",
466 " RRRRGGGGGGGRRRR ",
467 " RRRRGGGGGRRRR ",
468 " RRRRRRRRRRRRR ",
469 " RRRRRRRRR ",
470 " RRRRR ",
471 ]);
472 }
473
474 #[test]
477 #[ignore]
478 fn issue_484_stroke_should_not_overlap_outer_edge() {
479 let mut display = MockDisplay::<Rgb888>::new();
480
481 Sector::with_center(Point::new(10, 15), 11, 0.0.deg(), 90.0.deg())
482 .into_styled(
483 PrimitiveStyleBuilder::new()
484 .stroke_color(Rgb888::RED)
485 .stroke_width(21)
486 .fill_color(Rgb888::GREEN)
487 .build(),
488 )
489 .draw(&mut display)
490 .unwrap();
491
492 display.assert_pattern(&[
493 "RRRRRRRRRRRRRR ",
494 "RRRRRRRRRRRRRRRRR ",
495 "RRRRRRRRRRRRRRRRRRR ",
496 "RRRRRRRRRRRRRRRRRRRR ",
497 "RRRRRRRRRRRRRRRRRRRRR ",
498 "RRRRRRRRRRRRRRRRRRRRRR ",
499 "RRRRRRRRRRRRRRRRRRRRRRR ",
500 "RRRRRRRRRRRRRRRRRRRRRRRR ",
501 "RRRRRRRRRRRRRRRRRRRRRRRR ",
502 "RRRRRRRRRRRRRRRRRRRRRRRRR ",
503 "RRRRRRRRRRRRRRRRRRRRRRRRR ",
504 "RRRRRRRRRRRRRRRRRRRRRRRRR ",
505 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
506 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
507 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
508 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
509 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
510 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
511 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
512 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
513 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
514 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
515 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
516 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
517 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
518 "RRRRRRRRRRRRRRRRRRRRRRRRRR",
519 ]);
520 }
521
522 #[test]
524 fn issue_484_stroke_center_semicircle() {
525 let mut display = MockDisplay::new();
526
527 Sector::new(Point::new_equal(1), 15, 180.0.deg(), 180.0.deg())
528 .into_styled(
529 PrimitiveStyleBuilder::new()
530 .fill_color(BinaryColor::On)
531 .stroke_color(BinaryColor::Off)
532 .stroke_width(2)
533 .stroke_alignment(StrokeAlignment::Center)
534 .build(),
535 )
536 .draw(&mut display)
537 .unwrap();
538
539 display.assert_pattern(&[
540 " ..... ",
541 " ......... ",
542 " ....#####.... ",
543 " ..#########.. ",
544 " ..###########.. ",
545 " ..###########.. ",
546 "..#############..",
547 "..#############..",
548 ".................",
549 ".................",
550 ]);
551 }
552
553 #[test]
555 fn issue_484_stroke_center_semicircle_vertical() {
556 let mut display = MockDisplay::new();
557
558 Sector::new(Point::new_equal(1), 15, 90.0.deg(), 180.0.deg())
559 .into_styled(
560 PrimitiveStyleBuilder::new()
561 .fill_color(BinaryColor::On)
562 .stroke_color(BinaryColor::Off)
563 .stroke_width(2)
564 .stroke_alignment(StrokeAlignment::Center)
565 .build(),
566 )
567 .draw(&mut display)
568 .unwrap();
569
570 display.assert_pattern(&[
571 " ....",
572 " ......",
573 " ....##..",
574 " ..####..",
575 " ..#####..",
576 " ..#####..",
577 "..######..",
578 "..######..",
579 "..######..",
580 "..######..",
581 "..######..",
582 " ..#####..",
583 " ..#####..",
584 " ..####..",
585 " ....##..",
586 " ......",
587 " ....",
588 ]);
589 }
590
591 #[test]
593 fn issue_484_gaps_and_overlap() {
594 let mut display = MockDisplay::new();
595
596 Sector::with_center(Point::new(2, 20), 40, 14.0.deg(), -90.0.deg())
597 .into_styled(
598 PrimitiveStyleBuilder::new()
599 .fill_color(Rgb888::GREEN)
600 .stroke_color(Rgb888::RED)
601 .stroke_width(2)
602 .build(),
603 )
604 .draw(&mut display)
605 .unwrap();
606
607 display.assert_pattern(&[
608 " R ",
609 " RRRRR ",
610 " RRRRRRR ",
611 " RRGGRRRRR ",
612 " RRGGGGRRRR ",
613 " RRGGGGGGGRRR ",
614 " RRGGGGGGGGRRR ",
615 " RRGGGGGGGGGRRR ",
616 " RRGGGGGGGGGGRRR ",
617 " RRGGGGGGGGGGGGRRR ",
618 " RRGGGGGGGGGGGGGRR ",
619 " RRGGGGGGGGGGGGGRRR ",
620 " RRGGGGGGGGGGGGGGRR ",
621 " RRGGGGGGGGGGGGGGGRRR ",
622 " RRGGGGGGGGGGGGGGGGRR ",
623 " RRGGGGGGGGGGGGGGGGRR ",
624 " RRGGGGGGGGGGGGGGGGRRR",
625 " RRGGGGGGGGGGGGGGGGGGRR",
626 " RRGGGGGGGGGGGGGGGGGGRR",
627 " RRGGGGGGGGGGGGGGGGGGRR",
628 " RRGGGGGGGGGGGGGGGGGGRR",
629 " RRRRRRGGGGGGGGGGGGGGGRR",
630 " RRRRRRRRGGGGGGGGGGGRR",
631 " RRRRRRRRGGGGGGGRR",
632 " RRRRRRRRGGGRR",
633 " RRRRRRRRR",
634 " RRRR ",
635 ]);
636 }
637
638 #[test]
640 fn issue_484_no_radial_lines_for_360_degree_sweep_angle() {
641 let style = PrimitiveStyleBuilder::new()
642 .fill_color(Rgb888::GREEN)
643 .stroke_color(Rgb888::RED)
644 .stroke_width(1)
645 .build();
646
647 let circle = Circle::new(Point::new_equal(1), 11);
648
649 let mut expected = MockDisplay::new();
650 circle.into_styled(style).draw(&mut expected).unwrap();
651
652 let mut display = MockDisplay::new();
653
654 Sector::new(Point::new_equal(1), 11, 0.0.deg(), 360.0.deg())
655 .into_styled(style)
656 .draw(&mut display)
657 .unwrap();
658
659 display.assert_eq(&expected);
660 }
661
662 #[test]
664 fn issue_484_no_radial_lines_for_sweep_angles_larger_than_360_degree() {
665 let style = PrimitiveStyleBuilder::new()
666 .fill_color(Rgb888::GREEN)
667 .stroke_color(Rgb888::RED)
668 .stroke_width(1)
669 .build();
670
671 let circle = Circle::new(Point::new_equal(1), 11);
672
673 let mut expected = MockDisplay::new();
674 circle.into_styled(style).draw(&mut expected).unwrap();
675
676 let mut display = MockDisplay::new();
677
678 Sector::from_circle(circle, 90.0.deg(), -472.0.deg())
679 .into_styled(style)
680 .draw(&mut display)
681 .unwrap();
682
683 display.assert_eq(&expected);
684 }
685
686 #[test]
688 fn issue_484_sector_flips_at_360_degrees() {
689 let mut display = MockDisplay::new();
690
691 Sector::new(Point::new(-15, 0), 31, 360.0.deg(), 90.0.deg())
694 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
695 .draw(&mut display)
696 .unwrap();
697 }
698}