• Windows Terminal ベスト設定 第19回 Sixelグラフィックス「応用編」

今回は、前回の続きで、Sixelグラフィックスの具体的な描画方法などを解説する。前回と同じく、Sixel関連用語を(表01)に示す。

  • ■表01

Sixel文字

Sixelグラフィックスは、エスケープシーケンス内で、ASCIIコードの“?”(0x3F)から"~"(0x7E)までの文字を使い縦6ピクセルのパターンを表す。これを本記事ではSixel文字と呼ぶ。

Sixel文字の描画位置は、Sixelポインタで決まる。Sixelポインタは、Sixelディスプレイモード(SDM。Sixel Display Mode)に応じて、初期値が決まる。SDMが有効な場合、Sixel初期位置は、ターミナルの左上(0,0)に設定される。SDMが有効な場合のSixelポインタの初期値は、Sixelエスケープシーケンスを実行するごとにターミナル右上にリセットされる。

SDMが無効な場合、現在のカーソル位置がSixelポインタの初期位置となる。これは、CUP(Cursor Position。CSI <Y> ";" <X>H)のようなカーソル位置変更エスケープシーケンスを実行するごとに変化する。

Sixelポインタは、Sixel文字を出力するごとに右側に1ピクセル移動する。また、(表02)以下の文字により、Sixelポインタが再設定される。

  • ■表02

“$”は、Sixelグラフィックスのキャリッジリターン(CR)に対応し、Sixelポインタを初期位置に戻す。Sixelグラフィックスでは、一度に1色しか描画できないため、Sixelに複数色で描画する場合、CRで位置を戻して、異なる色で描画を行う。たとえば、以下のPowerShellコマンドは、画面右上に、複数の色で描画を行う(写真01)。


cls;write-host "`e[?80l`eP2;1;0;q#1!20;~$#2!3;@#3!3;A#4!3;C#5!3;G#6!3;O#7!3;_$#8!3;C#9!3;G#10!3;O#11!3;_#12!3;@#13!3;A`e\`e[5;1H"
  • 写真01: 同一Sixelエスケープシーケンス内での描画は、背景モードにかかわらず、ゼロのピクセルは、元の値を維持する

なお、1つのSixelエスケープシーケンス内でSixelに対して異なる色で描画を行う場合、ゼロになっているSixelピクセル描画は、Sixelエスケープシーケンスの背景モードにかかわらず、元の値を維持し、デフォルトの背景色での描画を行わない。このため、背景色モードを2(背景色で描画)に変更した下記のエスケープシーケンスでも、おなじ結果が得られる。


cls;write-host "`e[?80l`eP2;1;2;q#1!20;~$#2!3;@#3!3;A#4!3;C#5!3;G#6!3;O#7!3;_$#8!3;C#9!3;G#10!3;O#11!3;_#12!3;@#13!3;A`e\`e[10;1H"

ただし、背景色での描画が無効になるのは、単一のSixelエスケープシーケンス内のみで、複数のSixelエスケープシーケンスを使う場合、上書きしたときに、背景色モードに従った描画が行われる(写真02)。


cls;write-host "`e[?80l`eP2;0;;q#1!20;~$#2!3;@#3!3;A#4!3;C#5!3;G#6!3;O#7!3;_`e\`eP2;0;;q#8!3;C#9!3;G#10!3;O#11!3;_#12!3;@#13!3;A`e\`e[2;1H"
  • 写真02: 2つのSixelエスケープシーケンスによる同一Sixelへの描画は、「背景色モード」に従う

これに対して“-”は、ニューライン(NewLine。CR+LF)を表す。SixelポインタのX位置は、初期値に戻り、Y位置は、6ピクセル下に移動する。なお、物理画面上での移動距離は、現在のアスペクト比に応じて異なる。なお、単一のSixelエスケープシーケンス内では、NewLineを使ったあと、以前のY位置に戻ることはできない。この場合、一回、Sixelエスケープシーケンスを終了させ、新規にエスケープシーケンスを開始することで、以前のSixelポインタ初期値(カーソル位置または、画面左上)に復帰させることができる。また、SDMが無効の場合には、CUPなど、カーソル位置を変更するエスケープシーケンスを実行してもよい(このときSixelエスケープシーケンスは、一回終了させなければならない)。

「"」は、ラスターアトリビュート(Raster Attribute)と呼ばれ、後続するSixel文字のアスペクト比を指定する。書式は以下のようなものだ。


“"” <Pan> “;” <Pad> “;” <Ph> “;” <Pv>

ラスターアトリビュートでは、<Pan>と<Pad>を使い、後続するSixelピクセルの1ピクセルの縦方向サイズを指定する。1 Sixelピクセルの縦方向のドット数は、


Round(<Pan>÷<Pad>)
※ただしRound(X)は、Xに最も近い整数を返す関数

となる。なお、現行のWindowsターミナルでは、<Pan>の最大値は、6 Sixelピクセル(=Sixel)がターミナル表示領域のY方向のサイズと同じになるまで指定できる(写真03)。具体的には、


Round(ターミナルの表示行数×20÷6)

となる。PowerShellであれば、以下の式で最大値を求めることができる。


[int]((20*$host.UI.RawUI.WindowSize.Height)/6)

なお、<Pan>にこれ以上の値を指定しても、Sixelピクセルの縦方向のサイズはかわらない。

  • 写真03: ラスターアトリビュートでは、大きなアスペクト比(Sixelピクセルの垂直方向ドット数)を指定できるが、最大値は、ターミナルの縦方向の1/6になるSixelドット数に制限される。つまり、画面縦方向を1つのSixel(=6Sixelピクセル)でカバーできるSixelピクセルの大きさに制限される

Sixelエスケープシーケンスの「背景モード」が0または2の場合に、ラスターアトリビュートが、Sixel文字より前に配置されると、<Ph>と<Pv>で指定された指定範囲をデフォルトの背景色(黒)でクリアする(写真04)。

  • 写真04: Sixelエスケープシーケンスの背景色モードが0または2の場合、Sixel文字による描画以前に指定されたラスターアトリビュートは、PH×PV(単位はSixelドット)の範囲をデフォルトの背景色でクリアする

<Ph>と<Pv>ともに、単位はSixelドットである。このため、Pv=20、Ph=10で半角文字1文字分の範囲となる。たとえば、以下のようなラスターアトリビュート指定は、


"1;1;100;100

では、縦5行、横10文字の範囲がクリア(背景モードが0または2の場合)される。

ただし、現在のWindowsターミナルの実装では、Sixel文字でのピクセル描画後のラスターアトリビュートの指定では、<Ph>と<Pv>の指定にかかわらず、Sixelポインタ初期値から、ターミナル右下までをクリアする。つまり、<Ph>と<Pv>で指定範囲のクリアを行うには、Sixel描画を行う前にラスターアトリビュートを指定しておく必要がある。

この数値は、描画されるグラフィックスのサイズを、エスケープシーケンスを出力するアプリケーションに伝えるためにも使われるというが、実際には、範囲を超えての描画も可能なため、参考情報でしかない。

任意の座標に点を描画する

グラフィックスの基本は、指定された座標に点を描画することである。ここでは、Sixelグラフィックスで指定された座標に点を打つことを考える。

アスペクト比指定やラスターアトリビュートにより、Sixelの縦方向のピクセルサイズが異なり、Y方向の座標指定方法が違ってくる。ここでは、話を簡単にするため、アスペクト比は、1:1で固定であるとする。

Sixelグラフィックスは、SDM(Sixel Display Mode)により、Sixelポインタ初期値が異なる。このため、SDMにより点を描画する方法が異なる。SDMが無効の場合、Sixelポインタの初期位置は、文字カーソル位置となる。任意の座標にSixelを描画するには、最も近い文字位置に文字カーソルを移動させ、その後、Sixelポインタを動かして、描画位置を指定する必要がある。

水平方向への移動は、ビットパターンがすべてゼロの“?”をピクセル数出力して、Sixelポインタを動かす。

垂直方向は、6ピクセルごとにニューライン(“-”)を出力して移動させる。このとき、水平位置がSixelポインタ初期値に戻るため、エスケープシーケンス内では、先に垂直方向を指定し、その後、水平位置を“?”で合わせる。

(リスト01)は、Sixelでドットを打つためのPowerShellのスクリプト(抜粋)である。完全なコードは、筆者のGitHubに置いてある。

■リスト01


# 文字カーソル位置
$cposx= [math]::Floor( $x / 10);
$cposy= [math]::Floor( $y / 20);

# エスケープシーケンス出力用文字列変数の初期化、CUPで文字カーソル位置を移動
$outstring = "${CSI}$(1+$cposy);$(1+$cposx)H"

# Sixelのエスケープシーケンス開始。描画色は$cで指定
$outstring += "${SIXELS}#${c}";

# 文字カーソル位置からのオフセット
$sx = $x - $cposx * 10
$sy = $y - $cposy * 20

# y方向のSixel行数(Sixel New Lineの数)
$nCr=[Math]::Floor( $sy / 6)

# Sixel New Lineの出力
$outstring += "-" * $nCr

# x方向のオフセットがゼロでなければオールゼロのSixelを必要数描画
if($sx -gt 0) {
    $outstring += '$'+('?'*$sx)
}

# Sixel内の1のビットを計算
$wp=[math]::Pow( 2,$sy % 6)

# Sixel文字に変換
$outstring +=[char]([int][char]'?'+[int]$wp)

# ST(String Terminator)を最後に追加
$outstring += $ST

描画は、SDM無効で行っている。というのは、Sixelを描画する座標を指定するのに、カーソル移動を使うのが簡単だからだ。SDM有効の場合、常に画面左上がSixelポインタとなるため、座標移動が煩雑になるからだ。

指定された座標から、最も近い文字カーソル位置を求め、変数$cposx、$cposyに保存する。これを使って、VTエスケープシーケンスCUPで文字カーソルを移動させる。ただし、エスケープシーケンスはすぐに出力するのではなく、変数$outstringに追加していき、最後にまとめて出力する。

次にSixelエスケープシーケンスを開始し、カラーレジスタ番号を描画色として指定する。

次に、文字カーソル位置からのSixelでの水平、垂直のオフセットを計算し、$sx、$syに保存する。

Sixelは縦方向は6ピクセルごとにNewLineで行を移動させる必要がある。オフセット$syから必要なNewLineの数($nCr)を求め出力する。

水平方向のオフセット$sxがゼロでなければ、すべてゼロのSixel文字“?”を$sx個出力して、Sixelポインタを横に動かす。

次にドットのSixel文字内での位置($wp)を求めて、Sixel文字を作る。

最後に、Sixelエスケープシーケンスを終了させるST(String Terminator。Esc \)を出力して終わる。

PowerShellでは、「$host.UI.RawUI.WindowSize」で、コンソールの文字数(Width)、行数(Height)を調べることができる。


$host.UI.RawUI.WindowSize

Sixelでのグラフィック座標は、それぞれに10、20をかけたものになる。今、コンソールが80文字40行だとすると、コンソールの表示領域は、800(=80×10)ドット×800(=40×20)ドットとなる。

Sixelグラフィックスは、ツールを使って、画像ファイルの表示に利用できるほか、コンソール内にグラフィックス描画を行うことも可能になる。かつては、Unix上でLaTexのプレビューなどを行わせることもあったようだ。ただ、当時と違い、Windowsターミナルは、シリアル接続ではなく、仮想TTY経由なので描画はかなり高速になる。ツールによってはアニメーションGIFの表示も可能だ。

》 Windows Terminal ベスト設定 連載バックナンバー
https://news.mynavi.jp/tag/winterminal/