Graphics に対して塗り潰しを行う
Bitmap に対してではなく Graphics に対して、GDI の ExtFloodFill のようなことをしてみたいわけである。塗り潰しの対象が Bitmap であれば、かなりの量のサンプルや動作する実コードが検索できるのだけど、対 Graphics というとサッパリ存在しない。
Paint イベントの実装から呼び出したりすることも考慮すると、引数を Bitmap に固定するのは得策ではない。ごく単純に考えるならば、
public void Fill(Graphics g, Point pt, Color color) { // HDC 取り出し IntPtr hdc = g.GetHdc(); try { // 目的の色のブラシ作成して選択 IntPtr brush = GDI32.NativeMethods.CreateSolidBrush(ColorTranslator.ToWin32(color)); brush = GDI32.NativeMethods.SelectObject(hdc, brush); try { // 開始点の色を取得する int start = GDI32.NativeMethods.GetPixel(hdc, pt.X, pt.Y); // 取得した色を塗りつぶす bool rc = GDI32.NativeMethods.ExtFloodFill(hdc, pt.X, pt.Y, start, GDI32.FLOODFILL.SURFACE); Trace.Assert(rc); } finally { // 元のブラシに戻し、作成したブラシを削除 brush = GDI32.NativeMethods.SelectObject(hdc, brush); GDI32.NativeMethods.DeleteObject(brush); } } finally { g.ReleaseHdc(hdc); } }
これでいけそうだと考えられるし、単純にフォームの Paint イベントに乗せると期待とおりに動いたようにみえる。しかし、フォームの上を別のウィンドウが横切ると GetPixel が INVALID_COLOR を返し、ExtFloodFill が false を返すようになる。
これは Form の描画がクリッピングを利用しているからで GetPixel の仕様通りなのだが、Fill に渡した座標がクリッピング領域に含まれておらず、実際に塗り潰された範囲がクリッピング領域に含まれている場合、再描画されるべき部分があるにも関わらず描画できないことになるため正常な結果を得られなくなる。
ここまでテストした時点で、実際に塗り潰しを必要としている部分に上記の実装を導入してみた。なぜなら、今使いたい場所での処理がクリッピングされることは絶対にないからである(苦笑
結果はNG、塗り潰しが境界線を越えて Graphics で描画する先の全体が塗り潰し色で埋まってしまった。
デバッガで確認したところ、GetPixel が常に 0x0C0B0D を返すようだとわかり、引数の Graphics が Graphics.FromImage(Bitmap) で得られたものではないかとテストプログラムを Graphics.FromImage(Bitmap) に対するものに変更してみたところ、確かに同じ結果を得ることができた。これについて調べると、KB311221 に記載されていて、未初期化の書き込み専用の HDC が生成され、そこに対する書き込みが Graphics へ反映されるようになっているということらしい。
ということは…、Graphics を引数にもらった場合、その Graphics に書かれた絵に依存した絵を描き足すことは非常に困難ということになる……、検索して見つかるのが Bitmap を対象とした Fill ばっかりなわけだ(笑