1 | use crate::{ |
2 | draw_target::DrawTarget, |
3 | geometry::{Dimensions, Point, PointExt}, |
4 | pixelcolor::PixelColor, |
5 | primitives::{ |
6 | circle::{points::Scanlines, Circle}, |
7 | common::{Scanline, StyledScanline}, |
8 | rectangle::Rectangle, |
9 | styled::{StyledDimensions, StyledDrawable, StyledPixels}, |
10 | PrimitiveStyle, |
11 | }, |
12 | Pixel, |
13 | }; |
14 | use az::SaturatingAs; |
15 | |
16 | /// Pixel iterator for each pixel in the circle border |
17 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
18 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
19 | pub struct StyledPixelsIterator<C> { |
20 | styled_scanlines: StyledScanlines, |
21 | |
22 | stroke_left: Scanline, |
23 | fill: Scanline, |
24 | stroke_right: Scanline, |
25 | |
26 | stroke_color: Option<C>, |
27 | fill_color: Option<C>, |
28 | } |
29 | |
30 | impl<C: PixelColor> StyledPixelsIterator<C> { |
31 | pub(in crate::primitives) fn new(primitive: &Circle, style: &PrimitiveStyle<C>) -> Self { |
32 | let stroke_area: Circle = style.stroke_area(primitive); |
33 | let fill_area: Circle = style.fill_area(primitive); |
34 | |
35 | Self { |
36 | styled_scanlines: StyledScanlines::new(&stroke_area, &fill_area), |
37 | stroke_left: Scanline::new_empty(0), |
38 | fill: Scanline::new_empty(0), |
39 | stroke_right: Scanline::new_empty(0), |
40 | stroke_color: style.stroke_color, |
41 | fill_color: style.fill_color, |
42 | } |
43 | } |
44 | } |
45 | |
46 | impl<C> Iterator for StyledPixelsIterator<C> |
47 | where |
48 | C: PixelColor, |
49 | { |
50 | type Item = Pixel<C>; |
51 | |
52 | fn next(&mut self) -> Option<Self::Item> { |
53 | match (self.stroke_color, self.fill_color) { |
54 | (Some(stroke_color), None) => loop { |
55 | if let Some(pixel) = self |
56 | .stroke_left |
57 | .next() |
58 | .or_else(|| self.stroke_right.next()) |
59 | .map(|p| Pixel(p, stroke_color)) |
60 | { |
61 | return Some(pixel); |
62 | } |
63 | |
64 | let scanline = self.styled_scanlines.next()?; |
65 | self.stroke_left = scanline.stroke_left(); |
66 | self.stroke_right = scanline.stroke_right(); |
67 | }, |
68 | (Some(stroke_color), Some(fill_color)) => loop { |
69 | if let Some(pixel) = self |
70 | .stroke_left |
71 | .next() |
72 | .map(|p| Pixel(p, stroke_color)) |
73 | .or_else(|| self.fill.next().map(|p| Pixel(p, fill_color))) |
74 | .or_else(|| self.stroke_right.next().map(|p| Pixel(p, stroke_color))) |
75 | { |
76 | return Some(pixel); |
77 | } |
78 | |
79 | let scanline = self.styled_scanlines.next()?; |
80 | self.stroke_left = scanline.stroke_left(); |
81 | self.fill = scanline.fill(); |
82 | self.stroke_right = scanline.stroke_right(); |
83 | }, |
84 | (None, Some(fill_color)) => loop { |
85 | if let Some(pixel) = self.fill.next().map(|p| Pixel(p, fill_color)) { |
86 | return Some(pixel); |
87 | } |
88 | |
89 | let scanline = self.styled_scanlines.next()?; |
90 | self.fill = scanline.fill(); |
91 | }, |
92 | (None, None) => None, |
93 | } |
94 | } |
95 | } |
96 | |
97 | impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Circle { |
98 | type Iter = StyledPixelsIterator<C>; |
99 | |
100 | fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter { |
101 | StyledPixelsIterator::new(self, style) |
102 | } |
103 | } |
104 | |
105 | impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Circle { |
106 | type Color = C; |
107 | type Output = (); |
108 | |
109 | fn draw_styled<D>( |
110 | &self, |
111 | style: &PrimitiveStyle<C>, |
112 | target: &mut D, |
113 | ) -> Result<Self::Output, D::Error> |
114 | where |
115 | D: DrawTarget<Color = C>, |
116 | { |
117 | match (style.effective_stroke_color(), style.fill_color) { |
118 | (Some(stroke_color), None) => { |
119 | for scanline in |
120 | StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self)) |
121 | { |
122 | scanline.draw_stroke(target, stroke_color)?; |
123 | } |
124 | } |
125 | (Some(stroke_color), Some(fill_color)) => { |
126 | for scanline in |
127 | StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self)) |
128 | { |
129 | scanline.draw_stroke_and_fill(target, stroke_color, fill_color)?; |
130 | } |
131 | } |
132 | (None, Some(fill_color)) => { |
133 | for scanline in Scanlines::new(&style.fill_area(self)) { |
134 | scanline.draw(target, fill_color)?; |
135 | } |
136 | } |
137 | (None, None) => {} |
138 | } |
139 | |
140 | Ok(()) |
141 | } |
142 | } |
143 | |
144 | impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Circle { |
145 | fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle { |
146 | let offset: i32 = style.outside_stroke_width().saturating_as(); |
147 | |
148 | self.bounding_box().offset(offset) |
149 | } |
150 | } |
151 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
152 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
153 | struct StyledScanlines { |
154 | scanlines: Scanlines, |
155 | fill_threshold: u32, |
156 | } |
157 | |
158 | impl StyledScanlines { |
159 | pub fn new(stroke_area: &Circle, fill_area: &Circle) -> Self { |
160 | Self { |
161 | scanlines: Scanlines::new(circle:stroke_area), |
162 | fill_threshold: fill_area.threshold(), |
163 | } |
164 | } |
165 | } |
166 | |
167 | impl Iterator for StyledScanlines { |
168 | type Item = StyledScanline; |
169 | |
170 | fn next(&mut self) -> Option<Self::Item> { |
171 | self.scanlines.next().map(|scanline: Scanline| { |
172 | let fill_range: Option> = scanlineOption |
173 | .x |
174 | .clone() |
175 | .find(|x: &i32| { |
176 | let delta: Point = Point::new(*x, scanline.y) * 2 - self.scanlines.center_2x; |
177 | (delta.length_squared() as u32) < self.fill_threshold |
178 | }) |
179 | .map(|x: i32| x..scanline.x.end - (x - scanline.x.start)); |
180 | |
181 | StyledScanline::new(scanline.y, stroke_range:scanline.x, fill_range) |
182 | }) |
183 | } |
184 | } |
185 | |
186 | #[cfg (test)] |
187 | mod tests { |
188 | use super::*; |
189 | use crate::{ |
190 | geometry::{Dimensions, Point}, |
191 | iterator::PixelIteratorExt, |
192 | mock_display::MockDisplay, |
193 | pixelcolor::BinaryColor, |
194 | primitives::{ |
195 | OffsetOutline, PointsIter, Primitive, PrimitiveStyleBuilder, StrokeAlignment, Styled, |
196 | }, |
197 | Drawable, |
198 | }; |
199 | |
200 | /// Draws a styled circle by only using the points iterator. |
201 | fn draw_circle( |
202 | diameter: u32, |
203 | stroke_color: Option<BinaryColor>, |
204 | stroke_width: u32, |
205 | fill_color: Option<BinaryColor>, |
206 | ) -> MockDisplay<BinaryColor> { |
207 | let circle = Circle::with_center(Point::new_equal(10), diameter); |
208 | |
209 | let mut display = MockDisplay::new(); |
210 | display.set_pixels(circle.points(), stroke_color); |
211 | display.set_pixels( |
212 | circle.offset(-stroke_width.saturating_as::<i32>()).points(), |
213 | fill_color, |
214 | ); |
215 | |
216 | display |
217 | } |
218 | |
219 | #[test ] |
220 | fn fill() { |
221 | for diameter in 5..=6 { |
222 | let expected = draw_circle(diameter, None, 0, Some(BinaryColor::On)); |
223 | |
224 | let circle = Circle::with_center(Point::new_equal(10), diameter) |
225 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); |
226 | |
227 | let mut drawable = MockDisplay::new(); |
228 | circle.draw(&mut drawable).unwrap(); |
229 | drawable.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}" , diameter)); |
230 | |
231 | let mut pixels = MockDisplay::new(); |
232 | circle.pixels().draw(&mut pixels).unwrap(); |
233 | pixels.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}" , diameter)); |
234 | } |
235 | } |
236 | |
237 | #[test ] |
238 | fn stroke() { |
239 | for (diameter, stroke_width) in [(5, 2), (5, 3), (6, 2), (6, 3)].iter().copied() { |
240 | let expected = draw_circle(diameter, Some(BinaryColor::On), stroke_width, None); |
241 | |
242 | let style = PrimitiveStyleBuilder::new() |
243 | .stroke_color(BinaryColor::On) |
244 | .stroke_width(stroke_width) |
245 | .stroke_alignment(StrokeAlignment::Inside) |
246 | .build(); |
247 | |
248 | let circle = Circle::with_center(Point::new_equal(10), diameter).into_styled(style); |
249 | |
250 | let mut drawable = MockDisplay::new(); |
251 | circle.draw(&mut drawable).unwrap(); |
252 | drawable.assert_eq_with_message(&expected, |f| { |
253 | write!( |
254 | f, |
255 | "diameter = {}, stroke_width = {}" , |
256 | diameter, stroke_width |
257 | ) |
258 | }); |
259 | |
260 | let mut pixels = MockDisplay::new(); |
261 | circle.pixels().draw(&mut pixels).unwrap(); |
262 | pixels.assert_eq_with_message(&expected, |f| { |
263 | write!( |
264 | f, |
265 | "diameter = {}, stroke_width = {}" , |
266 | diameter, stroke_width |
267 | ) |
268 | }); |
269 | } |
270 | } |
271 | |
272 | #[test ] |
273 | fn stroke_and_fill() { |
274 | for (diameter, stroke_width) in [(5, 2), (5, 3), (6, 2), (6, 3)].iter().copied() { |
275 | let expected = draw_circle( |
276 | diameter, |
277 | Some(BinaryColor::On), |
278 | stroke_width, |
279 | Some(BinaryColor::Off), |
280 | ); |
281 | |
282 | let style = PrimitiveStyleBuilder::new() |
283 | .fill_color(BinaryColor::Off) |
284 | .stroke_color(BinaryColor::On) |
285 | .stroke_width(stroke_width) |
286 | .stroke_alignment(StrokeAlignment::Inside) |
287 | .build(); |
288 | |
289 | let circle = Circle::with_center(Point::new_equal(10), diameter).into_styled(style); |
290 | |
291 | let mut drawable = MockDisplay::new(); |
292 | circle.draw(&mut drawable).unwrap(); |
293 | drawable.assert_eq_with_message(&expected, |f| { |
294 | write!( |
295 | f, |
296 | "diameter = {}, stroke_width = {}" , |
297 | diameter, stroke_width |
298 | ) |
299 | }); |
300 | |
301 | let mut pixels = MockDisplay::new(); |
302 | circle.pixels().draw(&mut pixels).unwrap(); |
303 | pixels.assert_eq_with_message(&expected, |f| { |
304 | write!( |
305 | f, |
306 | "diameter = {}, stroke_width = {}" , |
307 | diameter, stroke_width |
308 | ) |
309 | }); |
310 | } |
311 | } |
312 | |
313 | #[test ] |
314 | fn filled_styled_points_matches_points() { |
315 | let circle = Circle::with_center(Point::new(10, 10), 5); |
316 | |
317 | let styled_points = circle |
318 | .clone() |
319 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) |
320 | .pixels() |
321 | .map(|Pixel(p, _)| p); |
322 | |
323 | assert!(circle.points().eq(styled_points)); |
324 | } |
325 | |
326 | // Check that tiny circles render as a "+" shape with a hole in the center |
327 | #[test ] |
328 | fn tiny_circle() { |
329 | let mut display = MockDisplay::new(); |
330 | |
331 | Circle::new(Point::new(0, 0), 3) |
332 | .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) |
333 | .draw(&mut display) |
334 | .unwrap(); |
335 | |
336 | display.assert_pattern(&[ |
337 | " # " , // |
338 | "# #" , // |
339 | " # " , // |
340 | ]); |
341 | } |
342 | |
343 | // Check that tiny filled circle render as a "+" shape with NO hole in the center |
344 | #[test ] |
345 | fn tiny_circle_filled() { |
346 | let mut display = MockDisplay::new(); |
347 | |
348 | Circle::new(Point::new(0, 0), 3) |
349 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) |
350 | .draw(&mut display) |
351 | .unwrap(); |
352 | |
353 | display.assert_pattern(&[ |
354 | " # " , // |
355 | "###" , // |
356 | " # " , // |
357 | ]); |
358 | } |
359 | |
360 | #[test ] |
361 | fn transparent_border() { |
362 | let circle: Styled<Circle, PrimitiveStyle<BinaryColor>> = |
363 | Circle::new(Point::new(-5, -5), 21) |
364 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); |
365 | |
366 | assert!(circle.pixels().count() > 0); |
367 | } |
368 | |
369 | #[test ] |
370 | fn it_handles_negative_coordinates() { |
371 | let positive = Circle::new(Point::new(10, 10), 5) |
372 | .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) |
373 | .pixels(); |
374 | |
375 | let negative = Circle::new(Point::new(-10, -10), 5) |
376 | .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) |
377 | .pixels(); |
378 | |
379 | assert!(negative.eq(positive.map(|Pixel(p, c)| Pixel(p - Point::new(20, 20), c)))); |
380 | } |
381 | |
382 | #[test ] |
383 | fn stroke_alignment() { |
384 | const CENTER: Point = Point::new(15, 15); |
385 | const SIZE: u32 = 10; |
386 | |
387 | let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3); |
388 | |
389 | let mut display_center = MockDisplay::new(); |
390 | Circle::with_center(CENTER, SIZE) |
391 | .into_styled(style) |
392 | .draw(&mut display_center) |
393 | .unwrap(); |
394 | |
395 | let mut display_inside = MockDisplay::new(); |
396 | Circle::with_center(CENTER, SIZE + 2) |
397 | .into_styled( |
398 | PrimitiveStyleBuilder::from(&style) |
399 | .stroke_alignment(StrokeAlignment::Inside) |
400 | .build(), |
401 | ) |
402 | .draw(&mut display_inside) |
403 | .unwrap(); |
404 | |
405 | let mut display_outside = MockDisplay::new(); |
406 | Circle::with_center(CENTER, SIZE - 4) |
407 | .into_styled( |
408 | PrimitiveStyleBuilder::from(&style) |
409 | .stroke_alignment(StrokeAlignment::Outside) |
410 | .build(), |
411 | ) |
412 | .draw(&mut display_outside) |
413 | .unwrap(); |
414 | |
415 | display_inside.assert_eq(&display_center); |
416 | display_outside.assert_eq(&display_center); |
417 | } |
418 | |
419 | /// Test for issue #143 |
420 | #[test ] |
421 | fn issue_143_stroke_and_fill() { |
422 | for size in 0..10 { |
423 | let circle_no_stroke: Styled<Circle, PrimitiveStyle<BinaryColor>> = |
424 | Circle::new(Point::new(10, 16), size) |
425 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); |
426 | |
427 | let style = PrimitiveStyleBuilder::new() |
428 | .fill_color(BinaryColor::On) |
429 | .stroke_color(BinaryColor::On) |
430 | .stroke_width(1) |
431 | .build(); |
432 | let circle_stroke: Styled<Circle, PrimitiveStyle<BinaryColor>> = |
433 | Circle::new(Point::new(10, 16), size).into_styled(style); |
434 | |
435 | assert_eq!( |
436 | circle_stroke.bounding_box(), |
437 | circle_no_stroke.bounding_box(), |
438 | "Filled and unfilled circle bounding boxes are unequal for radius {}" , |
439 | size |
440 | ); |
441 | assert!( |
442 | circle_no_stroke.pixels().eq(circle_stroke.pixels()), |
443 | "Filled and unfilled circle iters are unequal for radius {}" , |
444 | size |
445 | ); |
446 | } |
447 | } |
448 | |
449 | #[test ] |
450 | fn bounding_boxes() { |
451 | const CENTER: Point = Point::new(15, 15); |
452 | const SIZE: u32 = 10; |
453 | |
454 | let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3); |
455 | |
456 | let center = Circle::with_center(CENTER, SIZE).into_styled(style); |
457 | |
458 | let inside = Circle::with_center(CENTER, SIZE).into_styled( |
459 | PrimitiveStyleBuilder::from(&style) |
460 | .stroke_alignment(StrokeAlignment::Inside) |
461 | .build(), |
462 | ); |
463 | |
464 | let outside = Circle::with_center(CENTER, SIZE).into_styled( |
465 | PrimitiveStyleBuilder::from(&style) |
466 | .stroke_alignment(StrokeAlignment::Outside) |
467 | .build(), |
468 | ); |
469 | |
470 | let mut display = MockDisplay::new(); |
471 | center.draw(&mut display).unwrap(); |
472 | assert_eq!(display.affected_area(), center.bounding_box()); |
473 | |
474 | let mut display = MockDisplay::new(); |
475 | outside.draw(&mut display).unwrap(); |
476 | assert_eq!(display.affected_area(), outside.bounding_box()); |
477 | |
478 | let mut display = MockDisplay::new(); |
479 | inside.draw(&mut display).unwrap(); |
480 | assert_eq!(display.affected_area(), inside.bounding_box()); |
481 | } |
482 | |
483 | #[test ] |
484 | fn bounding_box_is_independent_of_colors() { |
485 | let transparent_circle = |
486 | Circle::new(Point::new(5, 5), 11).into_styled(PrimitiveStyle::<BinaryColor>::new()); |
487 | let filled_circle = Circle::new(Point::new(5, 5), 11) |
488 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); |
489 | |
490 | assert_eq!( |
491 | transparent_circle.bounding_box(), |
492 | filled_circle.bounding_box(), |
493 | ); |
494 | } |
495 | } |
496 | |