PR

Rust と WebAssembly を使って Canvas に描画してみる

1. はじめに

前回、Rust から WebAssembly を使う方法を書いたので、今回はその続きとして、Rust と WebAssembly を使って Canvas に描画してみます。

Canvas は、HTML5 で追加された描画用の要素で、JavaScript を使って描画することができます。
wasm-bindgen を使うことで、Rust から JavaScript の関数を呼び出すことができるので、Rust から Canvas に描画することができるようです。
wasm-pack は内部的に wasm-bindgen を使っているので、wasm-pack を使うことで wasm-bindgen の機能を使うことができます。
wasm-pack を使うことで開発ワークフローを効率化することができるようですが、npm で動かすためせっかく Docker で動くようにしたので、wasm-pack は使わず、wasm-bindgen で試してみます。

2. Cargo.toml の編集

[dependencies]
js-sys = "0.3.77"
wasm-bindgen = "0.2.100"
web-sys = { version = "0.3.77", features = ['CanvasRenderingContext2d', 'Document', 'Element', 'HtmlCanvasElement', 'Window'] }

3つのクレートを使います。

  • js-sys: JavaScript の API を Rust から使うためのクレート
  • wasm-bindgen: Rust と JavaScript の相互運用を可能にするためのクレート
  • web-sys: Web API を Rust から使うためのクレート

3. lib.rs の編集

2D Canvas にあるコードを丸っとコピペします。

use std::f64;
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
fn start() {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("Canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();

    context.begin_path();

    // Draw the outer circle.
    context
        .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the mouth.
    context.move_to(110.0, 75.0);
    context.arc(75.0, 75.0, 35.0, 0.0, f64::consts::PI).unwrap();

    // Draw the left eye.
    context.move_to(65.0, 65.0);
    context
        .arc(60.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the right eye.
    context.move_to(95.0, 65.0);
    context
        .arc(90.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    context.stroke();
}

let canvas = document.get_element_by_id("Canvas").unwrap(); の部分で、HTML の Canvas 要素を取得しています。

4. wasm-bindgen-cli のインストール

wasm-bindgen-cli というコマンドラインツールをインストールします。

cargo install wasm-bindgen-cli

5. ビルド

※プロジェクトをwasm32で作っています
.wasm ファイルを作成します。

cargo build --target wasm32-unknown-unknown

wasm32.wasm と wasm32.d が作成されます。

wasm-bindgen-cli を使うことで、.js ファイルを作成します。
ブラウザで表示するために --target web を指定します。

wasm-bindgen .\target\wasm32-unknown-unknown\debug\wasm32.wasm --out-dir .\target\wasm32-unknown-unknown\debug\ --target web

--out-dir で出力先を指定します。
出力先に index.html があり、 Docker からアクセスできる場所に配置します。

wasm32.d.ts、wasm32.js、wasm32_bg.wasm、wasm32_bg.wasm.d.ts が作成されます。
bg は "bindings generated"(バインディング付き) の意味のようです。

wasm32_bg.wasm は Rust コードを wasm-bindgen 経由でコンパイルした WebAssembly バイナリ
wasm32.js は wasm32_bg.wasm をロード&バインドする JavaScript ローダー(Rust の関数を JS 経由で呼び出せるようにする)
wasm32.d.ts は wasm32.js の型定義ファイル(TypeScript用)
wasm32_bg.wasm.d.ts は wasm32_bg.js に対応する補助的な TypeScript 型定義ファイル

6. index.html の編集

body タグの中に、canvas 要素を追加し、wasm-bindgen で作成した .js ファイルを読み込みます。

<body>
    <h1>WASM Example</h1>
    <canvas id="Canvas" width="320" height="240"></canvas>
    <script type="module">
        import init, { start } from './wasm32.js';

        async function run() {
            await init();
            start();
        }

        run();
    </script>
</body>

7. ブラウザにて確認

以下が表示されます。(Chromeで確認)

8. まとめ

今回は、Rust と WebAssembly を使って Canvas に描画してみました。
wasm-bindgen を使うことで、Rust から JavaScript の関数を呼び出すことができるので、Rust から Canvas に描画することができるようです。
次の機会ではもう少し凝った描画をしてみたいと思います。

今回の記事も、Github Copilot が7割くらい書いてくれました。

A. 参考サイト

Rust における wasm-bindgen と wasm-pack と cargo-web と stdweb の違い
2D Canvas
RustのWebAssembly で canvas に描画する
(前編)MDNブロック崩しをJSからRustに移植してWebAssemblyに入門
Rust/wgpuをWebブラウザで動かす

B. 関連書籍

コメント