1 | use 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 | /// Pixel iterator for each pixel in the triangle border |
15 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
16 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
17 | pub 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 | |
25 | impl<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 | |
54 | impl<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: Point) = self.current_line.next() { |
60 | return Some(Pixel(p, self.current_color?)); |
61 | } else { |
62 | let (next_line: Scanline, next_type: PointType) = 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 | |
75 | impl<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 | |
83 | impl<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 | |
124 | impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Triangle { |
125 | fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle { |
126 | // Short circuit special cases |
127 | 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)] |
158 | mod 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 | " " , // |
200 | " " , // |
201 | " ###" , // |
202 | " ## " , // |
203 | " # " , // |
204 | ]); |
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 | // Believe it or not, this is actually a triangle. |
359 | 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 | // In the failing case, there are some `G`s sitting on the end of each line that |
418 | // shouldn't be there. |
419 | 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 | // Original bug has a weird "lump" drawn at one end of a colinear triangle. |
485 | #[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 | } |
557 | |