1use crate::{
2 draw_target::{DrawTarget, DrawTargetExt},
3 geometry::{Dimensions, Point, Size},
4 pixelcolor::PixelColor,
5 primitives::{
6 common::{Scanline, StrokeOffset, ThickSegmentIter},
7 polyline::{self, scanline_iterator::ScanlineIterator, Polyline},
8 styled::{StyledDimensions, StyledDrawable, StyledPixels},
9 PointsIter, PrimitiveStyle, Rectangle,
10 },
11 transform::Transform,
12 Pixel,
13};
14
15pub(in crate::primitives::polyline) fn untranslated_bounding_box<C: PixelColor>(
17 primitive: &Polyline,
18 style: &PrimitiveStyle<C>,
19) -> Rectangle {
20 if style.effective_stroke_color().is_some() && primitive.vertices.len() > 1 {
21 let (min, max) =
22 ThickSegmentIter::new(primitive.vertices, style.stroke_width, StrokeOffset::None).fold(
23 (
24 Point::new_equal(core::i32::MAX),
25 Point::new_equal(core::i32::MIN),
26 ),
27 |(min, max), segment| {
28 let bb = segment.edges_bounding_box();
29
30 (
31 min.component_min(bb.top_left),
32 max.component_max(bb.bottom_right().unwrap_or(bb.top_left)),
33 )
34 },
35 );
36
37 Rectangle::with_corners(min, max)
38 } else {
39 Rectangle::new(primitive.bounding_box().center(), Size::zero())
40 }
41}
42
43fn draw_thick<D>(
44 polyline: &Polyline,
45 style: &PrimitiveStyle<D::Color>,
46 stroke_color: D::Color,
47 target: &mut D,
48) -> Result<(), D::Error>
49where
50 D: DrawTarget,
51{
52 for line in ScanlineIterator::new(polyline, style) {
53 let rect = line.to_rectangle();
54
55 if !rect.is_zero_sized() {
56 target.fill_solid(&rect, stroke_color)?;
57 }
58 }
59
60 Ok(())
61}
62
63#[derive(Clone, Debug)]
64#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
65enum StyledIter<'a> {
66 Thin(polyline::Points<'a>),
67 Thick {
68 scanline_iter: ScanlineIterator<'a>,
69 line_iter: Scanline,
70 translate: Point,
71 },
72}
73
74#[derive(Clone, Debug)]
76#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
77pub struct StyledPixelsIterator<'a, C> {
78 stroke_color: Option<C>,
79 line_iter: StyledIter<'a>,
80}
81
82impl<'a, C: PixelColor> StyledPixelsIterator<'a, C> {
83 pub(in crate::primitives) fn new(primitive: &Polyline<'a>, style: &PrimitiveStyle<C>) -> Self {
84 let line_iter = if style.stroke_width <= 1 {
85 StyledIter::Thin(primitive.points())
86 } else {
87 let mut scanline_iter = ScanlineIterator::new(primitive, style);
88 let line_iter = scanline_iter
89 .next()
90 .unwrap_or_else(|| Scanline::new_empty(0));
91
92 StyledIter::Thick {
93 scanline_iter,
94 line_iter,
95 translate: primitive.translate,
96 }
97 };
98
99 StyledPixelsIterator {
100 stroke_color: style.effective_stroke_color(),
101 line_iter,
102 }
103 }
104}
105
106impl<C: PixelColor> Iterator for StyledPixelsIterator<'_, C> {
107 type Item = Pixel<C>;
108
109 fn next(&mut self) -> Option<Self::Item> {
110 let stroke_color = self.stroke_color?;
112
113 match self.line_iter {
114 StyledIter::Thin(ref mut it) => it.next(),
115 StyledIter::Thick {
116 ref mut scanline_iter,
117 ref mut line_iter,
118 translate,
119 } => {
120 if let Some(p) = line_iter.next() {
122 Some(p)
123 }
124 else {
126 *line_iter = scanline_iter.next()?;
127
128 line_iter.next()
129 }
130 .map(|p| p + translate)
131 }
132 }
133 .map(|point| Pixel(point, stroke_color))
134 }
135}
136
137impl<'a, C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Polyline<'a> {
138 type Iter = StyledPixelsIterator<'a, C>;
139
140 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
141 StyledPixelsIterator::new(self, style)
142 }
143}
144
145impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Polyline<'_> {
146 type Color = C;
147 type Output = ();
148
149 fn draw_styled<D>(
150 &self,
151 style: &PrimitiveStyle<C>,
152 target: &mut D,
153 ) -> Result<Self::Output, D::Error>
154 where
155 D: DrawTarget<Color = C>,
156 {
157 if let Some(stroke_color) = style.stroke_color {
158 match style.stroke_width {
159 0 => Ok(()),
160 1 => target.draw_iter(self.points().map(|point| Pixel(point, stroke_color))),
161 _ => {
162 if self.translate != Point::zero() {
163 draw_thick(
164 self,
165 style,
166 stroke_color,
167 &mut target.translated(self.translate),
168 )
169 } else {
170 draw_thick(self, style, stroke_color, target)
171 }
172 }
173 }
174 } else {
175 Ok(())
176 }
177 }
178}
179
180impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Polyline<'_> {
181 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
182 untranslated_bounding_box(self, style).translate(self.translate)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::{
190 geometry::Point,
191 iterator::PixelIteratorExt,
192 mock_display::MockDisplay,
193 pixelcolor::{BinaryColor, Rgb565, RgbColor},
194 primitives::{Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
195 Drawable,
196 };
197
198 pub(in crate::primitives::polyline) const PATTERN: [Point; 4] = [
200 Point::new(5, 10),
201 Point::new(13, 5),
202 Point::new(20, 10),
203 Point::new(30, 5),
204 ];
205
206 #[test]
207 fn one_px_stroke() {
208 let mut display = MockDisplay::new();
209
210 Polyline::new(&PATTERN)
211 .translate(Point::new(-5, -5))
212 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
213 .draw(&mut display)
214 .unwrap();
215
216 display.assert_pattern(&[
217 " # #",
218 " ## ## ## ",
219 " # # ## ",
220 " ## # ## ",
221 " ## ## ## ",
222 "# ## ",
223 ]);
224 }
225
226 #[test]
227 fn one_px_stroke_translated() {
228 let mut display = MockDisplay::new();
229
230 Polyline::new(&PATTERN)
231 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
232 .draw(&mut display)
233 .unwrap();
234
235 display.assert_pattern(&[
236 " ",
237 " ",
238 " ",
239 " ",
240 " ",
241 " # #",
242 " ## ## ## ",
243 " # # ## ",
244 " ## # ## ",
245 " ## ## ## ",
246 " # ## ",
247 ]);
248 }
249
250 #[test]
251 fn thick_stroke() {
252 let mut display = MockDisplay::new();
253
254 Polyline::new(&PATTERN)
255 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
256 .draw(&mut display)
257 .unwrap();
258
259 display.assert_pattern(&[
260 " ",
261 " ",
262 " ",
263 " # ",
264 " ##### ## ",
265 " ####### ##### ",
266 " ########## ####### ",
267 " ############# ##########",
268 " ######## ################# ",
269 " ####### ############# ",
270 " #### ######### ",
271 " # ###### ",
272 " ## ",
273 ]);
274 }
275
276 #[test]
277 fn thick_stroke_translated() {
278 let mut display = MockDisplay::new();
279
280 let styled = Polyline::new(&PATTERN)
281 .translate(Point::new(-4, -3))
282 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4));
283
284 assert_eq!(
285 styled.bounding_box(),
286 Rectangle::new(Point::zero(), Size::new(28, 10))
287 );
288
289 styled.draw(&mut display).unwrap();
290
291 display.assert_pattern(&[
292 " # ",
293 " ##### ## ",
294 " ####### ##### ",
295 " ########## ####### ",
296 " ############# ##########",
297 " ######## ################# ",
298 "####### ############# ",
299 " #### ######### ",
300 " # ###### ",
301 " ## ",
302 ]);
303 }
304
305 #[test]
306 fn thick_stroke_points() {
307 let mut d1 = MockDisplay::new();
308 let mut d2 = MockDisplay::new();
309
310 let pl = Polyline::new(&PATTERN)
311 .translate(Point::new(2, 3))
312 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4));
313
314 pl.draw(&mut d1).unwrap();
315
316 pl.pixels().draw(&mut d2).unwrap();
317
318 d1.assert_eq(&d2);
319 }
320
321 #[test]
322 fn joints() {
323 let cases: [(&str, &[Point], &[&str]); 4] = [
324 (
325 "Bevel with outside on right",
326 &[Point::new(0, 6), Point::new(25, 6), Point::new(3, 1)],
327 &[
328 " ### ",
329 " ####### ",
330 " ########### ",
331 " ################ ",
332 "####################### ",
333 "##########################",
334 "##########################",
335 "##########################",
336 ],
337 ),
338 (
339 "Bevel with outside on left",
340 &[Point::new(0, 2), Point::new(20, 2), Point::new(3, 8)],
341 &[
342 "##################### ",
343 "##################### ",
344 "##################### ",
345 "######################",
346 " ############",
347 " ############ ",
348 " ############ ",
349 " ########### ",
350 " ######### ",
351 " ##### ",
352 " ## ",
353 ],
354 ),
355 (
356 "Miter with outside on right",
357 &[Point::new(0, 6), Point::new(10, 6), Point::new(3, 1)],
358 &[
359 " # ",
360 " #### ",
361 " ###### ",
362 " ###### ",
363 "########### ",
364 "############ ",
365 "############## ",
366 "###############",
367 ],
368 ),
369 (
370 "Miter with outside on left",
371 &[Point::new(0, 2), Point::new(10, 2), Point::new(3, 8)],
372 &[
373 "################",
374 "############### ",
375 "############## ",
376 "############ ",
377 " ##### ",
378 " ###### ",
379 " ###### ",
380 " ###### ",
381 " ### ",
382 " # ",
383 ],
384 ),
385 ];
386
387 for (case, points, expected) in cases.iter() {
388 let mut display = MockDisplay::new();
389
390 Polyline::new(points)
391 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
392 .draw(&mut display)
393 .unwrap();
394
395 display.assert_pattern_with_message(expected, |f| write!(f, "Join {}", case));
396 }
397 }
398
399 #[test]
400 fn degenerate_joint() {
401 let mut display = MockDisplay::new();
402
403 Polyline::new(&[Point::new(2, 5), Point::new(25, 5), Point::new(5, 2)])
404 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 5))
405 .draw(&mut display)
406 .unwrap();
407
408 display.assert_pattern(&[
409 " #### ",
410 " ########## ",
411 " ################# ",
412 " ########################",
413 " ########################",
414 " ########################",
415 " ########################",
416 " ########################",
417 ]);
418 }
419
420 #[test]
421 fn alignment_has_no_effect() {
422 let base_style = PrimitiveStyleBuilder::new()
423 .stroke_color(BinaryColor::On)
424 .stroke_width(3);
425
426 let mut expected_display = MockDisplay::new();
427
428 Polyline::new(&PATTERN)
429 .into_styled(base_style.build())
430 .draw(&mut expected_display)
431 .unwrap();
432
433 for alignment in [StrokeAlignment::Inside, StrokeAlignment::Outside].iter() {
434 let mut display = MockDisplay::new();
435
436 Polyline::new(&PATTERN)
437 .into_styled(base_style.stroke_alignment(*alignment).build())
438 .draw(&mut display)
439 .unwrap();
440
441 display.assert_eq_with_message(&expected_display, |f| write!(f, "{:?}", alignment));
442 }
443 }
444
445 #[test]
446 fn thick_points() {
447 let base_style = PrimitiveStyle::with_stroke(BinaryColor::On, 5);
448
449 let mut expected_display = MockDisplay::new();
450 let mut display = MockDisplay::new();
451
452 Polyline::new(&PATTERN)
453 .into_styled(base_style)
454 .draw(&mut expected_display)
455 .unwrap();
456
457 Polyline::new(&PATTERN)
458 .into_styled(base_style)
459 .pixels()
460 .draw(&mut display)
461 .unwrap();
462
463 display.assert_eq(&expected_display);
464 }
465
466 #[test]
467 fn empty_styled_iterators() {
468 let points: [Point; 3] = [Point::new(2, 5), Point::new(3, 4), Point::new(4, 3)];
469
470 assert!(Polyline::new(&points)
472 .into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 0))
473 .pixels()
474 .eq(core::iter::empty()));
475
476 assert!(Polyline::new(&points)
478 .into_styled(
479 PrimitiveStyleBuilder::<Rgb565>::new()
480 .stroke_width(1)
481 .build()
482 )
483 .pixels()
484 .eq(core::iter::empty()));
485 }
486
487 #[test]
488 fn bounding_box() {
489 let pl = Polyline::new(&PATTERN);
490
491 let styled = pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 5));
492
493 let mut display = MockDisplay::new();
494 styled.draw(&mut display).unwrap();
495 assert_eq!(display.affected_area(), styled.bounding_box());
496
497 assert_eq!(
498 pl.into_styled(
499 PrimitiveStyleBuilder::<Rgb565>::new()
500 .stroke_width(5)
501 .build()
502 )
503 .bounding_box(),
504 Rectangle::new(pl.bounding_box().center(), Size::zero()),
505 "transparent"
506 );
507
508 assert_eq!(
509 pl.into_styled(PrimitiveStyle::with_fill(Rgb565::RED))
510 .bounding_box(),
511 Rectangle::new(pl.bounding_box().center(), Size::zero()),
512 "filled"
513 );
514
515 assert_eq!(
516 Polyline::new(&PATTERN[0..2])
517 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
518 .bounding_box(),
519 Rectangle::new(Point::new(4, 3), Size::new(11, 9)),
520 "two points"
521 );
522
523 assert_eq!(
524 Polyline::new(&PATTERN[0..1])
525 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
526 .bounding_box(),
527 Rectangle::new(Point::new(5, 10), Size::zero()),
528 "one point"
529 );
530 }
531
532 #[test]
533 fn translated_bounding_box() {
534 let by = Point::new(10, 12);
535 let pl = Polyline::new(&PATTERN).translate(by);
536
537 assert_eq!(
538 pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1))
539 .bounding_box(),
540 Rectangle::new(Point::new(15, 17), Size::new(26, 6)),
541 "thin translated"
542 );
543
544 assert_eq!(
545 pl.into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
546 .bounding_box(),
547 Rectangle::new(Point::new(14, 14), Size::new(28, 11)),
548 "thick translated"
549 );
550
551 assert_eq!(
552 Polyline::new(&PATTERN[0..2])
553 .translate(by)
554 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
555 .bounding_box(),
556 Rectangle::new(Point::new(14, 15), Size::new(11, 9)),
557 "two points translated"
558 );
559
560 assert_eq!(
561 Polyline::new(&PATTERN[0..1])
562 .translate(by)
563 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
564 .bounding_box(),
565 Rectangle::new(Point::new(15, 22), Size::zero()),
566 "one point translated"
567 );
568 }
569
570 #[test]
571 fn empty_line_no_draw() {
572 let mut display = MockDisplay::new();
573
574 Polyline::new(&[])
575 .into_styled(PrimitiveStyle::with_stroke(Rgb565::GREEN, 2))
576 .pixels()
577 .draw(&mut display)
578 .unwrap();
579
580 display.assert_eq(&MockDisplay::new());
581 }
582
583 #[test]
584 fn issue_489_overdraw() {
585 let mut display = MockDisplay::new();
586
587 Polyline::new(&[Point::new(10, 5), Point::new(5, 10), Point::new(10, 10)])
589 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 5))
590 .draw(&mut display)
591 .unwrap();
592 }
593
594 #[test]
595 fn issue_471_spurs() {
596 let points = [Point::new(10, 70), Point::new(20, 50), Point::new(31, 30)];
597
598 let line = Polyline::new(&points)
599 .translate(Point::new(0, -15))
600 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 18));
601
602 let bb = line.bounding_box();
603
604 assert_eq!(bb, Rectangle::new(Point::new(1, 11), Size::new(39, 49)));
606
607 let mut display = MockDisplay::new();
608 line.draw(&mut display).unwrap();
609
610 assert_eq!(display.affected_area(), bb);
612 }
613
614 #[test]
615 #[ignore]
618 fn issue_471_spurs_2() {
619 let points = [Point::new(13, 65), Point::new(20, 50), Point::new(31, 30)];
620
621 let line = Polyline::new(&points)
622 .translate(Point::new(0, -15))
623 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 18));
624
625 let bb = line.bounding_box();
626
627 assert_eq!(bb, Rectangle::new(Point::new(4, 26), Size::new(36, 44)));
629
630 let mut display = MockDisplay::new();
631 line.draw(&mut display).unwrap();
632
633 assert_eq!(display.affected_area(), bb);
635 }
636}