CloverPaint雑記

CloverPaintの解説・開発状況・内部仕様・利用法などについてつらつらと

内部データフォーマット

日本でも、かなりペイントアプリに造詣が深いと思われるユーザーが増えてきたので、ここらへんで少しディープな話をしようということで今回はTwitterで私がよく愚痴ってる、Clover Paint内部の画像データ形式について話したいと思います。

少々プログラマ向けの話ではありますが、デザイナーが見てもClover Paintがどんな風にメモリを使うのかを知ることで、限られたメモリ内で大きな画像や多数のレイヤーを扱う場合のヒントになるかもしれません。

 

ピクセルデータ

RGBA各チャネル毎に16bit幅のデータで持っています。1ピクセル=16bit*4=64bit=8バイトになります。各チャンネルの階調は14bit=1.0にしているので16384段階です。

あまった2bitは将来的に合成等で計算途中に負数や多少のオーバーフローを許す中間値保存が必要になる可能性を考えてバッファを取っています。

 ユーザーに見せているのは一般的な0~255の256段階=8bitですがブラシ効果による色の変化や合成で作成される内部的な演算結果の精度としては最大14bitということです。

 

Cell/Tileデータ

ここからがかなり特徴的で、描画ツールを作るときにこれに対応させるのが大変だったりするわけですが、Clover Paintは画像データを小さい順にPixel→Cell→Tile→全体画像というように複数段階に分けて管理しています。編集画像サイズを予め決めるタイプのペイントアプリだとPixel→直接全体画像となっているものが多い(むろん、高度なキャッシュ・並列実行機能等を備えるPCの有名市販アプリの場合、その多くはそんな単純なものではないでしょうが)のですが、Clover Paintは自動拡張キャンバスを採用しているため、こういう構造をとらざるを得ません。

 

最も単純な内部データ形式 

 f:id:CloverPaint:20130124193230p:plain

 

 Clover Paintの内部データ形式

f:id:CloverPaint:20130124193321p:plain

 一番上の白枠がキャンバスイメージ全体で、内部の青い枠に(0,0)とか(5,0)などと表記されていますが、これはTile単位の座標値です。(実際には負数もとれます)現在のClover Paintではこの座標値は符号付き16bitなので、x座標、y座標それぞれ-32768~32767までの数値をとります。Tileは内部にCellを横16個、縦16個固定で持っていて、16x16=256個のCellを持つことになります。最後にCellは内部に8x8pixelを持ちます。

16個のセルぞれぞれが8pixel幅なので、Tileは縦横16*8=128pixelサイズということになります。Tileの座標値が-32768~32767でそのそれぞれが128pixelなので、キャンバスのとれる座標はおよそ-400万Pixel~+400万Pixelとなります。まあ浮動小数点を内部で使っているのでその演算精度の問題や、そもそもメモリが足りないのでそんなキャンバスを現実的に使うことは不可能ですが、仕様上はそうなります。

これだけだと、ただデータの持ち方をややこしくしただけの話なので、いろいろと例外があります。

まず、イメージプレーン(プログラム内部ではこう名付けています。要するにビットマップ形式のレイヤー)一枚毎にディフォルト色を1つ持てます。ディフォルト色は何に使うかというと、あるTile座標を調べて、Tileデータが存在しない場合、そのTile座標が本来占める128x128Pixelの矩形領域はディフォルト色で埋め尽くされていると判断する…というルールを設定してあります。つまり、何も画像がないただ一色で塗りつぶされたレイヤーは、どんなに大きな領域を占めているように見えても実際には1ピクセル分のデータしか使われないということです。(正確にはブレンド情報その他もあるため、数百バイトくらいは使ってると思われますが)

次にCellにも実は2種類あって、64ピクセルを全て独立して持つタイプと、1ピクセル分のデータで64ピクセル全てを表現するという、Cellが同じ色で塗りつぶされている時に使えるタイプがあります。データサイズが1/64に圧縮されるわけです。この2種類のCellはいつでも交換可能で、Tileの16x16のCellにはどちらかが入っています。

 

さて、上のルールを守りながら、画面上にペンを走らせるとどうなるのか?ということですが、このようになります。 

f:id:CloverPaint:20130124225235p:plain

 

黄色はCellの存在するTileのうち、先ほど説明した1Pixelだけを含むCellです。面倒なので描いていませんが、実際には小さなCellの集合です。赤い四角はペンが通った部分で、8x8Pixel=64Pixelを持つCellに変化しています。青い部分はペンが通っていないので影響がなく、セルを作る必要がないのでそもそもTileすら存在しない部分です。このように、ペンが通った部分だけ完全なデータを持つようになっています。

 

現状のClover Paintで、各描画ツールやその他操作がどうなっているかというと、現状は時間がないため、かなり手を抜いた処理になっています。例えばアニメ風の絵をロードした時は、背景に透明部分等が広くあれば、それは存在しないTileや1ピクセルだけを含むCellとして表現してデータを節約できるはずですが、何もしません。

太い二値ペンでは最適化しようと思えばストローク後に調べて一部のCellを1ピクセルにまとめることも可能ですが、やっていません。

セーブ・ロード時にも特に何もしていません。

とりあえず将来的には対応したいのですが、目に見えない、特にこのままでも致命的な支障はない部分なので後回しになっています。

ただし、バケツ処理だけは最適化を行うので、塗潰しは太い筆よりもできるだけバケツを使うことを意識すれば多少はデータを節約できます。また、レイヤー全体の塗潰しも、内部データをしっかり消してくれます。 

そのうちこの辺はちゃんと書いてメモリを節約したいところですね。

 

2013/03/03追記

Clover Paint v1.16以降ではPSD画像とネイティブ画像フォーマットの読み込み時に完全な最適化を行うようになりました。