RustとWebAssemblyでAsciiアート生成ツール作ってみた


# はじめに

無料個人非商用版があるRustIDE「RustRover」が正式リリースされたついでに、以前から触ってみたかったRustを学習していきたいと思います。

なんとなくですが、学習用にRustとWebAssembly使ってAsciiアートGenerator的なのを作成しようと思います。

RustRoverについて

https://blog.jetbrains.com/ja/rust/2024/05/28/rustrover-is-released-and-includes-a-free-non-commercial-option/


# プロジェクトの作成

wasm-packのテンプレートを作成。

![RustとWebAssemblyでAsciiアート生成ツール作ってみた_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/1bfda14e-57ab-ffae-4385-936c8b5bf7cf.jpeg)

buildのtargetをwebにする。

![RustとWebAssemblyでAsciiアート生成ツール作ってみた_002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/919ffdd4-da7b-ecca-c3d8-fea89051e1d3.jpeg)

wasm-packのbuildのtargetをwebにする理由(Gemini1.5proに聞いてみた)

```
target web を指定すると、以下の様な Web ブラウザ向けの最適化が行われます。
・JavaScript のモジュール形式: Web ブラウザで読み込めるように、ES Modules 形式の JavaScript ファイルが出力されます。
・Web API との連携: web-sys クレートなどを利用して、DOM 操作や fetch API などの Web API を利用するためのコードを含めることができます。
・サイズ最適化: Web ブラウザでのダウンロードを高速化するために、コードサイズが小さく保たれます。
```


wasmでimageのdataを受け取り、解析して、文字列を返すような関数を作成。

```rust:lib.rs
// 省略

#[wasm_bindgen]
pub fn process_image(image_data: &[u8]) -> String {
    // 画像データからImageBufferを作成
    let mut img = ImageReader::new(std::io::Cursor::new(image_data))
        .with_guessed_format()
        .unwrap()
        .decode()
        .unwrap()
        .to_rgba8();

    // セルのサイズ
    let cell_size = 16;
    let mut result = String::new();

    // セルごとに処理
    for y in 0..img.height() / cell_size {
        for x in 0..img.width() / cell_size {
            // セルを切り出す
            let cell = img.sub_image(
                x * cell_size,
                y * cell_size,
                cell_size,
                cell_size,
            );

            // セルを解析
            let recognized_char = analyze_cell(cell.to_image());
            result.push(recognized_char);
            result.push(recognized_char);
        }
        result.push('\n');
    }
    result
}
fn analyze_cell(cell: ImageBuffer<Rgba<u8>, Vec<u8>>) -> char {
    // 黒ピクセルの数をカウント
    let black_pixels = cell.pixels().filter(|p| p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] != 0).count();

    // 黒ピクセル数に応じて文字を判定
    match black_pixels {
        0..=50 => '-',
        _ => '@',
    }
}

//略

```

要所のコード説明(Gemini1.5proに聞いてみた)

```
・ wasm_bindgen は、RustのコードをWebAssemblyにコンパイルし、JavaScriptから呼び出すことを可能にするためのツールです。
・ std::io::Cursor を使用することで、バイト列を ImageReader が読み込める形式に変換しています。
・ .with_guessed_format() 画像のフォーマットを自動判別します。
・ sub_image メソッドを使って、現在のセルに対応する部分画像を切り出します。
・ p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] != 0　赤、緑、青成分がすべて0（黒）で、アルファ成分が0以外（透明ではない）のピクセル数をカウントします。
・ 0..=50 => '-',　黒ピクセル数が50以下の場合は、 - を返します。
・ _ => '@',　それ以外の場合は、 @ を返します。
```


生成された成果物のwasmを使い、

キャンバスに書いた絵や文字が出力されるような処理を実装

(お絵描き処理はFabric.jsで実装)

```javascript:asciiart.js

import init, { process_image } from './rust_asciiart.js';

//略

async function run() {
    await init();

    document.getElementById("GeneBtn").addEventListener("click", (event) => {  
        canvasToUint8Array()
        .then((uint8Array) => {
            console.log(uint8Array);
            const result = process_image(uint8Array);
            document.getElementById("AsciiArea").innerText = result;
        })
        .catch((error) => {
            console.error('Error converting canvas to Uint8Array:', error);
        });
    });       

}
run();

//略

```


# 今回の成果物

デモURL

https://shisojuice.github.io/Rust_AsciiArt/

デモ画像

![RustとWebAssemblyでAsciiアート生成ツール作ってみた_003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/4c396dc4-8885-a777-d24b-880a5459f7da.jpeg)

![RustとWebAssemblyでAsciiアート生成ツール作ってみた_004.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/0eadfb87-f07c-1ad2-ddec-5a812b14739b.jpeg)

ソース

https://github.com/shisojuice/Rust_AsciiArt



# まとめ

Rust自体触ったことがなかったがこれを機に学習していきたい。📚

RustRoverははじめてでも使いやすかったので今後も使い倒していきます。💪

今回、Gemini1.5proを利用しながらプログラミングしましたが、改めてAI強いなと思いました。🤖

※間違い等ありましたら、ご指摘いただけると助かります。