tiny_skia_display/
lib.rs

1use std::marker::PhantomData;
2
3use tiny_skia::*;
4
5use embedded_graphics_core::{
6    draw_target::*,
7    geometry::{Point, Size},
8    pixelcolor::*,
9    prelude::Dimensions,
10    prelude::*,
11    primitives::Rectangle,
12    Pixel,
13};
14
15/// This display is based on raqote's `DrawTarget` and is used as draw target for the embedded graphics crate.
16///
17/// # Example
18///
19/// ```rust
20/// use tiny_skia_display::*;
21/// use embedded_graphics::{
22///     pixelcolor::Rgb888,
23///     prelude::*,
24///     primitives::{rectangle::Rectangle, PrimitiveStyleBuilder},
25///     };
26///
27/// let mut display = TinySkiaDisplay::new(160, 128).unwrap();
28///
29/// let style = PrimitiveStyleBuilder::new().fill_color(Rgb888::BLACK).build();
30/// let black_backdrop = Rectangle::new(Point::new(0, 0), Size::new(160, 128)).into_styled(style);
31/// black_backdrop.draw(&mut display).unwrap();
32/// ```
33pub struct TinySkiaDisplay<C>
34where
35    C: PixelColor + From<<C as PixelColor>::Raw>,
36{
37    pix_map: Pixmap,
38    size: Size,
39    _pixel_color: PhantomData<C>,
40}
41
42impl<C> DrawTarget for TinySkiaDisplay<C>
43where
44    C: PixelColor + From<<C as PixelColor>::Raw> + Into<Rgb888>,
45{
46    type Color = C;
47
48    type Error = String;
49
50    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
51    where
52        I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
53    {
54        // let now = SystemTime::now();
55
56        for Pixel(p, color) in pixels.into_iter() {
57            self.draw_pixel(p, color);
58        }
59
60        // println!("draw_iter: {:?}", now.elapsed());
61
62        Ok(())
63    }
64
65    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
66        let area = area.intersection(&self.bounding_box());
67
68        if let Some(rect) = Rect::from_xywh(
69            area.top_left.x as f32,
70            area.top_left.y as f32,
71            area.size.width as f32,
72            area.size.height as f32,
73        ) {
74            self.pix_map.fill_rect(
75                rect,
76                &convert_color_to_paint(color),
77                Transform::identity(),
78                None,
79            );
80        } else {
81            return Err(String::from("Cannot create tiny-skia rect"));
82        }
83
84        // println!("fill_solid: {:?}", now.elapsed());
85
86        Ok(())
87    }
88
89    fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
90    where
91        I: IntoIterator<Item = Self::Color>,
92    {
93        // Clamp the rectangle coordinates to the valid range by determining
94        // the intersection of the fill area and the visible display area
95        // by using Rectangle::intersection.
96        let area = area.intersection(&self.bounding_box());
97
98        // let now = SystemTime::now();
99        if area.size != Size::zero() {
100            self.draw_iter(
101                area.points()
102                    .zip(colors)
103                    .filter(|(pos, _color)| area.contains(*pos))
104                    .map(|(pos, color)| Pixel(pos, color)),
105            )?;
106        }
107
108        Ok(())
109    }
110}
111
112impl<C> OriginDimensions for TinySkiaDisplay<C>
113where
114    C: PixelColor + From<<C as PixelColor>::Raw>,
115{
116    fn size(&self) -> Size {
117        self.size
118    }
119}
120
121impl<C> TinySkiaDisplay<C>
122where
123    C: PixelColor + From<<C as PixelColor>::Raw> + Into<Rgb888>,
124{
125    /// Creates a new display display with the given size.
126    pub fn new(width: u32, height: u32) -> Result<Self, String> {
127        Ok(TinySkiaDisplay {
128            pix_map: Pixmap::new(width, height).ok_or("Cannot create tiny-skia Pixmap")?,
129            size: Size::new(width, height),
130            // fonts: HashMap::new(),
131            _pixel_color: PhantomData::default(),
132        })
133    }
134
135    /// Returns a reference to the underlying pixel data.
136    pub fn data(&self) -> &[u8] {
137        self.pix_map.data()
138    }
139
140    pub fn draw_pixel(&mut self, position: Point, color: C) {
141        let (r, g, b, a) = rgba(color);
142
143        if position.x >= 0
144            && position.y >= 0
145            && position.x < self.size.width as i32
146            && position.y < self.size.height as i32
147        {
148            let index = (position.y as usize * self.size.width as usize + position.x as usize) * 4;
149
150            self.pix_map.data_mut()[index] = r;
151            self.pix_map.data_mut()[index + 1] = g;
152            self.pix_map.data_mut()[index + 2] = b;
153            self.pix_map.data_mut()[index + 3] = a;
154        }
155    }
156
157    /// Pushes the frame buffer to the given surface and clears the frame buffer.
158    pub fn flip(&mut self, surface: &mut [u8]) {
159        surface.copy_from_slice(self.pix_map.data_mut());
160    }
161}
162
163#[cfg(not(target_arch = "wasm32"))]
164fn rgba<C: PixelColor + Into<Rgb888>>(color: C) -> (u8, u8, u8, u8) {
165    let color: Rgb888 = color.into();
166
167    (color.b(), color.g(), color.r(), 255)
168}
169
170#[cfg(target_arch = "wasm32")]
171fn rgba<C: PixelColor + Into<Rgb888>>(color: C) -> (u8, u8, u8, u8) {
172    let color: Rgb888 = color.into();
173
174    (color.r(), color.g(), color.b(), 255)
175}
176
177// converts the given embedded-graphics pixel color to a tiny-skia paint.
178fn convert_color_to_paint<'a, C: PixelColor + Into<Rgb888>>(color: C) -> Paint<'a> {
179    let (r, g, b, a) = rgba(color);
180
181    let mut paint = Paint {
182        anti_alias: true,
183        ..Default::default()
184    };
185    paint.set_color_rgba8(r, g, b, a);
186    paint
187}