1 | //! A target for embedded-graphics drawing operations. |
2 | |
3 | use crate::{ |
4 | geometry::Dimensions, |
5 | pixelcolor::PixelColor, |
6 | primitives::{PointsIter, Rectangle}, |
7 | Pixel, |
8 | }; |
9 | |
10 | /// A target for embedded-graphics drawing operations. |
11 | /// |
12 | /// The `DrawTarget` trait is used to add embedded-graphics support to a display |
13 | /// driver or similar targets like framebuffers or image files. |
14 | /// Targets are required to at least implement the [`draw_iter`] method and the [`Dimensions`] |
15 | /// trait. All other methods provide default implementations which use these methods internally. |
16 | /// |
17 | /// Because the default implementations cannot use features specific to the target hardware they |
18 | /// can be overridden to improve performance. These target specific implementations might, for |
19 | /// example, use hardware accelerated drawing operations provided by a display controller or |
20 | /// specialized hardware modules in a microcontroller. |
21 | /// |
22 | /// Note that some displays require a "flush" operation to write changes from a framebuffer to the |
23 | /// display. See docs associated with the chosen display driver for details on how to update the |
24 | /// display. |
25 | /// |
26 | /// # Examples |
27 | /// |
28 | /// ## Minimum implementation |
29 | /// |
30 | /// In this example `DrawTarget` is implemented for an an imaginary 64px x 64px 8-bit grayscale display |
31 | /// that is connected using a simplified SPI interface. Because the hardware doesn't support any |
32 | /// acceleration only the [`draw_iter`] method and [`OriginDimensions`] trait need to be implemented. |
33 | /// |
34 | /// To reduce the overhead caused by communicating with the display for each drawing operation |
35 | /// the display driver uses and framebuffer to store the pixel data in memory. This way all drawing |
36 | /// operations can be executed in local memory and the actual display is only updated on demand |
37 | /// by calling the `flush` method. |
38 | /// |
39 | /// Because all drawing operations are using a local framebuffer no communication error can occur |
40 | /// while they are executed and the [`Error` type] can be set to `core::convert::Infallible`. |
41 | /// |
42 | /// ```rust |
43 | /// use core::convert::TryInto; |
44 | /// use embedded_graphics::{ |
45 | /// pixelcolor::{Gray8, GrayColor}, |
46 | /// prelude::*, |
47 | /// primitives::{Circle, PrimitiveStyle}, |
48 | /// }; |
49 | /// # |
50 | /// # struct SPI1; |
51 | /// # |
52 | /// # impl SPI1 { |
53 | /// # pub fn send_bytes(&self, buf: &[u8]) -> Result<(), CommError> { |
54 | /// # Ok(()) |
55 | /// # } |
56 | /// # } |
57 | /// # |
58 | /// |
59 | /// /// SPI communication error |
60 | /// #[derive(Debug)] |
61 | /// struct CommError; |
62 | /// |
63 | /// /// A fake 64px x 64px display. |
64 | /// struct ExampleDisplay { |
65 | /// /// The framebuffer with one `u8` value per pixel. |
66 | /// framebuffer: [u8; 64 * 64], |
67 | /// |
68 | /// /// The interface to the display controller. |
69 | /// iface: SPI1, |
70 | /// } |
71 | /// |
72 | /// impl ExampleDisplay { |
73 | /// /// Updates the display from the framebuffer. |
74 | /// pub fn flush(&self) -> Result<(), CommError> { |
75 | /// self.iface.send_bytes(&self.framebuffer) |
76 | /// } |
77 | /// } |
78 | /// |
79 | /// impl DrawTarget for ExampleDisplay { |
80 | /// type Color = Gray8; |
81 | /// // `ExampleDisplay` uses a framebuffer and doesn't need to communicate with the display |
82 | /// // controller to draw pixel, which means that drawing operations can never fail. To reflect |
83 | /// // this the type `Infallible` was chosen as the `Error` type. |
84 | /// type Error = core::convert::Infallible; |
85 | /// |
86 | /// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> |
87 | /// where |
88 | /// I: IntoIterator<Item = Pixel<Self::Color>>, |
89 | /// { |
90 | /// for Pixel(coord, color) in pixels.into_iter() { |
91 | /// // Check if the pixel coordinates are out of bounds (negative or greater than |
92 | /// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds |
93 | /// // pixels without returning an error or causing a panic. |
94 | /// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() { |
95 | /// // Calculate the index in the framebuffer. |
96 | /// let index: u32 = x + y * 64; |
97 | /// self.framebuffer[index as usize] = color.luma(); |
98 | /// } |
99 | /// } |
100 | /// |
101 | /// Ok(()) |
102 | /// } |
103 | /// } |
104 | /// |
105 | /// impl OriginDimensions for ExampleDisplay { |
106 | /// fn size(&self) -> Size { |
107 | /// Size::new(64, 64) |
108 | /// } |
109 | /// } |
110 | /// |
111 | /// let mut display = ExampleDisplay { |
112 | /// framebuffer: [0; 4096], |
113 | /// iface: SPI1, |
114 | /// }; |
115 | /// |
116 | /// // Draw a circle with top-left at `(22, 22)` with a diameter of `20` and a white stroke |
117 | /// let circle = Circle::new(Point::new(22, 22), 20) |
118 | /// .into_styled(PrimitiveStyle::with_stroke(Gray8::WHITE, 1)); |
119 | /// |
120 | /// circle.draw(&mut display)?; |
121 | /// |
122 | /// // Update the display |
123 | /// display.flush().unwrap(); |
124 | /// # Ok::<(), core::convert::Infallible>(()) |
125 | /// ``` |
126 | /// |
127 | /// # Hardware acceleration - solid rectangular fill |
128 | /// |
129 | /// This example uses an imaginary display with 16bpp RGB565 colors and hardware support for |
130 | /// filling of rectangular areas with a solid color. A real display controller that supports this |
131 | /// operation is the SSD1331 with it's "Draw Rectangle" (`22h`) command which this example |
132 | /// is loosely based on. |
133 | /// |
134 | /// To leverage this feature in a `DrawTarget`, the default implementation of [`fill_solid`] can be |
135 | /// overridden by a custom implementation. Instead of drawing individual pixels, this target |
136 | /// specific version will only send a single command to the display controller in one transaction. |
137 | /// Because the command size is independent of the filled area, all [`fill_solid`] calls will only |
138 | /// transmit 8 bytes to the display, which is far less then what is required to transmit each pixel |
139 | /// color inside the filled area. |
140 | /// ```rust |
141 | /// use core::convert::TryInto; |
142 | /// use embedded_graphics::{ |
143 | /// pixelcolor::{raw::RawU16, Rgb565, RgbColor}, |
144 | /// prelude::*, |
145 | /// primitives::{Circle, Rectangle, PrimitiveStyle, PrimitiveStyleBuilder}, |
146 | /// }; |
147 | /// # |
148 | /// # struct SPI1; |
149 | /// # |
150 | /// # impl SPI1 { |
151 | /// # pub fn send_bytes(&self, buf: &[u16]) -> Result<(), ()> { |
152 | /// # Ok(()) |
153 | /// # } |
154 | /// # } |
155 | /// # |
156 | /// |
157 | /// /// SPI communication error |
158 | /// #[derive(Debug)] |
159 | /// struct CommError; |
160 | /// |
161 | /// /// An example display connected over SPI. |
162 | /// struct ExampleDisplay { |
163 | /// iface: SPI1, |
164 | /// } |
165 | /// |
166 | /// impl ExampleDisplay { |
167 | /// /// Send a single pixel to the display |
168 | /// pub fn set_pixel(&self, x: u32, y: u32, color: u16) -> Result<(), CommError> { |
169 | /// // ... |
170 | /// |
171 | /// Ok(()) |
172 | /// } |
173 | /// |
174 | /// /// Send commands to the display |
175 | /// pub fn send_commands(&self, commands: &[u8]) -> Result<(), CommError> { |
176 | /// // Send data marked as commands to the display. |
177 | /// |
178 | /// Ok(()) |
179 | /// } |
180 | /// } |
181 | /// |
182 | /// impl DrawTarget for ExampleDisplay { |
183 | /// type Color = Rgb565; |
184 | /// type Error = CommError; |
185 | /// |
186 | /// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> |
187 | /// where |
188 | /// I: IntoIterator<Item = Pixel<Self::Color>>, |
189 | /// { |
190 | /// for Pixel(coord, color) in pixels.into_iter() { |
191 | /// // Check if the pixel coordinates are out of bounds (negative or greater than |
192 | /// // (63,63)). `DrawTarget` implementation are required to discard any out of bounds |
193 | /// // pixels without returning an error or causing a panic. |
194 | /// if let Ok((x @ 0..=63, y @ 0..=63)) = coord.try_into() { |
195 | /// self.set_pixel(x, y, RawU16::from(color).into_inner())?; |
196 | /// } |
197 | /// } |
198 | /// |
199 | /// Ok(()) |
200 | /// } |
201 | /// |
202 | /// fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { |
203 | /// // Clamp the rectangle coordinates to the valid range by determining |
204 | /// // the intersection of the fill area and the visible display area |
205 | /// // by using Rectangle::intersection. |
206 | /// let area = area.intersection(&self.bounding_box()); |
207 | /// |
208 | /// // Do not send a draw rectangle command if the intersection size if zero. |
209 | /// // The size is checked by using `Rectangle::bottom_right`, which returns `None` |
210 | /// // if the size is zero. |
211 | /// let bottom_right = if let Some(bottom_right) = area.bottom_right() { |
212 | /// bottom_right |
213 | /// } else { |
214 | /// return Ok(()); |
215 | /// }; |
216 | /// |
217 | /// self.send_commands(&[ |
218 | /// // Draw rectangle command |
219 | /// 0x22, |
220 | /// // Top left X coordinate |
221 | /// area.top_left.x as u8, |
222 | /// // Top left Y coordinate |
223 | /// area.top_left.y as u8, |
224 | /// // Bottom right X coordinate |
225 | /// bottom_right.x as u8, |
226 | /// // Bottom right Y coordinate |
227 | /// bottom_right.y as u8, |
228 | /// // Fill color red channel |
229 | /// color.r(), |
230 | /// // Fill color green channel |
231 | /// color.g(), |
232 | /// // Fill color blue channel |
233 | /// color.b(), |
234 | /// ]) |
235 | /// } |
236 | /// } |
237 | /// |
238 | /// impl OriginDimensions for ExampleDisplay { |
239 | /// fn size(&self) -> Size { |
240 | /// Size::new(64, 64) |
241 | /// } |
242 | /// } |
243 | /// |
244 | /// let mut display = ExampleDisplay { iface: SPI1 }; |
245 | /// |
246 | /// // Draw a rectangle with 5px red stroke and green fill. |
247 | /// // The stroke and fill can be broken down into multiple individual rectangles, |
248 | /// // so this uses `fill_solid` internally. |
249 | /// Rectangle::new(Point::new(20, 20), Size::new(50, 40)) |
250 | /// .into_styled( |
251 | /// PrimitiveStyleBuilder::new() |
252 | /// .stroke_color(Rgb565::RED) |
253 | /// .stroke_width(5) |
254 | /// .fill_color(Rgb565::GREEN) |
255 | /// .build(), |
256 | /// ) |
257 | /// .draw(&mut display)?; |
258 | /// |
259 | /// // Draw a circle with top-left at `(5, 5)` with a diameter of `10` and a magenta stroke with |
260 | /// // cyan fill. This shape cannot be optimized by calls to `fill_solid` as it contains transparent |
261 | /// // pixels as well as pixels of different colors. It will instead delegate to `draw_iter` |
262 | /// // internally. |
263 | /// Circle::new(Point::new(5, 5), 10) |
264 | /// .into_styled( |
265 | /// PrimitiveStyleBuilder::new() |
266 | /// .stroke_color(Rgb565::MAGENTA) |
267 | /// .stroke_width(1) |
268 | /// .fill_color(Rgb565::CYAN) |
269 | /// .build(), |
270 | /// ) |
271 | /// .draw(&mut display)?; |
272 | /// |
273 | /// # Ok::<(), CommError>(()) |
274 | /// ``` |
275 | /// |
276 | /// [`fill_solid`]: DrawTarget::fill_solid() |
277 | /// [`draw_iter`]: DrawTarget::draw_iter |
278 | /// [`Dimensions`]: super::geometry::Dimensions |
279 | /// [`OriginDimensions`]: super::geometry::OriginDimensions |
280 | /// [`Error` type]: DrawTarget::Error |
281 | pub trait DrawTarget: Dimensions { |
282 | /// The pixel color type the targetted display supports. |
283 | type Color: PixelColor; |
284 | |
285 | /// Error type to return when a drawing operation fails. |
286 | /// |
287 | /// This error is returned if an error occurred during a drawing operation. This mainly applies |
288 | /// to drivers that need to communicate with the display for each drawing operation, where a |
289 | /// communication error can occur. For drivers that use an internal framebuffer where drawing |
290 | /// operations can never fail, [`core::convert::Infallible`] can instead be used as the `Error` |
291 | /// type. |
292 | /// |
293 | /// [`core::convert::Infallible`]: https://doc.rust-lang.org/stable/core/convert/enum.Infallible.html |
294 | type Error; |
295 | |
296 | /// Draw individual pixels to the display without a defined order. |
297 | /// |
298 | /// Due to the unordered nature of the pixel iterator, this method is likely to be the slowest |
299 | /// drawing method for a display that writes data to the hardware immediately. If possible, the |
300 | /// other methods in this trait should be implemented to improve performance when rendering |
301 | /// more contiguous pixel patterns. |
302 | fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> |
303 | where |
304 | I: IntoIterator<Item = Pixel<Self::Color>>; |
305 | |
306 | /// Fill a given area with an iterator providing a contiguous stream of pixel colors. |
307 | /// |
308 | /// Use this method to fill an area with contiguous, non-transparent pixel colors. Pixel |
309 | /// coordinates are iterated over from the top left to the bottom right corner of the area in |
310 | /// row-first order. The provided iterator must provide pixel color values based on this |
311 | /// ordering to produce correct output. |
312 | /// |
313 | /// As seen in the example below, the [`PointsIter::points`] method can be used to get an |
314 | /// iterator over all points in the provided area. |
315 | /// |
316 | /// The provided iterator is not required to provide `width * height` pixels to completely fill |
317 | /// the area. In this case, `fill_contiguous` should return without error. |
318 | /// |
319 | /// This method should not attempt to draw any pixels that fall outside the drawable area of the |
320 | /// target display. The `area` argument can be clipped to the drawable area using the |
321 | /// [`Rectangle::intersection`] method. |
322 | /// |
323 | /// The default implementation of this method delegates to [`draw_iter`](DrawTarget::draw_iter). |
324 | /// |
325 | /// # Examples |
326 | /// |
327 | /// This is an example implementation of `fill_contiguous` that delegates to [`draw_iter`]. This |
328 | /// delegation behaviour is undesirable in a real application as it will be as slow as the |
329 | /// default trait implementation, however is shown here for demonstration purposes. |
330 | /// |
331 | /// The example demonstrates the usage of [`Rectangle::intersection`] on the passed `area` |
332 | /// argument to only draw visible pixels. If there is no intersection between `area` and the |
333 | /// display area, no pixels will be drawn. |
334 | /// |
335 | /// ```rust |
336 | /// use embedded_graphics::{ |
337 | /// pixelcolor::{Gray8, GrayColor}, |
338 | /// prelude::*, |
339 | /// primitives::{ContainsPoint, Rectangle}, |
340 | /// }; |
341 | /// |
342 | /// struct ExampleDisplay; |
343 | /// |
344 | /// impl DrawTarget for ExampleDisplay { |
345 | /// type Color = Gray8; |
346 | /// type Error = core::convert::Infallible; |
347 | /// |
348 | /// fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> |
349 | /// where |
350 | /// I: IntoIterator<Item = Pixel<Self::Color>>, |
351 | /// { |
352 | /// // Draw pixels to the display |
353 | /// |
354 | /// Ok(()) |
355 | /// } |
356 | /// |
357 | /// fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> |
358 | /// where |
359 | /// I: IntoIterator<Item = Self::Color>, |
360 | /// { |
361 | /// // Clamp area to drawable part of the display target |
362 | /// let drawable_area = area.intersection(&self.bounding_box()); |
363 | /// |
364 | /// // Check that there are visible pixels to be drawn |
365 | /// if drawable_area.size != Size::zero() { |
366 | /// self.draw_iter( |
367 | /// area.points() |
368 | /// .zip(colors) |
369 | /// .filter(|(pos, _color)| drawable_area.contains(*pos)) |
370 | /// .map(|(pos, color)| Pixel(pos, color)), |
371 | /// ) |
372 | /// } else { |
373 | /// Ok(()) |
374 | /// } |
375 | /// } |
376 | /// } |
377 | /// |
378 | /// impl OriginDimensions for ExampleDisplay { |
379 | /// fn size(&self) -> Size { |
380 | /// Size::new(64, 64) |
381 | /// } |
382 | /// } |
383 | /// ``` |
384 | /// |
385 | /// [`draw_iter`]: DrawTarget::draw_iter |
386 | /// [`Rectangle::intersection`]: super::primitives::rectangle::Rectangle::intersection() |
387 | /// [`PointsIter::points`]: super::primitives::PointsIter::points |
388 | fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> |
389 | where |
390 | I: IntoIterator<Item = Self::Color>, |
391 | { |
392 | self.draw_iter( |
393 | area.points() |
394 | .zip(colors) |
395 | .map(|(pos, color)| Pixel(pos, color)), |
396 | ) |
397 | } |
398 | |
399 | /// Fill a given area with a solid color. |
400 | /// |
401 | /// If the target display provides optimized hardware commands for filling a rectangular area of |
402 | /// the display with a solid color, this method should be overridden to use those commands to |
403 | /// improve performance. |
404 | /// |
405 | /// The default implementation of this method calls [`fill_contiguous`](DrawTarget::fill_contiguous()) |
406 | /// with an iterator that repeats the given `color` for every point in `area`. |
407 | fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { |
408 | self.fill_contiguous(area, core::iter::repeat(color)) |
409 | } |
410 | |
411 | /// Fill the entire display with a solid color. |
412 | /// |
413 | /// If the target hardware supports a more optimized way of filling the entire display with a |
414 | /// solid color, this method should be overridden to use those commands. |
415 | /// |
416 | /// The default implementation of this method delegates to [`fill_solid`] to fill the |
417 | /// [`bounding_box`] returned by the [`Dimensions`] implementation. |
418 | /// |
419 | /// [`Dimensions`]: super::geometry::Dimensions |
420 | /// [`bounding_box`]: super::geometry::Dimensions::bounding_box |
421 | /// [`fill_solid`]: DrawTarget::fill_solid() |
422 | fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { |
423 | self.fill_solid(&self.bounding_box(), color) |
424 | } |
425 | } |
426 | |