embedded_graphics/image/
image_raw.rs

1use core::marker::PhantomData;
2
3use crate::{
4    draw_target::DrawTarget,
5    geometry::{Dimensions, OriginDimensions, Point, Size},
6    image::{GetPixel, ImageDrawable},
7    iterator::raw::RawDataSlice,
8    pixelcolor::{
9        raw::{BigEndian, ByteOrder, LittleEndian, RawData},
10        PixelColor,
11    },
12    primitives::Rectangle,
13};
14
15/// Image with little endian data.
16pub type ImageRawLE<'a, C> = ImageRaw<'a, C, LittleEndian>;
17
18/// Image with big endian data.
19pub type ImageRawBE<'a, C> = ImageRaw<'a, C, BigEndian>;
20
21/// An image constructed from a slice of raw pixel data.
22///
23/// The `ImageRaw` struct can be used to construct an image from a slice
24/// of raw image data. The storage format is determined by the [`PixelColor`]
25/// type `C` and the [`ByteOrder`] `BO`. The byteorder doesn't need to be
26/// specified for colors which aren't stored in multiple bytes.
27///
28/// For color types with less than 8 bits per pixels the start of each row is
29/// aligned to the next whole byte.
30///
31/// Details about the conversion of raw data to color types are explained in the
32/// [`raw` module documentation].
33///
34/// To draw an `ImageRaw` object it needs to be wrapped in an [`Image`] object.
35///
36/// # Examples
37///
38/// ## Draw a 1BPP image
39///
40/// This example creates an image from 1 bit per pixel data.
41///
42/// ```
43/// use embedded_graphics::{
44///     image::{Image, ImageRaw},
45///     pixelcolor::BinaryColor,
46///     prelude::*,
47/// };
48/// # use embedded_graphics::mock_display::MockDisplay as Display;
49///
50/// /// 12 x 5 pixel image with 1 bit per pixel.
51/// /// The data for each row is 12 bits long and is padded with zeros on the
52/// /// end because each row needs to contain a whole number of bytes.
53/// #[rustfmt::skip]
54/// const DATA: &[u8] = &[
55///     0b11101111, 0b0101_0000,
56///     0b10001000, 0b0101_0000,
57///     0b11101011, 0b0101_0000,
58///     0b10001001, 0b0101_0000,
59///     0b11101111, 0b0101_0000,
60/// ];
61///
62/// // The image dimensions and the format of the stored raw data must be specified
63/// // when the `new` function is called. The data format can, for example, be specified
64/// // by using the turbofish syntax. For the image dimensions only the width must be
65/// // passed to the `new` function. The image height will be calculated based on the
66/// // length of the image data and the data format.
67/// let raw_image = ImageRaw::<BinaryColor>::new(DATA, 12);
68///
69/// let image = Image::new(&raw_image, Point::zero());
70///
71/// let mut display = Display::default();
72///
73/// image.draw(&mut display)?;
74/// # Ok::<(), core::convert::Infallible>(())
75/// ```
76///
77/// ## Draw an image that uses multibyte pixel encoding
78///
79/// Colors with more than one byte per pixel need an additional type annotation for the byte order.
80/// For convenience, the [`ImageRawBE`] and [`ImageRawLE`] type aliases can be used to abbreviate
81/// the type.
82///
83/// ```
84/// use embedded_graphics::{
85///     image::{Image, ImageRaw, ImageRawBE, ImageRawLE},
86///     pixelcolor::{
87///         raw::{BigEndian, LittleEndian},
88///         Rgb565, Rgb888,
89///     },
90///     prelude::*,
91/// };
92/// # const DATA: &[u8] = &[0x55; 8 * 8 * 3];
93///
94/// // Rgb888 image with 24 bits per pixel and big endian byte order
95/// let image1 = ImageRawBE::<Rgb888>::new(DATA, 8);
96/// // or:
97/// let image2 = ImageRaw::<Rgb888, BigEndian>::new(DATA, 8);
98/// # assert_eq!(image1, image2);
99///
100/// // Rgb565 image with 16 bits per pixel and little endian byte order
101/// let image1 = ImageRawLE::<Rgb565>::new(DATA, 16);
102/// // or:
103/// let image2 = ImageRaw::<Rgb565, LittleEndian>::new(DATA, 16);
104/// # assert_eq!(image1, image2);
105/// ```
106///
107/// [`raw` module documentation]: crate::pixelcolor::raw
108/// [`Image`]: crate::image::Image
109/// [`Drawable`]: crate::drawable::Drawable
110/// [`PixelColor`]: crate::pixelcolor::PixelColor
111/// [`ByteOrder`]: crate::pixelcolor::raw::ByteOrder
112#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
113#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
114pub struct ImageRaw<'a, C, BO = BigEndian>
115where
116    C: PixelColor + From<<C as PixelColor>::Raw>,
117    BO: ByteOrder,
118{
119    /// Image data, packed as dictated by raw data type `C::Raw`
120    data: &'a [u8],
121
122    /// Image size in pixels
123    size: Size,
124
125    pixel_type: PhantomData<C>,
126    byte_order: PhantomData<BO>,
127}
128
129impl<'a, C, BO> ImageRaw<'a, C, BO>
130where
131    C: PixelColor + From<<C as PixelColor>::Raw>,
132    BO: ByteOrder,
133{
134    /// Creates a new image.
135    ///
136    /// Only the width of the image needs to be specified. The height of the image will be
137    /// calculated based on the length of the given image data. If the length of the image data
138    /// isn't an integer multiple of the data length for a single row the last partial row will
139    /// be ignored.
140    pub const fn new(data: &'a [u8], width: u32) -> Self {
141        // Prevent panic for `width == 0` by returning a zero sized image.
142        if width == 0 {
143            return Self {
144                data: &[],
145                size: Size::zero(),
146                pixel_type: PhantomData,
147                byte_order: PhantomData,
148            };
149        }
150
151        let height = data.len() / bytes_per_row(width, C::Raw::BITS_PER_PIXEL);
152
153        Self {
154            data,
155            size: Size::new(width, height as u32),
156            pixel_type: PhantomData,
157            byte_order: PhantomData,
158        }
159    }
160
161    /// Returns the actual row width in pixels.
162    ///
163    /// For images with less than 8 bits per pixel each row is padded to contain an integer number
164    /// of bytes. This method returns the width of each row including the padding pixels.
165    const fn data_width(&self) -> u32 {
166        if C::Raw::BITS_PER_PIXEL < 8 {
167            let pixels_per_byte = 8 / C::Raw::BITS_PER_PIXEL as u32;
168
169            bytes_per_row(self.size.width, C::Raw::BITS_PER_PIXEL) as u32 * pixels_per_byte
170        } else {
171            self.size.width
172        }
173    }
174}
175
176/// Returns the length of each row in bytes.
177const fn bytes_per_row(width: u32, bits_per_pixel: usize) -> usize {
178    (width as usize * bits_per_pixel + 7) / 8
179}
180
181impl<'a, C, BO> ImageDrawable for ImageRaw<'a, C, BO>
182where
183    C: PixelColor + From<<C as PixelColor>::Raw>,
184    BO: ByteOrder,
185    RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
186{
187    type Color = C;
188
189    fn draw<D>(&self, target: &mut D) -> Result<(), D::Error>
190    where
191        D: DrawTarget<Color = C>,
192    {
193        let row_skip = self.data_width() - self.size.width;
194
195        target.fill_contiguous(
196            &self.bounding_box(),
197            ContiguousPixels::new(self, self.size, 0, row_skip as usize),
198        )
199    }
200
201    fn draw_sub_image<D>(&self, target: &mut D, area: &Rectangle) -> Result<(), D::Error>
202    where
203        D: DrawTarget<Color = Self::Color>,
204    {
205        // Don't draw anything if `area` is zero sized or partially outside the image.
206        if area.is_zero_sized()
207            || area.top_left.x < 0
208            || area.top_left.y < 0
209            || area.top_left.x as u32 + area.size.width > self.size.width
210            || area.top_left.y as u32 + area.size.height > self.size.height
211        {
212            return Ok(());
213        }
214
215        let data_width = self.data_width() as usize;
216
217        let initial_skip = area.top_left.y as usize * data_width + area.top_left.x as usize;
218        let row_skip = data_width - area.size.width as usize;
219
220        target.fill_contiguous(
221            &Rectangle::new(Point::zero(), area.size),
222            ContiguousPixels::new(self, area.size, initial_skip, row_skip),
223        )
224    }
225}
226
227impl<C, BO> OriginDimensions for ImageRaw<'_, C, BO>
228where
229    C: PixelColor + From<<C as PixelColor>::Raw>,
230    BO: ByteOrder,
231{
232    fn size(&self) -> Size {
233        self.size
234    }
235}
236
237impl<'a, C, BO> GetPixel for ImageRaw<'a, C, BO>
238where
239    C: PixelColor + From<<C as PixelColor>::Raw>,
240    BO: ByteOrder,
241    RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
242{
243    type Color = C;
244
245    fn pixel(&self, p: Point) -> Option<Self::Color> {
246        if p.x < 0 || p.y < 0 || p.x >= self.size.width as i32 || p.y >= self.size.height as i32 {
247            return None;
248        }
249
250        RawDataSlice::new(self.data)
251            .into_iter()
252            .nth(p.x as usize + p.y as usize * self.data_width() as usize)
253            .map(|r| r.into())
254    }
255}
256
257struct ContiguousPixels<'a, C, BO>
258where
259    C: PixelColor + From<<C as PixelColor>::Raw>,
260    BO: ByteOrder,
261    RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
262{
263    iter: <RawDataSlice<'a, C::Raw, BO> as IntoIterator>::IntoIter,
264
265    remaining_x: u32,
266    width: u32,
267
268    remaining_y: u32,
269    row_skip: usize,
270}
271
272impl<'a, C, BO> ContiguousPixels<'a, C, BO>
273where
274    C: PixelColor + From<<C as PixelColor>::Raw>,
275    BO: ByteOrder,
276    RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
277{
278    fn new(image: &ImageRaw<'a, C, BO>, size: Size, initial_skip: usize, row_skip: usize) -> Self {
279        let mut iter = RawDataSlice::new(image.data).into_iter();
280
281        if initial_skip > 0 {
282            iter.nth(initial_skip - 1);
283        }
284
285        // Set `remaining_y` to `0` if `width == 0` to prevent integer underflow in `next`.
286        let remaining_y = if size.width > 0 { size.height } else { 0 };
287
288        Self {
289            iter,
290            remaining_x: size.width,
291            width: size.width,
292            remaining_y,
293            row_skip,
294        }
295    }
296}
297
298impl<'a, C, BO> Iterator for ContiguousPixels<'a, C, BO>
299where
300    C: PixelColor + From<<C as PixelColor>::Raw>,
301    BO: ByteOrder,
302    RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
303{
304    type Item = C;
305
306    fn next(&mut self) -> Option<Self::Item> {
307        if self.remaining_x > 0 {
308            self.remaining_x -= 1;
309
310            self.iter.next()
311        } else {
312            if self.remaining_y == 0 {
313                return None;
314            }
315
316            self.remaining_y -= 1;
317            self.remaining_x = self.width - 1;
318
319            self.iter.nth(self.row_skip)
320        }
321        .map(|c| c.into())
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use crate::{
329        draw_target::DrawTarget,
330        geometry::Point,
331        image::Image,
332        iterator::PixelIteratorExt,
333        mock_display::{ColorMapping, MockDisplay},
334        pixelcolor::{raw::RawU32, *},
335        Drawable, Pixel,
336    };
337
338    #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
339    struct TestColorU32(RawU32);
340
341    impl PixelColor for TestColorU32 {
342        type Raw = RawU32;
343    }
344
345    impl From<RawU32> for TestColorU32 {
346        fn from(data: RawU32) -> Self {
347            Self(data)
348        }
349    }
350
351    /// Tests if the given image data matches an excepted `MockDisplay` pattern.
352    fn assert_pattern<C, BO>(image_data: ImageRaw<C, BO>, expected_pattern: &[&str])
353    where
354        C: PixelColor + From<<C as PixelColor>::Raw> + ColorMapping,
355        BO: ByteOrder,
356        for<'a> RawDataSlice<'a, C::Raw, BO>: IntoIterator<Item = C::Raw>,
357    {
358        let image = Image::new(&image_data, Point::zero());
359        let mut display = MockDisplay::new();
360        image.draw(&mut display).unwrap();
361
362        display.assert_pattern(expected_pattern);
363    }
364
365    #[test]
366    fn image_dimensions() {
367        let data = [
368            0xAA, 0x00, //
369            0x55, 0xFF, //
370            0xAA, 0x80, //
371        ];
372        let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
373
374        assert_eq!(image_data.size(), Size::new(9, 3));
375    }
376
377    #[test]
378    fn truncated_data() {
379        let data = [
380            0xAA, 0x00, //
381            0x55, 0xFF, //
382            0xAA, //
383        ];
384        let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
385
386        assert_pattern(
387            image_data,
388            &[
389                "#.#.#.#..", //
390                ".#.#.#.##", //
391            ],
392        );
393    }
394
395    #[test]
396    fn bpp1_new() {
397        let data = [
398            0xAA, 0x00, //
399            0x55, 0xFF, //
400            0xAA, 0x80, //
401        ];
402        let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
403
404        assert_pattern(
405            image_data,
406            &[
407                "#.#.#.#..", //
408                ".#.#.#.##", //
409                "#.#.#.#.#", //
410            ],
411        );
412    }
413
414    #[test]
415    fn bpp1_get_pixel() {
416        let data = [
417            0xAA, 0x00, //
418            0x55, 0xFF, //
419            0xAA, 0x80, //
420        ];
421        let image_data: ImageRaw<BinaryColor> = ImageRaw::new(&data, 9);
422
423        assert_eq!(image_data.pixel(Point::new(0, 0)), Some(BinaryColor::On));
424        assert_eq!(image_data.pixel(Point::new(8, 0)), Some(BinaryColor::Off));
425        assert_eq!(image_data.pixel(Point::new(0, 1)), Some(BinaryColor::Off));
426        assert_eq!(image_data.pixel(Point::new(8, 1)), Some(BinaryColor::On));
427        assert_eq!(image_data.pixel(Point::new(0, 2)), Some(BinaryColor::On));
428        assert_eq!(image_data.pixel(Point::new(8, 2)), Some(BinaryColor::On));
429    }
430
431    #[test]
432    fn bpp2() {
433        let data = [
434            0b00_01_10_11, //
435            0b00_00_00_00, //
436            0b11_10_01_00, //
437            0b11_11_11_11, //
438        ];
439        let image_data: ImageRaw<Gray2> = ImageRaw::new(&data, 5);
440
441        assert_pattern(
442            image_data,
443            &[
444                "01230", //
445                "32103", //
446            ],
447        );
448    }
449
450    #[test]
451    fn bpp4() {
452        let data = [
453            0b0001_1000, //
454            0b1111_0000, //
455            0b0101_1010, //
456            0b0000_0000, //
457        ];
458        let image_data: ImageRaw<Gray4> = ImageRaw::new(&data, 3);
459
460        assert_pattern(
461            image_data,
462            &[
463                "18F", //
464                "5A0", //
465            ],
466        );
467    }
468
469    #[test]
470    fn bpp8_1() {
471        let data = [
472            0x11, 0x22, //
473            0x33, 0x44, //
474            0x55, 0x66, //
475        ];
476        let image_data: ImageRaw<Gray8> = ImageRaw::new(&data, 2);
477
478        assert_pattern(
479            image_data,
480            &[
481                "12", //
482                "34", //
483                "56", //
484            ],
485        );
486    }
487
488    /// Additional test for luma values with different low and high nibbles,
489    /// which are not supported by `MockDisplay` patterns.
490    #[test]
491    fn bpp8_2() {
492        let data = [0x01, 0x08, 0x10, 0x80];
493        let image_data: ImageRaw<Gray8> = ImageRaw::new(&data, 4);
494
495        let mut display = MockDisplay::new();
496        Image::new(&image_data, Point::zero())
497            .draw(&mut display)
498            .unwrap();
499
500        let mut expected = MockDisplay::new();
501        expected
502            .fill_contiguous(
503                &expected.bounding_box(),
504                data.iter().copied().map(Gray8::new),
505            )
506            .unwrap();
507
508        display.assert_eq(&expected);
509    }
510
511    #[test]
512    fn bpp16_little_endian() {
513        let data = [
514            0x00, 0xF8, //
515            0xE0, 0x07, //
516            0x1F, 0x00, //
517            0x00, 0x00, //
518        ];
519        let image_data: ImageRawLE<Rgb565> = ImageRaw::new(&data, 1);
520
521        assert_pattern(
522            image_data,
523            &[
524                "R", //
525                "G", //
526                "B", //
527                "K", //
528            ],
529        );
530    }
531
532    #[test]
533    fn bpp16_big_endian() {
534        let data = [
535            0xF8, 0x00, //
536            0x07, 0xE0, //
537            0x00, 0x1F, //
538            0x00, 0x00, //
539        ];
540        let image_data: ImageRawBE<Rgb565> = ImageRaw::new(&data, 2);
541
542        assert_pattern(
543            image_data,
544            &[
545                "RG", //
546                "BK", //
547            ],
548        );
549    }
550
551    #[test]
552    fn bpp16_big_endian_get_pixel() {
553        let data = [
554            0xF8, 0x00, //
555            0x07, 0xE0, //
556            0x00, 0x1F, //
557            0x00, 0x00, //
558        ];
559        let image_data: ImageRawBE<Rgb565> = ImageRaw::new(&data, 2);
560
561        assert_eq!(image_data.pixel(Point::new(0, 0)), Some(Rgb565::RED));
562        assert_eq!(image_data.pixel(Point::new(1, 0)), Some(Rgb565::GREEN));
563        assert_eq!(image_data.pixel(Point::new(0, 1)), Some(Rgb565::BLUE));
564        assert_eq!(image_data.pixel(Point::new(1, 1)), Some(Rgb565::BLACK));
565    }
566
567    #[test]
568    fn bpp24_little_endian() {
569        let data = [
570            0xFF, 0x00, 0x00, //
571            0x00, 0xFF, 0x00, //
572            0x00, 0x00, 0xFF, //
573            0x00, 0x00, 0x00, //
574        ];
575        let image_data: ImageRawLE<Bgr888> = ImageRaw::new(&data, 1);
576
577        assert_pattern(
578            image_data,
579            &[
580                "R", //
581                "G", //
582                "B", //
583                "K", //
584            ],
585        );
586    }
587
588    #[test]
589    fn bpp24_big_endian() {
590        let data = [
591            0xFF, 0x00, 0x00, //
592            0x00, 0xFF, 0x00, //
593            0x00, 0x00, 0xFF, //
594            0x00, 0x00, 0x00, //
595        ];
596        let image_data: ImageRawBE<Rgb888> = ImageRaw::new(&data, 4);
597
598        assert_pattern(image_data, &["RGBK"]);
599    }
600
601    #[test]
602    fn bpp32_little_endian() {
603        let data = [
604            0x12, 0x34, 0x56, 0x78, //
605            0x9A, 0xBC, 0xDE, 0xF0, //
606            0x00, 0x00, 0x00, 0x00, //
607            0xFF, 0xFF, 0xFF, 0xFF, //
608        ];
609        let image_data: ImageRawLE<TestColorU32> = ImageRaw::new(&data, 2);
610
611        let mut display = MockDisplay::new();
612        Image::new(&image_data, Point::zero())
613            .draw(&mut display)
614            .unwrap();
615
616        let expected = [
617            Pixel(Point::new(0, 0), TestColorU32(RawU32::new(0x78563412))),
618            Pixel(Point::new(1, 0), TestColorU32(RawU32::new(0xF0DEBC9A))),
619            Pixel(Point::new(0, 1), TestColorU32(RawU32::new(0x00000000))),
620            Pixel(Point::new(1, 1), TestColorU32(RawU32::new(0xFFFFFFFF))),
621        ];
622
623        let mut expected_display = MockDisplay::new();
624        expected
625            .iter()
626            .copied()
627            .draw(&mut expected_display)
628            .unwrap();
629
630        // assert_eq can't be used here because ColorMapping isn't implemented for TestColorU32
631        assert!(display.eq(&expected_display));
632    }
633
634    #[test]
635    fn bpp32_big_endian() {
636        let data = [
637            0x12, 0x34, 0x56, 0x78, //
638            0x9A, 0xBC, 0xDE, 0xF0, //
639            0x00, 0x00, 0x00, 0x00, //
640            0xFF, 0xFF, 0xFF, 0xFF, //
641        ];
642        let image_data: ImageRawBE<TestColorU32> = ImageRaw::new(&data, 4);
643
644        let mut display = MockDisplay::new();
645        Image::new(&image_data, Point::zero())
646            .draw(&mut display)
647            .unwrap();
648
649        let expected = [
650            Pixel(Point::new(0, 0), TestColorU32(RawU32::new(0x12345678))),
651            Pixel(Point::new(1, 0), TestColorU32(RawU32::new(0x9ABCDEF0))),
652            Pixel(Point::new(2, 0), TestColorU32(RawU32::new(0x00000000))),
653            Pixel(Point::new(3, 0), TestColorU32(RawU32::new(0xFFFFFFFF))),
654        ];
655
656        let mut expected_display = MockDisplay::new();
657        expected
658            .iter()
659            .copied()
660            .draw(&mut expected_display)
661            .unwrap();
662
663        // assert_eq can't be used here because ColorMapping isn't implemented for TestColorU32
664        assert!(display.eq(&expected_display));
665    }
666
667    #[test]
668    fn calculated_height() {
669        let data = [0u8; 1];
670        assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 0);
671
672        let data = [0u8; 2];
673        assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 1);
674
675        let data = [0u8; 3];
676        assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 1);
677
678        let data = [0u8; 4];
679        assert_eq!(ImageRaw::<BinaryColor>::new(&data, 12).size().height, 2);
680    }
681
682    #[test]
683    fn binary_image_with_zero_width() {
684        let image = ImageRaw::<BinaryColor>::new(&[], 0);
685
686        assert_eq!(image.size, Size::zero());
687    }
688
689    #[test]
690    fn pixel_out_of_bounds() {
691        let data = [
692            0xAA, 0x00, //
693            0x55, 0xFF, //
694            0xAA, 0x80, //
695        ];
696        let image_data = ImageRaw::<BinaryColor>::new(&data, 9);
697
698        assert_eq!(image_data.pixel(Point::new(-1, 0)), None);
699        assert_eq!(image_data.pixel(Point::new(0, -1)), None);
700        assert_eq!(image_data.pixel(Point::new(9, 0)), None);
701        assert_eq!(image_data.pixel(Point::new(9, 3)), None);
702    }
703}