電光掲示板シェル芸 その3

# 多色電光掲示板シェル芸
前回までの電光掲示板シェル芸は無圧縮PBMという白黒2色のみの画像フォーマットを介しているため、矩形領域内に2色の図形を描くものでした。
しかし、![💩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/63261819-3eff-2c20-3a58-007616bc1c0d.png)や![💃.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/80c5cc5a-0db7-f243-4ccf-9594b32d1e11.png)など、フルカラーの絵文字を描くためには2色では足りません。
今回は矩形領域内に3色以上の多色で図形を描く電光掲示板シェル芸をやっていきます。

# PGM電光掲示板シェル芸
多色の電光掲示板シェル芸をやるには、画像の各ピクセルをどの絵文字で置き換えるかを決定する必要があります。
この置換絵文字決定を、無圧縮PGMフォーマットを介して、グレースケールの明るさを用いて行うのが、PGM電光掲示板シェル芸です。
まず無圧縮PGMってなんなの？ということを![💩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/5c67c690-d2d7-0e45-e2da-c9d98b4eb2b7.png)を無圧縮PGMにして見てみましょう。

```bash
$ echo -e "\n💩 "|textimg|convert - -trim -compress none pgm:-
P2
20 20
255
0 0 0 0 0 0 0 49 71 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 108 110 71 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 70 155 160 160 71 2 0 0 0 0 0 0 0 
0 0 0 0 0 0 71 155 156 156 156 156 71 0 0 0 0 0 0 0 
0 0 0 0 0 0 71 151 151 151 151 151 71 0 0 0 0 0 0 0 
0 0 0 0 0 7 71 145 145 145 145 145 107 71 1 0 0 0 0 0 
0 0 0 0 23 90 140 140 140 140 140 140 140 140 79 10 0 0 0 0 
0 0 0 0 71 135 135 135 135 135 135 135 135 135 135 71 0 0 0 0 
0 0 0 0 71 154 255 255 255 130 130 255 255 255 154 71 0 0 0 0 
0 0 0 36 71 255 36 36 255 133 133 255 36 36 255 71 13 0 0 0 
0 0 68 119 122 255 44 36 255 123 123 255 36 46 255 122 97 53 0 0 
0 0 71 116 116 119 255 255 253 116 116 253 255 255 119 116 116 71 0 0 
0 0 71 111 111 111 111 111 111 111 111 111 111 111 111 111 111 71 0 0 
0 30 71 106 106 255 255 245 232 220 220 232 245 255 255 106 106 71 22 0 
71 98 101 101 101 0 5 214 255 255 255 255 214 5 0 101 101 101 96 71 
78 97 97 97 97 97 0 0 0 0 0 0 0 0 97 97 97 97 97 78 
89 93 93 93 93 93 93 0 0 0 0 0 0 93 93 93 93 93 93 89 
71 93 93 93 93 93 93 93 93 90 90 93 93 93 93 93 93 93 93 71 
0 64 71 71 75 84 89 93 93 93 93 93 93 85 80 74 71 71 71 1 
0 0 0 0 0 0 0 7 14 14 14 7 7 0 0 0 0 0 0 0 
```
1行目の P2 は、無圧縮PGMであることを示しています。
2行目 20 20 は画像の横縦のサイズ。
3行目 255 は、画素値が 0-255 の256段階であることを示しています。
4行目以降は画素値ですね。値が大きいほど明るく、0が黒で255が白になります。
白目のあたりに255、黒目の当たりに36という値がありますね。
グレースケール画像の見た目も見ておきましょう。

```bash
$ echo -e "\n💩 "|textimg|convert - -trim -compress none pgm:-|convert - -scale 200x200 /images/x.png
```
![x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/8605ad48-4cda-c664-7896-9dbe9372906b.png)

色分けを考えます。
背景・黒目・口の中は黒、白目と歯は白、それ以外のグレーの部分を茶色にすればよさそうです。
3色ですね。

現時点で0-255の256色ある状態です。すこし色数を減らしてみます。

```bash
$ echo -e "\n💩 "|textimg|convert - -trim -compress none pgm:-|tail -n +4|opy '[x//26 for x in F[1:]]'|tr -d ' '
00000001200000000000
00000000442000000000
00000002566200000000
00000025666620000000
00000025555520000000
00000025555542000000
00000355555555300000
00002555555555520000
00002599955999520000
00012911955911920000
00244911944911943200
00244499944999444200
00244444444444444200
01244999888899944200
23333008999980033332
33333300000000333333
33333330000003333333
23333333333333333332
02222333333333322220
00000000000000000000
```
tailでPGMのヘッダを削って、データ部分をopyで26で割り、trでスペースを削除しました。
あとはsedで0-9の10文字を3色の絵文字に置き換えれば電光掲示板になります。

```bash
$ echo -e "\n💩 "|textimg|convert - -trim -compress none pgm:-|tail -n +4|opy '[x//26 for x in F[1:]]'|tr -d ' '|sed 'y/0123456789/⚫⚫💩💩💩💩⚪⚪⚪⚪/'|textimg -s
```
01を⚫、2345を![💩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/6a2d7b3c-0718-66c6-cf55-fe3265dd8710.png)、6789を⚪で置き換えてみました。
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/e00f552a-8cbc-2e8e-c188-cde23f3e9aed.png)
頭のてっぺんが白くなっちゃってますね。
6も![💩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/6a2d7b3c-0718-66c6-cf55-fe3265dd8710.png)にして調整しましょう。

```bash
$ echo -e "\n💩 "|textimg|convert - -trim -compress none pgm:-|tail -n +4|opy '[x//26 for x in F[1:]]'|tr -d ' '|sed 'y/0123456789/⚫⚫💩💩💩💩💩⚪⚪⚪/'|textimg -s
```
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/8a3586de-b45f-f902-e2b8-c7bf066d5210.png)
完成です。
余談ですが、PGMフォーマットというのは、ロボットが移動経路を計画するための地図データに使われたりしますね。簡単に言うと「ロボットが現在位置から目的地まで移動する時に、なるべく白っぽいところを通って行く」みたいな感じです。なので、

```bash
$ convert map.pgm -compress none pgm:-
```
としてマップに入っている値をざっと見てみるみたいな、imagemagick を使ったPGMのハンドリングができると、ちょっと便利です。

# denko-multi 関数
[@egpl0_sh_2](https://twitter.com/egpl0_sh_2) さんがシェル芸botで使える denko-multi という関数を定義しています。これを利用してシェル芸botでPGM電光掲示板シェル芸ができます。
関数定義のツイートをコメント付きリツイートすることで、シェル芸botがツイートのチェーンを認識して、関数を呼ぶことができます。
関数定義ツイート
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">denkoh-multi(){<br> s=`seq 0 $[256/$4-1]|tr -d &#39;\n &#39;`<br> echo-sd $[256/$4]色<br> echo -e &quot;$1&quot;|<br> textimg -F$3|<br> convert - -trim -compress none pgm:-|<br> awk &#39;NR&gt;3{for(i=0;i&lt;NF;i++){<br> printf int($i/a)}print &quot;&quot;}&#39; a=&quot;$4&quot;|<br> sed &quot;y/$s/$2/&quot;|<br> textimg -s<br>};</p>&mdash; シェル児 (@egpl0_sh_2) <a href="https://twitter.com/egpl0_sh_2/status/1192097307329646592?ref_src=twsrc%5Etfw">November 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
関数を呼ぶツイート
<blockquote class="twitter-tweet"><p lang="in" dir="ltr">denkoh-multi 🌇 &#39;　■🔴🔴🌑🌑🌕🌕&#39; 50 32 <a href="https://twitter.com/hashtag/%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8?src=hash&amp;ref_src=twsrc%5Etfw">#シェル芸</a> <a href="https://t.co/rdNQOEzZvv">https://t.co/rdNQOEzZvv</a></p>&mdash; シェル児 (@egpl0_sh_2) <a href="https://twitter.com/egpl0_sh_2/status/1192125484781338624?ref_src=twsrc%5Etfw">November 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
実行結果
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">＿人人人＿<br>＞　8色　＜<br>￣Y^Y^Y^￣<br> <a href="https://t.co/OYcxuGhBf6">https://t.co/OYcxuGhBf6</a> <a href="https://t.co/7Tdd7koi6Z">pic.twitter.com/7Tdd7koi6Z</a></p>&mdash; シェル芸bot (@minyoruminyon) <a href="https://twitter.com/minyoruminyon/status/1192125530989924352?ref_src=twsrc%5Etfw">November 6, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# PPM電光掲示板シェル芸
グレースケールの陰影だけでは色の決定がうまくいかないような場合、無圧縮PPMフォーマットを介して、RGB空間で置換絵文字を決定することができます。これがPPM電光掲示板シェル芸です。
ここでは![💃.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/36427d9b-6aa5-f307-ecce-b8516d564ff9.png)（ダンサーの絵文字。コードポイントU+1F483）を電光掲示板にしてみましょう。
![x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/ad4d168c-2ebc-4610-34cd-602f6495c807.png)
こんな感じの絵文字です。

```bash
$ echo -e "\n💃 "|textimg -F50|convert - -trim -compress none ppm:-|tail -n +4|awk '{for(i=0;i<NF/3;i++){r=3*i+1;g=3*i+2;b=3*i+3;printf int($r/128)*4+int($g/128)*2+int($b/28)}print ""}'|sed 'y/01234567/🌑🔵🔵🔵🔴🔵🌕🌕/'|textimg -s
```
RGBそれぞれの画素値が128以上か否かで 2^3 = 8象限に分割して、sedで置換しています。
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/4f625b8b-d936-f902-90c8-ee9f34753037.png)

髪色・肌色・服色が分別されているので、絵文字を変更するとちょっとした着せ替えができます。
ピンク髪・緑肌・青服にしてみましょう。

```bash
$ echo -e "\n💃 "|textimg -F50|convert - -trim -compress none ppm:-|tail -n +4|awk '{for(i=0;i<NF/3;i++){r=3*i+1;g=3*i+2;b=3*i+3;printf int($r/128)*4+int($g/128)*2+int($b/28)}print ""}'|sed 'y/01234567/🌑🍑🍑🍑🔵🍑🍈🍈/'|textimg -s
```
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/2c328e09-4843-4824-1b76-77787ce977d2.png)
なかなかいいですね。
これをさらに高精度でやろうとすると、RGB空間を分割する数式を考えることになりますが大変そうですね。
それをやらずに済ませるためには、imagemagick でカラーパレットを指定して、カラーパレットに含まれる色だけを使って減色する方法があります。

```bash
$ convert <(echo -e "\n💃 "|textimg -F50) -trim -map <(echo P3 5 1 255 199 26 0 169 32 2 255 191 20 88 60 51 0 0 0) -compress none ppm:-|tail -n +4|sed 's/199 26 0/🍎/g;s/255 191 20/🌕/g;s/169 32 2/🍫/g;s/88 60 51/🍩/g;s/0 0 0/🌑/g'|tr -d ' '|textimg -s
```
準備段階として、カラーピッカー(gpick)で「服の明るい部分(199 26 0)」「肌(169 32 2)」「服の暗い部分(88 60 51)」「髪(88 60 51)」「背景(0 0 0)」の画素値を調べています。それをパレットにして減色して、無圧縮PPMで出力すると、パレットにある画素値の列になるので、それを絵文字に置換しています。
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/80fff909-16fc-c01c-609b-2ccaf5d775d2.png)
なかなかいいですね。
本当は1ピクセルのデータごとに区切って検索しないと、あるピクセルのGBと次のピクセルのRが、別の色のRGBとマッチしちゃったりしてよろしくないんですけど、今回はそうならないパレットになってるのでよしとしましょう。
この方式なら色数の多い画像でも強引に電光掲示板にできそうです。
試してみましょう。
入力画像: monalisa.jpg
![monalisa.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/be960d65-0cd4-b8e5-e4de-c9a0361b525e.jpeg)

```bash
$ a="204 183 76";b="39 19 36";c="59 45 49";d="120 138 80";e="74 83 59";f="125 87 30";convert monalisa.jpg -scale 80x80 -map <(echo P3 6 1 255 $a $b $c $d $e $f) -compress none ppm:-|tail -n +4|sed "s/$a/🥚/g;s/$b/🌑/g;s/$c/🎱/g;s/$d/🍈/g;s/$e/🍀/g;s/$f/🍩/g"|tr -d ' '|textimg -s
```
![t.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/2521c732-dfbb-bb38-0138-746d9b690c24.png)

なるほど。今回はとりあえずこんなところです。
# 所感
電光掲示板シェル芸の現状を3回に分けてやってみました。
もともとやっていたことをまとめるだけなので楽チンかなーと思っていたのですが、いくつか新手が見つかったりしてちょっと手間がかかりました。
僕の考える電光掲示板シェル芸の面白いところは、無圧縮PNMというフォーマットを介して「画像の空間」と「文字列の空間」を行ったり来たりできるところで、シェル芸で文字列操作・数値計算をして生成した文字列が画像になったり、imagemagickなどで生成した画像が文字列になったりする。そのあたりが醍醐味かと思います。
ところで![🍩.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143996/5ed14fdf-e42f-abd7-1579-cfe85047e2b9.png)みたいな暗い色って、実際の電光掲示板では出しにくそうな気がしますね。PWMで出せるのかな？環境の明るさの影響をすごく受けそう。