WebAssembly 是一种新的编码方式,可以在现代的 Web 浏览器中运行------它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C/C++、C# 和 Rust 等语言提供编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。
1. Rust安装
这里我是使用Rust语言,将Rust编译成WebAssembly。
首先我们安装下 Rust 环境,这部分看MDN官方文档即可:https://developer.mozilla.org/zh-CN/docs/WebAssembly/Guides/Rust_to_Wasm
我的电脑是mac电脑,首先执行
cpp
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装好Rust和Rust 的包管理工具 cargo
要构建我们的包,我们需要一个额外工具 wasm-pack。它会帮助我们把我们的代码编译成 WebAssembly 并制造出正确的 npm 包。使用下面的命令可以下载并安装它:
cpp
cargo install wasm-pack
这里我遇到
zsh: command not found: cargo
执行下处理
cpp
source $HOME/.cargo/env
然后执行命令
cpp
rustc --version
cargo --version
看看有没有安装成功
2. 为什么用 Rust + WASM 处理图片?
浏览器端处理图片常见方案是 Canvas/WebGL/WebCodecs 或纯 JS 算法。但当处理链路更复杂(解码、滤镜、编码、批处理)时,Rust + WASM 有几个优势:
- CPU 密集型逻辑更稳定:Rust 编译成本地级指令(WASM),在大量像素运算时更可控。
- 复用成熟库生态 :例如
image提供解码、缩放、颜色变换、编码等能力。 - 与前端无缝协作 :通过
wasm-bindgen,Rust 函数可以直接在 JS 中调用,入参出参还能自动做类型转换。
3. 构建配置
我们先创建一个文件wasm_image_gray
然后执行
cpp
cargo init
这个命令相当于npm init,用于初始化配置

这里的 Cargo.toml相当于package.json
cpp
[package]
name = "wasm_image_gray"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ['cdylib']
[dependencies]
wasm-bindgen = "0.2"
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg"]
在 wasm_image_gray/Cargo.toml 里有 3 个关键点:
-
crate-type = ['cdylib']这是给 WASM 输出用的 crate 类型,能生成适合被外部(JS)加载的二进制产物。
-
依赖
wasm-bindgen = "0.2"它负责把 Rust 导出的函数生成 JS "胶水代码",并处理 JS/WASM 之间的类型映射(如
&[u8]、Vec<u8>等)。 -
依赖
imagerust内置库,并关闭默认特性:
toml
[dependencies.image]
version = "0.24"
default-features = false
features = ["png", "jpeg"]
这样做的目的通常是减小体积、减少不必要编译内容;但启用 png/jpeg 仍会带来相当多编译量(这也是很多人第一次 wasm-pack build 觉得"卡住"的原因之一:首次编译依赖很重)。
4. Rust 侧:导出给 JS 的 WASM API
核心代码在 wasm_image_gray/src/lib.rs。
cpp
use std::io::Cursor;
use wasm_bindgen::prelude::*;
use image::ImageOutputFormat;
// 宏定义,来给 web 端暴露 wasm 提供方法
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
Ok(())
}
// 定义一个函数,来给 web 端调用
#[wasm_bindgen]
pub fn greeting(name: &str) -> Result<String, JsValue> {
Ok(format!("Hello, {}!", name))
}
// 处理图片
#[wasm_bindgen]
pub fn process_image_wasm(input_data:&[u8]) -> Result<Vec<u8>, JsValue>{
let img = image::load_from_memory(input_data).map_err(|e|JsValue::from_str(&format!("图片加载失败: {}", e)))?;
let scaled = img.resize(800, 600, image::imageops::FilterType::Lanczos3);
// 滤镜处理
let grayscale = scaled.grayscale().blur(3.5).brighten(5);
let mut result_buf = Vec::new();
// 将处理后的图片写入缓冲区,格式为 JPEG,质量默认 (通常是 75)
grayscale.write_to(&mut Cursor::new(&mut result_buf), ImageOutputFormat::Jpeg(80))
.map_err(|e| JsValue::from_str(&format!("图片编码失败: {}", e)))?;
// 4. 返回 Vec<u8>
// wasm_bindgen 会自动将其转换为 JS 的 Uint8Array
Ok(result_buf)
}
4.1 #[wasm_bindgen(start)]:模块初始化入口
cpp
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
Ok(())
}
- 被标记为 start 的函数会在 WASM 模块初始化时调用(对应 JS 侧 init() 完成后的启动阶段)。
- 这里暂时不做事,只返回 Ok(()),但你可以在这里做全局初始化、设置 panic hook(用于更友好的错误)等。
4.2 process_image_wasm:图片处理主函数
cpp
#[wasm_bindgen]
pub fn process_image_wasm(input_data: &[u8]) -> Result<Vec<u8>, JsValue> { ... }
这个函数是整个案例的核心:输入是图片文件的字节数组,输出是处理后的 JPEG 字节数组。
处理链路可以拆成 4 步:
Step 1:从内存字节解码图片
cpp
let img = image::load_from_memory(input_data)
.map_err(|e| JsValue::from_str(&format!("图片加载失败: {}", e)))?;
load_from_memory会根据图片格式自动识别解码(这里启用了 png 和 jpeg)。- 错误通过 JsValue 返回给 JS,便于前端 catch 并提示。
Step 2:缩放(Resize)
cpp
let scaled = img.resize(800, 600, image::imageops::FilterType::Lanczos3);
这里固定缩放到 800x600,滤波器用 Lanczos3,质量相对更好,但计算量也更大。
如果你追求更快速度可以考虑 Triangle 或 Nearest(质量下降但更快)。
Step 3:滤镜链
cpp
let grayscale = scaled.grayscale().blur(3.5).brighten(5);
grayscale():转灰度blur(3.5):高斯模糊(参数越大越慢)brighten(5):提亮
这段代码很"链式",可读性强,也清晰表达图像处理 pipeline。
Step 4:编码为 JPEG 并返回字节
cpp
let mut result_buf = Vec::new();
grayscale
.write_to(&mut Cursor::new(&mut result_buf), ImageOutputFormat::Jpeg(80))
.map_err(|e| JsValue::from_str(&format!("图片编码失败: {}", e)))?;
Ok(result_buf)
Cursor把Vec<u8>伪装成一个"可写入流",给write_to使用。Jpeg(80)是质量参数(0-100),80通常在清晰度与压缩率之间比较平衡。- 返回
Vec<u8>后,wasm-bindgen会把它转换成 JS 可用的Uint8Array。
5. JS/HTML 侧:如何把文件字节送进 WASM,再把结果展示出来
入口页面在 wasm_image_gray/index.html。核心逻辑分 3 段:
cpp
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Rust + WASM 图片极速处理实战</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
max-width: 800px;
margin: 0 auto;
background: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.btn-group {
margin: 20px 0;
}
button {
padding: 10px 20px;
cursor: pointer;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.preview-area {
display: flex;
gap: 20px;
margin-top: 20px;
}
.img-box {
flex: 1;
border: 1px dashed #ddd;
padding: 10px;
text-align: center;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
max-height: 300px;
}
.stats {
margin-top: 10px;
font-size: 14px;
color: #666;
font-family: monospace;
}
h3 {
margin: 0 0 10px 0;
font-size: 16px;
color: #333;
}
</style>
</head>
<body>
<div class="container">
<h2>⚡ WASM 图片处理器 (Rust Powered)</h2>
<p>处理流程:Resize(800px) -> Grayscale -> JPEG Encode</p>
<!-- 文件选择 -->
<input type="file" id="upload" accept="image/*" />
<!-- 预览区域 -->
<div class="preview-area">
<div class="img-box">
<h3>原始图片 (JS)</h3>
<img id="img-origin" />
<div id="stats-origin" class="stats"></div>
</div>
<div class="img-box">
<h3>WASM 处理结果</h3>
<img id="img-result" />
<div id="stats-result" class="stats">等待处理...</div>
</div>
</div>
</div>
<!-- 引入 WASM 胶水代码 -->
<script type="module">
// 注意:需先运行 wasm-pack build --target web,打包生成 wasm 文件,输出至 pkg 文件夹中
// 并且 index.html 与 pkg 文件夹在同一级目录
import init, { process_image_wasm } from "./pkg/wasm_image_gray.js";
async function main() {
// 1. 初始化 WASM 实例 (加载 .wasm 文件)
await init();
console.log("✅ WASM 模块加载完成");
const uploadInput = document.getElementById("upload");
const imgOrigin = document.getElementById("img-origin");
const imgResult = document.getElementById("img-result");
const statsOrigin = document.getElementById("stats-origin");
const statsResult = document.getElementById("stats-result");
uploadInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
console.log("🚀 ~ main ~ file:", file);
if (!file) return;
// 2. 显示原图信息
statsOrigin.innerHTML = `大小: ${(file.size / 1024).toFixed(2)} KB`;
imgOrigin.src = URL.createObjectURL(file);
// 3. 读取文件为 ArrayBuffer (JS -> WASM 内存交互的第一步)
const reader = new FileReader();
reader.onload = async (event) => {
const arrayBuffer = event.target.result;
// 转换为 Uint8Array,这是 Rust 能够识别的 &[u8]
const uint8Array = new Uint8Array(arrayBuffer);
try {
statsResult.innerHTML = "处理中...";
// --- ⏱️ 开始计时 ---
const start = performance.now();
// 4. 调用 Rust 函数
// 这一步发生了数据穿越:JS内存 -> WASM线性内存 -> 计算 -> 写入WASM线性内存 -> 拷贝回JS
const resultData = process_image_wasm(uint8Array);
const end = performance.now();
// --- ⏱️ 结束计时 ---
// 5. 将结果 Uint8Array 转回 Blob 显示
const blob = new Blob([resultData], { type: "image/jpeg" });
imgResult.src = URL.createObjectURL(blob);
statsResult.innerHTML = `
耗时: <b style="color:red">${(end - start).toFixed(
2
)} ms</b><br>
大小: ${(blob.size / 1024).toFixed(2)} KB<br>
压缩率: -${((1 - blob.size / file.size) * 100).toFixed(
1
)}%
`;
} catch (err) {
console.error(err);
statsResult.innerHTML = "处理失败,请看控制台";
}
};
reader.readAsArrayBuffer(file);
});
}
main();
</script>
</body>
</html>
5.1 初始化 WASM 模块
cpp
import init, { process_image_wasm } from "./pkg/wasm_image_gray.js";
await init();
console.log("✅ WASM 模块加载完成");
wasm-pack build --target web会在pkg/下生成wasm_image_gray.js和.wasm文件。init()负责加载并实例化wasm。
5.2 读取用户上传的文件为 Uint8Array
cpp
const reader = new FileReader();
reader.onload = async (event) => {
const arrayBuffer = event.target.result;
const uint8Array = new Uint8Array(arrayBuffer);
const resultData = process_image_wasm(uint8Array);
};
reader.readAsArrayBuffer(file);
FileReader把图片读取为ArrayBuffer- 再用
Uint8Array包装,这样才能以字节序列形式传给Rust 的 &[u8]
5.3性能统计与展示结果
cpp
const start = performance.now();
const resultData = process_image_wasm(uint8Array);
const end = performance.now();
const blob = new Blob([resultData], { type: "image/jpeg" });
imgResult.src = URL.createObjectURL(blob);
- 用
performance.now()统计一次处理耗时 - 返回的 resultData 作为 Blob 生成 URL,展示到
<img>标签 - 另外也计算了输出大小和压缩率
6.构建与运行
在 wasm_image_gray/ 目录下执行:
- 构建 WASM 包
cpp
wasm-pack build --target web
看下效果
