embedded_graphics_core/draw_target/
mod.rs

1//! A target for embedded-graphics drawing operations.
2
3use 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
281pub 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}