Node.js 构建一个 Rust 应用图片处理应用

WebAssembly/wasm WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。
WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web的沙箱环境下。

构建 wasm 的开发语言

目前wasm支持多种后端语言开发:
常用的C/C++、Golang、JS、Python等
可以参考 WebAssembly Awesome Github.

为什么使用rust

最主要原因是目前rust对于wasm有整套的运行编译链,更加完善的环境

引用 rust官网 的文字可以说明一下

1.高性能

Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。

2.可靠性

Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。

3.生产力

Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具——包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。

rust opencv调用 rust调用js_javascript

rust 安装与可编译生成的 wasm 目标 target

安装rust 参考官网的document即可
查看一下安装版本

$ rustc -V
rustc 1.46.0-nightly (feb3536eb 2020-06-09)

安装成功
通过执行

rustup target list

可以查看到rust支持的wasm类型

asmjs-unknown-emscripten
wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-wasi

前3个主要编译用于前端web页面的js调用,没有IO操作。
本文主要讲解第4种 ,WASI 是 The WebAssembly System Interface 的缩写,简单来说就是一套接口标准,将 wasm 的应用领域从 web 中拓展到更广阔的各个平台中去(可以想像成类似 libc 之类的东西,但是是跨平台的)。
目前 wasi 的执行主要依靠安全可靠的运行时(可理解成沙箱或者虚拟机环境)环境执行,保证更加安全的数据,主要开发嵌入开发、区块链技术、人工智能等应用。
常用的运行时环境有wasmtime、wasmer、ssvm

在 SSVM 环境中开发一款图片处理应用

开发环境必备:
Ubuntu
docker
Koajs

开发流程图如下:


web端 Koa后端 rust生成的wasm 图片上传至后端 图片数据生成 uint8Array 图片处理后的base64码 前端渲染base64图片 web端 Koa后端 rust生成的wasm


具体代码实现

  1. web 页面实现
<form action="/api/image" method="post" enctype="multipart/form-data">
    <input type="file" name="file" id="file" value=""/>
    <input type="submit" value="提交"/>
</form>

form表单提交文件,action为后端接口

  1. Koa处理图片数据转成 uint8Array 类型
    安装koa-body处理参数
const koaBody = require('koa-body');
app.use(koaBody({
  multipart: true,
  formidable: {
    maxFileSize: 200 * 1024 * 1024    // 设置上传文件大小最大限制,默认2M
  }
}));

具体处理图片

if (ctx.url === '/api/image' && ctx.method === 'POST') {
      let data = await fs.readFileSync(ctx.request.files.file.path)
      let u8arr = new Uint8Array(data)
      let data2 = get_image_gray(u8arr)
      ctx.body = {
        msg: `${data2}`
      }
    }
  1. rust后端处理
    通过cargo创建项目,在cargo.toml插入
[lib]
name = "ssvm_nodejs_starter_lib"
path = "src/lib.rs"
crate-type =["cdylib"]

[dependencies]
wasm-bindgen = "=0.2.61"
image = { version = "0.23.8", default-features = false, features = ["jpeg", "png"] }
base64 = "0.12.1"

具体图片处理使用image以及base64

直接上代码

use wasm_bindgen::prelude::*;
extern crate image;
use base64::encode;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::vec::Vec;

// 加载图片获取图片信息
fn load_image_from_array(arr: &[u8]) -> image::DynamicImage {
    let img = match image::load_from_memory_with_format(arr, image::ImageFormat::Png) {
        Ok(img) => img,
        Err(err) => {
            panic!("format error, {:?}", err);
        }
    };
    img
}
//将图片转成base64返回
fn get_base64_image(img: image::DynamicImage) -> String {
    let mut c = Cursor::new(Vec::new());
    match img.write_to(&mut c, image::ImageFormat::Png) {
        Ok(c) => c,
        Err(error) => panic!(
            "There was a problem writing the resulting buffer: {:?}",
            error
        ),
    };
    c.seek(SeekFrom::Start(0)).unwrap();
    let mut out = Vec::new();
    c.read_to_end(&mut out).unwrap();
    let stt = encode(&mut out);
    let together = format!("{}{}", "data:image/png;base64,", stt);
    return together;
}

// 需要传到前端的处理图片函数
#[wasm_bindgen]
pub fn get_image_blur(data: &[u8]) -> String {
    let mut image = load_image_from_array(data);
    image = image.blur(2.0);// 将图片模糊处理、也可以缩放、灰度、对比度等处理
    let base64_str = get_base64_image(image);
    return base64_str;
}

参考源代码

github 地址ssvm 相关