1use crate::{
2 draw_target::DrawTarget,
3 geometry::{Dimensions, Point, Size},
4 pixelcolor::PixelColor,
5 primitives::{
6 rectangle::{Points, Rectangle},
7 styled::{StyledDimensions, StyledDrawable, StyledPixels},
8 PointsIter, PrimitiveStyle,
9 },
10 transform::Transform,
11 Pixel,
12};
13use az::SaturatingAs;
14
15#[derive(Clone, Eq, PartialEq, Hash, Debug)]
17#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
18pub struct StyledPixelsIterator<C> {
19 iter: Points,
20
21 stroke_color: Option<C>,
22
23 fill_area: Rectangle,
24 fill_color: Option<C>,
25}
26
27impl<C: PixelColor> StyledPixelsIterator<C> {
28 pub(in crate::primitives) fn new(primitive: &Rectangle, style: &PrimitiveStyle<C>) -> Self {
29 let iter = if !style.is_transparent() {
30 style.stroke_area(primitive).points()
31 } else {
32 Points::empty()
33 };
34
35 Self {
36 iter,
37 fill_area: style.fill_area(primitive),
38 stroke_color: style.stroke_color,
39 fill_color: style.fill_color,
40 }
41 }
42}
43
44impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
45 type Item = Pixel<C>;
46
47 fn next(&mut self) -> Option<Self::Item> {
48 for point in &mut self.iter {
49 let color = if self.fill_area.contains(point) {
50 self.fill_color
51 } else {
52 self.stroke_color
53 };
54
55 if let Some(color) = color {
56 return Some(Pixel(point, color));
57 }
58 }
59
60 None
61 }
62}
63
64impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for Rectangle {
65 type Iter = StyledPixelsIterator<C>;
66
67 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
68 Self::Iter::new(self, style)
69 }
70}
71
72impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for Rectangle {
73 type Color = C;
74 type Output = ();
75
76 fn draw_styled<D>(
77 &self,
78 style: &PrimitiveStyle<C>,
79 target: &mut D,
80 ) -> Result<Self::Output, D::Error>
81 where
82 D: DrawTarget<Color = C>,
83 {
84 let fill_area = style.fill_area(self);
85
86 if let Some(fill_color) = style.fill_color {
88 target.fill_solid(&fill_area, fill_color)?;
89 }
90
91 if let Some(stroke_color) = style.effective_stroke_color() {
93 let stroke_width = style.stroke_width;
94
95 let stroke_area = style.stroke_area(self);
96
97 let top_border = Rectangle::new(
98 stroke_area.top_left,
99 Size::new(
100 stroke_area.size.width,
101 stroke_width.min(stroke_area.size.height / 2),
102 ),
103 );
104
105 let bottom_stroke_width =
106 stroke_width.min(stroke_area.size.height - top_border.size.height);
107
108 let bottom_border = Rectangle::new(
109 top_border.top_left
110 + Size::new(
111 0,
112 stroke_area.size.height.saturating_sub(bottom_stroke_width),
113 ),
114 Size::new(stroke_area.size.width, bottom_stroke_width),
115 );
116
117 target.fill_solid(&top_border, stroke_color)?;
118 target.fill_solid(&bottom_border, stroke_color)?;
119
120 if fill_area.size.height > 0 {
121 let left_border = Rectangle::new(
122 stroke_area.top_left + top_border.size.y_axis(),
123 Size::new(
124 (stroke_width * 2).min(stroke_area.size.width + 1) / 2,
125 fill_area.size.height,
126 ),
127 );
128
129 let right_border = left_border.translate(Point::new(
130 stroke_area
131 .size
132 .width
133 .saturating_sub(left_border.size.width) as i32,
134 0,
135 ));
136
137 target.fill_solid(&left_border, stroke_color)?;
138 target.fill_solid(&right_border, stroke_color)?;
139 }
140 }
141
142 Ok(())
143 }
144}
145
146impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for Rectangle {
147 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
148 let offset = style.outside_stroke_width().saturating_as();
149
150 self.bounding_box().offset(offset)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::{
158 geometry::{Point, Size},
159 iterator::PixelIteratorExt,
160 mock_display::MockDisplay,
161 pixelcolor::{BinaryColor, Rgb565, RgbColor},
162 primitives::{Primitive, PrimitiveStyle, PrimitiveStyleBuilder, StrokeAlignment},
163 Drawable,
164 };
165
166 #[test]
167 fn it_draws_unfilled_rect() {
168 let mut rect = Rectangle::new(Point::new(2, 2), Size::new(3, 3))
169 .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1))
170 .pixels();
171
172 assert_eq!(rect.next(), Some(Pixel(Point::new(2, 2), Rgb565::RED)));
173 assert_eq!(rect.next(), Some(Pixel(Point::new(3, 2), Rgb565::RED)));
174 assert_eq!(rect.next(), Some(Pixel(Point::new(4, 2), Rgb565::RED)));
175
176 assert_eq!(rect.next(), Some(Pixel(Point::new(2, 3), Rgb565::RED)));
177 assert_eq!(rect.next(), Some(Pixel(Point::new(4, 3), Rgb565::RED)));
178
179 assert_eq!(rect.next(), Some(Pixel(Point::new(2, 4), Rgb565::RED)));
180 assert_eq!(rect.next(), Some(Pixel(Point::new(3, 4), Rgb565::RED)));
181 assert_eq!(rect.next(), Some(Pixel(Point::new(4, 4), Rgb565::RED)));
182 }
183
184 #[test]
185 fn points_iter_matches_filled_styled() {
186 let rectangle = Rectangle::new(Point::new(10, 10), Size::new(20, 30));
187
188 let styled_points = rectangle
189 .clone()
190 .into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE))
191 .pixels()
192 .map(|Pixel(p, _)| p);
193
194 assert!(rectangle.points().eq(styled_points));
195 }
196
197 #[test]
198 fn stroke_alignment() {
199 const TOP_LEFT: Point = Point::new(5, 6);
200 const SIZE: Size = Size::new(10, 5);
201
202 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
203
204 let mut display_center = MockDisplay::new();
205 Rectangle::new(TOP_LEFT, SIZE)
206 .into_styled(style)
207 .draw(&mut display_center)
208 .unwrap();
209
210 let mut display_inside = MockDisplay::new();
211 Rectangle::new(TOP_LEFT - Point::new(1, 1), SIZE + Size::new(2, 2))
212 .into_styled(
213 PrimitiveStyleBuilder::from(&style)
214 .stroke_alignment(StrokeAlignment::Inside)
215 .build(),
216 )
217 .draw(&mut display_inside)
218 .unwrap();
219
220 let mut display_outside = MockDisplay::new();
221 Rectangle::new(TOP_LEFT + Point::new(2, 2), SIZE - Size::new(4, 4))
222 .into_styled(
223 PrimitiveStyleBuilder::from(&style)
224 .stroke_alignment(StrokeAlignment::Outside)
225 .build(),
226 )
227 .draw(&mut display_outside)
228 .unwrap();
229
230 display_inside.assert_eq(&display_center);
231 display_outside.assert_eq(&display_center);
232 }
233
234 #[test]
235 fn stroke_iter_vs_draw() {
236 const TOP_LEFT: Point = Point::new(5, 6);
237 const SIZE: Size = Size::new(10, 5);
238
239 let style = PrimitiveStyle::with_stroke(BinaryColor::On, 3);
240
241 let rectangle_center = Rectangle::new(TOP_LEFT, SIZE).into_styled(style);
242
243 let mut drawn_center = MockDisplay::new();
244 let mut iter_center = MockDisplay::new();
245 rectangle_center.draw(&mut drawn_center).unwrap();
246 rectangle_center.pixels().draw(&mut iter_center).unwrap();
247 drawn_center.assert_eq(&iter_center);
248
249 let rectangle_inside = Rectangle::new(TOP_LEFT - Point::new(1, 1), SIZE + Size::new(2, 2))
250 .into_styled(
251 PrimitiveStyleBuilder::from(&style)
252 .stroke_alignment(StrokeAlignment::Inside)
253 .build(),
254 );
255
256 let mut drawn_inside = MockDisplay::new();
257 let mut iter_inside = MockDisplay::new();
258 rectangle_inside.draw(&mut drawn_inside).unwrap();
259 rectangle_inside.pixels().draw(&mut iter_inside).unwrap();
260 drawn_inside.assert_eq(&iter_inside);
261
262 let rectangle_outside = Rectangle::new(TOP_LEFT + Point::new(2, 2), SIZE - Size::new(4, 4))
263 .into_styled(
264 PrimitiveStyleBuilder::from(&style)
265 .stroke_alignment(StrokeAlignment::Outside)
266 .build(),
267 );
268
269 let mut drawn_outside = MockDisplay::new();
270 let mut iter_outside = MockDisplay::new();
271 rectangle_outside.draw(&mut drawn_outside).unwrap();
272 rectangle_outside.pixels().draw(&mut iter_outside).unwrap();
273 drawn_outside.assert_eq(&iter_outside);
274 }
275
276 #[test]
277 fn fill_iter_vs_draw() {
278 const TOP_LEFT: Point = Point::new(5, 6);
279 const SIZE: Size = Size::new(10, 5);
280
281 let style = PrimitiveStyle::with_fill(BinaryColor::On);
282
283 let rectangle = Rectangle::new(TOP_LEFT, SIZE).into_styled(style);
284
285 let mut drawn = MockDisplay::new();
286 let mut iter = MockDisplay::new();
287 rectangle.draw(&mut drawn).unwrap();
288 rectangle.pixels().draw(&mut iter).unwrap();
289 drawn.assert_eq(&iter);
290 }
291
292 fn compare_drawable_iter(rect: Rectangle) {
295 let thin_stroke = PrimitiveStyle::with_stroke(Rgb565::RED, 1);
296 let stroke = PrimitiveStyle::with_stroke(Rgb565::RED, 5);
297 let stroke_fill = PrimitiveStyleBuilder::new()
298 .stroke_color(Rgb565::RED)
299 .stroke_width(5)
300 .fill_color(Rgb565::GREEN)
301 .build();
302 let fill = PrimitiveStyle::with_fill(Rgb565::BLUE);
303
304 for (name, style) in [
305 ("thin_stroke", thin_stroke),
306 ("stroke", stroke),
307 ("stroke_fill", stroke_fill),
308 ("fill", fill),
309 ]
310 .iter()
311 {
312 for alignment in [
313 StrokeAlignment::Center,
314 StrokeAlignment::Inside,
315 StrokeAlignment::Outside,
316 ]
317 .iter()
318 {
319 let style = PrimitiveStyleBuilder::from(style)
320 .stroke_alignment(*alignment)
321 .build();
322
323 let mut display_drawable = MockDisplay::new();
324 let mut display_iter = MockDisplay::new();
325
326 rect.into_styled(style).draw(&mut display_drawable).unwrap();
328
329 rect.into_styled(style)
331 .pixels()
332 .draw(&mut display_iter)
333 .unwrap();
334
335 display_drawable.assert_eq_with_message(
336 &display_iter,
337 |f| write!(f,
338 "{} x {} rectangle with style '{}' and alignment {:?} does not match iterator",
339 rect.size.width, rect.size.height, name, alignment
340 )
341 );
342 }
343 }
344 }
345
346 #[test]
347 fn drawable_vs_iterator() {
348 compare_drawable_iter(Rectangle::new(Point::new(10, 20), Size::new(20, 30)))
349 }
350
351 #[test]
352 fn drawable_vs_iterator_squares() {
353 for i in 0..20 {
354 compare_drawable_iter(Rectangle::new(Point::new(7, 7), Size::new_equal(i)))
355 }
356 }
357
358 #[test]
359 fn reuse() {
360 let rectangle = Rectangle::new(Point::zero(), Size::new_equal(10));
361
362 let styled = rectangle.into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
363
364 let _pixels = styled.pixels();
365
366 let moved = rectangle.translate(Point::new(1, 2));
367
368 assert_eq!(moved, Rectangle::new(Point::new(1, 2), Size::new_equal(10)));
369 }
370
371 #[test]
372 fn bounding_box() {
373 let rectangle = Rectangle::new(Point::new(10, 10), Size::new(15, 20));
374
375 let base = PrimitiveStyleBuilder::new()
376 .stroke_color(BinaryColor::On)
377 .stroke_width(5);
378
379 let center = rectangle.into_styled(base.stroke_alignment(StrokeAlignment::Center).build());
380 let inside = rectangle.into_styled(base.stroke_alignment(StrokeAlignment::Inside).build());
381 let outside =
382 rectangle.into_styled(base.stroke_alignment(StrokeAlignment::Outside).build());
383
384 let mut display = MockDisplay::new();
385 center.draw(&mut display).unwrap();
386 assert_eq!(display.affected_area(), center.bounding_box());
387 let mut display = MockDisplay::new();
388 inside.draw(&mut display).unwrap();
389 assert_eq!(display.affected_area(), inside.bounding_box());
390 let mut display = MockDisplay::new();
391 outside.draw(&mut display).unwrap();
392 assert_eq!(display.affected_area(), outside.bounding_box());
393 }
394
395 #[test]
396 fn bounding_box_is_independent_of_colors() {
397 let rect = Rectangle::new(Point::new(5, 5), Size::new(11, 14));
398
399 let transparent_rect = rect.into_styled(PrimitiveStyle::<BinaryColor>::new());
400 let filled_rect = rect.into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
401
402 assert_eq!(transparent_rect.bounding_box(), filled_rect.bounding_box(),);
403 }
404}