概要
256色出力に対応することで、macOS標準のターミナル (Terminal.app) での longcat の動作を修正したのでその過程で学んだこと。
問題と回避方法を確認
もともとdockerで実行した時の現象がTwitterで報告されていたのだが、macOS標準のターミナル(Terminal.app)で longcat を実行するとおかしな表示になるとのこと。再現確認すると、dockerは関係なくて、そのまま実行しても駄目で、tmux -2 の上で実行すれば綺麗に出ることがわかった。
← tmux、→ tmux -2 #longcat on https://t.co/31bQQo50fd pic.twitter.com/Jnm9BeVs18
— Yoichi Nakayama (@yoichi22) 2020年6月23日
tmux を経由するとうまくいくということは、何らか対処すれば tmux なしでもいけるだろうと期待して調査に着手。
pixterm出力を読む
そもそもどうやってターミナルに描画しているのかを理解してなかったので、まずはそこから。
longcat | more
すると、
のような文字列が出力されている。
ANSI escape code - Wikipedia によると、
- 38 は forground color の設定
- 48 は background color の設定
で、それに続く 2;r;g;b (0 <= r,g,b <= 255) で色を指定するとのこと。
また、▄ についてはEmacsでM-x describe-charすると、LOWER HALF BLOCKだった。
つまり、background colorで上半分を塗り、foreground color で下半分を塗ることで、1文字で2ピクセル分を表現しているとわかった。
longcat がうまく動作した tmux のオプションの意味は
-2 Force tmux to assume the terminal supports 256 colours.
で、 https://gist.github.com/XVilka/8346728 にも Terminal.app は true color をサポートしてないとあるので、256色で出力すれば何とかなりそうと予想した。
256色指定にしてみた
上述のエスケープシーケンスの説明にもあるが、ESC[38;5;n あるいは ESC[48;5;n (0 <= n <= 255) で256色での指定ができ、うち、16 <= n <= 231 で r, g, b をそれぞれ 0-5 の 6x6x6 で表現できるとわかったので、0-255 から 0-5 に変換するように python でフィルタを書いて試すと、
#longcat on https://t.co/jS9jsiClXR with 256 colors pic.twitter.com/rkW75oENnr
— Yoichi Nakayama (@yoichi22) 2020年7月8日
とそれっぽいが、tmux の出力と比べると若干見劣りする猫が出た。何かが足りてない。
tmux の実装を確認すると、 232-255 のグレースケールも使って、6x6x6と比べてより近い方を選ぶロジックだった。それに合わせてフィルタの実装を修正して試すと綺麗な猫が出せた。
tmux/colour.c の colour_find_rgb() と同じようにグレースケールと近い方を選ぶようにしたらいい感じになった pic.twitter.com/VoijGSujLS
— Yoichi Nakayama (@yoichi22) 2020年7月9日
フィードバック
どう対処すればうまく動作するかの仕組みはわかったのでほぼ満足してしまったのだが、せっかくなのでlongcatにフィードバックしようと思った。
活用範囲が広げられるよう、 pixterm に対処を入れたらどうかと最初に考えた。しかし、
- pixterm は true color 対応のターミナルをターゲットとしており、true color 非対応ターミナルのための対処を入れるのはどうか
- 拡張した時に longcat から動作を制御するにはインタフェースを変更する必要がありそう
というあたりが気になったのでやめた。
longcat で対応するにしても、応用ができるように色変換のロジックは外に出しておいたほうがいいと考えた。 tmuxの実装をgoに移植してライブラリを実装しようとも思ったが、適当なライブラリが既にあるのではと探して、まず gookit/color を組み込んでみた。動作させてみると6x6x6の部分に合わせた時のような色だったので却下。次に見つけた tomnomnom/xtermcolor を組み込むといい感じになったので、それでプルリクエストを作った。
プルリクエスト出したら一瞬で取り込まれてしまって、後からオプション指定の処理が中途半端になっていたことに気付き再度修正を送った。
まとめ
- エスケープシーケンスによるターミナル上の色付けの方法について知った。
- U+2584 (LOWER HALF BLOCK) を使ってターミナルに描画できることを知った。
- 現状の自動判定は環境変数TERM_PROGRAMに頼っており、 docker では明示的に伝達しないといけない。
- Check iTerm2/Terminal.app with DA2 · Issue #27 · mattn/longcat · GitHub で自動判定の改善が検討されているのでそれに期待。