rust - 编译为 WebAssembly 时无法使 image::load_from_memory() 工作

标签 rust webassembly

我正在尝试使用 image crate 将图像从 JavaScript 加载到带有 Rust 的 WebAssembly .

我有以下 Rust 代码:

extern crate image;
extern crate libc;

use libc::c_void;
use std::mem;

#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut c_void {
    let mut buf = Vec::with_capacity(size);
    let ptr = buf.as_mut_ptr();
    mem::forget(buf);

    return ptr as *mut c_void;
}

#[no_mangle]
pub extern "C" fn read_img(buff_ptr: *mut u8, buff_len: usize) -> *mut i32 {
    let mut img: Vec<u8> = unsafe { Vec::from_raw_parts(buff_ptr, buff_len, buff_len) };
    let ok = Box::new([333]);
    let err = Box::new([331]);

    return match image::load_from_memory(&img) {
        Ok(img) => Box::into_raw(ok) as *mut i32,
        Err(_) => Box::into_raw(err) as *mut i32,
    };
}

fn main() {}

我使用以下工具编译:

cargo +nightly build --target wasm32-unknown-unknown --release

read_img() 函数中,我天真地通过两个向量处理错误:[333] 用于 OK,[331] 用于任何错误。我在 JavaScript 端读取了这些向量,以了解图像是否已成功加载。

load_from_memory 方法失败,因为我得到了 [331] 向量。如果我用 guess_format 方法替换 load_from_memory 方法,我会得到 [333] 向量。我还为 PNG 和 JPG 做了一些模式匹配,它正确地读取了缓冲区。

我找不到如何更彻底地调试此类行为。

在 JavaScript 部分,我只是将图像的 arrayBuffer 加载到 WASM 的共享内存中。

这是我在 JavaScript 方面所做的:

function compile(wasmFile = 'distil_wasm.gc.wasm') {
    return fetch(wasmFile)
        .then(r => r.arrayBuffer())
        .then(r => {
            let module = new WebAssembly.Module(r);
            let importObject = {}
            for (let imp of WebAssembly.Module.imports(module)) {
                if (typeof importObject[imp.module] === "undefined")
                    importObject[imp.module] = {};
                switch (imp.kind) {
                case "function": importObject[imp.module][imp.name] = () => {}; break;
                case "table": importObject[imp.module][imp.name] = new WebAssembly.Table({ initial: 256, maximum: 256, element: "anyfunc" }); break;
                case "memory": importObject[imp.module][imp.name] = new WebAssembly.Memory({ initial: 256 }); break;
                case "global": importObject[imp.module][imp.name] = 0; break;
                }
            }

            return WebAssembly.instantiate(r, importObject);
        });
}

function loadImgIntoMem(img, memory, alloc) {
    return new Promise(resolve => {
        fetch(img)
            .then(r => r.arrayBuffer())
            .then(buff => {
                const imgPtr = alloc(buff.byteLength);
                const imgHeap = new Uint8Array(memory.buffer, imgPtr, buff.byteLength);

                imgHeap.set(new Uint8Array(buff));

                resolve({ imgPtr, len: buff.byteLength });
            });
    });
}


function run(img) {
    return compile().then(m => {
        return loadImgIntoMem(img, m.instance.exports.memory, m.instance.exports.alloc).then(r => {
            window.WASM = m;
            return m.instance.exports.read_img(r.imgPtr, r.len);
        });
    });
}

run('img-2.jpg')
   .then(ptr => console.log(new Int32Array(WASM.instance.exports.memory.buffer, ptr, 1)))

此控制台记录:

Int32Array [ 331 ]

最佳答案

如果无法访问调试器或无法打印消息,则基本上不可能进行调试。因此,我移植了您的代码以使用 wasm-bindgen ,纯粹是为了能够从 Rust 代码内部访问控制台:

#![feature(proc_macro, wasm_custom_section, wasm_import_module)]

extern crate wasm_bindgen;
extern crate image;

use wasm_bindgen::prelude::*;
use std::mem;

pub mod console {
    use wasm_bindgen::prelude::*;

    #[wasm_bindgen]
    extern {
        #[wasm_bindgen(js_namespace = console)]
        pub fn log(s: &str);
    }
}

#[wasm_bindgen]
pub fn alloc(len: usize) -> *mut u8 {
    let mut buf = Vec::with_capacity(len);
    let ptr = buf.as_mut_ptr();
    mem::forget(buf);
    ptr
}

#[wasm_bindgen]
pub fn read_img(ptr: *mut u8, len: usize) {
    let img = unsafe { Vec::from_raw_parts(ptr, len, len) };

    if let Err(e) = image::load_from_memory(&img) {
        console::log(&e.to_string());
    }
}

更新后的 JavaScript:

const js = import("./imaj_bg");

async function loadImgIntoMem(img, { alloc, memory }) {
  const resp = await fetch(img);
  const buf = await resp.arrayBuffer();

  const len = buf.byteLength;
  const ptr = alloc(len);

  const imgArray = new Uint8Array(memory.buffer, ptr, len);    
  imgArray.set(new Uint8Array(buf));

  return { ptr, len };
}

async function go(js) {
  const { ptr, len } = await loadImgIntoMem('cat.jpg', js);
  js.read_img(ptr, len);
};

js.then(go);

构建和服务代码:

$ cargo build --target wasm32-unknown-unknown --release
$ wasm-bindgen target/wasm32-unknown-unknown/release/imaj.wasm --out-dir=.
$ yarn serve

访问页面并查看控制台日志会显示这条虎头蛇尾的消息:

operation not supported on wasm yet

事实是,Rust 标准库的很大一部分在 WebAssembly 中尚不存在。其中许多被删除以返回此错误。

我不确切知道您的代码缺少哪个平台支持。最明显的一个是线程,jpeg_rayonhdr 功能需要,但是关闭除 jpeg 之外的所有图像功能仍然报告相同的错误。可能还需要其他东西。

但是,它似乎确实特定于给定的图像编解码器。如果您尝试相同的代码但加载了 PNG 图像,则成功:

pub fn read_img(ptr: *mut u8, len: usize) {
    let img = unsafe { Vec::from_raw_parts(ptr, len, len) };

    let img = match image::load_from_memory(&img) {
        Ok(i) => i,
        Err(e) => {
            console::log(&e.to_string());
            return;
        }
    };

    console::log(&format!("{:?}", img.to_rgba()));
}
ImageBuffer { width: 305, height: 314, _phantom: PhantomData, data: [255, 255, 255, 0 /* remaining pixels skipped */

这表明 JPEG 代码还不适用于 WASM。给定的编解码器可能工作也可能不工作;最好向上游维护者提出问题。

关于rust - 编译为 WebAssembly 时无法使 image::load_from_memory() 工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50415623/

相关文章:

javascript - 我应该以哪种格式将图像从 Rust 传递到 JS?

javascript - 我可以使用 WebAssembly 在我的 Web 应用程序中安全地执行不受信任的用户代码吗?

rust - 将数据序列化到结构模型中,其中两个字段的数据是根据结构中的其他字段计算的

generics - 在 Rust 中,如何将 "add" `flatten"转换为 Option<Option<T>>?

rust - serialize::Decodable 没有为类型 `&str` 实现

javascript - WebAssembly、JavaScript 和其他语言

javascript - WebAssembly:类型错误:WebAssembly 实例化:必须存在导入参数

rust - 如何在以 WASM 为目标的 near-sdk Rust 代码中链接 WASM 二进制文件

rust - 是否可以将包含闭包的 Rust 结构存储在不同的结构中?

syntax - 为什么宏调用后没有分号?