你给网页加了个“复古滤镜”功能,结果一拖动滑块,页面直接卡死。用户点一下,风扇狂转,手机发烫。今天我们用 Rust + WebAssembly 写一个图片滤镜,让图像处理速度飞起来。原来C++能做的事,Rust也能做,而且更安全、更简单。
纯 JS 处理图像有多慢?假设你要把一张 4K 图片(约 8 百万像素)转成黑白,每个像素都要计算 R、G、B 的平均值。JS 需要遍历所有像素,做 2400 万次运算。这在现代设备上可能还要 100 毫秒,但一旦加上更复杂的滤镜(高斯模糊、边缘检测),帧率直接掉到个位数。
WebAssembly 的出现,让浏览器能以接近原生的速度执行代码。而 Rust 凭借零成本抽象和内存安全,成了写 Wasm 的首选语言。今天我们就来实战:用 Rust 写一个图像灰度滤镜,编译成 Wasm,然后在网页上让用户拖拽实时预览。全程可运行,不画饼。
wasm-pack 一键打包,自动生成 JS 胶水代码和 TypeScript 类型定义。你需要安装 Rust(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh)和 wasm-pack(cargo install wasm-pack)。
创建一个新项目:
cargo new --lib image-filter
cd image-filter
编辑 Cargo.toml:
[package]
name = "image-filter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
在 src/lib.rs 中:
use wasm_bindgen::prelude::*;
// 将 Rust 函数暴露给 JS
#[wasm_bindgen]
pubfngrayscale(data: &mut [u8], width: u32, height: u32) {
// data 是 RGBA 像素数组,每个像素 4 个字节:R, G, B, A
for pixel in data.chunks_exact_mut(4) {
let r = pixel[0] asu32;
let g = pixel[1] asu32;
let b = pixel[2] asu32;
// 灰度公式:0.299*R + 0.587*G + 0.114*B
let gray = ((r * 299 + g * 587 + b * 114) / 1000) asu8;
pixel[0] = gray;
pixel[1] = gray;
pixel[2] = gray;
// alpha 不变
}
}
这个函数会直接修改原数组,没有内存拷贝,效率极高。
wasm-pack build --target web
输出在 pkg/ 目录,包含 .wasm 文件、JS 绑定和 TypeScript 类型。
创建一个 index.html:
<!DOCTYPE html>
<html>
<head>
<title>Rust Wasm 图像滤镜</title>
<style>
canvas { border: 1px solid #ccc; max-width: 100%; }
.container { display: flex; gap: 20px; flex-wrap: wrap; }
button { margin-top: 10px; padding: 8px16px; }
</style>
</head>
<body>
<h1>Rust + WebAssembly 实时灰度滤镜</h1>
<inputtype="file"id="upload"accept="image/*">
<divclass="container">
<div>
<canvasid="original"width="400"height="400"></canvas>
<div>原图</div>
</div>
<div>
<canvasid="filtered"width="400"height="400"></canvas>
<div>灰度滤镜(Rust Wasm)</div>
</div>
</div>
<buttonid="apply">应用滤镜</button>
<scripttype="module">
import init, { grayscale } from'./pkg/image_filter.js';
asyncfunctionrun() {
await init(); // 加载 Wasm
const upload = document.getElementById('upload');
const originalCanvas = document.getElementById('original');
const filteredCanvas = document.getElementById('filtered');
const applyBtn = document.getElementById('apply');
let originalImageData = null;
upload.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const img = new Image();
img.onload = () => {
// 绘制原图
originalCanvas.width = img.width;
originalCanvas.height = img.height;
filteredCanvas.width = img.width;
filteredCanvas.height = img.height;
const ctx = originalCanvas.getContext('2d');
ctx.drawImage(img, 0, 0);
originalImageData = ctx.getImageData(0, 0, img.width, img.height);
// 默认显示原图到右侧
applyFilter();
};
img.src = URL.createObjectURL(file);
});
functionapplyFilter() {
if (!originalImageData) return;
// 复制图像数据(避免修改原图)
const dataCopy = newUint8ClampedArray(originalImageData.data);
const width = originalImageData.width;
const height = originalImageData.height;
// 调用 Rust 函数,直接修改 dataCopy
grayscale(dataCopy, width, height);
// 显示到右侧 canvas
const imageData = new ImageData(dataCopy, width, height);
const ctx = filteredCanvas.getContext('2d');
ctx.putImageData(imageData, 0, 0);
}
applyBtn.addEventListener('click', applyFilter);
}
run();
</script>
</body>
</html>
注意:pkg 目录需要在一个静态服务器下运行,比如 npx http-server。直接打开 HTML 会因 CORS 或跨域问题无法加载 Wasm。
选择一个高分辨率图片,点击“应用滤镜”。你会发现几乎是瞬间完成——因为 Rust 循环编译成 Wasm 后,速度比纯 JS 循环快 5-10 倍。即使 4K 图片,也感觉不到延迟。
如果想对比 JS 版本,可以用同样的灰度算法写一个纯 JS 函数,你会明显感受到卡顿(尤其是拖动滑块实时调整时)。
把 applyBtn 换成 range slider,监听 input 事件,每帧都调用 grayscale。由于 Wasm 够快,可以做到 60fps 实时调参。
const intensitySlider = document.getElementById('intensity');
intensitySlider.addEventListener('input', () => {
const val = parseFloat(intensitySlider.value);
// 将强度作为参数传给 Rust(需要修改 Rust 函数,增加亮度系数)
// 略...
});
你甚至可以实现更复杂的滤镜(模糊、边缘检测、油画效果),只要把算法用 Rust 实现,其余和灰度类似。
Uint8ClampedArray 传给 Rust,wasm-bindgen 会自动共享内存,不需要拷贝。ImageBitmap 和 OffscreenCanvas,避免阻塞主线程。Wasm 本身是在主线程跑的,除非你用 Worker。wasm-pack build --release 可以大幅减小体积。还可以用 wasm-opt 进一步优化。wasm-pack 生成开箱即用的 JS 模块。下次老板让你加个“实时滤镜”,别再写三重循环的 JS 了。用 Rust,一秒处理 4K 图,用户只会觉得你的网站“好丝滑”。