Sound Player Lilith for Unicode OSs (仮称) フェイス作成 Tips
Ver. 2008/10/23

このテキストでは、フェイス作成にまつわる数々の Tips (コツ)を紹介していきます。

【１】動作の軽いフェイスを作るには

uLilith では、レイヤーへの完全対応により、フェイスの描画処理が大変重くなっています。
おそらく、uLilith の使う CPU 時間の 7〜8 割はフェイス描画によるものかと思います。
そこで、無駄な処理を省き、動作の軽いフェイスを作るための Tips をいくつか紹介します。
ここで紹介する Tips は、ほとんどが 旧 Lilith にも当てはまりますので、
旧 Lilith 用フェイスを作る方にも有用な情報かと思います。

1) 更新頻度の高いアイテムを、他のアイテムと重ねない

更新頻度の高いアイテムは、自然と描画回数が増えるので、
処理負担の多くを占めます。
アイテムの重なりがあると、重なっているアイテムも
(重なっている部分だけ)描画し直す必要がありますので、
重なりが多ければ多いほど、無駄な描画処理が増えてしまいます。

最も更新頻度が高いアイテムが、毎フレーム描画される
スペクトラムアナライザとミリ秒表記の現在時間/残り時間です。
次点が、見落とされがちな、スクロールしているアイテムで、
以下、シークバー、秒表記の再生時間/残り時間と続きます。

このうち、もっとも更新頻度が高く、
また自身の描画負荷も大きいのがスペクトラムアナライザです。
スペクトラムアナライザは必ずαチャネルを持ち、
しかも最大３重のα処理が行われるという特徴があります。
使用する面積も比較的大きいアイテムでもあります。
ビジュアル上他のアイテムを手前に表示しているフェイスが多いですが、
先にも述べたように、重なっているアイテムも描画し直す必要があるため、
大変処理が重くなります。
ですが、逆に重ねても問題ない、あるいは、重ねた方がよい場合もあります。
それは、重ねるアイテムもまた、毎フレーム描画が必要なアイテムの場合です。
スクロールしているアイテムなどは、ほぼ毎フレーム描画が必要ですので、
同じ場所に重ねてしまった方が、描画面積が小さくなり、有利になる可能性があります。

実用性やデザイン面を考えると、このうち実際に重なりを避けて使えそうなのは、
シークバーと時間表記くらいでしょうか。
デザイン上の必要性が無ければ、スペクトラムアナライザの重なりも抑えましょう。
また、スクロールしているアイテムの描画負荷が大きいのなら、
スクロールさせなければいいのです。
再生時間などはもちろんのこと、
チャネル数や再生しているフォーマット名なども含め、
タイトル文字列とタグ表示系以外はの文字列アイテムは、
あらかじめ最大幅をある程度予想できます。
十分な量の表示幅を用意し、めったにスクロールすることはないようにしましょう。
ただし、表示幅を広げれば、それだけ普段の描画処理が重くなります。
また、そのせいで他のアイテムとの重なりが出てもよくありません。
そのあたりのバランスも考え、慎重に範囲を決定しましょう。


2) 無意味なαチャネルの濫用を避ける

uLilith では、フェイス上のアイテムにαチャネルを使用できるため、
旧 Lilith のものも含め、αチャネルを多用しているフェイスがよく目立ちます。
しかしながら、αチャネルを使用した描画は、処理負荷が高く、
濫用すると大きくパフォーマンスを悪化させます。
とはいえ、非矩形ボタンを実装するには絶対に必要では？と思われるかもしれません。
しかしながら、少しの工夫で大幅に処理負荷を抑えられる場合もあります。

例えば、角を落としただけの矩形ボタンなどです。
ボタンの大きさがある程度ある場合には、ボタン全体にαチャネルを指定し、
角の部分を透明にして画像を作成し、描画させるよりも、
αチャネルを設定しないで、マスク画像で角を隠す方が処理が軽くなります。
完全透明でない、アンチエイリアスの半透明部分でも同様です。

小さなサイズで済む角を隠すαチャネルつきのアイテムを作成し、
イメージ部分には背景と同じものを描いておきます。
それをαチャネルのついていないボタンイメージよりも手前に、
四隅を隠すように配置すると、アイテム数は増えても、
結果的にα描画を行う面積が大幅に減り、高速化されることがあります。
どれくらいの比率から早くなるかは実験してみないとわかりませんが、
大きなボタンであればあるほど、隠す領域が少なければ少ないほど、
効果は顕著に現れます。
ただし、マスク画像がボタンのサイズに比べて、
大幅に小さくならなければ意味がありません。
本来のボタンとαチャネルを逆転しただけの、
ボタンと同じサイズのマスク画像を重ねたりしたら、逆効果です。

この Tips は、uLilith よりも、旧 Lilith の方で効果が顕著です。
Gdiplus によるレイヤ処理ベースの uLilith では、
αチャネルのある画像とない画像での処理速度差が大幅に縮まっており、
効果がだいぶ薄れてしまっています。


3) 定数αの使用を避ける

uLilith では、定数 αも結果的にαチャネルとして処理されます。
Gdiplus は定数αをサポートしていないため、
動的にαチャネルを生成し、それにより描画を実現しているためです。
定数αを使用するくらいなら、同等の表示になるように
αチャネルをあらかじめ組み込んでしまいましょう。

αチャネルを使用できない文字列の場合はどうするの？
という疑問が上がるかもしれませんが、
uLilith では、文字色にもα情報を持たせられます。
影や縁取りをつける場合には、文字色αを使う場合と、
定数αを使う場合で、描画結果に差が生じますが、
表示してみて問題のない内容でしたら、
定数αを使わず、文字色αで処理しましょう。
こちらは相当効果があります。

また、定数αや、その代わりのαチャネルも、
無駄に使用してはいけません。
動的に内容が変化するアイテムとの重なりがない限り、
合成された結果の最終表示イメージは常に一定です。
背景や、裏に表示されるアイテムとの合成結果を
αチャネルなしの画像として用意してください。
ただし、背景などとの重なりをイメージに含めてしまうと、
ボタンの位置を移動させたときに作り直しになりますので、
フェイス作成の最終段階で行う必要があると思います。

ちなみに、旧 Lilith では、定数αの代わりとしてのαチャネルを
設定してはいけません。旧 Lilith は、定数αをきちんとサポートしているので、
αチャネルの方が速くなるなんてことはありません。

4) ボタンイメージのサイズはなるべく小さく

ボタンのイメージがアイコンだけで、
ボタンの面積分無駄に透明部分がある画像を結構見かけます。
表示領域として PosX,PosY,Width,Height を正しく指定すれば、
それより画像が小さくても、DisableRegion キーを使って、
画像より大きい範囲をボタンとして機能させられますので、
出来るだけ透明部分を削ってアイコンを作成しましょう。

また、この透明部分の外側に
3D エッジだけつけた画像などもよく使用されていますが、
これらはアイテムの重なりがない限り、αチャネルを使わずに、
背景を流し込んでαチャネルなしの画像にしましょう。
上でなんども述べていますが、無駄なαチャネルの使用はとにかく避けてください。

uLilith では、ボタンの状態に応じて別画像ファイルに分割していますが、
状態ごとに最小サイズの画像として用意し、
無駄な透明部分を省き、処理を高速化できるように、との意図があります。
旧 Lilith の仕様だと、どちらか大きい方のイメージに合わせて、
無駄に透明部分をつくらなければなりませんので。

現在は、イメージにはアライメントが使えないので、
左上には無駄な透明領域が必要なケースがあると思います。
ボタンの状態ごとにアライメントを管理するのと、
透明領域を削るのとで、おつりが来るようならやりたいですが、
その辺どうかわからないんですよね。


5) ネタ切れです。
もっとあった気がするんだけどなぁ(´・ω・`)
とりあえず、旧 Lilith で定説になっていた、
スペアナは 2 の累乗の幅がいいというのは、
現在はあまり効果がありません。
uLilith に変わったからではなく、
スペアナが対数表示がデフォルトになったからです。
対数表示の場合、スペアナの幅に関係なく、
2048 point の FFT を使用しますので、
スペアナ画像の幅は関係ありません。
もちろん、uLilith でも、対数表示をしない場合には、
2 の累乗の方がよいですが、
フェイス描画全体の負荷と比べれば、
差は微々たる物だと思います、たぶん。


【２】工夫して面白い表示を実現する

1) 進行バー状のスライダーの実装

このテクニックは、旧 Lilith 発表から比較的初期のころ、
VOS 氏が考案したものです。

まず、表示領域を決めます。
次に、表示領域と同じ幅(縦タイプなら高さ)の進行バー画像を作成します。
塗りつぶしでもグラデーションでもかまいません。

で、フェイス定義に書く表示領域は、幅 (あるいは高さ) を『２倍にした』上で、
PosX (あるいは PosY) を、幅 (あるいは高さ) 分引いた数値を入力します。
左 (あるいは上) 方向に、画像サイズ分の退避領域を作るのです。
これにより、進行バー画像の右端 (あるいは下端) が、
ポジション 0 のときにちょうど本来の表示領域の
左端 (あるいは上端) にくることになります。
そこから本来の表示領域の幅 (あるいは高さ) 分、
スライドできるようになるので、進行バーのように見えます。
表示領域部分は、あらかじめ未進行部分の色で塗りつぶすなどしておいてください。

左 (あるいは上) に拡張した部分に余計な表示が出来てしまいますが、
それはスライダーより優先度の高いアイテム(マスク画像)で隠してしまいます。
背景などの画像をコピーし、それを上に表示させることで、
あたかもその位置にはバーが描画されていないように見せます。
このテクニックを使うときには、スライダーの空欄をクリックしたときに、
ダイレクトに移動する機能を無効化してください。
uLilith なら、DisableDirectTrack = True に、
旧 Lilith なら、ENABLE オプションをはずします。
これでもまだ完全ではありません。
ドラッグでしか移動できないようになるわけですが、
ポジション 0 のとき、つかめる部分がない状態になっています。
進行バー画像は、完全にマスク画像で隠されていますから、つかめないのです。
旧 Lilith では、この不具合を取り去ることは出来ません。
ある程度進んでから、出てきた画像をつかんでドラッグしてもらうしかありません。
uLilith では、この問題を解決すべく、SliderAlwaysDrag キーが用意されています。
SliderAlwaysDrag = True とすると、画像が表示されていない部分を
つかむことができるようになるので、進行バー画像部分が完全に隠れていても、
ドラッグをすることが出来ます。
なお、この SliderAlwaysDrag オプションを指定すると、
ダイレクト移動自体意味がなくなりますから、
DisableDirectTrack キーの指定は無視されます。
SliderAlwaysDrag = True だけあれば十分です。
DisableDirectTrack キーは、通常のスライダーで、
クリックでダイレクト移動させたくないときだけ指定してください。

発展系で、表示領域を左右両方に拡張し、
左半分を進行バーの進行した状態色、
右半分を未進行の状態色にした、
本来の表示領域の２倍の画像を作成し、
右側にもマスク画像を用意することで、
背景色に頼らずに未進行の部分を表すことも出来ます。

優先度を高くしたマスクアイテムは、
いろいろなテクニックに使用できますが、
それをうまく活用した先駆け的な使用方法かと思います。

