プログラミングの備忘録

プログラムをつくる過程を残すもの

processingの備忘録 -ASCIIアート-

こんにちは。
今回は、processing で「ASCIIアート」をつくります。


目次


ASCIIアートとは

ASCIIアートは、文字で描かれた絵のことを指し、略称として「AA」とよばれることもあります。
2ちゃんねるニコニコ動画などでよく見られるので、聞いたり見たりしたことがあるという方もいるのではないでしょうか。

そもそも「ASCII」とは文字コードの一種で「American Standard Code for Information Interchange」の頭字語になっています。
今では「Unicode」が主流になっていてASCIIにない文字が使われたりもしていますが…


ASCIIアートには「:D」や「(´・ω・`)」、「orz」のような一行に収まるものも、複数行にわたるものもあります。
「人生オワタの大冒険」や以前取りあげた「Stone Story RPG」(「Stone Story RPG」で遊んでみる - プログラミングの備忘録) などはASCIIアートで描かれたゲームとして有名かと思います。


実装

流れとしては、

  1. 画像の読み込み
  2. グレースケール化
  3. 文字列に変換
  4. 出力

という感じです。


画像の読み込み

まずは、ASCIIアートにしたい画像を用意します。
私のアイコンの元になっている、こちらの画像でやってみることにしました。


画像を読み込む方法については、過去に取りあげています。

taq.hatenadiary.jp


読み込み自体は1行でおしまいです。

PImage img = loadImage("image.png");


グレースケール化

元の画像そのままではカラーになっていて直接文字に変換するのが難しいので、グレースケールに変えます。
グレースケールでは画像の色が黒 (0) から白 (255) の256段階で表されるようになります。


processing の色には大きく RGB と HSB の2つがあります。

RGB なら各値の平均を取れば良いでしょう。
(単純な平均以外にも様々やり方があるようですが。グレースケール画像のうんちく #rgb - Qiita)

float average = (red(col) + green(col) + blue(col)) / 3;


HSB では明度の情報のみを使えば良いので、 brightness() で明度を取得すれば良さそうです。

float brightness = brightness(col);


ここで、col は画像中のある点での色の情報を持った変数で、例えば画像 img の座標 (i, j) での色を取得したいときは以下のようにします。

color col = img.get(i, j);


文字列に変換

画像の各点について明度を取得し、その値に対応した文字に置き換えた配列をつくる、ということを考えます。
変換に使う文字は、Shiffman先生のものを参考にさせていただきました。

String letters = "N@#W$9876543210?!abc;:+=-,._  "; // 左に行くほど明るい部分に対応


(i, j) における brightness (or average) の値を letters の文字と対応させた後、対応する文字を letters から取り出して配列 ascii に入れていきます。

int index = floor(map(brightness, 0, 255, 0, letters.length()-1));
ascii[i][j] = letters.charAt(index);


出力

以上でASCIIアートの素材をつくることができたので、これを描画します。


キャンバスに描くのであれば、text() を使って文字を描画します。

textSize(2);
text(ascii[i][j], i*2, j*2);

ただし、文字の大きさ倍だけキャンバスのサイズも大きくなってしまうので工夫が必要です。

あと、本当に文字でできているのか確認ができない。


追記

一応、letters = "NOo+^*-. "; としてみたり、表示する文字を間引いてみたりして、それなりに見えるようにできました。

粗くとったときは letters も粗く変化させた方が良いということですね。

以上


コンソールに描くのであれば、print() でひたすら出力します。

for(int j = 0; j < img.height; j ++){
  for(int i = 0; i < img.width; i ++){
    print(ascii[i][j]);
  }
  print("\n");
}

しかし、横幅が十分長くないと途中で改行されてしまい、正しく表示されません。
そこで、テキストファイルとして出力することとします。


テキストファイルの出力方法は過去に取りあげました。

taq.hatenadiary.jp


createWriter() を使う方法だと構造を変えなくて良いので楽です。

PrintWriter file = createWriter("text.txt"); // text.txt を作成
for(int j = 0; j < img.height; j ++){
  for(int i = 0; i < img.width; i ++){
    file.print(ascii[i][j]);
  }
  file.print("\n");
  file.flush(); // テキストファイルに書き込み
}
file.close(); // ファイルを閉じる


文字サイズを小さくしたり頑張って縮小したりしても全貌が見られなかったので、書き込む文字を少し間引きました。
(フォントは等幅のもの (MSゴシックなど) を使う必要があることに注意です。)

拡大すると、ちゃんと文字で構成されていることがわかります。


まとめ

以上、processing でASCIIアートをつくることができるようになりました。

今回は画像に対して変換を行いましたが、動画やカメラからの入力に対しても同様にできるはずです。
また、明度によって文字を変えていきましたが、画像の区切り方を変えれば線の向きなどにも対応した、より実際のASCIIアートに近いものもつくることができるのではないかと思います。
機会があればまた記事にするかもしれません。


最後まで読んでいただいてありがとうございました。また次回。


参考