境界の色が違う複数の図形の塗りつぶし

タグの編集
投稿者 下田の住人  (社会人) 投稿日時 2012/2/9 17:13:23
初めて投稿致します。よろしくお願い致します。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=9715のyamada様の投稿を基本にして、2つの境界色を異にする長方形を描き、その領域の塗りつぶしを試みました。
閉領域の塗りつぶしは出来るようにはなりましたが、すぐ消えてしまい、再度のマウスクリックで安定する状態となります。「消え」を防ぐための方法につきよろしく御指導下さい。

[CODE]
 Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        PictureBox1.Refresh()
        PictureBox1.CreateGraphics.DrawRectangle(Pens.Red, 50, 80, 120, 100)
        PictureBox1.CreateGraphics.DrawRectangle(Pens.Black, 80, 120, 120, 100)
 End Sub

Private Sub PictureBox1_MouseClick(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseClick
        Dim g As Graphics = PictureBox1.CreateGraphics
        Dim wFillType As UInteger = 1 ' FLOODFILLSURFACE 
        Dim hNewBrush As IntPtr
        Dim hOldBrush As IntPtr
        Dim NewBrush As LOGBRUSH
        Dim hDC As IntPtr
        hDC = g.GetHdc
        NewBrush.lbColor = ColorTranslator.ToWin32(Color.Yellow)
        NewBrush.lbStyle = 0
        NewBrush.lbHatch = 0
        hNewBrush = CreateBrushIndirect(NewBrush)
        hOldBrush = SelectObject(hDC, hNewBrush)
        ExtFloodFill(hDC, e.X, e.Y, GetPixel(hDC, e.X, e.Y), wFillType)
        g.ReleaseHdc()
        hNewBrush = SelectObject(hDC, hOldBrush)
        DeleteObject(hNewBrush)
        g.Dispose()
end sub
{/CODE]
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/2/9 18:49:20
CreateGraphics は、基本的には使わないでください。

これで描画した結果は、次の再描画タイミングでは消去されるため、
描画された状態を維持するためには向いていません。

http://msdn.microsoft.com/ja-jp/library/system.windows.forms.control.creategraphics.aspx
》 CreateGraphics メソッドを通じて取得する Graphics オブジェクトは通常、
》 現在の Windows メッセージが処理された後には保持されません。
》 これは、このオブジェクトを使用して塗りつぶされたオブジェクトは、
》 次の WM_PAINT メッセージで消去されるためです。


PictureBox への描画結果を残しておきたい場合には、以下のいずれかの方法を使います。

(案1) New Bitmap で空のビットマップを作成し、Graphics.FromImage に対して描画した後、
 そのビットマップを PictureBox の Image / BackgroundImage に割り当てる。

(案2) PictureBox の Paint イベントで、e.Graphics に対して、毎回再描画する。
 (Paint は、再描画が必要になった場合にそれを知らせるためのイベントです)
投稿者 下田の住人  (社会人) 投稿日時 2012/2/10 15:12:25
早速に御投稿有難うございます。
CreateGraphicsは使用するなという 御注意があることは覚悟しておりました。

(案1) 「そのビットマップを PictureBox の Image / BackgroundImage に割り当てる。」にはどういう手法を用いるのか御教示下さい。
マウスでクリックした閉領域のみのデバイスコンテキストhDCを得るためにCreateGraphicsを使用しましたがこれはよろしいのでしようか。

塗りつぶしが消えた後,再度のクリックで塗りつぶしが安定するカラクリがわかればよいのでしょうが
87歳の超初心者には難問です。
よろしく御指導下さい。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/2/10 18:01:31
> CreateGraphicsは使用するなという 御注意があることは覚悟しておりました。
Paint イベントなら、e.Graphics に描画するのが本来のありようですから、
わざわざ CreateGraphics する必要は無いはずです。

それに先のコードでは、Paint イベント時の CreateGraphics を Dispose していませんよね。
(MouseClick 時には Dispose しているのに…)

また、Paint 内での描画内容が固定的な場合は、Paint をその都度処理する代わりに、
予め Bitmap に描画しておいて、それを PictureBox に表示した方が楽かもしれません。



> 「そのビットマップを PictureBox の Image / BackgroundImage に割り当てる。」にはどういう手法を用いるのか御教示下さい。
PictureBox1.Image = bmp
のように、描画結果の Bitmap オブジェクトを指定するということです。
こうしておけば、Paint イベントで毎回描画する必要は無くなりますし。


> デバイスコンテキストhDCを得るためにCreateGraphicsを使用しましたがこれはよろしいのでしようか
ビットマップに対して描画する場合は、以下が参考になりそうです。
http://hanatyan.sakura.ne.jp/vbnetbbs/wforum.cgi?mode=allread&no=8474&page=0
投稿者 下田の住人  (社会人) 投稿日時 2012/2/12 14:50:41
>また、Paint 内での描画内容が固定的な場合は、Paint をその都度処理する代わりに、
>予め Bitmap に描画しておいて、それを PictureBox に表示した方が楽かもしれません。
固定的なものてはではなく「子供達のための「お絵かき」を作ろうと思っています。

デバイスコンテキストを得るための手法につきましては、完全に行き詰まっています。具体的なヒントを頂ければと存じます。

Imports System.Runtime.InteropServices

Public Class Form1
    Private Declare Function ExtFloodFill Lib "gdi32" ( _
     ByVal hdc As IntPtr, _
     ByVal x As Integer, _
     ByVal y As Integer, _
     ByVal crColor As Integer, _
     ByVal wFillType As UIntegerAs Boolean

    Private Declare Function CreateBrushIndirect Lib "gdi32" (ByRef lpLogBrush As LOGBRUSH) As IntPtr
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As IntPtr) As IntPtr
    Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As IntPtr, ByVal hdc As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As IntPtr, _
        ByVal hObject As IntegerAs <MarshalAs(UnmanagedType.Bool)> Boolean
    Private Declare Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As IntPtr, ByVal x As IntegerByVal y As IntegerAs Integer
    Private Declare Function CreateCompatibleDC Lib "gdi32" Alias "CreateCompatibleDC" (ByVal hdc As IntPtr) As IntPtr

    Private Const FLOODFILLBORDER As UInteger = 0 'UI 
    Private Const FLOODFILLSURFACE As UInteger = 1 'UI 
    Private Structure LOGBRUSH
        Public lbStyle As Integer
        Public lbColor As Integer
        Public lbHatch As Integer
    End Structure

    Dim oldX, oldY As Integer

    Private Sub Form1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
        'PictureBox1.Refresh() 
        PictureBox1.Image = New Bitmap(PictureBox1.Width, PictureBox1.Height)
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove       
       '---- 自由ラインを描く ----  閉じた図形とする 
        If e.Button = System.Windows.Forms.MouseButtons.Left Then
            Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
            Dim myPen As New Pen(Color.Green, 2)
            g.DrawLine(myPen, oldX, oldY, e.X, e.Y)
            PictureBox1.Image = PictureBox1.Image
            g.Dispose()
            PictureBox1.Refresh() 'Invalidate() 
        End If
        oldX = e.X : oldY = e.Y
    End Sub

    Private Sub PictureBox1_MouseDown(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
     '-----  塗りつぶしは マウス 右クリック ------- 
        Dim g As Graphics = PictureBox1.CreateGraphics
        If e.Button = System.Windows.Forms.MouseButtons.Right Then
            Dim hDC As Long = g.GetHdc()
            Dim wFillType As UInteger = 1 ' FLOODFILLSURFACE  
            Dim hNewBrush As IntPtr
            Dim hOldBrush As IntPtr
            Dim NewBrush As LOGBRUSH

            NewBrush.lbColor = ColorTranslator.ToWin32(Color.Yellow)
            NewBrush.lbStyle = 0
            NewBrush.lbHatch = 0
            hNewBrush = CreateBrushIndirect(NewBrush)
            hOldBrush = SelectObject(hDC, hNewBrush)
            ExtFloodFill(hDC, e.X, e.Y, GetPixel(hDC, e.X, e.Y), wFillType)
            g.ReleaseHdc()
            hNewBrush = SelectObject(hDC, hOldBrush)
            DeleteObject(hNewBrush)
        End If
        g.Dispose()
    End Sub
End Class
投稿者 ラオシス  (中学生) 投稿日時 2012/2/12 17:05:52
こんにちは。
質問とは関係ありませんが、
    Private Const FLOODFILLBORDER As UInteger = 0 'UI  
    Private Const FLOODFILLSURFACE As UInteger = 1 'UI  

列挙体としてまとめるべきかなと思います。

Enum ステートメント (Visual Basic)
http://msdn.microsoft.com/ja-jp/library/8h84wky1.aspx
UInteger型も使えるみたいですし。

>デバイスコンテキストを得るための手法につきましては、完全に行き詰まっています
何で行き詰っていますか?
投稿者 るきお  (社会人) 投稿日時 2012/2/12 18:45:21
直接の回答ではありません。

ExtFloodFillは誰にも使いこなせない意地悪な関数です。
VBの数々の高度な描画処理の中に採用されなかった理由もこの意地悪さに由来しているのかもしれません。
誰か、簡単でよいので塗りつぶし機能付きのお絵かきプログラムの作り方がわかる方は是非教えてください。

手段は2つあります。
手段1.ExtFloodFillでなんとかする。
手段2.塗りつぶしのロジックを自作する。

手段1については、下田の住人さんもExtFloodFillで描画するところまではできているのに、うまくほかの機能と調和が採れていませんよね?世間に公開されているサンプルでもExtFloodFill自体はなんとかできているものがありますが、望みどおりになるかは疑問があります。
参考:ExtFloodFill in VB.NET
http://www.planetsourcecode.com/vb/scripts/ShowCode.asp?txtCodeId=3977&lngWId=10

手段2の「塗りつぶしのロジックを自作」は試したことはありませんが、他の機能と調和させるという意味では有望だと思います。
参考:Flood Fill Algorithms in C# and GDI+
http://www.codeproject.com/Articles/5133/Flood-Fill-Algorithms-in-C-and-GDI
unsafeを使っているのでVBに翻訳するのは困難ですが、.NET言語同士の呼び出しは簡単なのでそのまま利用してVBから呼び出せばいいかもしれません。(私はダウンロードしていないので実際のところは不明です。)

さて、うまくはいきませんでしたが、私が手段1に挑戦してみた結果も載せておきます。
GDI+をほとんど使わず、GDIだけで処理すればなんとかなるのではないかと考えてみました。
Graphics.GetHdc~Graphics.ReleaseHdcの間で行う描画と塗りつぶしはうまくできました。
ただ、ReleaseHdcしないと画面には表示されないようで、目隠しされた状態でのお絵かきを強いられます。(Button2のクリックでReleaseHdcできるようにしておきました。)
また、GetHdc~ReleaseHdcの間を仮に「セッション」と呼ぶことにすると、別のセッションどうしはうまく連携してくれず、前のセッションで描画した線の中に、次のセッションで黄色く塗ろうとすると全体が黄色になってしまいます。
・ReleaseHdcしないでも表示が更新されるようにできれば、全体が1つのセッションになるので問題は解決できますが、その手段が不明です。
・複数セッションをまたいでお互いが連携できるようにしても問題は解決できますが、その手段が不明です。



Option Strict On

Public Class Form1

    Dim oldX, oldY As Integer
    Dim gdi As GDIWrapper

    Private Sub Form1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load

        Dim baseImage As Bitmap = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        PictureBox1.Image = baseImage
        gdi = New GDIWrapper(baseImage)

    End Sub

    Private Sub Form1_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        'gdi.Dispose() 
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove

        If e.Button = System.Windows.Forms.MouseButtons.Left Then
            gdi.DrawLine(Pens.Green, oldX, oldY, e.X, e.Y)
        End If
        oldX = e.X
        oldY = e.Y
    End Sub

    Private Sub PictureBox1_MouseDown(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown

        If e.Button = System.Windows.Forms.MouseButtons.Right Then
            gdi.FloodFill(Color.Yellow, New Point(e.X, e.Y))
        End If
    End Sub

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        gdi.Dispose()
        PictureBox1.Invalidate()
        gdi = New GDIWrapper(CType(PictureBox1.Image, Bitmap))

    End Sub

End Class


長いので分割します。



投稿者 るきお  (社会人) 投稿日時 2012/2/12 18:45:46
続きです。
Public Class GDIWrapper
    Implements IDisposable

    Private Declare Function ExtFloodFill Lib "gdi32" (ByVal hdc As IntPtr, ByVal X As IntegerByVal Y As IntegerByVal crColor As IntegerByVal wFillType As IntegerAs Integer
    Private Declare Function CreateSolidBrush Lib "gdi32" (ByVal crColor As IntegerAs IntPtr
    Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As IntPtr, ByVal X As IntegerByVal Y As IntegerAs Integer
    Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As IntPtr, ByVal hObject As IntPtr) As IntPtr
    Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As IntPtr) As Integer
    Private Declare Function GdiFlush Lib "gdi32" Alias "GdiFlush" () As Integer
    Const PS_SOLID As Integer = 0
    Private Declare Function CreatePen Lib "gdi32" (ByVal fnPenStyle As IntegerByVal nWidth As IntegerByVal crColor As IntegerAs IntPtr
    Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As IntPtr, ByVal X As IntegerByVal Y As IntegerByVal lpPoint As IntegerAs Boolean
    Private Declare Function LineTo Lib "gdi32" (ByVal hdc As IntPtr, ByVal nXEnd As IntegerByVal nYEnd As IntegerAs Boolean
    Private Const FLOODFILLBORDER As Integer = 0
    Private Const FLOODFILLSURFACE As Integer = 1

    Protected mainGraphics As Graphics
    Protected hDC As IntPtr

    Public Sub New(image As Bitmap)
        mainGraphics = Graphics.FromImage(image)
        hDC = mainGraphics.GetHdc
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        mainGraphics.ReleaseHdc()
        mainGraphics.Dispose()
    End Sub

    Private Sub CreatePen(color As Color)
        '新しいペンを作成 
        Dim hPen As IntPtr
        hPen = CreatePen(PS_SOLID, 2, ColorTranslator.ToOle(color))
        Dim hOldPen As IntPtr
        hOldPen = SelectObject(hDC, hPen)
        DeleteObject(hOldPen)
    End Sub

    Private Sub CreateBrush(color As Color)
        '新しいブラシを作成 
        Dim hBrush As IntPtr
        hBrush = CreateSolidBrush(ColorTranslator.ToOle(color))
        Dim hOldBrush As IntPtr
        hOldBrush = SelectObject(hDC, hBrush)
        DeleteObject(hOldBrush)
    End Sub

    Public Sub DrawLine(pen As Pen, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer)
        Me.DrawLine(pen, New Point(x1, y1), New Point(x2, y2))

    End Sub

    Public Sub DrawLine(pen As Pen, pt1 As Point, pt2 As Point)
        CreatePen(pen.Color)
        MoveToEx(hDC, pt1.X, pt1.Y, 0)
        LineTo(hDC, pt2.X, pt2.Y)
    End Sub

    Public Sub FloodFill(color As Color, pt As Point)

        CreateBrush(color)

        Dim backColor As Integer = GetPixel(hDC, pt.X, pt.Y)
        ExtFloodFill(hDC, pt.X, pt.Y, backColor, FLOODFILLSURFACE)

    End Sub

    Public Sub Flush()
        GdiFlush()
    End Sub

End Class
投稿者 下田の住人  (社会人) 投稿日時 2012/2/13 09:53:55
るきお様 投稿を拝見しExtFloodFillがだんだん遠く名なり、初心者の手には益々届かぬものになっていると感じています。

VB6ではAutoredraw、FillStyle、FillColer、Pointのプロパティによって簡単に閉領域の塗りつぶしができました。 
  Picture1.FillStyle = 0   
  Picture1.FillColor = RGB(255, 255, 0)          
   ExtFloodFill Picture1.hdc, X, Y, Picture1.Point(X, Y), 1

VB.NetではFillStyle、FillColorが無くなり、Pointも違うものになり、その為 初心者から遠く離れた存在になってしまいました。

複数の境界線の閉じた図形を描いておいて、後から図形の1つ1つを塗りつぶす時に、その個別の図形をいかに安定して選ぶか、また塗りつぶし後 それが消えないようにするか、皆様から御指導を頂きたく よろしくお願い致します。
 
投稿者 下田の住人  (社会人) 投稿日時 2012/2/19 15:55:11

 Picturebox2を設け、そこにAPI BitBltでPicturebox1のコピーをとり、その後Picturebox2.ImageをPicturebox1に戻すことによって「塗りつぶし」は可能となりました。

 Private Sub PictureBox1_MouseDown(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
        If e.Button = System.Windows.Forms.MouseButtons.Right Then
            Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
            Dim hWnd As IntPtr = PictureBox1.Handle
            Dim hdc As IntPtr = GetWindowDC(hWnd)
            Dim wFillType As UInteger = 1 ' FLOODFILLSURFACE  
            Dim hNewBrush As IntPtr
            Dim hOldBrush As IntPtr
            Dim NewBrush As LOGBRUSH

            NewBrush.lbColor = ColorTranslator.ToWin32(Color.Yellow)
            NewBrush.lbStyle = 0
            NewBrush.lbHatch = 0
            hNewBrush = CreateBrushIndirect(NewBrush)
            hOldBrush = SelectObject(hdc, hNewBrush)

            ExtFloodFill(hdc, e.X, e.Y, GetPixel(hdc, e.X, e.Y), wFillType)

            Dim gr As Graphics = Graphics.FromImage(PictureBox2.Image)
            Dim hDC2 = gr.GetHdc() 
           
       PictureBox2.Visible = False
            BitBlt(hDC2, 0, 0, 300, 300, hdc, 0, 0, &HCC0020&)
            PictureBox1.Image = PictureBox2.Image

            ReleaseDC(hWnd, hdc)                       'デバイスコンテキストを開放する  
            hNewBrush = SelectObject(hdc, hOldBrush)   '元のブラシに戻す  
            DeleteObject(hNewBrush)                     '不要になったブラシを開放する  
            g.Dispose()
            ReleaseDC(hWnd, hDC2)
            gr.Dispose()
            'ListBox1.Items.Add(hdc) 
        End If
    End Sub


 End If の1行前 ListBox1.Items.Add(hdc)を実行してみましたが、 hDCは同じ数字がつづきます。そもそも開放とはどういうことなのでしようか。開放の為のコードの使い方が間違っているのでしょうか。

投稿者 ラオシス  (中学生) 投稿日時 2012/2/19 22:49:22
>開放とはどういうことなのでしようか。
開放とは、終了,破棄とかそういう感じですね。
いらなくなったものを、破棄、終了させ自由にさせるというニュアンスでしょうか。

Disposeも同じものです。なぜ開放をしなければならないかというと、メモリが重くなりパフォーマンスが低くなるためです。
たとえばファイルの読み書きなどの場合、開放をしなければほかのプロセスは読み書きできないという状態になるので開放させる必要があります。

>開放
解放のほうがしっくりきますね・・・

以下もご参照に。
http://homepage1.nifty.com/rucio/main/dotnet/shokyu/standard46.htm

>ListBox1.Items.Add(hdc)を実行してみましたが、 hDCは同じ数字がつづきます。
大丈夫だと思いますよ。
投稿者 shu  (社会人) 投稿日時 2012/2/20 07:35:31
> >開放
> 解放のほうがしっくりきますね・・・

開放・・・ドアとかを開けっ放しにすることです。
    ITではポートの開放という使い方はします。
投稿者 下田の住人  (社会人) 投稿日時 2012/2/20 11:17:58
 皆様 御指導ありがとうございました。小生は「開放」とは 1度使ったものを破棄して、「解放」して次に違った新たなものを呼び込むものかと誤解していました。

>開放・・・ドアとかを開けっ放しにすることです。
というshuさんの御説明でスッキ理解出来ました。やはり「開放」ですね。

初心者は自分のコーディングしたものが、たまたま出来たのか、或いは正しいのか、判断がつきません。すなわち、「解決」にチェックを入れていいのか迷っている状態です。つまり通信簿か採点をお願い出来れば、今後の励みとなります。御意見を頂きたく存じます。