RustとWebAssemblyでドット絵フィルター作ってみた

# はじめに

webcamの映像にドット絵フィルターをかける方法をまとめておきます。

ドット絵化とモザイク処理の違いについて(Gemini1.5proより)

```
・ドット絵化（ピクセレーション）
　各ブロック（ドット）内のピクセルを平均化して、そのブロック全体を 単一の平均色 で塗りつぶします。
　ブロック内の色の情報は失われ、均一な色のブロックになります。
　レトロゲームのような、ドットがはっきりとした表現になります。

・モザイク処理
　各ブロック内で ランダムに1つのピクセルを選択 し、そのピクセルの色でブロック全体を塗りつぶします。
　ブロック内の色の情報は完全に失われるわけではなく、一部のピクセルの色がブロック全体に反映されます。
　プライバシー保護などの目的で、部分的に画像を隠す場合に用いられます。
```

# プロジェクトの作成

まず、HTMLとJavascriptで映像をCanvas要素に描画する。

前回作成した下記記事を参考にする。

https://qiita.com/shisojuice/items/d5906127e031354db800

Rust側でドット絵化する関数を実装する。

Javascript側からUint8ArrayとCanvas要素の幅・高さ・ドットサイズを受け取る。

映像をドット単位で走査し、

各ドット内のピクセルの平均色を計算して、その平均色でドット全体を塗りつぶす。

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

#[wasm_bindgen]
pub fn pixel_filter(mut buffer: Vec<u8>,canvas_width :u32,canvas_height :u32,dot_size :u32) -> Vec<u8> {
    let width = canvas_width as usize;
    let height = canvas_height as usize;
    let dot_size = dot_size as usize;

    for y in (0..height).step_by(dot_size) {
        for x in (0..width).step_by(dot_size) {
            let mut r = 0;
            let mut g = 0;
            let mut b = 0;

            for dy in 0..dot_size {
                for dx in 0..dot_size {
                    let i = ((y + dy) * width + (x + dx)) * 4; // RGBAなので4倍
                    if i + 3 < buffer.len() {
                        r += buffer[i] as u32;
                        g += buffer[i + 1] as u32;
                        b += buffer[i + 2] as u32;
                    }
                }
            }

            // ドット内のすべてのピクセルに平均色を設定
            r /= (dot_size * dot_size) as u32;
            g /= (dot_size * dot_size) as u32;
            b /= (dot_size * dot_size) as u32;

            for dy in 0..dot_size {
                for dx in 0..dot_size {
                    let i = ((y + dy) * width + (x + dx)) * 4;
                    if i + 3 < buffer.len() {
                        buffer[i] = r as u8;
                        buffer[i + 1] = g as u8;
                        buffer[i + 2] = b as u8;
                        buffer[i + 3] = 255; // アルファ値
                    }
                }
            }
        }
    }

    buffer
}

// 省略

```

Javascript側でCanvas要素に描画したImageDataを受け取り、Uint8Arrayに変換する。

requestAnimationFrameを使用して、上記で作成した関数を呼び出す。

```Javascript:index.js

// 省略

document.getElementById("pixel_filter").addEventListener("click", () => {
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  function draw() {
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      const ret = pixel_filter(new Uint8Array(imageData.data.buffer),canvas.width,canvas.height,dot_size.value);
      ctx.putImageData(new ImageData(new Uint8ClampedArray(ret.buffer), canvas.width, canvas.height), 0, 0);
      requestAnimationFrame(draw);
  }
  draw();
});

// 省略

```

これでメインの処理は実装完了！！

# 今回の成果物

デモURL

https://shisojuice.github.io/Rust_VideoFilter/


デモ画像

![RustとWebAssemblyでドット絵フィルター作ってみた_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/e66be5b9-f563-9964-2fe9-b63dcd24d8fe.jpeg)

![RustとWebAssemblyでドット絵フィルター作ってみた_002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/a4b9d9db-acfb-161c-a55c-4398b3940e46.jpeg)

![RustとWebAssemblyでドット絵フィルター作ってみた_003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/49cc62e9-85d6-9695-e317-fd2946fa7227.jpeg)

![RustとWebAssemblyでドット絵フィルター作ってみた_004.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3777149/688a6f1a-1d55-4370-dadd-37cfc8f9ac44.jpeg)

ソース

https://github.com/shisojuice/Rust_VideoFilter



# まとめ

VideoからCanvas要素に描画した映像にドット絵フィルターをかけてみた。💪

ドット絵化は以前作成したドット絵ツールのソースとChatGPTとGemini1.5proを利用しました。🤖

ドット絵化・モザイク処理にもいろんなやり方があると思うので調べてみたいと思う。📝


以前作成したドット絵ツールについて

https://qiita.com/shisojuice/items/dd4c99bdab0c4cef2387


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