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 | |
98 | mod image_drawable_ext; |
99 | mod image_raw; |
100 | mod sub_image; |
101 | |
102 | pub use embedded_graphics_core::image::{GetPixel, ImageDrawable}; |
103 | pub use image_drawable_ext::ImageDrawableExt; |
104 | pub use image_raw::{ImageRaw, ImageRawBE, ImageRawLE}; |
105 | pub use sub_image::SubImage; |
106 | |
107 | use crate::{ |
108 | draw_target::{DrawTarget, DrawTargetExt}, |
109 | geometry::{Dimensions, OriginDimensions, Point}, |
110 | primitives::Rectangle, |
111 | transform::Transform, |
112 | Drawable, |
113 | }; |
114 | use 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))] |
131 | pub struct Image<'a, T> { |
132 | image_drawable: &'a T, |
133 | offset: Point, |
134 | } |
135 | |
136 | impl<'a, T> Image<'a, T> |
137 | where |
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: Point = Rectangle::with_center(center, image_drawable.size()).top_left; |
151 | |
152 | Self { |
153 | image_drawable, |
154 | offset, |
155 | } |
156 | } |
157 | } |
158 | |
159 | impl<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 | |
225 | impl<'a, T> Drawable for Image<'a, T> |
226 | where |
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 | |
241 | impl<'a, T> Dimensions for Image<'a, T> |
242 | where |
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)] |
251 | mod 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 | } |
316 | |