6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クソアプリAdvent Calendar 2024

Day 3

RustとWebAssemblyでAsciiVideo作ってみた

Posted at

はじめに

「何番煎じのアイデアだよ」とツッコみたくなりますが🙄、

WebCameraの映像を適当な文字列に変換して出力するようなアプリを作ってみる。

プロジェクトの作成

大まかな手順としては入力映像をグレースケール化し、

指定されたドットサイズでセルに分割して、

各セルを適当な文字に変換することで、ASCII アート風の文字列を生成し出力する

グレースケールの設定

グレースケール時の重みは検索してみたら「ITU-R Rec BT.601」というSDTVの規格があるようなので採用してみる。

lib.rs
let gray = (r * 0.299 + g * 0.587 + b * 0.114) as u8; // 相対輝度の計算式

(グレースケールについて下記Qiitaを見た感じ奥が深そう。。。🤯)

文字濃度の設定

濃度順に設定する文字は下記から適当な文字を採用してみる。

(今回の文字の濃度順は、特に根拠はなく、主観です。🙇‍♂️)

lib.rs

  if black_ratio > 0.8 {
        '@'
    } else if black_ratio > 0.6 {
        '#'
    } else if black_ratio > 0.4 {
        '*'
    } else if black_ratio > 0.2 {
        '+'
    } else {
        '-'
    }

下記全体のコード

lib.rs

// 省略

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

    // グレースケール化
    let mut new_buffer = vec![0; buffer.len()];
    for y in 1..height - 1 {
        for x in 1..width - 1 {
            let index = (y * width + x) * 4;
            let r = buffer[index] as f32;
            let g = buffer[index + 1] as f32;
            let b = buffer[index + 2] as f32;
            let gray = (r * 0.299 + g * 0.587 + b * 0.114) as u8; // 相対輝度の計算式

            new_buffer[index] = gray;
            new_buffer[index + 1] = gray;
            new_buffer[index + 2] = gray;
            new_buffer[index + 3] = buffer[index + 3];
        }
    }

    // 文字変換
    let mut result = String::new();
    for y in (0..height).step_by(dot_size) {
        for x in (0..width).step_by(dot_size) {
            let cell = extract_cell(&new_buffer, x, y, width, height, dot_size);
            let recognized_char = analyze_cell(&cell);
            result.push(recognized_char);
            result.push(recognized_char); // 横幅を持たせる
        }
        result.push('\n');
    }
    result
}

fn extract_cell(buffer: &[u8], x: usize, y: usize, width: usize, height: usize, dot_size: usize) -> Vec<u8> {
    let mut cell = Vec::new();
    for dy in 0..dot_size {
        for dx in 0..dot_size {
            let px = x + dx;
            let py = y + dy;
            if px < width && py < height {
                let index = (py * width + px) * 4;
                if index + 4 <= buffer.len() {
                    cell.extend_from_slice(&buffer[index..index + 4]);
                } else {
                    // バッファの範囲外の場合は、透明ピクセルを追加
                    cell.extend_from_slice(&[0, 0, 0, 0]);
                }
            }
        }
    }
    cell
}

fn analyze_cell(cell: &[u8]) -> char {
    let black_pixels = cell.chunks(4)
        .filter(|p| p[0] < 128 && p[1] < 128 && p[2] < 128 && p[3] > 0)
        .count();

    let total_pixels = cell.len() / 4;
    let black_ratio = black_pixels as f32 / total_pixels as f32;

    if black_ratio > 0.8 {
        '@'
    } else if black_ratio > 0.6 {
        '#'
    } else if black_ratio > 0.4 {
        '*'
    } else if black_ratio > 0.2 {
        '+'
    } else {
        '-'
    }
}

// 省略

(まだまだ微妙なところもあるが)とりあえず完成!

今回の成果物

デモURL

デモ動画

RustとWebAssemblyでAsciiVideo作ってみた.gif

デモ画像

RustとWebAssemblyでAsciiVideo作ってみた_2.jpg

ソース

まとめ

今回は入力映像をAsciiアート風のVideoにしてみた。

背景にメリハリがないところだと、エッジ検出が甘く、つぶれたりして表現できない場合もあった。

もう少し立体感をAsciiアートで表現できると綺麗に見えるようになるかもです。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?