Ko-Taのバ・ー・ルのようなもの このページをアンテナに追加 RSSフィード

2014-08-03

[]ノートPCのタッチパッドにおけるホイール処理

ノートPCについているキーボード下のタッチパッドにおけるマウスホイール処理についてメモしておきたいと思います。

実は難物なんですこれ・・・。


イベント内容

パッドを使ったホイールスクロールの場合は、マウスによるホイールスクロールとは異なり、特殊な数値のイベントが飛んでくるので、処理やインターフェイスの根本的な見直しが必要になります。

・マウスによるホイール
MouseWheel(WheelDelta:-120) 230ms
MouseWheel(WheelDelta:-120) 102ms
MouseWheel(WheelDelta:-120) 85ms
MouseWheel(WheelDelta:-120) 123ms

・ノートPCのパッドによるホイール
MouseWheel(WheelDelta:-14) 12ms
MouseWheel(WheelDelta:-10) 10ms
MouseWheel(WheelDelta:-8) 13ms
MouseWheel(WheelDelta:-9) 8ms
・・・

マウスのホイールでは上下に1回2回といったグリッド間隔による回数によってイベントが発生していますが、パッドの場合はもっと細かい単位(ピクセル)の連続したイベントが飛んできます。

また、デバイスの設定によって離した後のイージングも発生します。

(イージングを判定するコードは見つけられませんでした。判別出来るか不明)


と、こんな違いがありますので、対応処理が必要です。

まじめに対応すると場合は、プログラム的(イベント処理)な対応と、GUI(グラフィックインターフェイス)の両方が必要になります。


対応しないとどうなるの

対応していないからと言って、そこまで深刻なバグが発生するわけではありません。

よくある実害が、パッドでスクロールさせると超高速でスクロールするぐらいです。

上記の通り、スクロールイベントが細切れで大量に飛んでくるのが原因です。


対応 - イベント処理

パッドによるイベントは、間隔が非常に狭いため、Wheel値をポーリング処理(ゲームの1frame単位で値を取得して処理)で取得している場合はかならず抜けオチが発生します。

抜けオチが発生すると、スクロールする移動量が一定で無くなります。(早くスクロールしたのに遅かったり、逆になったり)

なので、イベントから値を取得し、WheelDelta値は加算、累積しておく必要があります。


対応 - GUI(グラフィックインターフェイス)

大変なのがこれ(GUI)です。

デザインそのものから見直す場合もありますので、昔の作品をプログラムを弄って対応…と単純にはいかないでしょう。


簡潔に言ってしまえば、GUIをピクセル単位のスクロールに対応させることが対応処理になります。

ということとは必然的に、「1回のスクロールでページが切り替わる」といった処理はこれに反します。

例えば、スクロールでページが1進むようなのがこれに該当します。

…厳しいですね。

「一定ピクセル数のスクロールになったらページを進ませる」というのも手ですが、引っ張り動作(スクロールさせた量だけ移動するが一定未満だと元の位置に戻る)を入れないと大変不親切なインターフェイスになってしまいます。

また、イージング処理イベントも飛んでくるので、引っ張り動作が正常に出来る保証もありません…。


というわけで、ピクセル単位のスムーズなスクロールを前提に作り直さないとどうにもならないなー、と個人的には思っています。

この方式は「見えている部分のリソースだけ処理して処理とメモリをコンパクトに」というのが大変やりにくいので、リッチな処理(重い)になる傾向が強いですし、全てを対応とかでなく、天秤にかけて取捨選択が現実ラインではないかと思います。

ページ切り替えぐらいで悩むならボタンでもいいかなってね。

かしこ

2014-07-27

[]タッチデバイス

何年ぶりかわからないですが、タッチデバイスについて調べる機会があったので記しておきたいと思います。

ログまでとってあるので、それなりに役立つはず・・・。

まず使う必要はあるのか?

マウスの代用であればそのままの状態である「レガシーモード」でも一応は事足ります。

が、タッチ操作においては「マウスダウン相当が無い」というのがマウスやペンと大きく異なる挙動の1つです。

これは主に「プレスアンドホールド」機能(押しっぱなしで右クリック)の弊害で、指を離すまでは左クリックか右クリックかを決定出来ないため、指で押してもマウスダウンが発生しません。(勿論回避方法はあるのでそれは後記)

もし、クリックだけで構成されたユーザインターフェイスであれば、そのままでもタッチデバイス対応のアプリであることを理解してください。

もし自前で実装したスクロールバーや特殊なGUI(ゲームライクなGUI)がある場合は、タッチデ操作を意識したプログラムを組む必要があるでしょう。

モード

windowsにおけるタッチデバイスは主に3種類のモードがあります。

  1. LegacyDevice(レガシーデバイス)
  2. TouchMDevice(タッチデバイス)
  3. GestureDevice(ジェスチャーデバイス)

レガシーはそのままの状態でOSがタッチ操作をマウス操作に置き換えてくれます。

タッチはタッチ操作の生データを貰う方法で、より反応の良い挙動が可能です。が、後記する制約により単純に反応が早くなるわけではなく、ズームジェスチャーなどにも対応しません。

ジェスチャーはパンやズームなど特殊な操作をOSで解析して使用出来るモードです。タッチといえばコレですが、ほぼタッチアプリ専用です。マウスと混同使用を考えると相当苦戦されるでしょう。

プレスアンドホールド

windowsではジェスチャの1つであるプレスアンドホールドだけが特別な機能として別枠でもうけられています。

ペン(デジタイザ)とタッチ共用設定で、OSの設定関係なくアプリケーション層でOn/Off切り替えが可能です。

設定はグローバルアトムにて行います。

const TABLET_DISABLE_PRESSANDHOLD=$00000001;

TabletAtom := 'MicrosoftTabletPenServiceProperty';
AtomID := GlobalFindAtom(TabletAtom);
if (AtomID=0)then AtomID := GlobalAddAtom(TabletAtom);
flg := GetProp(windowhandle,TabletAtom);
SetProp(windowhandle, TabletAtom,flg or TABLET_DISABLE_PRESSANDHOLD);

このプレスアンドホールドの有無によってタッチ操作時のイベントの内容が変わります。

なので、3モードx2プレスアンドホールド=6 のパターンが存在します。

マウスダウンとクリック

タッチ操作でもうひとつ考慮したい点がこのマウスダウンです。

マウスで左ボタンを押し込んだ時にボタンのグラフィックが変わる挙動を実現するには、このイベントが必要になります。

タッチ操作では基本マウスダウンが指を押したときに発生しません。

プレスアンドホールドの関係で指を離すまで左右のボタン判定が決定しないため、離すタイミングで一気に(クリック)イベントがなだれ込みます。

wait	//指を離すまでイベント無し
mouse(FromTouch).move...0(194,154) 7543ms
mouse(FromTouch).down...0(194,154)Left 11ms
mouse(FromTouch).up...0(194,154)Left 6ms
mouse(FromMouse).move...0(194,154) 7ms

これはペンやマウスとは全く異質なイベントの流れで、もしクリック処理を「GetAsyncKeyState」によってポーリングを行っている場合、マウスダウン間隔が短すぎてクリック処理がすり抜けます。

必ずイベントを見て補間なりしないと、クリックすらできないアプリが出来上がります。

と、ここまでが前置きです。

レガシーデバイス

旧来のそのままの状態をレガシーとwindowsは定義しています。

この状態ではwindowsはタッチ操作をマウス操作(イベント)に置き換え、マウスの代行としてアプリを制御出来るようにしています。

・Windows Touch Gestures Overview

http://www.domoneo.com/windows_touch_gestures_overview.htm

(うちではpanによるマウススクロールが何故か拾えなかったんですけど、まぁいいや)

結論から言うと、プレスアンドホールドを無効化してもマウスダウンが発生しないのがレガシーの最大の弱点ですが、後記する魔法のおまじないを行うことで発生させることが可能なため、意外と使えます。

■legacy.singletouch
・pressandhold enable
wait	//指を離すまでイベント無し
mouse(FromTouch).move...0(194,154) 7543ms
mouse(FromTouch).down...0(194,154)Left 11ms
mouse(FromTouch).up...0(194,154)Left 6ms
mouse(FromMouse).move...0(194,154) 7ms

・pressandhold disable
mouse(FromTouch).move...0(149,187) 2542ms
mouse(FromTouch).down...0(149,187)Left 3ms
wait	//指を離すまでイベント無し
mouse(FromTouch).up...0(149,187)Left 60ms
mouse(FromMouse).move...0(149,187) 7ms

■legacy.pressandhold
・pressandhold enable
wait    //指を離すまでイベント無し
mouse(FromTouch).move...0(121,171) 9534ms
mouse(FromTouch).down...0(121,171)Right 18ms
mouse(FromTouch).up...0(121,171)Right 11ms

・pressandhold disable
wait	//SetGestureConfig(Block:Allgesture)で発生しなくなる。OSのバグ?
mouse(FromTouch).move...0(131,207) 4246ms
mouse(FromTouch).down...0(131,207)Left 3ms
wait	//指を離すまでイベント無し
mouse(FromTouch).up...0(131,207)Left 1374ms
mouse(FromMouse).move...0(131,207) 8ms

■legasy.pan
・pressandhold enable
mouse(FromTouch).move...0(132,104) 6204ms
mouse(FromTouch).down...0(132,104)Left 24ms
mouse(FromTouch).move...0(132,126)Left 12ms
mouse(FromTouch).move...0(132,134)Left 5ms
〜〜〜
mouse(FromTouch).move...0(132,170)Left 18ms
mouse(FromTouch).move...0(132,171)Left 27ms
mouse(FromTouch).up...0(132,171)Left 48ms
mouse(FromMouse).move...0(132,171) 7ms

・pressandhold disable
mouse(FromTouch).move...0(89,151) 7831ms
mouse(FromTouch).down...0(89,151)Left 7ms
mouse(FromTouch).move...0(89,179)Left 49ms
mouse(FromTouch).move...0(92,186)Left 9ms
〜〜〜
mouse(FromTouch).move...0(110,292)Left 18ms
mouse(FromTouch).move...0(111,293)Left 8ms
mouse(FromTouch).up...0(111,293)Left 22ms
mouse(FromMouse).move...0(111,293) 5ms

レガシーでマウスダウンを発生させる

レガシーに以下のおまじないをかけると発生するようになります。

gconf.dwID   := 0;
gconf.dwWant := 0;
gconf.dwBlack:= GC_ALLGESTURES;
SetGestureConfig(Handle, 0, 1, gconf, sizeof(TGESTURECONFIG));

後記するジェスチャーモードを全てロックする命令です。

これにより、プレスアンドホールド無効化時にマウスダウンが正しく発生してくれるようになります。

きっと、初期状態ではなにかのジェスチャーと干渉しているのでしょうね。

初期状態のロックについては資料が全然無かったのでちょっとよくわかりません。

タッチデバイス

生データに直接アクセス、がこのモードになります。

生データだけあって、触れている指の数(ID)、座標、ぐらいしか取得出来ません。

反応は最も早く、ログを見て貰うとわかるとおり、マウスのマウスダウンよりも10ms以上早くタッチイベントが飛んでくるので、ゲーム向きな素早い入力を得ることが出来ます。

ただし、プレスアンドホールドを考慮するのであれば、このモードの恩恵(素早い入力)は全く受けられないので注意してください。

コードサンプルはこちらの方が簡潔にまとめられております。

http://wlog.flatlib.jp/item/1320

■touch.singletouch
・pressandhold enable
touch.down...0(177,126) 3875ms		//LeftClickの前にsigletouchが発生する
touch.move...0(177,126) 2ms
touch.move...0(177,126) 5ms
〜〜〜
touch.move...0(177,126) 9ms
touch.move...0(177,126) 9ms
touch.move...0(177,126) 18ms
touch.up...0(177,126) 5ms
mouse(FromTouch).move...0(177,126) 5ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,126)Left 4ms
mouse(FromTouch).up...0(177,126)Left 4ms
mouse(FromMouse).move...0(177,126) 4ms

・pressandhold disable
touch.down...0(191,152) 4397ms
mouse(FromTouch).move...0(191,152) 3ms
mouse(FromTouch).down...0(191,152)Left 3ms  //mousedownがちゃんと発生
touch.move...0(191,152) 3ms
touch.move...0(191,152) 7ms
〜〜〜
touch.move...0(191,152) 9ms
touch.move...0(191,152) 19ms
touch.up...0(191,152) 3ms
mouse(FromTouch).up...0(191,152)Left 5ms
mouse(FromMouse).move...0(191,152) 4ms

■touch.pressandhold
・pressandhold enable
touch.down...0(177,189) 7346ms    //RightClickの前にsigletouchが発生する
touch.move...0(177,189) 10ms
touch.move...0(177,189) 8ms
〜〜〜
touch.move...0(177,189) 9ms
touch.move...0(177,189) 8ms
touch.move...0(177,189) 19ms
touch.up...0(177,189) 6ms
mouse(FromTouch).move...0(177,189) 6ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,189)Right 5ms
mouse(FromTouch).up...0(177,189)Right 5ms

・pressandhold disable
touch.down...0(227,193) 5982ms
touch.move...0(227,193) 9ms
mouse(FromTouch).move...0(227,193) 5ms
mouse(FromTouch).down...0(227,193)Left 5ms
touch.move...0(227,193) 5ms
touch.move...0(227,193) 5ms
〜〜〜
touch.move...0(227,193) 8ms
touch.move...0(227,193) 20ms
touch.up...0(227,193) 7ms
mouse(FromTouch).up...0(227,193)Left 5ms
mouse(FromMouse).move...0(227,193) 5ms

■touch.pan
・pressandhold enable
touch.down...0(141,154) 11386ms  //pan開始前にtouch.downが発生
touch.move...0(141,154) 14ms
touch.move...0(141,154) 7ms
〜〜〜
mouse(FromTouch).move...0(141,154) 7ms  //pan開始と同時にmouse.moveが追加発生
mouse(FromTouch).down...0(141,154)Left 6ms
mouse(FromTouch).move...0(155,172)Left 5ms
touch.move...0(160,182) 5ms
mouse(FromTouch).move...0(160,182)Left 5ms
touch.move...0(163,188) 5ms
〜〜〜
mouse(FromTouch).move...0(181,275)Left 6ms
touch.move...0(181,276) 5ms
mouse(FromTouch).move...0(181,276)Left 6ms
touch.move...0(181,276) 5ms
touch.move...0(181,276) 12ms
touch.up...0(181,276) 6ms  //touch.up/mouse.upの順序は機種依存
mouse(FromTouch).up...0(181,276)Left 5ms
mouse(FromMouse).move...0(181,276) 6ms

・pressandhold disable
touch.down...0(170,146) 5781ms
touch.move...0(170,146) 16ms
mouse(FromTouch).move...0(170,146) 7ms
mouse(FromTouch).down...0(170,146)Left 5ms  //mouse.downはtouch.downより遅延がある
touch.move...0(170,146) 5ms
touch.move...0(170,146) 5ms
〜〜〜
touch.move...0(184,157) 9ms
touch.move...0(186,159) 9ms
〜〜〜
mouse(FromTouch).move...0(186,159)Left 5ms  //pan開始と同時にmouseイベントが追加される
touch.move...0(188,163) 5ms
mouse(FromTouch).move...0(188,163)Left 6ms
touch.move...0(190,166) 5ms
〜〜〜
mouse(FromTouch).move...0(162,299)Left 5ms
touch.move...0(154,299) 6ms
mouse(FromTouch).move...0(154,299)Left 5ms
touch.move...0(154,299) 10ms
touch.up...0(154,299) 6ms
mouse(FromTouch).up...0(154,299)Left 6ms
mouse(FromMouse).move...0(154,299) 6ms

プレスアンドホールド対応時の注意点

生データだけあって、マウスイベントより早い段階でタッチ操作イベントが飛んできます。

コレを使えばより素早い反応が可能ですが、プレスアンドホールドを考慮する場合は、このイベントが使用出来ません。

ログを見て貰うとわかるのですが、

・pressandhold enable
touch.down...0(177,189) 7346ms    //RightClickの前にsigletouchが発生する
touch.move...0(177,189) 10ms
touch.move...0(177,189) 8ms
〜〜〜
touch.move...0(177,189) 9ms
touch.move...0(177,189) 8ms
touch.move...0(177,189) 19ms
touch.up...0(177,189) 6ms
mouse(FromTouch).move...0(177,189) 6ms  //指を離したタイミングでmouseイベントが発生
mouse(FromTouch).down...0(177,189)Right 5ms
mouse(FromTouch).up...0(177,189)Right 5ms

[タッチイベント(左)][マウスイベント(右)]と完全に分かれてくるため、左クリックして右クリックしたかのような挙動を示します。

対応を考えるなら、タッチイベントは捨ててしまうのが賢明なようです。

ジェスチャーデバイス

タッチ操作をOSが処理して、専用のイベントを発生させます。

このモードの特徴は、クリックとプレスアンドホールド以外は、マウスイベントが全く発生しません。

パンであってもマウスムーブ(MouseMove)が発生しません。

これはスクロールバーの操作などを行えないことを意味します。(クリックはできるけど)

また、ウインドウのタイトルバーでもイベントを吸収してしまうため、ウインドウの移動やリサイズなどの処理に特殊なコードを書く必要があります。


まさしくタッチ操作に特化したモードではあるのですが、マウスやペンと共存させる場合はいささか機能不足や痒いところに手が届かない事になるでしょう。

フルスクリーンタッチアプリを作るのであればこれほど強力な物は無いのですが。

■gestue.singletouch
・pressandhold enbale
wait		//指を一定以上動かすまで反応無し
mouse(FromTouch).move...0(186,180) 3627ms
mouse(FromTouch).down...0(186,180)Left 5ms
mouse(FromTouch).up...0(186,180)Left 7ms
mouse(FromMouse).move...0(186,180) 6ms

・pressandhold disable
※singletouch.enableに同じ

■gestue.pressandhold
・pressandhold enable
wait		//指を一定以上動かすまで反応無し
mouse(FromTouch).move...0(158,172) 8052ms
mouse(FromTouch).down...0(158,172)Right 13ms
mouse(FromTouch).up...0(158,172)Right 8ms  //singletouchと異なりmoveが来ない

・pressandhold disable
※singletouch.enableに同じ

■gestue.enable.pan
・pressandhold enable
wait		//指を一定以上動かすまで反応無し
gesture.begin...(137,129) 4220ms  //マウスイベントは発生しない
gesture.pan...length=0 vector=0,0(137,129) 9ms
gesture.pan...length=0 vector=0,0(137,155) 9ms
gesture.pan...length=0 vector=0,0(137,157) 7ms
〜〜〜
gesture.pan...length=0 vector=0,1(137,184) 9ms
gesture.pan...length=0 vector=0,1(137,184) 8ms
gesture.pan...length=0 vector=0,0(137,184) 19ms
gesture.pan...length=0 vector=0,1308104(137,184) 6ms
gesture.end...(137,184) 6ms

・pressandhold disable
※pan.enableに同じ

■gesture.zoom
・pressandhold enable
gesture.begin...(201,139) 6558ms
gesture.zoom...zoom=106 pos=0,0(214,190) 27ms
gesture.zoom...zoom=148 pos=0,0(233,206) 9ms
〜〜〜
gesture.zoom...zoom=193 pos=0,0(230,203) 10ms  //zoomには慣性は無い
gesture.zoom...zoom=193 pos=0,0(230,203) 6ms
gesture.end...(230,203) 6ms

・pressandhpld disable
※zoom.enableに同じ


■gesture.rotate
・pressandhold enable
gesture.begin...(221,312) 4195ms
gesture.rotate...rot=40807 pos=0,0(221,312) 6ms
gesture.rotate...rot=33791 pos=0,0(221,312) 7ms
〜〜〜
gesture.rotate...rot=34541 pos=0,0(225,313) 5ms	 //rotateに慣性は無い
gesture.rotate...rot=34541 pos=0,0(225,313) 5ms
gesture.end...(199,231) 49ms


・pressandhold disable
※enableに同じ

おまけ

タッチデバイス時の他のジェスチャについてのログです。

ズームなどはこのイベント情報から自前で処理する必要があります。

windowsはID番号があって、これがとても役に立って優秀なのですが、それでもジェスチャー開始と終了処理は相当気を配って処理する必要があります。


■twofingertap
・pressandhold enable
touch.down...0(130,132) 2641ms
touch.move...0(130,132) 5ms
touch.down...1(227,280) 3ms
touch.move...0(130,132) 3ms
touch.move...1(227,280) 3ms
touch.move...0(130,132) 5ms
touch.move...1(227,280) 3ms
touch.move...0(130,132) 16ms
touch.move...1(227,280) 4ms
touch.up...0(130,132) 4ms
touch.move...1(227,280) 3ms
touch.up...0(227,280) 5ms
mouse(FromTouch).move...0(130,132) 4ms
mouse(FromTouch).down...0(130,132)Left 4ms
mouse(FromTouch).up...0(130,132)Left 4ms
mouse(FromMouse).move...0(130,132) 4ms

・pressandhold disable
touch.down...0(141,176) 6649ms
touch.move...0(141,176) 6ms
touch.down...1(208,299) 3ms
touch.move...0(141,176) 3ms
touch.move...1(208,299) 3ms
mouse(FromTouch).move...0(141,176) 4ms
mouse(FromTouch).down...0(141,176)Left 3ms
touch.move...0(141,176) 3ms
touch.move...1(208,299) 3ms
touch.move...0(141,176) 9ms
touch.move...1(208,299) 4ms
touch.up...0(141,176) 3ms
touch.move...1(208,299) 4ms
touch.up...0(208,299) 5ms
mouse(FromTouch).up...0(141,176)Left 4ms
mouse(FromMouse).move...0(141,176) 4ms

■touch.zoom
touch.down...0[2276](179,160) 27957ms
touch.move...0[2276](179,160) 7ms
touch.down...1[2277](336,261) 2ms
touch.move...0[2276](179,160) 4ms
touch.move...1[2277](352,286) 5ms
touch.move...0[2276](158,148) 7ms
touch.move...1[2277](356,292) 6ms
mouse(FromTouch).move...0(179,160) 6ms
mouse(FromTouch).down...0(179,160)Left 6ms
mouse(FromTouch).move...0(154,144)Left 5ms
touch.move...0[2276](154,144) 7ms
touch.move...1[2277](366,305) 5ms
mouse(FromTouch).move...0(146,137)Left 6ms
touch.move...0[2276](146,137) 7ms
touch.move...1[2277](372,312) 5ms
mouse(FromTouch).move...0(141,132)Left 5ms
touch.move...0[2276](141,132) 7ms
touch.move...1[2277](377,316) 5ms
mouse(FromTouch).move...0(138,129)Left 6ms
touch.move...0[2276](131,123) 6ms
touch.move...1[2277](391,331) 5ms
touch.up...0[2276](131,123) 5ms
touch.move...1[2277](391,331) 5ms
touch.move...0[2277](391,331) 6ms
mouse(FromTouch).up...0(131,123)Left 5ms
touch.move...0[2277](391,331) 6ms
touch.up...0[2277](391,331) 5ms
mouse(FromMouse).move...0(131,123) 6ms

2011-05-12

[]CharaCombine(画像結合全パターン出力ツール)

ふと自動トリミングで良いアイデアが浮かんだので形にしようと、GWで仕上げる予定だったツールですがちょっと足が出てしまいました…。

f:id:Ko-Ta:20110512203344j:image

・CharaCombine ダウンロード

http://kota.dokkoisho.com/#CharaCombine


・概要

複数の画像をあれこれ合成して、全パターンを出力します。

つまり、立ち絵の切り出しなんかに使うと作業効率が良くなるんじゃないかな?と思われるツールです。

ポーズ8種、服装7種、持ち物4種、表情12種の場合は、8x7x4x12=2688ファイル出力します。

ただし、ポーズが大きく変わる(表情の位置など)場合は、プロジェクトを分けて管理しなければなりません。


このツールの特徴は自動トリミングで、中央位置を維持したり、顔の部分だけ抜き出すような任意指定もできたりします。

PSD読み込みは出来ないので、一度PNG出力する手間はありますが、それでも楽になると思います。

出力の際は、透明部分の色を塗りつぶして読み込みを軽くしたり、リニアフィルタ時の色漏れ処理も行います。


・使い方

ちょっと色々設定項目と機能があるので難しいかもしれませんので、付属マニュアルとにらめっこしてください。


バッチ処理対応

コマンドプロンプトからカキカキすることで、自動的にはき出して出力するバッチ機能がついています。

複数のプロジェクトを一度に吐かせる際に役立つでしょう。


・対応フォーマット

読み込み:PNG(24bit/24+8bit),Bitmap(8/24/32),Jpeg

書き出し:PNG(24+8bit),Bitmap(32)


・バージョンアップ情報

2011/10/25...オーバーラップイメージ機能を修正

2011/05/19...小さい範囲を切り出す際に座標が狂うバグを修正。

2011/05/16...トリミング範囲設定に有効設定を追加。URL間違ってたのを修正。

2011/05/12...公開

[]PhtoShopのレイヤ結合計算式

さて、今回は画像の結合を行う必要があったので、普通のアルファブレンドではなく、Photoshopにおけるレイヤ結合の計算式を使用しました。

普通の合成と違ってMatrixのCompositeと似てるように思えます。

ゲームではほとんど、または全く使用されない類のものだと思います。


基本的には2枚の画像、src(上)、dest(下)を元に計算します。

何枚も重なる場合は、下から上に順に何回も行っていきます。

(たぶん上からでも大丈夫なんじゃないかな。そのほうが手前描画と同じ原理で最適化が利く事が多いかもしれません)

計算式はこんなかんじ。

src...上の画像
dest..下の画像
out...結合結果

da = (1.0 - src.a) * dest.a
out.a = src.a + dest.a

out.rgb = src.rgb * (src.a/out.a) + dest.rgb * (da/out.a)
  ↓
oa=1.0/out.a
out.rgb = src.rgb * (src.a*oa) + dest.rgb * (da*oa)

※例外
src.a==0.0...スキップ
src.a==1.0...dest.argb=src.argb
dest.a==0.0..dest.argb=src.argb

Alpha50%とAlpha50%を合成する場合、上の画像が50%、下の画像が0.5x0.5=25%の配分になるよー、ということ以外はアルファブレンドと似たような計算式です。

ただ50%と25%では100%にならないので、足した75%で割ることで、66.6%と33.3%に比率を調整する必要があり、割り算が発生します。

でも逆数(1.0/n)を一回求めておけば加算と乗算の式に置き換えることが出来るので、最適化する際は行列計算を上手く使えそうな気がします。

srcが100%と0%の時に最適化、destが0%の時に例外があるので、Shaderの時はちょっと考えないといけませんね。

上の式では「通常」合成に当たります。

ゲームでは割り算は御法度なため、alphaの計算にスクリーン*1を用いられることが常な用です。

*1:a1+a2)-(a1*a2

2011-04-18

[]TBitmapでマルチコア分散処理するときの注意点

NHKで赤い…。

それはともかく、ホームページをごにょごにょと整理したついでに、バグってる公開済みコードも直そうとマルチコアのソースを修正しておりました際に見つけた、かなりドハマリなお話です。

以前(XP)なら特に問題もなかったのですが、環境が変わったためか、分散するだけ重くなると言う現象が発生しました。

分散するだけ重くなる・・・フフフ素敵ですね。


色々やった結果、結局のところ、TBitmap.Scanline[]がマルチコア分散処理に使えないという事が分かりました。

なんだか以前は大丈夫だった気がするんですが、過去を振り返っても仕方ありません。

procedure __MultiCoreFunction(dptr:pointer);
begin
  for 
    dest := TBitmap(dptr).Scanline[y]; //call GetScanline
  end
end;

こんな感じだと、アウトです。


回避するには、ScanlineのPointerを変数配列に待避させて、それを渡せば解決です。

type
  THogeRecord=recrd
    scanline : array of pointer;
  end;
  PHogeRecord=^THogeRecord;

procedure __MultiCoreFunction(dptr:pointer);
begin
  for 
    dest := PHogeRecord(dptr)^.Scanline[y];
  end;
end;

//前処理:scanline pointerの取得
procedure main;
var
  h,i : Integer;
  r : THogeRecord;
begin
  h = bmp.height;
  SetLength(r.Scanline,h);
  for i:=0 to h-1 do
    r.scanline[i] := bmp.Scanline[i];
  end;
  〜〜
end;

構造体なりクラス宣言する必要があるので、ちょっと面倒ですね。


今まで全然気づかなかったのは、自前画像ライブラリだと高速化するためにLoadやSetSize時にScanlineを配列に待避してそれを使っているので、知らないうちに回避されてたためで、いやはや怖いですね。

TBitmap.scanlineのどこが悪いかまでは探ってませんが(探る気も無いですが)、DIBNeedとかあのへんが怪しい気がします。

とはいえ、上記で簡単に回避できるので、原因を深くは考えないようにしたいと思います:-Q

2011-04-17

ようやくファイルリンクを修正しました

iswebさん死亡後、長らくご無沙汰しておりましたが、ようやくファイルのリンクをなおしました。

今のところNinja----さんに置かせていただいております。ありがたや。


大きいアプリを1つ作ってるので、ブログの更新もほんと減ってしまいましたが、最近またちょっと幸せに慣れそうな小アプリの構想が浮かんだので、出来たらご報告したいと思います。

2011-01-26

メイン環境をようやくWindows7に

仕事が忙しくなる前にメイン開発環境をWindows7に移行してみました。いや移行中です。

これまでは64bitを使っていましたが、いろいろ入れていくうちにSoundBlasterAudigy2先生の挙動がおかしくて、結局32bitに入れ直したり、右往左往です。

ほんとは64bitで行きたかったのですけど…。


OSのインストールは楽になりましたが、環境移行作業はいつの時代も大変です。

メールぐらいなら外付けに入れておけばいいのですが、開発系はOSに根付いてたり、Adobeさんはライセンスに厳しいですし、結局総入れ替え&総設定に明け暮れる他ありません。

ちなみに、Adobeさんに「インストールライセンス数が限界を超えたよ」って初めて言われました。

前の環境を立ち上げて、認証解除をするわけですが、もしフォーマットしちゃってたりしたらどうなるんでしょうねこれ…。こわいこわい。

2010-12-23

[]PNGファイルのマルチコア処理

また日が空いてしまいました。

ここ最近はマルチコア対応のコードを書いておりました。

マルチコアについては以前オブジェクトクラスを書きましたが、バグ入りでしたねごめんなさい。

何がいけなかったかと申しますと、具体的には終了同期部分で、スレッド側が自分でサスペンスするところがだめだったみたい。ごめんなさい。


さて年末年始に向けて謝ったところで、マルチコア化で効果が大きかった、PNG読み込みのマルチコア化について書いておこうと思います。

マルチコア処理は、CPUならグラフィックレンダリングとか、物理計算とかと相性が良いようですが、割とヘビーなPNGファイルの読み込みにもなかなか効果的です。

しかし、ふつーのPNGライブラリはマルチコア対応はしてません。(探せばあるのかな?)

そこで、単純にファイルを分割してデコード処理を分散してやろうという、なんとも安上がりで安直な方法で対処します。可逆圧縮ですし、結合時に分割線が現れる心配もありませんし。


まず、画像を単純に分割します。

分割数が多いほど分散時の偏りが少なくなりますが、256分割もすると逆にデータを分割する処理が読み込みよりも重くなってしまいますので、4か8分割ぐらいがお薦めです。(5などはCPUコア数で割ると端数になる環境が多いので2の累乗が好ましいようです)

また、分割方向は横ではなく縦を(メモリジャンプの回避)、分割領域のサイズ・座標は8の倍数にするあたりがコツかもしれません。

とりあえず例では4分割するとしましょう。

size:62x122
+-----------+  y=0
|  bmp1     |
+-----------+  y=32(8x4)
|  bmp2     |
+-----------+  y=64(8x8)
|  bmp3     |
+-----------+  y=96(8x12)
|  bmp4     |
+-----------+  y=122(最後は高さに合わせる)

[bmp1][bmp2][bmp3][bmp4] → [png1][png2][png3][png4]
 ↓(独自形式にして保存)
[FileHedder][png1][png2][png3][png4]

それぞれ分割したものをPNGファイルにして独自ファイルにまとめれば準備は出来上がりです。


次はコレを読み込み、マルチコア分散処理で高速展開します。

−−−[シングルコア処理]−−−
[FileHedder][png1][png2][png3][png4]
  ↓(ファイル分解)
[png1] [png2] [png3] [png4]
−−−[マルチタスク処理]−−−
[png1] [png2] [png3] [png4]
(core1)(core2)(core3)(core4)
  ↓     ↓     ↓     ↓(展開)
[bmp1] [bmp2] [bmp3] [bmp4]
  ↓(結合・転送)
[bmp]
−−−[タスク同期]−−−

pngデータをメモリ上に4つ分離します。分解まではシングルタスクで処理します。

分解した4つのpngデータをそれぞれのコアに分散処理させます。

展開後、できたbmpデータを1つのbmpに結合(転送処理)するところも、転送領域が被らないので分散処理の対象となります。

最後にマルチコア処理の同期をとって終了です。

ね、簡単でしょ。


さて、画像を縦方向に分割しましたが、分割された画像をみると、チョットした最適化が思い浮かぶと思います。

もし左右に透明ピクセルがあれば、それをトリミングすることで高速化できるはずです。

透明部分であっても圧縮・展開処理が入るわけですから、PNG領域は小さいに越したことはありません。

+---------------+
|   **** *** *  |
+---------------+
      ↓
...+----------+.. ※トリミングも8ピクセル単位に行う
...|**** *** *|..
...+----------+..

このような透明部分が多い画像を見る機会は少ないかも知れませんが、キャラクターの立ち絵なんかでは透明部分が多く存在するので割と効果的みたいです。

適当な立ち絵サンプルで数値を取ると、だいたい10〜30%ぐらいの上昇率でした。

この機構を組み込むと若干処理が複雑化しますが、悪くない数値だと思います。