1 | use crate::{ |
2 | draw_target::DrawTarget, |
3 | geometry::{Dimensions, Point}, |
4 | pixelcolor::PixelColor, |
5 | primitives::{ |
6 | common::{Scanline, StyledScanline}, |
7 | ellipse::{points::Scanlines, Ellipse, EllipseContains}, |
8 | styled::{StyledDimensions, StyledDrawable, StyledPixels}, |
9 | PrimitiveStyle, Rectangle, |
10 | }, |
11 | Pixel, |
12 | }; |
13 | use az::SaturatingAs; |
14 | |
15 | /// Pixel iterator for each pixel in the ellipse border |
16 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
17 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
18 | pub struct StyledPixelsIterator<C> { |
19 | styled_scanlines: StyledScanlines, |
20 | |
21 | stroke_left: Scanline, |
22 | fill: Scanline, |
23 | stroke_right: Scanline, |
24 | |
25 | stroke_color: Option<C>, |
26 | fill_color: Option<C>, |
27 | } |
28 | |
29 | impl<C: PixelColor> StyledPixelsIterator<C> { |
30 | pub(in crate::primitives) fn new(primitive: &Ellipse, style: &PrimitiveStyle<C>) -> Self { |
31 | let stroke_area: Ellipse = style.stroke_area(primitive); |
32 | let fill_area: Ellipse = style.fill_area(primitive); |
33 | |
34 | Self { |
35 | styled_scanlines: StyledScanlines::new(&stroke_area, &fill_area), |
36 | stroke_left: Scanline::new_empty(0), |
37 | fill: Scanline::new_empty(0), |
38 | stroke_right: Scanline::new_empty(0), |
39 | stroke_color: style.stroke_color, |
40 | fill_color: style.fill_color, |
41 | } |
42 | } |
43 | } |
44 | |
45 | impl<C: PixelColor> Iterator for StyledPixelsIterator<C> { |
46 | type Item = Pixel<C>; |
47 | |
48 | fn next(&mut self) -> Option<Self::Item> { |
49 | match (self.stroke_color, self.fill_color) { |
50 | (Some(stroke_color), None) => loop { |
51 | if let Some(pixel) = self |
52 | .stroke_left |
53 | .next() |
54 | .or_else(|| self.stroke_right.next()) |
55 | .map(|p| Pixel(p, stroke_color)) |
56 | { |
57 | return Some(pixel); |
58 | } |
59 | |
60 | let scanline = self.styled_scanlines.next()?; |
61 | self.stroke_left = scanline.stroke_left(); |
62 | self.stroke_right = scanline.stroke_right(); |
63 | }, |
64 | (Some(stroke_color), Some(fill_color)) => loop { |
65 | if let Some(pixel) = self |
66 | .stroke_left |
67 | .next() |
68 | .map(|p| Pixel(p, stroke_color)) |
69 | .or_else(|| self.fill.next().map(|p| Pixel(p, fill_color))) |
70 | .or_else(|| self.stroke_right.next().map(|p| Pixel(p, stroke_color))) |
71 | { |
72 | return Some(pixel); |
73 | } |
74 | |
75 | let scanline = self.styled_scanlines.next()?; |
76 | self.stroke_left = scanline.stroke_left(); |
77 | self.fill = scanline.fill(); |
78 | self.stroke_right = scanline.stroke_right(); |
79 | }, |
80 | (None, Some(fill_color)) => loop { |
81 | if let Some(pixel) = self.fill.next().map(|p| Pixel(p, fill_color)) { |
82 | return Some(pixel); |
83 | } |
84 | |
85 | let scanline = self.styled_scanlines.next()?; |
86 | self.fill = scanline.fill(); |
87 | }, |
88 | (None, None) => None, |
89 | } |
90 | } |
91 | } |
92 | |
93 | impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Ellipse { |
94 | type Iter = StyledPixelsIterator<C>; |
95 | |
96 | fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter { |
97 | StyledPixelsIterator::new(self, style) |
98 | } |
99 | } |
100 | |
101 | impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Ellipse { |
102 | type Color = C; |
103 | type Output = (); |
104 | |
105 | fn draw_styled<D>( |
106 | &self, |
107 | style: &PrimitiveStyle<C>, |
108 | target: &mut D, |
109 | ) -> Result<Self::Output, D::Error> |
110 | where |
111 | D: DrawTarget<Color = C>, |
112 | { |
113 | match (style.effective_stroke_color(), style.fill_color) { |
114 | (Some(stroke_color), None) => { |
115 | for scanline in |
116 | StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self)) |
117 | { |
118 | scanline.draw_stroke(target, stroke_color)?; |
119 | } |
120 | } |
121 | (Some(stroke_color), Some(fill_color)) => { |
122 | for scanline in |
123 | StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self)) |
124 | { |
125 | scanline.draw_stroke_and_fill(target, stroke_color, fill_color)?; |
126 | } |
127 | } |
128 | (None, Some(fill_color)) => { |
129 | for scanline in Scanlines::new(&style.fill_area(self)) { |
130 | scanline.draw(target, fill_color)?; |
131 | } |
132 | } |
133 | (None, None) => {} |
134 | } |
135 | |
136 | Ok(()) |
137 | } |
138 | } |
139 | |
140 | impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Ellipse { |
141 | fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle { |
142 | let offset: i32 = style.outside_stroke_width().saturating_as(); |
143 | |
144 | self.bounding_box().offset(offset) |
145 | } |
146 | } |
147 | |
148 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
149 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
150 | struct StyledScanlines { |
151 | scanlines: Scanlines, |
152 | fill_area: EllipseContains, |
153 | } |
154 | |
155 | impl StyledScanlines { |
156 | pub fn new(stroke_area: &Ellipse, fill_area: &Ellipse) -> Self { |
157 | Self { |
158 | scanlines: Scanlines::new(ellipse:stroke_area), |
159 | fill_area: EllipseContains::new(fill_area.size), |
160 | } |
161 | } |
162 | } |
163 | |
164 | impl Iterator for StyledScanlines { |
165 | type Item = StyledScanline; |
166 | |
167 | fn next(&mut self) -> Option<Self::Item> { |
168 | self.scanlines.next().map(|scanline: Scanline| { |
169 | let scaled_y: i32 = scanline.y * 2 - self.scanlines.center_2x.y; |
170 | |
171 | let fill_range: Option> = scanlineOption |
172 | .x |
173 | .clone() |
174 | .find(|x: &i32| { |
175 | self.fill_area |
176 | .contains(Point::new(*x * 2 - self.scanlines.center_2x.x, scaled_y)) |
177 | }) |
178 | .map(|x: i32| x..scanline.x.end - (x - scanline.x.start)); |
179 | |
180 | StyledScanline::new(scanline.y, stroke_range:scanline.x, fill_range) |
181 | }) |
182 | } |
183 | } |
184 | |
185 | #[cfg (test)] |
186 | mod tests { |
187 | use super::*; |
188 | use crate::{ |
189 | geometry::{Point, Size}, |
190 | iterator::PixelIteratorExt, |
191 | mock_display::MockDisplay, |
192 | pixelcolor::BinaryColor, |
193 | primitives::{Circle, Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment}, |
194 | Drawable, |
195 | }; |
196 | |
197 | fn test_circles(style: PrimitiveStyle<BinaryColor>) { |
198 | for diameter in 0..50 { |
199 | let top_left = Point::new_equal(style.stroke_width.saturating_as()); |
200 | |
201 | let mut expected = MockDisplay::new(); |
202 | Circle::new(top_left, diameter) |
203 | .into_styled(style) |
204 | .draw(&mut expected) |
205 | .unwrap(); |
206 | |
207 | let mut display = MockDisplay::new(); |
208 | Ellipse::new(top_left, Size::new(diameter, diameter)) |
209 | .into_styled(style) |
210 | .draw(&mut display) |
211 | .unwrap(); |
212 | |
213 | display.assert_eq_with_message(&expected, |f| write!(f, "diameter = {}" , diameter)); |
214 | } |
215 | } |
216 | |
217 | fn test_ellipse(size: Size, style: PrimitiveStyle<BinaryColor>, pattern: &[&str]) { |
218 | let ellipse = Ellipse::new(Point::new(0, 0), size).into_styled(style); |
219 | |
220 | let mut drawable = MockDisplay::new(); |
221 | ellipse.draw(&mut drawable).unwrap(); |
222 | drawable.assert_pattern(pattern); |
223 | |
224 | let mut pixels = MockDisplay::new(); |
225 | ellipse.pixels().draw(&mut pixels).unwrap(); |
226 | pixels.assert_pattern(pattern); |
227 | } |
228 | |
229 | #[test ] |
230 | fn ellipse_equals_circle_fill() { |
231 | test_circles(PrimitiveStyle::with_fill(BinaryColor::On)); |
232 | } |
233 | |
234 | #[test ] |
235 | fn ellipse_equals_circle_stroke_1px() { |
236 | test_circles(PrimitiveStyle::with_stroke(BinaryColor::On, 1)); |
237 | } |
238 | |
239 | #[test ] |
240 | fn ellipse_equals_circle_stroke_10px() { |
241 | test_circles(PrimitiveStyle::with_stroke(BinaryColor::On, 10)); |
242 | } |
243 | |
244 | #[test ] |
245 | fn filled_ellipse() { |
246 | #[rustfmt::skip] |
247 | test_ellipse(Size::new(20, 10), PrimitiveStyle::with_fill(BinaryColor::On), &[ |
248 | " ######## " , |
249 | " ############## " , |
250 | " ################## " , |
251 | "####################" , |
252 | "####################" , |
253 | "####################" , |
254 | "####################" , |
255 | " ################## " , |
256 | " ############## " , |
257 | " ######## " , |
258 | ],); |
259 | } |
260 | |
261 | #[test ] |
262 | fn thick_stroke_glitch() { |
263 | test_ellipse( |
264 | Size::new(11, 21), |
265 | PrimitiveStyleBuilder::new() |
266 | .stroke_width(10) |
267 | .stroke_color(BinaryColor::On) |
268 | .stroke_alignment(StrokeAlignment::Inside) |
269 | .fill_color(BinaryColor::Off) |
270 | .build(), |
271 | &[ |
272 | " ### " , |
273 | " ##### " , |
274 | " ####### " , |
275 | " ######### " , |
276 | " ######### " , |
277 | " ######### " , |
278 | "###########" , |
279 | "###########" , |
280 | "###########" , |
281 | "###########" , |
282 | "###########" , |
283 | "###########" , |
284 | "###########" , |
285 | "###########" , |
286 | "###########" , |
287 | " ######### " , |
288 | " ######### " , |
289 | " ######### " , |
290 | " ####### " , |
291 | " ##### " , |
292 | " ### " , |
293 | ], |
294 | ); |
295 | } |
296 | |
297 | #[test ] |
298 | fn thin_stroked_ellipse() { |
299 | #[rustfmt::skip] |
300 | test_ellipse(Size::new(20, 10), PrimitiveStyle::with_stroke(BinaryColor::On, 1), &[ |
301 | " ######## " , |
302 | " ### ### " , |
303 | " ## ## " , |
304 | "## ##" , |
305 | "# #" , |
306 | "# #" , |
307 | "## ##" , |
308 | " ## ## " , |
309 | " ### ### " , |
310 | " ######## " , |
311 | ],); |
312 | } |
313 | |
314 | #[test ] |
315 | fn fill_and_stroke() { |
316 | test_ellipse( |
317 | Size::new(20, 10), |
318 | PrimitiveStyleBuilder::new() |
319 | .stroke_width(3) |
320 | .stroke_color(BinaryColor::Off) |
321 | .stroke_alignment(StrokeAlignment::Inside) |
322 | .fill_color(BinaryColor::On) |
323 | .build(), |
324 | &[ |
325 | " ........ " , |
326 | " .............. " , |
327 | " .................. " , |
328 | ".....##########....." , |
329 | "...##############..." , |
330 | "...##############..." , |
331 | ".....##########....." , |
332 | " .................. " , |
333 | " .............. " , |
334 | " ........ " , |
335 | ], |
336 | ); |
337 | } |
338 | |
339 | #[test ] |
340 | fn stroke_alignment() { |
341 | const CENTER: Point = Point::new(15, 15); |
342 | const SIZE: Size = Size::new(10, 5); |
343 | |
344 | let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3); |
345 | |
346 | let mut display_center = MockDisplay::new(); |
347 | Ellipse::with_center(CENTER, SIZE) |
348 | .into_styled(style) |
349 | .draw(&mut display_center) |
350 | .unwrap(); |
351 | |
352 | let mut display_inside = MockDisplay::new(); |
353 | Ellipse::with_center(CENTER, SIZE + Size::new(2, 2)) |
354 | .into_styled( |
355 | PrimitiveStyleBuilder::from(&style) |
356 | .stroke_alignment(StrokeAlignment::Inside) |
357 | .build(), |
358 | ) |
359 | .draw(&mut display_inside) |
360 | .unwrap(); |
361 | |
362 | let mut display_outside = MockDisplay::new(); |
363 | Ellipse::with_center(CENTER, SIZE - Size::new(4, 4)) |
364 | .into_styled( |
365 | PrimitiveStyleBuilder::from(&style) |
366 | .stroke_alignment(StrokeAlignment::Outside) |
367 | .build(), |
368 | ) |
369 | .draw(&mut display_outside) |
370 | .unwrap(); |
371 | |
372 | display_inside.assert_eq(&display_center); |
373 | display_outside.assert_eq(&display_center); |
374 | } |
375 | |
376 | #[test ] |
377 | fn bounding_boxes() { |
378 | const CENTER: Point = Point::new(15, 15); |
379 | const SIZE: Size = Size::new(15, 25); |
380 | |
381 | let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3); |
382 | |
383 | let center = Ellipse::with_center(CENTER, SIZE).into_styled(style); |
384 | let inside = Ellipse::with_center(CENTER, SIZE).into_styled( |
385 | PrimitiveStyleBuilder::from(&style) |
386 | .stroke_alignment(StrokeAlignment::Inside) |
387 | .build(), |
388 | ); |
389 | let outside = Ellipse::with_center(CENTER, SIZE).into_styled( |
390 | PrimitiveStyleBuilder::from(&style) |
391 | .stroke_alignment(StrokeAlignment::Outside) |
392 | .build(), |
393 | ); |
394 | |
395 | let mut display = MockDisplay::new(); |
396 | center.draw(&mut display).unwrap(); |
397 | assert_eq!(display.affected_area(), center.bounding_box()); |
398 | |
399 | let mut display = MockDisplay::new(); |
400 | outside.draw(&mut display).unwrap(); |
401 | assert_eq!(display.affected_area(), outside.bounding_box()); |
402 | |
403 | let mut display = MockDisplay::new(); |
404 | inside.draw(&mut display).unwrap(); |
405 | assert_eq!(display.affected_area(), inside.bounding_box()); |
406 | } |
407 | |
408 | #[test ] |
409 | fn bounding_box_is_independent_of_colors() { |
410 | let transparent_ellipse = Ellipse::new(Point::new(5, 5), Size::new(11, 14)) |
411 | .into_styled(PrimitiveStyle::<BinaryColor>::new()); |
412 | let filled_ellipse = Ellipse::new(Point::new(5, 5), Size::new(11, 14)) |
413 | .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)); |
414 | |
415 | assert_eq!( |
416 | transparent_ellipse.bounding_box(), |
417 | filled_ellipse.bounding_box(), |
418 | ); |
419 | } |
420 | } |
421 | |