1 | use 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 | }; |
15 | use az::SaturatingAs; |
16 | |
17 | /// Pixel iterator for each pixel in the sector border |
18 | #[derive (Clone, PartialEq, Debug)] |
19 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
20 | pub 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 | |
37 | impl<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 | // PERF: The distance iterator should use the smaller sector bounding box |
46 | 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 | // TODO: Polylines and sectors should use the same miter limit. |
65 | 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 | |
104 | impl<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 | // Check if point is inside the radial stroke lines or the fill. |
116 | 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 | // Bevel the line join. |
126 | 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 | // Add the outer circular stroke. |
138 | 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 | |
154 | impl<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 | |
162 | impl<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(pixels:StyledPixelsIterator::new(self, style)) |
175 | } |
176 | } |
177 | |
178 | impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Sector { |
179 | // FIXME: This doesn't take into account start/end angles. This should be fixed to close #405. |
180 | fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle { |
181 | let offset: i32 = 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))] |
189 | enum BevelKind { |
190 | Interior, |
191 | Exterior, |
192 | } |
193 | |
194 | #[cfg (test)] |
195 | mod 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 | // Check the rendering of a simple sector |
208 | #[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 | " ##### " , // |
219 | " ## ## " , // |
220 | "## ##" , // |
221 | " ## ## " , // |
222 | " ### " , // |
223 | ]); |
224 | } |
225 | |
226 | // Check the rendering of a filled sector with negative sweep |
227 | // TODO: Re-enable this test for `fixed_point` and track as part of #484 |
228 | #[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 | " ### " , // |
240 | " ##### " , // |
241 | "###### " , // |
242 | "##### " , // |
243 | "###### " , // |
244 | " ##### " , // |
245 | " ### " , // |
246 | ]); |
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 | // TODO: Uncomment when arc bounding box is fixed in #405 |
371 | // let mut display = MockDisplay::new(); |
372 | // center.draw(&mut display).unwrap(); |
373 | // assert_eq!(display.affected_area(), center.bounding_box()); |
374 | |
375 | 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 | /// The radial lines should be connected using a line join. |
381 | #[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 | /// The radial lines should be connected using a line join. |
411 | #[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 | /// The radial lines should be connected using a line join. |
437 | #[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 | /// The stroke for the radial lines shouldn't overlap the outer edge of the stroke on the |
475 | /// circular part of the sector. |
476 | #[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 | /// Both radial lines should be perfectly aligned for 180° sweep angle. |
523 | #[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 | /// Both radial lines should be perfectly aligned for 180° sweep angle. |
554 | #[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 | /// The fill shouldn't overlap the stroke and there should be no gaps between stroke and fill. |
592 | #[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 | /// No radial lines should be drawn if the sweep angle is 360°. |
639 | #[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 | /// No radial lines should be drawn for sweep angles larger than 360°. |
663 | #[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 | /// The sector was mirrored along the Y axis if the start angle was exactly 360°. |
687 | #[test ] |
688 | fn issue_484_sector_flips_at_360_degrees() { |
689 | let mut display = MockDisplay::new(); |
690 | |
691 | // This would trigger the out of bounds drawing check if the sector |
692 | // would be mirrored along the Y axis. |
693 | 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 | } |
699 | |