1use crate::{
2 draw_target::DrawTarget,
3 geometry::{Dimensions, Point},
4 pixelcolor::PixelColor,
5 primitives::{
6 common::{ClosedThickSegmentIter, PointType, Scanline, StrokeOffset},
7 styled::{StyledDimensions, StyledDrawable, StyledPixels},
8 triangle::{scanline_iterator::ScanlineIterator, Triangle},
9 PrimitiveStyle, Rectangle, StrokeAlignment,
10 },
11 Pixel,
12};
13
14#[derive(Clone, Eq, PartialEq, Hash, Debug)]
16#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
17pub struct StyledPixelsIterator<C> {
18 lines_iter: ScanlineIterator,
19 current_line: Scanline,
20 current_color: Option<C>,
21 fill_color: Option<C>,
22 stroke_color: Option<C>,
23}
24
25impl<C: PixelColor> StyledPixelsIterator<C> {
26 pub(in crate::primitives) fn new(primitive: &Triangle, style: &PrimitiveStyle<C>) -> Self {
27 let mut lines_iter = ScanlineIterator::new(
28 primitive,
29 style.stroke_width,
30 StrokeOffset::from(style.stroke_alignment),
31 style.fill_color.is_some(),
32 &primitive.styled_bounding_box(style),
33 );
34
35 let (current_line, point_type) = lines_iter
36 .next()
37 .unwrap_or_else(|| (Scanline::new_empty(0), PointType::Stroke));
38
39 let current_color = match point_type {
40 PointType::Stroke => style.effective_stroke_color(),
41 PointType::Fill => style.fill_color,
42 };
43
44 Self {
45 lines_iter,
46 current_line,
47 current_color,
48 fill_color: style.fill_color,
49 stroke_color: style.effective_stroke_color(),
50 }
51 }
52}
53
54impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
55 type Item = Pixel<C>;
56
57 fn next(&mut self) -> Option<Self::Item> {
58 loop {
59 if let Some(p) = self.current_line.next() {
60 return Some(Pixel(p, self.current_color?));
61 } else {
62 let (next_line, next_type) = self.lines_iter.next()?;
63
64 self.current_line = next_line;
65
66 self.current_color = match next_type {
67 PointType::Stroke => self.stroke_color,
68 PointType::Fill => self.fill_color,
69 };
70 }
71 }
72 }
73}
74
75impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Triangle {
76 type Iter = StyledPixelsIterator<C>;
77
78 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
79 StyledPixelsIterator::new(self, style)
80 }
81}
82
83impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Triangle {
84 type Color = C;
85 type Output = ();
86
87 fn draw_styled<D>(
88 &self,
89 style: &PrimitiveStyle<C>,
90 target: &mut D,
91 ) -> Result<Self::Output, D::Error>
92 where
93 D: DrawTarget<Color = C>,
94 {
95 if style.is_transparent() {
96 return Ok(());
97 }
98
99 for (line, kind) in ScanlineIterator::new(
100 self,
101 style.stroke_width,
102 StrokeOffset::from(style.stroke_alignment),
103 style.fill_color.is_some(),
104 &self.styled_bounding_box(style),
105 ) {
106 let color = match kind {
107 PointType::Stroke => style.effective_stroke_color(),
108 PointType::Fill => style.fill_color,
109 };
110
111 if let Some(color) = color {
112 let rect = line.to_rectangle();
113
114 if !rect.is_zero_sized() {
115 target.fill_solid(&rect, color)?;
116 }
117 }
118 }
119
120 Ok(())
121 }
122}
123
124impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Triangle {
125 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
126 if style.stroke_width < 2 || style.stroke_alignment == StrokeAlignment::Inside {
128 return self.bounding_box();
129 }
130
131 let t = self.sorted_clockwise();
132
133 let (min, max) = ClosedThickSegmentIter::new(
134 &t.vertices,
135 style.stroke_width,
136 StrokeOffset::from(style.stroke_alignment),
137 )
138 .fold(
139 (
140 Point::new_equal(core::i32::MAX),
141 Point::new_equal(core::i32::MIN),
142 ),
143 |(min, max), segment| {
144 let bb = segment.edges_bounding_box();
145
146 (
147 min.component_min(bb.top_left),
148 max.component_max(bb.bottom_right().unwrap_or(bb.top_left)),
149 )
150 },
151 );
152
153 Rectangle::with_corners(min, max)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::{
161 geometry::Point,
162 mock_display::MockDisplay,
163 pixelcolor::{BinaryColor, Rgb565, Rgb888, RgbColor},
164 primitives::{Line, Primitive, PrimitiveStyleBuilder, StrokeAlignment},
165 transform::Transform,
166 Drawable,
167 };
168
169 #[test]
170 fn unfilled_no_stroke_width_no_triangle() {
171 let mut tri = Triangle::new(Point::new(2, 2), Point::new(4, 2), Point::new(2, 4))
172 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 0))
173 .pixels();
174
175 assert_eq!(tri.next(), None);
176 }
177
178 #[test]
179 fn issue_308_infinite() {
180 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
181 display.set_allow_out_of_bounds_drawing(true);
182
183 Triangle::new(Point::new(10, 10), Point::new(20, 30), Point::new(30, -10))
184 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
185 .draw(&mut display)
186 .unwrap();
187 }
188
189 #[test]
190 fn it_draws_filled_strokeless_tri() {
191 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
192
193 Triangle::new(Point::new(2, 2), Point::new(2, 4), Point::new(4, 2))
194 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
195 .draw(&mut display)
196 .unwrap();
197
198 display.assert_pattern(&[
199 " ", " ", " ###", " ## ", " # ", ]);
205 }
206
207 #[test]
208 fn stroke_fill_colors() {
209 let mut display: MockDisplay<Rgb888> = MockDisplay::new();
210
211 Triangle::new(Point::new(2, 2), Point::new(8, 2), Point::new(2, 8))
212 .into_styled(
213 PrimitiveStyleBuilder::new()
214 .stroke_width(1)
215 .stroke_color(Rgb888::RED)
216 .fill_color(Rgb888::GREEN)
217 .build(),
218 )
219 .draw(&mut display)
220 .unwrap();
221
222 display.assert_pattern(&[
223 " ",
224 " ",
225 " RRRRRRR ",
226 " RGGGGR ",
227 " RGGGR ",
228 " RGGR ",
229 " RGR ",
230 " RR ",
231 " R ",
232 ]);
233 }
234
235 #[test]
236 fn off_screen() {
237 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
238 display.set_allow_out_of_bounds_drawing(true);
239
240 Triangle::new(Point::new(5, 5), Point::new(10, 15), Point::new(15, -5))
241 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On))
242 .draw(&mut display)
243 .unwrap();
244
245 display.assert_pattern(&[
246 " #####",
247 " ######",
248 " ###### ",
249 " ####### ",
250 " ######## ",
251 " ######### ",
252 " ######## ",
253 " ####### ",
254 " ####### ",
255 " ###### ",
256 " ##### ",
257 " #### ",
258 " #### ",
259 " ### ",
260 " ## ",
261 " # ",
262 ]);
263 }
264
265 #[test]
266 fn styled_off_screen_still_draws_points() {
267 let off_screen = Triangle::new(Point::new(10, 10), Point::new(20, 20), Point::new(30, -30))
268 .into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
269 let on_screen = off_screen.translate(Point::new(0, 35));
270
271 assert!(off_screen.pixels().eq(on_screen
272 .pixels()
273 .map(|Pixel(p, col)| Pixel(p - Point::new(0, 35), col))));
274 }
275
276 #[test]
277 fn styled_stroke_equals_lines() {
278 let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
279
280 let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
281
282 let mut tri_display: MockDisplay<BinaryColor> = MockDisplay::new();
283 styled.draw(&mut tri_display).unwrap();
284
285 let mut lines_display: MockDisplay<BinaryColor> = MockDisplay::new();
286 lines_display.set_allow_overdraw(true);
287
288 let [p1, p2, p3] = triangle.vertices;
289
290 Line::new(p1, p2)
291 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
292 .draw(&mut lines_display)
293 .unwrap();
294 Line::new(p2, p3)
295 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
296 .draw(&mut lines_display)
297 .unwrap();
298 Line::new(p3, p1)
299 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
300 .draw(&mut lines_display)
301 .unwrap();
302
303 tri_display.assert_eq(&lines_display);
304 }
305
306 #[test]
307 fn no_stroke_overdraw() {
308 let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
309
310 let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1));
311
312 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
313
314 styled.draw(&mut display).unwrap();
315 }
316
317 #[test]
318 fn bounding_box() {
319 let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
320
321 let styled = triangle.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 20));
322
323 let mut display = MockDisplay::new();
324
325 styled.draw(&mut display).unwrap();
326 assert_eq!(display.affected_area(), styled.bounding_box());
327 }
328
329 #[test]
330 fn bounding_box_is_independent_of_colors() {
331 let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25));
332
333 let transparent = triangle.into_styled(PrimitiveStyle::<BinaryColor>::new());
334 let filled = triangle.into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
335
336 assert_eq!(transparent.bounding_box(), filled.bounding_box(),);
337 }
338
339 #[test]
340 fn outside_rendering_missing_lines() {
341 let p1 = Point::new(10, 11);
342 let p2 = Point::new(20, 11);
343 let p3 = Point::new(8, 4);
344
345 let styled = Triangle::new(p1, p2, p3).into_styled(
346 PrimitiveStyleBuilder::new()
347 .stroke_alignment(StrokeAlignment::Outside)
348 .stroke_width(5)
349 .stroke_color(Rgb565::RED)
350 .fill_color(Rgb565::GREEN)
351 .build(),
352 );
353
354 let mut display = MockDisplay::new();
355
356 styled.draw(&mut display).unwrap();
357
358 display.assert_pattern(&[
360 " R ",
361 " RRRR ",
362 " RRRRRRR ",
363 " RRRRRRRRR ",
364 " RRRRRRRRRRRRR ",
365 " RRRRRRRRRRRRRRRR ",
366 " RRRRRRGRRRRRRRRRRR ",
367 " RRRRRGGGRRRRRRRRRR",
368 " RRRRRGGGGGRRRRRRRR",
369 " RRRRRGGGGGGRRRRRRR",
370 " RRRRRRGGGGGGGRRRR ",
371 " RRRRRRRRRRRRRRRR ",
372 " RRRRRRRRRRRRRRRR ",
373 " RRRRRRRRRRRRRRR ",
374 " RRRRRRRRRRRRRR ",
375 " RRRRRRRRRRRRRR ",
376 ]);
377 }
378
379 #[test]
380 fn thick_stroke_only_no_overdraw() {
381 let p1 = Point::new(10, 11);
382 let p2 = Point::new(20, 11);
383 let p3 = Point::new(8, 4);
384
385 let styled = Triangle::new(p1, p2, p3).into_styled(
386 PrimitiveStyleBuilder::new()
387 .stroke_alignment(StrokeAlignment::Outside)
388 .stroke_width(5)
389 .stroke_color(Rgb565::RED)
390 .build(),
391 );
392
393 let mut display = MockDisplay::new();
394
395 styled.draw(&mut display).unwrap();
396 }
397
398 #[test]
399 fn inner_fill_leak() {
400 let p1 = Point::new(0, 20);
401 let p2 = Point::new(20, 0);
402 let p3 = Point::new(14, 24);
403
404 let styled = Triangle::new(p1, p2, p3).into_styled(
405 PrimitiveStyleBuilder::new()
406 .stroke_alignment(StrokeAlignment::Inside)
407 .stroke_width(5)
408 .stroke_color(Rgb565::RED)
409 .fill_color(Rgb565::GREEN)
410 .build(),
411 );
412
413 let mut display = MockDisplay::new();
414
415 styled.draw(&mut display).unwrap();
416
417 display.assert_pattern(&[
420 " R",
421 " RR",
422 " RR ",
423 " RRR ",
424 " RRRR ",
425 " RRRRR ",
426 " RRRRR ",
427 " RRRRRR ",
428 " RRRRRRR ",
429 " RRRRRRRR ",
430 " RRRRRRRR ",
431 " RRRRRRRRR ",
432 " RRRRRRRRRR ",
433 " RRRRRRRRRRR ",
434 " RRRRRRRRRRR ",
435 " RRRRRRRRRRRR ",
436 " RRRRRRRGRRRRR ",
437 " RRRRRRRGRRRRRR ",
438 " RRRRRRRRGRRRRR ",
439 " RRRRRRRRRRRRRRR ",
440 "RRRRRRRRRRRRRRRR ",
441 " RRRRRRRRRRRRRR ",
442 " RRRRRRRRR ",
443 " RRRRRR ",
444 " RR ",
445 ]);
446 }
447
448 #[test]
449 fn colinear() {
450 let p1 = Point::new(90, 80);
451 let p2 = Point::new(100, 70);
452 let p3 = Point::new(95, 75);
453
454 let t = Triangle::new(p1, p2, p3).translate(Point::new(-85, -70));
455
456 let styled = t.into_styled(
457 PrimitiveStyleBuilder::new()
458 .stroke_alignment(StrokeAlignment::Inside)
459 .stroke_width(5)
460 .stroke_color(Rgb565::RED)
461 .fill_color(Rgb565::GREEN)
462 .build(),
463 );
464
465 let mut display = MockDisplay::new();
466
467 styled.draw(&mut display).unwrap();
468
469 display.assert_pattern(&[
470 " R",
471 " R ",
472 " R ",
473 " R ",
474 " R ",
475 " R ",
476 " R ",
477 " R ",
478 " R ",
479 " R ",
480 " R ",
481 ]);
482 }
483
484 #[test]
486 fn colinear_lump() {
487 let p1 = Point::new(90, 80);
488 let p2 = Point::new(100, 70);
489 let p3 = Point::new(102, 73);
490
491 let t = Triangle::new(p1, p2, p3).translate(Point::new(-90, -70));
492
493 let styled = t.into_styled(
494 PrimitiveStyleBuilder::new()
495 .stroke_alignment(StrokeAlignment::Inside)
496 .stroke_width(5)
497 .stroke_color(Rgb565::RED)
498 .fill_color(Rgb565::GREEN)
499 .build(),
500 );
501
502 let mut display = MockDisplay::new();
503
504 styled.draw(&mut display).unwrap();
505
506 display.assert_pattern(&[
507 " R ",
508 " RRR ",
509 " RRRR ",
510 " RRRRRR",
511 " RRRRRR ",
512 " RRRRR ",
513 " RRRR ",
514 " RRR ",
515 " RRR ",
516 " RR ",
517 "R ",
518 ]);
519 }
520
521 #[test]
522 fn colinear_lump_2() {
523 let p1 = Point::new(90, 80);
524 let p2 = Point::new(100, 70);
525 let p3 = Point::new(102, 73);
526
527 let t = Triangle::new(p1, p2, p3).translate(Point::new(-90, -70));
528
529 let styled = t.into_styled(
530 PrimitiveStyleBuilder::new()
531 .stroke_alignment(StrokeAlignment::Inside)
532 .stroke_width(5)
533 .stroke_color(Rgb565::RED)
534 .fill_color(Rgb565::GREEN)
535 .build(),
536 );
537
538 let mut display = MockDisplay::new();
539
540 styled.draw(&mut display).unwrap();
541
542 display.assert_pattern(&[
543 " R ",
544 " RRR ",
545 " RRRR ",
546 " RRRRRR",
547 " RRRRRR ",
548 " RRRRR ",
549 " RRRR ",
550 " RRR ",
551 " RRR ",
552 " RR ",
553 "R ",
554 ]);
555 }
556}