1 | //! A target for embedded-graphics drawing operations. |
2 | |
3 | mod clipped; |
4 | mod color_converted; |
5 | mod cropped; |
6 | mod translated; |
7 | |
8 | use crate::{geometry::Point, pixelcolor::PixelColor, primitives::Rectangle}; |
9 | |
10 | pub use clipped::Clipped; |
11 | pub use color_converted::ColorConverted; |
12 | pub use cropped::Cropped; |
13 | pub use translated::Translated; |
14 | |
15 | pub use embedded_graphics_core::draw_target::DrawTarget; |
16 | |
17 | /// Extension trait for `DrawTarget`s. |
18 | pub trait DrawTargetExt: DrawTarget + Sized { |
19 | /// Creates a translated draw target based on this draw target. |
20 | /// |
21 | /// All drawing operations are translated by `offset` pixels, before being passed to the parent |
22 | /// draw target. |
23 | /// |
24 | /// # Examples |
25 | /// |
26 | /// ``` |
27 | /// use embedded_graphics::{ |
28 | /// mock_display::MockDisplay, |
29 | /// mono_font::{ascii::FONT_6X9, MonoTextStyle}, |
30 | /// pixelcolor::BinaryColor, |
31 | /// prelude::*, |
32 | /// text::Text, |
33 | /// }; |
34 | /// |
35 | /// let mut display = MockDisplay::new(); |
36 | /// let mut translated_display = display.translated(Point::new(5, 10)); |
37 | /// |
38 | /// let style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On); |
39 | /// |
40 | /// // Draws text at position (5, 10) in the display coordinate system |
41 | /// Text::new("Text" , Point::zero(), style).draw(&mut translated_display)?; |
42 | /// # |
43 | /// # let mut expected = MockDisplay::new(); |
44 | /// # |
45 | /// # Text::new("Text" , Point::new(5, 10), style).draw(&mut expected)?; |
46 | /// # |
47 | /// # display.assert_eq(&expected); |
48 | /// # |
49 | /// # Ok::<(), core::convert::Infallible>(()) |
50 | /// ``` |
51 | fn translated(&mut self, offset: Point) -> Translated<'_, Self>; |
52 | |
53 | /// Creates a cropped draw target based on this draw target. |
54 | /// |
55 | /// A cropped draw target is a draw target for a rectangular subregion of the parent draw target. |
56 | /// Its coordinate system is shifted so that the origin coincides with `area.top_left` in the |
57 | /// parent draw target's coordinate system. |
58 | /// |
59 | /// The bounding box of the returned target will always be contained inside the bounding box |
60 | /// of the parent target. If any of the requested `area` lies outside the parent target's bounding |
61 | /// box the intersection of the parent target's bounding box and `area` will be used. |
62 | /// |
63 | /// Drawing operations outside the bounding box will not be clipped. |
64 | /// |
65 | /// # Examples |
66 | /// |
67 | /// ``` |
68 | /// use embedded_graphics::{ |
69 | /// mock_display::MockDisplay, |
70 | /// mono_font::{ascii::FONT_6X9, MonoTextStyle}, |
71 | /// pixelcolor::Rgb565, |
72 | /// prelude::*, |
73 | /// primitives::Rectangle, |
74 | /// text::{Text, Alignment, Baseline, TextStyleBuilder}, |
75 | /// }; |
76 | /// |
77 | /// /// Fills a draw target with a blue background and prints centered yellow text. |
78 | /// fn draw_text<T>(target: &mut T, text: &str) -> Result<(), T::Error> |
79 | /// where |
80 | /// T: DrawTarget<Color = Rgb565>, |
81 | /// { |
82 | /// target.clear(Rgb565::BLUE)?; |
83 | /// |
84 | /// let text_position = target.bounding_box().center(); |
85 | /// |
86 | /// let character_style = MonoTextStyle::new(&FONT_6X9, Rgb565::YELLOW); |
87 | /// let text_style = TextStyleBuilder::new() |
88 | /// .alignment(Alignment::Center) |
89 | /// .baseline(Baseline::Middle) |
90 | /// .build(); |
91 | /// |
92 | /// Text::with_text_style(text, text_position, character_style, text_style).draw(target)?; |
93 | /// |
94 | /// Ok(()) |
95 | /// } |
96 | /// |
97 | /// let mut display = MockDisplay::new(); |
98 | /// display.set_allow_overdraw(true); |
99 | /// |
100 | /// let area = Rectangle::new(Point::new(5, 10), Size::new(40, 15)); |
101 | /// let mut cropped_display = display.cropped(&area); |
102 | /// |
103 | /// draw_text(&mut cropped_display, "Text" )?; |
104 | /// # |
105 | /// # Ok::<(), core::convert::Infallible>(()) |
106 | /// ``` |
107 | fn cropped(&mut self, area: &Rectangle) -> Cropped<'_, Self>; |
108 | |
109 | /// Creates a clipped draw target based on this draw target. |
110 | /// |
111 | /// A clipped draw target is a draw target for a rectangular subregion of the parent draw target. |
112 | /// The coordinate system of the created draw target is equal to the parent target's coordinate |
113 | /// system. All drawing operations outside the bounding box will be clipped. |
114 | /// |
115 | /// The bounding box of the returned target will always be contained inside the bounding box |
116 | /// of the parent target. If any of the requested `area` lies outside the parent target's bounding |
117 | /// box the intersection of the parent target's bounding box and `area` will be used. |
118 | /// |
119 | /// # Examples |
120 | /// |
121 | /// ``` |
122 | /// use embedded_graphics::{ |
123 | /// mock_display::MockDisplay, |
124 | /// mono_font::{ascii::FONT_10X20, MonoTextStyle}, |
125 | /// pixelcolor::BinaryColor, |
126 | /// prelude::*, |
127 | /// primitives::Rectangle, |
128 | /// text::Text, |
129 | /// }; |
130 | /// |
131 | /// let mut display = MockDisplay::new(); |
132 | /// |
133 | /// let area = Rectangle::new(Point::zero(), Size::new(4 * 10, 20)); |
134 | /// let mut clipped_display = display.clipped(&area); |
135 | /// |
136 | /// let style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On); |
137 | /// |
138 | /// // Only the first 4 characters will be drawn, because the others are outside |
139 | /// // the clipping area |
140 | /// Text::new("Clipped" , Point::new(0, 15), style).draw(&mut clipped_display)?; |
141 | /// # |
142 | /// # let mut expected = MockDisplay::new(); |
143 | /// # |
144 | /// # Text::new("Clip" , Point::new(0, 15), style).draw(&mut expected)?; |
145 | /// # |
146 | /// # display.assert_eq(&expected); |
147 | /// # |
148 | /// # Ok::<(), core::convert::Infallible>(()) |
149 | /// ``` |
150 | fn clipped(&mut self, area: &Rectangle) -> Clipped<'_, Self>; |
151 | |
152 | /// Creates a color conversion draw target. |
153 | /// |
154 | /// A color conversion draw target is used to draw drawables with a different color type to a |
155 | /// draw target. The drawable color type must implement `Into<C>`, where `C` is the draw |
156 | /// target color type. |
157 | /// |
158 | /// # Performance |
159 | /// |
160 | /// Color conversion can be expensive on embedded hardware and should be avoided if possible. |
161 | /// Using the same color type for drawables and the draw target makes sure that no unnecessary |
162 | /// color conversion is used. But in some cases color conversion will be required, for example, |
163 | /// to draw images with a color format only known at runtime. |
164 | /// |
165 | /// # Examples |
166 | /// |
167 | /// This example draws a `BinaryColor` image to an `Rgb888` display. |
168 | /// |
169 | /// ``` |
170 | /// use embedded_graphics::{ |
171 | /// image::{Image, ImageRaw}, |
172 | /// mock_display::MockDisplay, |
173 | /// pixelcolor::{BinaryColor, Rgb888}, |
174 | /// prelude::*, |
175 | /// }; |
176 | /// |
177 | /// /// The image data. |
178 | /// const DATA: &[u8] = &[ |
179 | /// 0b11110000, // |
180 | /// 0b10010000, // |
181 | /// 0b10010000, // |
182 | /// 0b11110000, // |
183 | /// ]; |
184 | /// |
185 | /// // Create a `BinaryColor` image from the image data. |
186 | /// let raw_image = ImageRaw::<BinaryColor>::new(DATA, 4); |
187 | /// let image = Image::new(&raw_image, Point::zero()); |
188 | /// |
189 | /// // Create a `Rgb888` display. |
190 | /// let mut display = MockDisplay::<Rgb888>::new(); |
191 | /// |
192 | /// // The image can't directly be drawn to the draw target because they use different |
193 | /// // color type. This will fail to compile: |
194 | /// // image.draw(&mut display)?; |
195 | /// |
196 | /// // To fix this `color_converted` is added to enable color conversion. |
197 | /// image.draw(&mut display.color_converted())?; |
198 | /// # |
199 | /// # let mut expected = MockDisplay::from_pattern(&[ |
200 | /// # "WWWW" , // |
201 | /// # "WKKW" , // |
202 | /// # "WKKW" , // |
203 | /// # "WWWW" , // |
204 | /// # ]); |
205 | /// # |
206 | /// # display.assert_eq(&expected); |
207 | /// # |
208 | /// # Ok::<(), core::convert::Infallible>(()) |
209 | /// ``` |
210 | fn color_converted<C>(&mut self) -> ColorConverted<'_, Self, C> |
211 | where |
212 | C: PixelColor + Into<Self::Color>; |
213 | } |
214 | |
215 | impl<T> DrawTargetExt for T |
216 | where |
217 | T: DrawTarget, |
218 | { |
219 | fn translated(&mut self, offset: Point) -> Translated<'_, Self> { |
220 | Translated::new(self, offset) |
221 | } |
222 | |
223 | fn cropped(&mut self, area: &Rectangle) -> Cropped<'_, Self> { |
224 | Cropped::new(self, area) |
225 | } |
226 | |
227 | fn clipped(&mut self, area: &Rectangle) -> Clipped<'_, Self> { |
228 | Clipped::new(self, area) |
229 | } |
230 | |
231 | fn color_converted<C>(&mut self) -> ColorConverted<'_, Self, C> |
232 | where |
233 | C: PixelColor + Into<Self::Color>, |
234 | { |
235 | ColorConverted::new(self) |
236 | } |
237 | } |
238 | |
239 | #[cfg (test)] |
240 | mod tests { |
241 | use crate::{ |
242 | draw_target::{DrawTarget, DrawTargetExt}, |
243 | geometry::{Dimensions, Point, Size}, |
244 | mock_display::MockDisplay, |
245 | pixelcolor::BinaryColor, |
246 | primitives::{Primitive, PrimitiveStyle, Rectangle}, |
247 | Drawable, Pixel, |
248 | }; |
249 | |
250 | #[test ] |
251 | fn draw_iter() { |
252 | let mut display = MockDisplay::new(); |
253 | |
254 | let area = Rectangle::new(Point::new(2, 1), Size::new(2, 4)); |
255 | let mut clipped = display.clipped(&area); |
256 | |
257 | let pixels = [ |
258 | Pixel(Point::new(0, 1), BinaryColor::On), |
259 | Pixel(Point::new(1, 1), BinaryColor::On), |
260 | Pixel(Point::new(2, 1), BinaryColor::On), |
261 | Pixel(Point::new(3, 1), BinaryColor::On), |
262 | Pixel(Point::new(4, 1), BinaryColor::On), |
263 | Pixel(Point::new(2, 0), BinaryColor::Off), |
264 | Pixel(Point::new(2, 2), BinaryColor::Off), |
265 | Pixel(Point::new(2, 3), BinaryColor::Off), |
266 | Pixel(Point::new(2, 4), BinaryColor::Off), |
267 | Pixel(Point::new(2, 5), BinaryColor::Off), |
268 | ]; |
269 | clipped.draw_iter(pixels.iter().copied()).unwrap(); |
270 | |
271 | display.assert_pattern(&[ |
272 | " " , // |
273 | " ##" , // |
274 | " . " , // |
275 | " . " , // |
276 | " . " , // |
277 | ]); |
278 | } |
279 | |
280 | #[test ] |
281 | fn fill_contiguous() { |
282 | let mut display = MockDisplay::new(); |
283 | |
284 | let area = Rectangle::new(Point::new(3, 2), Size::new(2, 3)); |
285 | let mut clipped = display.clipped(&area); |
286 | |
287 | let colors = [ |
288 | 1, 1, 1, 1, 1, // |
289 | 0, 0, 0, 0, 1, // |
290 | 0, 1, 0, 1, 1, // |
291 | 1, 0, 1, 0, 1, // |
292 | ]; |
293 | let area = Rectangle::new(Point::new(1, 2), Size::new(5, 4)); |
294 | clipped |
295 | .fill_contiguous(&area, colors.iter().map(|c| BinaryColor::from(*c != 0))) |
296 | .unwrap(); |
297 | |
298 | display.assert_pattern(&[ |
299 | " " , // |
300 | " " , // |
301 | " ##" , // |
302 | " .." , // |
303 | " .#" , // |
304 | ]); |
305 | } |
306 | |
307 | #[test ] |
308 | fn fill_solid() { |
309 | let mut display = MockDisplay::new(); |
310 | |
311 | let area = Rectangle::new(Point::new(3, 2), Size::new(4, 2)); |
312 | let mut clipped = display.clipped(&area); |
313 | |
314 | let area = Rectangle::new(Point::new(2, 1), Size::new(6, 4)); |
315 | clipped.fill_solid(&area, BinaryColor::On).unwrap(); |
316 | |
317 | display.assert_pattern(&[ |
318 | " " , // |
319 | " " , // |
320 | " ####" , // |
321 | " ####" , // |
322 | ]); |
323 | } |
324 | |
325 | #[test ] |
326 | fn clear() { |
327 | let mut display = MockDisplay::new(); |
328 | |
329 | let area = Rectangle::new(Point::new(1, 3), Size::new(3, 4)); |
330 | let mut clipped = display.clipped(&area); |
331 | clipped.clear(BinaryColor::On).unwrap(); |
332 | |
333 | let mut expected = MockDisplay::new(); |
334 | area.into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) |
335 | .draw(&mut expected) |
336 | .unwrap(); |
337 | |
338 | display.assert_eq(&expected); |
339 | } |
340 | |
341 | #[test ] |
342 | fn bounding_box() { |
343 | let mut display: MockDisplay<BinaryColor> = MockDisplay::new(); |
344 | |
345 | let area = Rectangle::new(Point::new(1, 3), Size::new(2, 4)); |
346 | let clipped = display.clipped(&area); |
347 | |
348 | assert_eq!(clipped.bounding_box(), area); |
349 | } |
350 | |
351 | #[test ] |
352 | fn bounding_box_is_clipped() { |
353 | let mut display: MockDisplay<BinaryColor> = MockDisplay::new(); |
354 | let display_bb = display.bounding_box(); |
355 | |
356 | let top_left = Point::new(10, 20); |
357 | let size = Size::new(1000, 1000); |
358 | let area = Rectangle::new(top_left, size); |
359 | let clipped = display.clipped(&area); |
360 | |
361 | let expected_size = display_bb.size - Size::new(top_left.x as u32, top_left.y as u32); |
362 | |
363 | assert_eq!( |
364 | clipped.bounding_box(), |
365 | Rectangle::new(top_left, expected_size), |
366 | ); |
367 | } |
368 | } |
369 | |