• ベストアンサー

画像表示について。

現在SDKにてBMPやRAWなどの画像表示ソフトを作成している段階です。 そして、一通り作成することができました。 ここで質問に答えてくださった皆様に感謝します。 新たに些細な問題が出てきてしまったので質問します。 作成したソフトはMDI型のソフトであり、その構成はフレーム、クライアント、ドキュメントウィンドウといった構成になっています。 もちろん読み込んだ画像はドキュメントウィンドウに表示をするのですが、このドキュメントウィンドウのサイズをマウスを使って変更した場合、画像にちらつきが生じてしまいます。 原因はプログラム上 WM_PAINT メッセージがきた場合に描画をおこなうことに他ならないのですが、解決する手段はないでしょうか? 例えばダブルバッファリングなどの手段があればよいのですが・・・

質問者が選んだベストアンサー

  • ベストアンサー
noname#30727
noname#30727
回答No.5

ANo.5です。 >・・・・・こんな流れでよいでしょうか。 お礼欄に書かれている流れで、ほぼ完璧だと思います。 >このリージョン1をhdcにクリップする。 「DCのクリッピングリージョンをリージョン1にする」が妥当な解釈だと思います。 何かを描画するときに、ある範囲だけに描画する事を「クリップする」または「クリッピング」と言い、その範囲をクリッピングリージョンと言います。 最終的なクリッピングリージョンは、ウィンドウの重なり方で決定されるシステムクリッピングリージョン、InvalidateRectなどで作成される更新リージョン、SelectClipRgnなどで登録されたユーザー定義クリッピングリージョンが合成されたものだと思います。 >これを行うとSelectClipRgn関数でhdcにクリップしたリージョンがなくなってしまうように思われるのですが・・・・ Platform SDKのドキュメントに書かれているのですが、SelectClipRgn で選択したリージョンは、そのコピーが使用されます。 なので、SelectClipRgn から戻ったら、リージョンは削除してもかまいません。

noconan
質問者

お礼

リージョンの解説までいただありがとうございました。 教えていただいた方法により、納得のいくソフトに近づけそうです。 ありがとうございました。

その他の回答 (4)

noname#30727
noname#30727
回答No.4

ANo.2です。 ビットマップのスタティックコントロールと同じように考えてしまったので、ウィンドウサイズとビットマップサイズが違う事を考慮していませんでした。 チャイルドウィンドウを作成して、ドキュメントウィンドウの上に重ねるのが普通かもしれません。ダイアログの考え方と同じです。 チャイルドウィンドウのクリッピング処理を自前でやる事もできなくはないと思います。メモ程度ですが参考にしてみてください。この場合、hbrBackgroundはNULLのままです。 RECT rect; HRGN hrgn; hdc = BeginPaint(...); GetClientRect(hwnd, &rect); hrgn = CreateRectRgnIndirect(&rect); SelectClipRgn(hdc, hrgn); DeleteObject(hrgn); rectにビットマップの描画領域を設定; hrgn = CreateRectRgnIndirect(&rect); ExtSelectClipRgn(hdc, hrgn, RGN_DIFF); DeleteObject(hrgn); クライアントエリアを背景色で塗りつぶす; SelectClipRgn(hdc, NULL); ビットマップの描画; EndPaint(...);

noconan
質問者

お礼

連続書き込みをお許しください。 調べた結果大体のことは分かりました。 最初にドキュメントウィンドウのクライアントサイズを取得し、その大きさと等しいリージョンを作成する。このリージョンをリージョン1とします。 このリージョン1をhdcにクリップする。 次に描画領域のサイズを取得し、その大きさと等しいリージョンを作成する。 このリージョンをリージョン2とします。 その後ExtSelectClipRgn関数のフラグをRGN_DIFFに設定することでリージョン1とリージョン2との差をとることができ、hdcには描画領域以外の領域をリージョンとすることができる。 その状態で背景を描画した場合、もちろんビットマップの描画領域以外がペイントされ、ビットマップの描画領域を更新しないですむ。 ここまで行われたら、hdcからクリップされたリージョンを削除するためSelectClipRgn関数の第2引数をNULLにする。 そのごビットマップはもちろんそれ自身の表示領域を指定するので、一度の描画更新処理だけですむ。 ・・・・・こんな流れでよいでしょうか。 とても参考になりました。 以前リージョンなんて画像処理ソフトには無意味だろうと思い、疎かにしてしまいましたが、こんなところで顔を出すことになるとは思いもよりませんでしたw ついでに質問してもよろしいでしょうか? SelectClipRgn関数のあとすぐにDeleteObjectによりリージョンハンドルを削除していますが、これを行うとSelectClipRgn関数でhdcにクリップしたリージョンがなくなってしまうように思われるのですが・・・・ 実際うまく動いているのでそんなことはないんでしょうが、動作がはっきり把握できません。 もしよろしければ、自分の考えが正しいか、そして最後の動作の理解も含め解説していただけたら光栄です。

noconan
質問者

補足

返答ありがとうございました。 教えていただいた方法で組んでみた結果、見事ちらつきを消去することに成功しました。 しかし・・・自分はまだ”リージョン”について詳しく勉強したことがないので、例に挙げてくださったプログラムを理解しなければいけません。 申し訳ないですが、例に挙げてくださったプログラムを大雑把でいいので解説してもらえないでしょうか?

  • Oh-Orange
  • ベストアンサー率63% (854/1345)
回答No.3

★別の回答を紹介します。 ・回答者 No.1 さんの方法以外にもう一つアイディアを紹介します。  それは『リサイズ』中、WM_PAINT の描画を禁止させて『リサイズ』後に描画処理を  実行させる方法です。 ・仕組みは  (1)リサイズの開始時、WM_ENTERSIZEMOVE をキャッチして描画フラグを OFF に設定  (2)リサイズの終了時、WM_EXITSIZEMOVE をキャッチして描画フラグを ON に設定  (3)リサイズの終了時、安全のために InvalidateRect( hWnd, NULL, TRUE ) を実行しておく  (4)WM_PAINT で描画フラグが ON のときだけ描画処理を行う  これだけです。それではサンプルを載せます。 サンプル: LRESULT CALLBACK DialogProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {  static BOOL bDrawEnable = TRUE; ←描画フラグ(初期値は TRUE にする)    switch ( uMsg ){   case WM_ENTERSIZEMOVE:    bDrawEnable = FALSE; // 描画禁止    break;   case WM_EXITSIZEMOVE:    bDrawEnable = TRUE; // 描画可能    InvalidateRect( hWnd, NULL, TRUE );    break;   case WM_PAINT:    if ( bDrawEnable ){     PAINTSTRUCT ps;     HDC hDC;          hDC = BeginPaint( hWnd, &ps );     /*     描画処理(ダブルバッファリングを BitBlt で転送するなど)     */     EndPaint( hWnd, &ps );    }    break;   default:return DefWindowProc( hWnd, uMsg, wParam, lParam );  }  return 0; } その他: ・この方法だとリサイズ中は一切の描画を行いません。  また、リサイズ終了時とは『マウスドラッグ』をやめたときでマウスボタンが離されたときです。  それまでは一切の描画がないのでちょっと見た目が不自然になるかもしれません。この場合は  タイマーを使って例えば 1 秒間隔で一時的に WM_PAINT させるように工夫すれば良い。  タイマーの間隔は自由です。→下のサンプル2 がタイマー版。 ・回答者 No.1 さんの方法もでちらつき過ぎる場合は、タイマー間隔でちらつく間隔をカスタマイズ  できます。→0.5秒、1.0秒が適当かな。試していないが…。その辺だろう。  そのほかマウスカーソルの移動位置を使って、移動量が 0 の時に一時描画の bDrawTimer=TRUE を  セットする方法などがあります。この方法の方が綺麗かも。動作はご自由にどうぞ。 サンプル2: LRESULT CALLBACK DialogProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {  static BOOL bDrawEnable = TRUE; ←描画フラグ(初期値は TRUE にする)  static BOOL bDrawTimer = FALSE; ←描画フラグ(タイマー用)    switch ( uMsg ){   case WM_ENTERSIZEMOVE:    bDrawEnable = FALSE; // 描画禁止    SetTimer( hWnd, タイマーID, タイマー間隔(ms), NULL );    break;   case WM_EXITSIZEMOVE:    bDrawEnable = TRUE; // 描画可能    KillTimer( hWnd, タイマーID );    InvalidateRect( hWnd, NULL, TRUE );    break;   case WM_TIMER:    bDrawTimer = TRUE; ←一時的に描画させるため    InvalidateRect( hWnd, NULL, TRUE );    break;   case WM_PAINT:    if ( bDrawEnable || bDrawTimer ){     PAINTSTRUCT ps;     HDC hDC;          hDC = BeginPaint( hWnd, &ps );     /*     描画処理(ダブルバッファリングを BitBlt で転送するなど)     */     EndPaint( hWnd, &ps );     bDrawTimer = FALSE; ←リセットしておく    }    break;   default:return DefWindowProc( hWnd, uMsg, wParam, lParam );  }  return 0; }

noconan
質問者

お礼

返答ありがとうございました。 タイマーを使うことは思いつきませんでしたね。 あるいみタイマーの応用範囲は広いですね。 早速実行してみたところ確かに以前に比べちらつきは少なくなりましたが、依然として発生しております。 今作成しているソフトは、ドキュメントウィンドウのサイズが変更されるたびに画像の表示位置を変更して、必ずドキュメントウィンドウの中央に表示されるようにプログラムを組んでいます。 もしサイズが変更されても表示位置は変更しないようなプログラムならおそらくちらつきはなくなるかもしれません。 ちょっと説明不足でしたね。申し訳ないです。 回答者No.2さんの方法とあわせて実行してみましたが、それでも解決できませんでした。 今後色々試行錯誤してみます。

noname#30727
noname#30727
回答No.2

これはWM_PAINTとWM_ERASEBKGNDの関係から起こる事です。 BeginPaintを実行すると内部でWM_ERASEBKGNDがSendMessageされます。 この時点でドキュメントウィンドウは一度クリアされるので、ちらつきが起こります。 何通りかの解決方法が考えられますが、私が良く使うのは、WNDCLASSEX の hbrBackground を NULL にしてウィンドウを作成する方法です。 このままだと、ドキュメントが無い状態ではウィンドウ背景が消去されなくなってしまうので、そのときは、WM_PAINTで空のドキュメントを描画する必要があります。(適当な色で塗りつぶすなど)

noconan
質問者

お礼

回答ありがとうございました。 最初に”適当な色で背景を塗りつぶす”作業を行わずに、hbrBackgroundのみNULLにして実行してみたところ、背景がないため移動軌跡が残ってしまいますが、ちらつきはまったく出ませんでした。 つぎに正常に戻すため、”適当な色で背景を塗りつぶす”作業を含め実行したところ、通常と変わらないちらつきが発生してしまいました。 うまくいくと思ったんですが・・・もう少しがんばってみようと思います。

  • noocyte
  • ベストアンサー率58% (171/291)
回答No.1

MDI は作ったことがありませんが,以前 SDI でやったときは, ウインドウクラススタイルに CS_HREDRAW | CS_VREDRAW を指定するのではなく, WM_SIZE メッセージの処理で InvalidateRect(hwnd, NULL, FALSE) を行う方が かなりちらつきが少なかったと思います. その理由は次のとおり (前半は推測). CS_HREDRAW | CS_VREDRAW だとウインドウをリサイズするたびに WM_PAINT メッセージが (たぶん) send され,非常に頻繁に再描画が行われます.(いまいち確信なし) これに対し,InvalidateRect は WM_PAINT メッセージを post する, つまり後で WM_PAINT がディスパッチされるように「予約」するだけなのに加え, WM_PAINT がディスパッチされる優先度は他のウインドウメッセージに比べて 優先度が低い (他にディスパッチすべきメッセージがなければ WM_PAINT が ディスパッチされる) ので,再描画が行われる回数が激減します. したがって,リサイズが行われている間は WM_SIZE などがディスパッチされ続け, WM_PAINT はほとんどディスパッチされないのに対し,リサイズが (一瞬でも) 停止して 他のメッセージがなくなると WM_PAINT がディスパッチされて再描画が行われます.

noconan
質問者

お礼

返答ありがとうございました。 実行してみたところ、確かにWM_PAINTでInvalidateRectを実行するよりもちらつきは少なくなりました。 しかし完全になくなったとはいえないので、もう少し改良を加えてみようと思います。