More than 1 year has passed since last update.

Vimで画像を見る(DRCSのSIXEL拡張の話)

ビデオ端末で画像を表現する試みはそこそこ古くから行われていたようで、
1970年代後半には世界各地でビデオテックスサービスが開始し、ベクタ図形やラスタ画像、
モザイク素片などで画像を表現するひととおりの方式が出揃っていました。

この流れの中でDEC社はVTシリーズに2つの機能を搭載しました。
SIXELとDRCS(Dinamically Redifined Character Set)です。

SIXELとDRCSについて

SIXELはラスタ画像を出力する方式で、写真などの繊細な画像を表現するのに向いています。
カラーに対応した仕様もあり表現力は高いので、それ単体として使用するのであればとても便利なものなのですが、
ひとたびテキストと組み合わせて画像をレイアウトするプログラムを書こうとすると、再描画の挙動で詰んだりして非常にむずかしいです。

DRCSはいわゆる外字を定義する方式で、あくまでも文字として扱えるものであるため、テキストとの親和性が高いという特徴があります。
またフォントを動的に定義できる点で、同じく文字として扱えるモザイク方式と異なります。

画像をテキストとして扱えるのであれば、VIMにも扱えてしかるべきですよね。
そう思い、今回は「Vimで画像を見る」ことを目標に作業しました。

DRCSの問題とSIXEL拡張

ただ、VT端末のDRCSは、残念ながらカラー画像を表現できないという弱点がありました。
(誤解の無いように補足しておくと、DRCSはDEC社固有の概念ではなく、NAPLPS規格のDRCSのように単なるモザイクキャラクタの再定義に過ぎないものから、日本のキャプテン方式のフォトDRCSのように色つきのものまであったりします)

それを克服すべく、RLoginという端末が両者の長所を組み合わせた拡張を提案しました。
SIXELのパレット定義とカラー表現を、DRCSのフォント定義の中に埋め込んだのです。
またISO-2022とUnicodeのそれぞれ一部の領域を等価とみなすマッピングが定義されたことにより、実用性が飛躍的に高まりました。

drcs.vim

この画像から見てとれるように、テキストと画像を混在させたり、編集したり、コピーしたりすることが、可能になってきました。
保存なども一応できますが、これから検討を重ねて交換可能なフォーマットを作成していく必要がありそうです。

Vimからの使い方の提案

  • まず、RLoginをインストールします。
    ※これ以降の操作は、いまのところRLogin以外では動作しません。

  • RLoginでなんらかのposix環境に接続し、netpbmをインストールしておきます。GNU Screenやtmuxはつかわないでください。対応も可能なんですが、今回は面倒なので割愛します。

  • 猫画像(neko.png)と、ビム画像(vim.png)を用意します。

  • neko.png から neko.drcsを生成します。
    なんとなくzshのワンライナーで書いてしまいましたが、zshが無い場合は適宜perlなどで書き替えてください。

% start=0; end=24; f="neko.png"; (for j in {$start..$end}; do echo -en "\x1bP1;0;0;15;1;3;24;0{ "; `printf "echo -en \\\\\\\\x%x" $((j + 64))`; cat $f|pngtopnm 2>/dev/null|pnmscale --width=1200|pnmcut 0 $((j * 24 - $start * 24)) 1200 24|pnmquant 256 2>/dev/null|ppmtosixel 2> /dev/null; done |sed 's/\x9c/\x1b\\/g' | sed 's/\x90.*$//g' && for j in {$start..$end}; do for i in {32..111}; do `printf "echo -en \\\\\\\\U10%x%x" $((j + 64)) i`; done; echo ""; done) > neko.drcs
  • vim.png から vim.drcsを生成します。
% start=25; end=49; f="vim.png"; (for j in {$start..$end}; do echo -en "\x1bP1;0;0;15;1;3;24;0{ "; `printf "echo -en \\\\\\\\x%x" $((j + 64))`; cat $f|pngtopnm 2>/dev/null|pnmscale --width=1200|pnmcut 0 $((j * 24 - $start * 24)) 1200 24|pnmquant 256 2>/dev/null|ppmtosixel 2> /dev/null; done |sed 's/\x9c/\x1b\\/g' | sed 's/\x90.*$//g' && for j in {$start..$end}; do for i in {32..111}; do `printf "echo -en \\\\\\\\U10%x%x" $((j + 64)) i`; done; echo ""; done) > vim.drcs
  • 実は、ここまでの操作は、PyDRCSを使うことでも代替できます。
% drcsconv -u -n256 -r24 neko.png > neko.drcs
% drcsconv -u -n256 -s24 vim.png > vim.drcs
  • ファイルタイプdrcsの自動検出設定を書きます。
$VIMRUNTIME/ftdetect/drcs.vim
autocmd BufRead *.drcs set filetype=drcs
  • ファイルタイプdrcsが画像として見えるような設定を書きます。

注意:「^[」のところは制御文字の\033です。「Control-V Esc」などとして入力して下さい

$VIMRUNTIME/ftplugin/drcs.vim
setlocal nowrap
setlocal foldlevel=0
setlocal foldmethod=marker
setlocal foldmarker=^[P,^[\
let lines = getline(0,'$')
call filter(lines, 'len(v:val) > 0 && char2nr(v:val[0]) < 128')
call writefile(lines, '/dev/tty')
call cursor(len(lines), 0)
call feedkeys("z\<CR>")
  • .vimrcに以下の追記を行います。

これは、もともとftpluginの方に書いていたのですが、
グローバルな状態に対する操作があるのでよろしくない、
との指摘を暗黒美夢王に賜ったので、vimrcで設定することにしました。

$HOME/.vimrc
" Vimであいまいな幅の文字の論理幅を1にします
set ambiwidth=single

" RLoginであいまいな幅の文字の論理幅を1にします。
call writefile(["\e[?8428h"], '/dev/tty')

以上の設定を行うことにより、

% vim vim.drcs

とすればビム画像が普通に表示されますし

:vsp neko.drcs

とすればその横に猫が表示されると思います。
文字なのでスクロールとかもちゃんと動きます。Vimと協調して動いているわけです。

問題点

今回はUnicodeの16面を使ってISO-2022とのマッピングをやってみたんですが、
現在の仕様(以前drcstermで提案したもの)だと5985文字しか画像に割り当てられなくて、画像がいくらでも見放題というわけではありません。
そのうちかつかつになるのは目に見えてますね。

おわりに

以上、Vimでテキストと画像を協調させながら扱う方法を試してみましたが、今回も書き終えてみればあまりVimの話じゃなかった気がします。
また、SIXELやDRCSの仕様の詳細や、ここで出てきた設定やコードの意味をつっこんで説明できてなくてすみません。
でも実はこの記事はVim Advent Calender 2013の4日目の記事なのでちょうどよかったです。
明日の5日目は@supermomongaさんです。よろしく!

追記 2014/02/04

PyDRCSがインストールされ、正常に動作する環境であれば、DRCSに変換する手間無しに画像ファイルを直接表示できます。私はこうやってます。

vim/ftdetect/image.vim
autocmd BufRead *.png set filetype=image
autocmd BufRead *.jpg set filetype=image
autocmd BufRead *.jpeg set filetype=image
autocmd BufRead *.gif set filetype=image
autocmd BufRead *.tga set filetype=image
autocmd BufRead *.tiff set filetype=image
vim/ftdetect/image.vim
function! s:ViewImage()
    if &ambiwidth == 'single'
        setlocal nowrap
        setlocal foldlevel=0
        setlocal foldmethod=marker
        setlocal foldmarker=^[P,^[\
        %delete
        call system('cat "' . expand('%:p') . '" | drcsconv -u --ncolor=256 > /tmp/drcs')
        let lines = readfile('/tmp/drcs')
        call append(0, lines)
        call filter(lines, 'len(v:val) > 0 && char2nr(v:val[0]) < 128')
        call writefile(lines, '/dev/tty', 'b')
        call cursor(len(lines) + 1, 0)
        call feedkeys("z\<CR>")
    endif
endfunction

autocmd! TermResponse * call s:ViewImage()
if len(v:termresponse) 
    call s:ViewImage()
endif

ここまでくると画像の編集もやりたくなってきました。
RLoginはDEC locator modeを使ってピクセル単位でのマウスイベントが取れるので、DECSIXELと組み合わせて任意の場所に点を打つことができそうです。こんど暇ができたらやってみます。