在WebAssembly/Rust中编辑画布像素数据

5

我正在尝试使用WebAssembly和Rust创建画布像素数据。作为一个初步的实验,我正在尝试让Rust写入其线性内存,然后我将使用它来创建一个ImageData对象,以便我可以将其写入画布。

ImageData的基础是一个Uint8Array,其中每个像素由rgba的4个数字组成。我在Rust中使用以下结构表示它:

struct Pixel {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

我已经将一个函数导出到JavaScript,它将尝试着对500 x 500像素的画布中的全部250,000个像素进行着色:

#[no_mangle]
pub fn color(width: u32, height: u32) {
    for i in 0..width * height {
        let ptr = (i * 4) as u64 as *mut Pixel;
        let mut pixel = unsafe { &mut *ptr };
        pixel.r = 10;
        pixel.g = 10;
        pixel.b = 10;
        pixel.a = 255;
    }
}

这是前端的相应HTML/JS代码:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

    <style>
        canvas {
            border: 1px solid red;
        }
    </style>
</head>

<body>
    <canvas id="canvas" height="500" width="500"></canvas>

    <script>

        const WIDTH = 500;
        const HEIGHT = 500;

        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');

        fetch('/rotate.wasm')
            .then((res) => res.arrayBuffer())
            .then((ab) => WebAssembly.instantiate(ab))
            .then(({ instance }) => {

                instance.exports.memory.grow(100);              // make memory big enough
                instance.exports.color(WIDTH, HEIGHT);

                const data = new Uint8ClampedArray(instance.exports.memory.buffer, 0, WIDTH * HEIGHT * 4)
                const imageData = new ImageData(data, 500, 500);

                ctx.putImageData(imageData, 0, 0);
            });
    </script>
</body>

</html>

结果是并非所有像素都被着色,只有顶部的一部分:

enter image description here

当我检查WebAssembly内存时,我发现它似乎在大约42k像素后停止了着色。

enter image description here

2个回答

6

我认为我找到了答案。不能保证线性内存的开头可供JavaScript使用。Rust在wasm二进制文件中包含的运行时可能自由地写入该内存位置。我通过在程序中静态分配一块内存并返回一个指针给JavaScript,以便它知道在哪里是安全的写入位置来解决了我的问题。

// Statically allocate space for 1m pixels

static mut PIXELS: [Pixel; 1_000_000] = [Pixel {
    r: 255,
    g: 0,
    b: 0,
    a: 255,
}; 1_000_000];

// return pointer to JavaScript

#[no_mangle]
pub fn get_memory_offset() -> i32 {
    return unsafe { &PIXELS as *const _ as i32 };
}

如果能够动态分配内存就更好了,但我还不确定如何做到这一点。


看起来你在我发布答案的同时找到了答案!https://dev59.com/06rka4cB1Zd3GeqPisTU#49937181 - ColinE
确实!发现这个问题并得到他人的确认感觉真好 :) - Matt Harrison

4
你的代码从位置0开始将图像数据写入线性内存,你确定这样做是安全的吗?大多数语言在编译成WebAssembly时,使用线性内存来运行它们自己的运行时。
更安全的选项是创建一个表示图像的结构体,然后从JavaScript代码获取对它的引用,以便确保JS和Rust代码对齐:

https://github.com/ColinEberhardt/wasm-rust-chip8/blob/master/web/chip8.js#L124


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接