embedded_graphics/image/mod.rs
1//! Image support for embedded-graphics
2//!
3//! The two main types used to draw images are [`ImageDrawable`] and [`Image`].
4//!
5//! [`ImageDrawable`] is implemented to add support for different image formats. This crate includes
6//! an implementation for [raw pixel data]. Additional implementations for other image formats are
7//! provided by external crates like [tinybmp] and [tinytga].
8//!
9//! The [`Image`] object is used to specify the location at which an [`ImageDrawable`] is drawn.
10//! Images are drawn relative to their top-left corner.
11//!
12//! # Examples
13//!
14//! ## Display an RGB565 raw data image
15//!
16//! This example displays a small image created from a raw data array. The image is RGB565 encoded,
17//! so it uses the `Rgb565` color type.
18//!
19//! ```rust
20//! use embedded_graphics::{
21//! image::{Image, ImageRaw, ImageRawBE},
22//! pixelcolor::Rgb565,
23//! prelude::*,
24//! };
25//! # use embedded_graphics::mock_display::MockDisplay as Display;
26//!
27//! let mut display: Display<Rgb565> = Display::default();
28//!
29//! // Raw big endian image data for demonstration purposes. A real image would likely be much
30//! // larger.
31//! let data = [
32//! 0x00, 0x00, 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, //
33//! 0x00, 0x1F, 0x07, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, //
34//! ];
35//!
36//! // Create a raw image instance. Other image formats will require different code to load them.
37//! // All code after loading is the same for any image format.
38//! let raw: ImageRawBE<Rgb565> = ImageRaw::new(&data, 4);
39//!
40//! // Create an `Image` object to position the image at `Point::zero()`.
41//! let image = Image::new(&raw, Point::zero());
42//!
43//! // Draw the image to the display.
44//! image.draw(&mut display)?;
45//!
46//! # Ok::<(), core::convert::Infallible>(())
47//! ```
48//!
49//! ## Sub images
50//!
51//! [`SubImage`]s are used to split a larger image drawables into multiple parts, e.g. to draw a
52//! single sprite from a sprite atlas as in this example. Use the [`sub_image`] method provided by
53//! [`ImageDrawableExt`] to get a sub image from an image drawable. [`ImageDrawableExt`] is included
54//! in the [`prelude`], which this example takes advantage of.
55//!
56//! ```rust
57//! use embedded_graphics::{
58//! image::{Image, ImageRaw, ImageRawBE},
59//! pixelcolor::Rgb565,
60//! prelude::*,
61//! primitives::Rectangle,
62//! };
63//! # use embedded_graphics::mock_display::MockDisplay as Display;
64//!
65//! let mut display: Display<Rgb565> = Display::default();
66//!
67//! let data = [ 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, /* ... */ ];
68//! // or: let data = include_bytes!("sprite_atlas.raw");
69//!
70//! # let data = [0u8; 32 * 16 * 2];
71//! let sprite_atlas: ImageRawBE<Rgb565> = ImageRaw::new(&data, 32);
72//!
73//! // Create individual sub images for each sprite in the sprite atlas.
74//! // The position and size of the sub images is defined by a `Rectangle`.
75//! let sprite_1 = sprite_atlas.sub_image(&Rectangle::new(Point::new(0, 0), Size::new(16, 16)));
76//! let sprite_2 = sprite_atlas.sub_image(&Rectangle::new(Point::new(16, 0), Size::new(16, 16)));
77//!
78//! // Create `Image` objects to draw the sprites at different positions on the display.
79//! Image::new(&sprite_1, Point::new(10, 10)).draw(&mut display)?;
80//! Image::new(&sprite_2, Point::new(40, 30)).draw(&mut display)?;
81//!
82//! # Ok::<(), core::convert::Infallible>(())
83//! ```
84//!
85//! # Implementing new image formats
86//!
87//! To add embedded-graphics support for an new image format the [`ImageDrawable`] and
88//! [`OriginDimensions`] traits must be implemented. See the [`ImageDrawable`] documentation
89//! for more information.
90//!
91//! [tinytga]: https://crates.io/crates/tinytga
92//! [tinybmp]: https://crates.io/crates/tinybmp
93//! [raw pixel data]: ImageRaw
94//! [`sub_image`]: ImageDrawableExt::sub_image
95//! [`OriginDimensions`]: super::geometry::OriginDimensions
96//! [`prelude`]: super::prelude
97
98mod image_drawable_ext;
99mod image_raw;
100mod sub_image;
101
102pub use embedded_graphics_core::image::{GetPixel, ImageDrawable};
103pub use image_drawable_ext::ImageDrawableExt;
104pub use image_raw::{ImageRaw, ImageRawBE, ImageRawLE};
105pub use sub_image::SubImage;
106
107use crate::{
108 draw_target::{DrawTarget, DrawTargetExt},
109 geometry::{Dimensions, OriginDimensions, Point},
110 primitives::Rectangle,
111 transform::Transform,
112 Drawable,
113};
114use core::fmt::Debug;
115
116/// Image object.
117///
118/// The `Image` struct serves as a wrapper around an [`ImageDrawable`] that provides support for
119/// an image format (raw bytes, BMP, TGA, etc). It allows an image to be repositioned using
120/// [`Transform::translate`] or [`Transform::translate_mut`] and drawn to a display that
121/// implements the [`DrawTarget`] trait.
122///
123/// Refer to the [module documentation] for examples.
124///
125/// [module documentation]: self
126/// [`Transform::translate`]: super::transform::Transform::translate
127/// [`Transform::translate_mut`]: super::transform::Transform::translate_mut
128/// [`DrawTarget`]: super::draw_target::DrawTarget
129#[derive(Debug, Clone, Copy)]
130#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
131pub struct Image<'a, T> {
132 image_drawable: &'a T,
133 offset: Point,
134}
135
136impl<'a, T> Image<'a, T>
137where
138 T: ImageDrawable,
139{
140 /// Creates a new `Image`.
141 pub const fn new(image_drawable: &'a T, position: Point) -> Self {
142 Self {
143 image_drawable,
144 offset: position,
145 }
146 }
147
148 /// Creates a new `Image` centered around a given point.
149 pub fn with_center(image_drawable: &'a T, center: Point) -> Self {
150 let offset = Rectangle::with_center(center, image_drawable.size()).top_left;
151
152 Self {
153 image_drawable,
154 offset,
155 }
156 }
157}
158
159impl<T> Transform for Image<'_, T> {
160 /// Translate the image by a given delta, returning a new image
161 ///
162 /// # Examples
163 ///
164 /// ## Move an image around
165 ///
166 /// This examples moves a 4x4 black and white image by `(10, 20)` pixels without mutating the
167 /// original image
168 ///
169 /// ```rust
170 /// use embedded_graphics::{
171 /// geometry::Point,
172 /// image::{Image, ImageRaw},
173 /// pixelcolor::BinaryColor,
174 /// prelude::*,
175 /// };
176 ///
177 /// let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
178 ///
179 /// let image = Image::new(&image, Point::zero());
180 ///
181 /// let image_moved = image.translate(Point::new(10, 20));
182 ///
183 /// assert_eq!(image.bounding_box().top_left, Point::zero());
184 /// assert_eq!(image_moved.bounding_box().top_left, Point::new(10, 20));
185 /// ```
186 fn translate(&self, by: Point) -> Self {
187 Self {
188 image_drawable: self.image_drawable,
189 offset: self.offset + by,
190 }
191 }
192
193 /// Translate the image by a given delta, modifying the original object
194 ///
195 /// # Examples
196 ///
197 /// ## Move an image around
198 ///
199 /// This examples moves a 4x4 black and white image by `(10, 20)` pixels by mutating the
200 /// original image
201 ///
202 /// ```rust
203 /// use embedded_graphics::{
204 /// geometry::Point,
205 /// image::{Image, ImageRaw},
206 /// pixelcolor::BinaryColor,
207 /// prelude::*,
208 /// };
209 ///
210 /// let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
211 ///
212 /// let mut image = Image::new(&image, Point::zero());
213 ///
214 /// image.translate_mut(Point::new(10, 20));
215 ///
216 /// assert_eq!(image.bounding_box().top_left, Point::new(10, 20));
217 /// ```
218 fn translate_mut(&mut self, by: Point) -> &mut Self {
219 self.offset += by;
220
221 self
222 }
223}
224
225impl<'a, T> Drawable for Image<'a, T>
226where
227 T: ImageDrawable,
228{
229 type Color = T::Color;
230 type Output = ();
231
232 fn draw<D>(&self, display: &mut D) -> Result<Self::Output, D::Error>
233 where
234 D: DrawTarget<Color = Self::Color>,
235 {
236 self.image_drawable
237 .draw(&mut display.translated(self.offset))
238 }
239}
240
241impl<'a, T> Dimensions for Image<'a, T>
242where
243 T: OriginDimensions,
244{
245 fn bounding_box(&self) -> Rectangle {
246 self.image_drawable.bounding_box().translate(self.offset)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use crate::{geometry::Size, mock_display::MockDisplay, pixelcolor::BinaryColor};
254
255 #[test]
256 fn negative_top_left() {
257 let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xff, 0x00], 4);
258
259 let image = Image::new(&image, Point::zero()).translate(Point::new(-1, -1));
260
261 assert_eq!(
262 image.bounding_box(),
263 Rectangle::new(Point::new(-1, -1), Size::new(4, 4))
264 );
265 }
266
267 #[test]
268 fn dimensions() {
269 let image: ImageRaw<BinaryColor> = ImageRaw::new(&[0xff, 0x00, 0xFF, 0x00], 4);
270
271 let image = Image::new(&image, Point::zero()).translate(Point::new(100, 200));
272
273 assert_eq!(
274 image.bounding_box(),
275 Rectangle::new(Point::new(100, 200), Size::new(4, 4))
276 );
277 }
278
279 #[test]
280 fn position() {
281 let image_raw: ImageRaw<BinaryColor> = ImageRaw::new(&[0xAA, 0x55, 0xAA, 0x55], 4);
282
283 let mut display = MockDisplay::new();
284 Image::new(&image_raw, Point::new(1, 2))
285 .draw(&mut display)
286 .unwrap();
287
288 display.assert_pattern(&[
289 " ", //
290 " ", //
291 " #.#.", //
292 " .#.#", //
293 " #.#.", //
294 " .#.#", //
295 ]);
296 }
297
298 #[test]
299 fn with_center() {
300 let image_raw: ImageRaw<BinaryColor> = ImageRaw::new(&[0xAA, 0x55, 0xAA, 0x55], 4);
301
302 let mut display = MockDisplay::new();
303 Image::with_center(&image_raw, Point::new(1, 2))
304 .draw(&mut display)
305 .unwrap();
306
307 display.assert_pattern(&[
308 " ", //
309 "#.#.", //
310 ".#.#", //
311 "#.#.", //
312 ".#.#", //
313 ]);
314 }
315}