いつもお世話になります。ebimayoです。
Bitmap表示の際のちらつき防止について教えてください。
タイトルに書いた定周期BITMAP更新処理の流れは以下の通りです。
定周期で画像Bitmapファイルを読み込み(毎回読み込むBitmapは違うもの)
↓
読み込んだBitmapの情報をCreateCompatibleBitmapで用意したメモリ上のDCにBitBlt
↓
InvalidateRect(Bitmapの領域,FALSE); で再描画
↓
OnPaintでBitBltを実行して描画
BitBlt( 実際に表示するDC, ・・・,
CreateCompatibleBitmapで用意したDC, 0, 0, SRCCOPY);
現在試しているのはこのような処理で、定周期は300ms、Bitmapは256色の1024×1024
です。表示する領域とBitmapのサイズは同じようにして実行しています。
Bitmapの表示処理自体はうまくいってますが、これを実行すると画面がちらついてしま
います。
(Bitmapの表示処理がうまくいっている確認はAPIなどでエラーが出ていないこと、
実際に表示したいものが画面に表示されていることで確認)
同様の処理で定周期ではなく(1つのBitmapを一度描画するだけ)、上に異なる
ダイアログ等が重なった場合の再描画などによるちらつきはまったくありません。
いろいろ探してみたのですが、どこでも画面のちらつき防止に裏画面
を使うと書いていますが、周期的に異なるBitmapを表示している所は見つからず
手詰まり状態です。
ちらつきを抑える方法を何か教えていただけたらと思い質問しました。
開発環境
Microsoft WindowsXP(SP2)
Microsoft Visual C++ 2005
ダイアログベースアプリケーション(MFC)
後考えられるとしたら、OnEraseBkgndをオバーライドしてFALSEで返せば、
背景の塗潰しが抑制されてちらつきが減るかもしれません。
やっていないのであれば、試してみてください。
ちなみにWM_ERASEBKGNDでの背景の塗潰しを抑制するのはちらつき対策で
良くやります。但し、これをやる場合はクライアント領域の背景部分の
描画を全て自前で行う必要があります。
行っていないと再描画がかかっていない部分は前の画像が残ってしまいます。
回答ありがとうございます。
書き漏れていて重要なことなのかもしれないのですが、この処理は
CDialogの派生クラスのOnPaint つまりダイアログの背景を直接描画する
処理として書いています。
CDialogの派生クラスでGOnEraseBkgndはオーバーライドすることも
試していたのですが、同様にちらついてしまいました。
ダイアログの背景に直接描画処理を書くとちらつく現象が
今回の件とは別に以前も起きたことを思い出したので、
試しにCStaticの派生クラスをCDialogの子ウィンドウとして作り
そのOnPaint処理で同様の処理を書いたところ描画によるちらつきがなくなりました。
原因がまったくわからずに解決にするのもいけないこととは思いつつ
時間に限りがあるため取りあえず解決とさせていただきます。
時間があればまた調べてみたいとは思っていますが、この現象で
何か思い当たることがありましたら教えてもらえないでしょうか。
宜しくお願いします。
誤記訂正
誤)GOnEraseBkgnd
正)OnEraseBkgnd
例え自前で背景描画を行っていたとしてもOnEraseBkgndと同じ実装をしていたら
結局、ちらつくと思いますよ。
ちらつく原因は、ビットマップを貼るスペースは本来ならビットマップが載るわけですから
その部分の背景描画は必要ないわけです。にもかかわらず背景を全てベタ塗りしてしまうと
その分だけ処理時間が掛かってしまい、背景が塗潰された後、ビットマップを貼り付ける
様子が
目に見えてしまうのが原因でしょう。
コントロールを貼り付けるとコントロールの後ろの部分の描画がスキップされるから
ちらつきが抑えられるのではないでしょうか。
自前で描画するのであれば、ビットマップをまず貼り付けてしまって
ビットマップが無い部分だけを背景描画するようにするとちらつきが少なくなると思います。
ちなみに実際のちらつきを全く無くすというのは多分無理です。
描画が早いと結果的にちらつきが目立たなくなるだけで書き換えが起こっている部分は
大なり小なりちらつきます。
あとは、PC側の描画性能によると思います。
回答ありがとうございます。
PATIOさんの話から考えると、
ダイアログで描画するとOnEraseBkgndオーバーライドしていても
OnPaint処理でその他の描画処理(実際にはIvalidateRectで描画対象にはならないと思
っていますけれど)
も書いてあるため、その分ちらつきが目立つのかもしれないと想像。
取りあえず基本クラスは何でもいいのですがCStaticの派生として
Bitmap更新時の描画をそのCStaticの派生クラス内でのみ描画すると
余計な処理が走らずに結果的にちらつきがなくなるのかな、全然まとはずれかもしれま
せんが・・・
OnPaint内で背景を全て矩形か何かで塗潰しているのであれば、
InvalidateRectをしたところで一度塗潰してからビットマップを貼り付けると言う
動きには変わりありませんよね。ビットマップが大きければ塗潰す面積は大きくなります。
PCの性能が低ければ、この書き換えがちらつきに見えてしまうくらい遅いという可能性は
ありえるのではないでしょうか。
ダイアログの場合、上にコントロールが載っているとその部分はクリッピングされるので
描画対象から外れるようになっていたと思います。
これは少しでも描画のパフォーマンスをあげる為に上書きされるはずの部分の描画を
しないで済ませると言う事です。
結果としてちらつきが少なくなるというのは私が書いた理屈に合っていると思います。
GDIで描画を行う場合、上に重なる事がわかっている部分は書かないというのは、
描画スピードを上げる為の基本思想です。
あと、バックバッファを使うのであれば、クライアント領域をそっくりカバーするような
物を作成しておいてその上で背景を塗潰してビットマップを貼り付け、バックバッファの
内容を実際のクライアント領域に貼り付けるようにしないとちらつきは抑えられません。
ちらつきを抑えるというのは、描画過程をユーザーに見せないようにする事で実現します。
今のebimayoさんの実装では背景の塗潰しと言う描画をクライアント画面に直接行って
いる為にその部分の描画過程をユーザーに見せてしまっているのでちらつくのです。
InvalidateRectを生かしたいのであれば、背景全体を矩形等で一気に塗潰すのでは
なくてビットマップの領域外の背景のみを塗り潰すようなコードを書けば生きてくると
思いますよ。
その場合はビットマップ外の部分の描画は表に出てきませんから。
コントロールにしたことで描画範囲が確実に狭くなった→ちらつき抑制
であろうことは現象を聞く限り同意ですし、範囲が広くて描画が重いのが根本原因だとは思います
が、
書き込みを見る限りでは、クライアント画面に直接描写しているわけではなさそうに見えます。
> 読み込んだBitmapの情報をCreateCompatibleBitmapで用意したメモリ上のDCにBitBlt
> ↓
> InvalidateRect(Bitmapの領域,FALSE); で再描画
> ↓
> OnPaintでBitBltを実行して描画
> BitBlt( 実際に表示するDC, ・・・,
> CreateCompatibleBitmapで用意したDC, 0, 0, SRCCOPY);
# あ、実際はどうなんでしょう?>スレ主様、ってだけのことですが。
PATIOさん
>ダイアログの場合、上にコントロールが載っているとその部分はクリッピングされるの
で
>描画対象から外れるようになっていたと思います。
ちょっと補足します。
このような動作をするのはダイアログのスタイルにWS_CLIPCHILDRENが含まれている場合
だけです。
WS_CLIPCHILDRENがない場合にダイアログ上にコントロールを配置してしまうと
逆にちらついてしまうと思われますのでご注意を。
#元記事からはコントロールがあるかどうかはわかりませんが・・・
PATIOさん、Banさん、subaruさんありがとうございます。
PATIOさんへ
何か私の書き方が下手で誤解を与えてしまっているかなと思ったので以下補足。
現在描画を行っている領域は1024×1024固定で、InvalidateRectもこの領域を
RECT構造体で渡して第二引数FALSEを設定して描画処理を走らせています。なので、
今回対象の描画領域外の背景の描画処理は走っていないと推測しています。
後、これが誤解させてしまって書き方が悪かったと思うのですが
「ダイアログの背景を直接描画」と書いたのは、ダイアログに貼り付けた
コントロール(CStaticなどの部品)に描画しているわけではないと言いたかった
のです。すいません。
実際には、最初の投稿で書いたように
裏画面(メモリデバイスコンテキスト)に書いたものをダイアログのOnPaint処理時に
BitBltしています。
Banさんへ
おっしゃる通り、 裏画面(メモリデバイスコンテキスト)に書いたものを
ダイアログの背景としてBitBltしています。
subaruさんへ
WS_CLIPCHILDRENはスタイルに含めていません。しかし、実際は1280×1024の
ダイアログ内のうち1024×1024の領域を同じサイズのビットマップでBitBlt
していて、それに重なるコントロールなどはないです。で、今回はInvalidateRect
でビットマップの領域の1024×1024をRECTで指定して、第二引数FALSEで
渡しているためWS_CLIPCHILDRENは関係ないかなと思ってたりします。
後、同じ親を持つコントロールのクリッピングは自前で用意しています。
今までこんなこと無かったのですが、今回作り方が変?特殊?なのか
なぜかウィンドウスタイルで指定してもクリッピングがうまく作用して
いなかったので・・・
はて、オフスクリーン上に全て描画していてOnPaintで背景の塗潰しをしていない
となるとなんでちらつくんでしょうね。オフスクリーンは実画面のコンパチブルの
ようだからBitBltの負荷もこれ以上は減らせないでしょうし。
InvalidateRectは多分、ビットマップのサイズと位置で実行していると思うので
ビットマップを貼り付ける実画面側のOnPaintにクライアント画面全体を背景色で
塗り潰すような処理があるのではないかと考えていたんですが、
そのような処理が一切ないとするとちょっと見当がつかないです。
私が言っているのはInvalidateRectした時にビットマップを貼る領域は当然再描画の
対象になるのでOnPaint内でクライアント領域全体の塗潰し処理をしていると
その領域の塗り潰しが走ってしまってちらついているのかなと考えたのです。
あと、InvalidateRectの第二引数をFALSEにした場合の背景描画の処理は
多分、WM_ERASEBKGNDの事を言っているのではないかと思うのでオバーライドして
つぶしている場合は関係ないと思いますし、OnPaint内で背景の塗潰しをしている場合は
このフラグの設定に関わらず、背景描画されると思います。
CStaticでコントロールを作ってそのコントロールに直接Invalidateをしてやると
ビットマップ以外の部分は全く関係ないレベルで再描画が起こると思うので
それでちらつきが少なくなるならやはりダイアログ側のOnPaintに何かあるのでは
ないかと思うんですが。
# 私もむかし、ダイアログはなんかチラついた記憶がありまして。
3 うろ覚えで、直接のお役に立てなかったのですが…