Wasm ImageData issue [SOLVED]

Hello everyone,

I'm trying to fill a canvas context (2d) with ImageData but I'm getting weird results.
Here is the rust code:

extern crate serde_derive;
extern crate serde_json;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
use web_sys::*;

use wasm_bindgen::Clamped;
use wasm_bindgen::JsCast;

#[wasm_bindgen]
pub struct Client {
    context: CanvasRenderingContext2d,
    image_data: ImageData,
}

#[wasm_bindgen]
impl Client {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        let canvas_elem = window()
            .unwrap()
            .document()
            .unwrap()
            .get_element_by_id("canvas")
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()
            .unwrap();

        let context_2d = canvas_elem
            .get_context("2d")
            .unwrap()
            .unwrap()
            .dyn_into::<CanvasRenderingContext2d>()
            .unwrap();

        let image_data = ImageData::new_with_u8_clamped_array_and_sh(
            Clamped(&mut vec![255; (400 * 300 * 4) as usize]),
            400,
            300,
        )
        .unwrap();

        Self {
            context: context_2d,
            image_data: image_data,
        }
    }

    #[wasm_bindgen]
    pub fn update(&self) {
        self.context
            .put_image_data(&self.image_data, 0.0, 0.0)
            .unwrap();
    }
}

And every time I run the "update" function through javascript I get this result (The white area is the canvas):
missingpixels

The first 4 pixels are not "painted". I've been struggling with this for some time now and cannot find any answer to my problem.
Does any one knows what I'm doing wrong here?

Thanks!

I'm new to working with Rust + wasm, but a few things jump out to me:

Given you are primarily using an existing canvas, could you link to a full example that contains that HTML? Canvas can be quite dependent on the DOM context.

I also see your left red border is a few pixels off from the right. Perhaps the width dimension in your new() method is not quite aligned with the DOM element's actual width?

Hi @efx , thanks for the reply.

The html is straight forward:

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test rust wasm</title>
    <link rel="icon" href="data:;base64, iVBORwOKGO=" />
    <style>
        html,
        body {
            display: flex;
            height: 100%;
            align-items: center;
            justify-content: center;
            background-color: red;
        }

        canvas {
            width: 400px;
            height: 300px;
            transform: scale(1.5);
            image-rendering: pixelated;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>

</html>

I've scaled the canvas a bit "transform: scale(1.5)" so it could be seen better. But even without scaling the problem persists.

The red background is actually the body element and its off because I've cropped the screenshot.
The width and height in my new() method are the same as my canvas element (w:400 h:300). I also have tried with different sizes like 800x600 but the first pixels are always missing.

One other strange thing that happens is if I declare a "unused" variable in my new() method, the pixels are properly filled:

#[wasm_bindgen]
impl Client {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {

        let random = vec![255; 10]; --->> UNUSED VARIABLE FIXES THE PROBLEM?

        let canvas_elem = window()
            .unwrap()
            .document()
            .unwrap()
            .get_element_by_id("canvas")
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()
            .unwrap();

       ...

        Self {
            context: context_2d,
            image_data: image_data,
        }
    }

   ...
}

Screenshot 2020-06-01 at 19.14.02

I'm missing something here but I still cant figure out what.

Thanks!

1 Like

Ah, thanks for sharing the context; helps me understand a bit better.

One Rust specific difference I now see is the vectors have different lengths. I printed them out here.

Do you intend for the vector to always have 255 elements?

1 Like

can you try ImageData::new_with_sw(400, 300)?
I don't know what that referenced data on the stack is doing… is it copied or not?

1 Like

A bit more sane, if not pretty:

  • store Vec<u8> in the Client, making sure it is not dropped.
  • create a ImageData::new…(Clamped(self.data.as_mut())) each time you update.

My suspicion is that Imagedata borrows the data from the temporary vec… which then gets dropped. So you end up showing some memory…

1 Like

thanks @efx!

That was also a problem and helped a bit. But some (fewer) pixels were still missing!

Eventually yours and @s3bk suggestions fixed my problem!

Thanks a lot for pointing me into the right direction!

1 Like

Thanks for the reply @s3bk!

That helped a lot, I stored the data (Vec) in the client and created a new ImageData on each update, now the canvas is properly filled!

Still not sure about efficiency since I'm trying to build a realtime 3D Software Renderer with rust and wasm, but I will figure it out along the way.

Thanks!

Creating a new ImageData is "almost free", and you can keep the ImageData around as long as the Vec does not get reallocated…

1 Like

Interesting… feel free to join #rust:matrix.org if you want some better response time.

I will check it out, cheers

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.