embedded_graphics_web_simulator/
display.rs

1use core::marker::PhantomData;
2use std::error::Error;
3
4use embedded_graphics::{
5    geometry::Size,
6    pixelcolor::{PixelColor, Rgb888},
7    prelude::*,
8    primitives::Rectangle,
9};
10use wasm_bindgen::{Clamped, JsCast, JsValue};
11use web_sys::{CanvasRenderingContext2d, Element, ImageData};
12
13use crate::output_settings::OutputSettings;
14
15/// WebSimulator display.
16pub struct WebSimulatorDisplay<C> {
17    size: Size,
18    canvas_size: Size,
19    output_settings: OutputSettings,
20    backing: Vec<u8>,
21    context: CanvasRenderingContext2d,
22    _color_type: PhantomData<C>,
23}
24
25impl<C> WebSimulatorDisplay<C>
26where
27    C: PixelColor + Into<Rgb888>,
28{
29    /// Creates a new display.
30    ///
31    /// This appends a `<canvas>` element with size corresponding to scale and pixel spacing used
32    /// The display is filled with black.
33    pub fn new(
34        size: (u32, u32),
35        output_settings: &OutputSettings,
36        parent: Option<&Element>,
37    ) -> Self {
38        // source: https://github.com/embedded-graphics/simulator/blob/master/src/output_settings.rs
39        let canvas_width =
40            size.0 * output_settings.scale + (size.0 - 1) * output_settings.pixel_spacing;
41        // source: https://github.com/embedded-graphics/simulator/blob/master/src/output_settings.rs
42        let canvas_height =
43            size.1 * output_settings.scale + (size.1 - 1) * output_settings.pixel_spacing;
44
45        let document = web_sys::window().unwrap().document().unwrap();
46        let canvas = document.create_element("canvas").unwrap();
47        let canvas: web_sys::HtmlCanvasElement = canvas
48            .dyn_into::<web_sys::HtmlCanvasElement>()
49            .map_err(|_| ())
50            .unwrap();
51        canvas.set_width(canvas_width);
52        canvas.set_height(canvas_height);
53        parent
54            .unwrap_or(
55                &document
56                    .body()
57                    .expect("document doesn't have a body and no alternative parent was supplied")
58                    .dyn_into::<web_sys::Element>()
59                    .map_err(|_| ())
60                    .unwrap(),
61            )
62            .append_child(&canvas)
63            .expect("couldn't append canvas to parent");
64        let context = canvas
65            .get_context("2d")
66            .unwrap()
67            .unwrap()
68            .dyn_into::<web_sys::CanvasRenderingContext2d>()
69            .unwrap();
70
71        // source: https://github.com/embedded-graphics/simulator/blob/master/src/output_settings.rs#L39
72
73        WebSimulatorDisplay {
74            size: Size::new(size.0, size.1),
75            canvas_size: Size::new(canvas_width, canvas_height),
76            backing: vec![0; (4 * canvas_width * canvas_height) as usize],
77            context,
78            output_settings: output_settings.clone(),
79            _color_type: PhantomData,
80        }
81    }
82
83    pub fn flush(&mut self) -> Result<(), JsValue> {
84        let backing = self.backing.as_mut_slice();
85        let image_data =
86            ImageData::new_with_u8_clamped_array(Clamped(backing), self.canvas_size.width)
87                .expect("could not create ImageData");
88        self.context.put_image_data(&image_data, 0., 0.)?;
89        Ok(())
90    }
91}
92impl<C> OriginDimensions for WebSimulatorDisplay<C>
93where
94    C: PixelColor + Into<Rgb888>,
95{
96    fn size(&self) -> Size {
97        self.size
98    }
99}
100
101impl<C> DrawTarget for WebSimulatorDisplay<C>
102where
103    C: PixelColor + Into<Rgb888>,
104{
105    type Color = C;
106    type Error = Box<dyn Error>;
107
108    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
109    where
110        I: IntoIterator<Item = Pixel<Self::Color>>,
111    {
112        let canvas_width = self.canvas_size.width as usize;
113        let backing = self.backing.as_mut_slice();
114
115        let scale = self.output_settings.scale as usize;
116
117        // source: https://github.com/embedded-graphics/simulator/blob/master/src/output_settings.rs#L39
118        let pitch = scale + self.output_settings.pixel_spacing as usize;
119
120        let bounding_box = Rectangle::new(Point::new(0, 0), self.size);
121        for pixel in pixels.into_iter() {
122            let point = pixel.0;
123            if bounding_box.contains(point) {
124                let rgb: Rgb888 = pixel.1.into();
125                let rgb_slice = &[rgb.r(), rgb.g(), rgb.b(), 255];
126                let py = point.y as usize;
127                let px = point.x as usize;
128
129                let x_offset = px * 4 * pitch;
130                for y in 0..scale {
131                    let y_offset = py * 4 * canvas_width * pitch + y * 4 * canvas_width;
132                    for x in 0..scale {
133                        let pixel_offset = y_offset + x_offset + x * 4;
134                        backing[pixel_offset..pixel_offset + 4].copy_from_slice(rgb_slice);
135                    }
136                }
137            }
138        }
139
140        Ok(())
141    }
142}