1 | use 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 | |
15 | /// Compute the bounding box of the non-translated polyline. |
16 | pub(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: Point, max: Point) = |
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: Point, max: Point), segment: ThickSegment| { |
28 | let bb: Rectangle = segment.edges_bounding_box(); |
29 | |
30 | ( |
31 | min.component_min(bb.top_left), |
32 | max.component_max(bb.bottom_right().unwrap_or(default:bb.top_left)), |
33 | ) |
34 | }, |
35 | ); |
36 | |
37 | Rectangle::with_corners(corner_1:min, corner_2:max) |
38 | } else { |
39 | Rectangle::new(top_left:primitive.bounding_box().center(), Size::zero()) |
40 | } |
41 | } |
42 | |
43 | fn draw_thick<D>( |
44 | polyline: &Polyline, |
45 | style: &PrimitiveStyle<D::Color>, |
46 | stroke_color: D::Color, |
47 | target: &mut D, |
48 | ) -> Result<(), D::Error> |
49 | where |
50 | D: DrawTarget, |
51 | { |
52 | for line: Scanline in ScanlineIterator::new(primitive:polyline, style) { |
53 | let rect: Rectangle = 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))] |
65 | enum 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 | /// Pixel iterator for each pixel in the line |
75 | #[derive (Clone, Debug)] |
76 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
77 | pub struct StyledPixelsIterator<'a, C> { |
78 | stroke_color: Option<C>, |
79 | line_iter: StyledIter<'a>, |
80 | } |
81 | |
82 | impl<'a, C: PixelColor> StyledPixelsIterator<'a, C> { |
83 | pub(in crate::primitives) fn new(primitive: &Polyline<'a>, style: &PrimitiveStyle<C>) -> Self { |
84 | let line_iter: StyledIter<'_> = if style.stroke_width <= 1 { |
85 | StyledIter::Thin(primitive.points()) |
86 | } else { |
87 | let mut scanline_iter: ScanlineIterator<'_> = ScanlineIterator::new(primitive, style); |
88 | let line_iter: Scanline = scanline_iterOption |
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 | |
106 | impl<C: PixelColor> Iterator for StyledPixelsIterator<'_, C> { |
107 | type Item = Pixel<C>; |
108 | |
109 | fn next(&mut self) -> Option<Self::Item> { |
110 | // Return none if stroke color is none |
111 | 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 | // We've got a line to iterate over, so get it's next pixel. |
121 | if let Some(p) = line_iter.next() { |
122 | Some(p) |
123 | } |
124 | // Finished this line. Get the next one from the scanline iterator. |
125 | 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 | |
137 | impl<'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 | |
145 | impl<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 | |
180 | impl<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)] |
187 | mod 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 | // Smaller test pattern for mock display |
199 | 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 | // No stroke width = no pixels |
471 | assert!(Polyline::new(&points) |
472 | .into_styled(PrimitiveStyle::with_stroke(Rgb565::BLUE, 0)) |
473 | .pixels() |
474 | .eq(core::iter::empty())); |
475 | |
476 | // No stroke color = no pixels |
477 | 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 | // Panics if pixel is drawn twice. |
588 | 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 | // Check bounding box is correct |
605 | 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 | // Check no pixels are drawn outside bounding box |
611 | assert_eq!(display.affected_area(), bb); |
612 | } |
613 | |
614 | #[test ] |
615 | // FIXME: Un-ignore when more polyline spur fixes are made. This test checks for a smaller |
616 | // spur created from a different set of points than `issue_471_spurs`. |
617 | #[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 | // Check bounding box is correct |
628 | 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 | // Check no pixels are drawn outside bounding box |
634 | assert_eq!(display.affected_area(), bb); |
635 | } |
636 | } |
637 | |