← レッスン一覧に戻る レッスン 20 / 28
中級 graphics
画像処理
はじめに
画像処理はWasmの最も強力なユースケースの1つです。ピクセル単位の操作はCPU集約的であり、Rust/WasmはこれらのJavaScript処理より5〜10倍高速です。すべての処理はクライアントサイドで行われるため、画像がユーザーのブラウザから外に出ることはありません。
仕組み
┌───────────┐ getImageData() ┌──────────┐ putImageData() ┌───────────┐
│ <canvas> │ ────────────────▶ │ Rust/Wasm │ ────────────────▶ │ <canvas> │
│ (ソース) │ Uint8Array │ (処理) │ Uint8Array │ (結果) │
└───────────┘ └──────────┘ └───────────┘- 画像をCanvasに読み込む
- ピクセルデータを
Uint8Array(RGBA形式)として取得 - Rustに渡してインプレースで変更
- 変更されたピクセルをCanvasに戻す
JavaScriptとの統合
import init, { grayscale, brightness, sepia } from './pkg/image_wasm.js';
await init();
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 画像を読み込む
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
};
img.src = 'photo.jpg';
// フィルターを適用
function applyFilter(filterFn) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
filterFn(imageData.data); // インプレースで変更!
ctx.putImageData(imageData, 0, 0);
}
// 使用例
applyFilter(grayscale);
applyFilter((pixels) => brightness(pixels, 30));
applyFilter(sepia);ピクセル形式
Canvasの ImageData はRGBA形式のフラットな Uint8Array です:
Index: 0 1 2 3 4 5 6 7 ...
Data: [R₀] [G₀] [B₀] [A₀] [R₁] [G₁] [B₁] [A₁] ...
└── pixel 0 ──┘ └── pixel 1 ──┘Rustでは chunks_exact_mut(4) を使って4バイトずつ処理します。
ボックスブラー
#[wasm_bindgen]
pub fn blur(pixels: &mut [u8], width: u32, height: u32, radius: u32) {
let src = pixels.to_vec();
let r = radius as i32;
let w = width as i32;
let h = height as i32;
for y in 0..h {
for x in 0..w {
let mut sum_r = 0u32;
let mut sum_g = 0u32;
let mut sum_b = 0u32;
let mut count = 0u32;
for dy in -r..=r {
for dx in -r..=r {
let nx = (x + dx).clamp(0, w - 1);
let ny = (y + dy).clamp(0, h - 1);
let idx = ((ny * w + nx) * 4) as usize;
sum_r += src[idx] as u32;
sum_g += src[idx + 1] as u32;
sum_b += src[idx + 2] as u32;
count += 1;
}
}
let idx = ((y * w + x) * 4) as usize;
pixels[idx] = (sum_r / count) as u8;
pixels[idx + 1] = (sum_g / count) as u8;
pixels[idx + 2] = (sum_b / count) as u8;
}
}
}パフォーマンス比較
| フィルター | JavaScript | Rust/Wasm | 高速化 |
|---|---|---|---|
| グレースケール(4K画像) | 45ms | 5ms | 9倍 |
| 明るさ(4K) | 40ms | 4ms | 10倍 |
| ボックスブラー r=3(4K) | 2,500ms | 280ms | 9倍 |
| セピア(4K) | 50ms | 6ms | 8倍 |
重要な最適化:インプレース変更
Rustは Uint8Array をインプレースで変更します。データのコピーは不要です:
pub fn grayscale(pixels: &mut [u8]) { // &mut = インプレースで変更
// pixelsはCanvasのImageDataバッファそのもの
// 変更はJS側に即座に反映される
}これは最も高速なアプローチです。アロケーションもコピーもゼロです。
試してみよう
スターターコードには4つの画像フィルターが含まれています。実際のプロジェクトでは、Canvasの imageData.data をこれらの関数に直接渡して、結果を即座に確認できます。
試してみる
チャプタークイズ
すべての問題に正解してレッスンを完了しましょう