none
System.Drawing.Region で輪郭線を描くには RRS feed

  • 質問

  • 以下のようなコードを実行すると,赤十字マークを描くことができますが,

    g.DrawRegion(Pens.Red, rgn1) とかいったメソッドが見当たらず,この

    図形の輪郭を描く方法が分かりません。

     

    リージョンの輪郭を描く方法をお教えください。

     

      Private Sub Button1_Click(ByVal sender As System.Object, _

                                ByVal e As System.EventArgs) _

                                Handles Button1.Click
        Dim rect1 As New Rectangle(50, 50, 30, 90)
        Dim rect2 As New Rectangle(20, 80, 90, 30)
        Dim rgn1 As New Region(rect1)
        Dim rgn2 As New Region(rect2)
        rgn1.Union(rgn2)
        Dim g As Graphics = Me.CreateGraphics()
        g.Clear(Color.White)
        g.FillRegion(Brushes.Red, rgn1)
      End Sub

    2007年12月29日 14:08

すべての返信

  • ありません。

    一般に、複数の図形をIntersect/Union/Xorした結果の図形の輪郭線を(正確に)求めることは、大変面倒くさいです。

    ので、Regionには、輪郭線を描く方法が、そもそも、用意されていないのだと、思います。

    2007年12月30日 1:37
  •  molic さんからの引用

    以下のようなコードを実行すると,赤十字マークを描くことができますが,

    g.DrawRegion(Pens.Red, rgn1) とかいったメソッドが見当たらず,この

    図形の輪郭を描く方法が分かりません。

     

    リージョンの輪郭を描く方法をお教えください。

     

      Private Sub Button1_Click(ByVal sender As System.Object, _

                                ByVal e As System.EventArgs) _

                                Handles Button1.Click
        Dim rect1 As New Rectangle(50, 50, 30, 90)
        Dim rect2 As New Rectangle(20, 80, 90, 30)
        Dim rgn1 As New Region(rect1)
        Dim rgn2 As New Region(rect2)
        rgn1.Union(rgn2)
        Dim g As Graphics = Me.CreateGraphics()
        g.Clear(Color.White)
        g.FillRegion(Brushes.Red, rgn1)
      End Sub

     

    一般的には、難しいかもしれませんが、ご提示の図形ならばたとえば、

    コード ブロック

    Dim rect1 As New Rectangle(50, 50, 30, 90)

    Dim rect2 As New Rectangle(20, 80, 90, 30)

    Dim rgn1 As New Region(rect1)

    Dim rgn2 As New Region(rect2)

    rgn1.Union(rgn2)

    Dim g As Graphics = Me.CreateGraphics()

    g.Clear(Color.White)

    Dim gp As New GraphicsPath()

    gp.AddRectangle(rect1)

    gp.AddRectangle(rect2)

    Dim pen1 As New Pen(Color.Black)

    pen1.Width = 4

    gp.Widen(pen1)

    g.SetClip(rgn1, CombineMode.Exclude)

    'g.FillRegion(Brushes.Red, rgn1)

    g.FillPath(Brushes.Red, gp)

    pen1.Dispose()

    gp.Dispose()

     

     

    とかでどうでしょうか?

    2007年12月30日 3:25
  • Tom Yama 様、IIJIMAS 様、レスをありがとうごました。

     

    GDIのAPIには、FillRgnとFrameRgnがあるので、簡単に出来る方法があるのではないかと思っていたのですが、難しいですね。

    ちなみに、GDI+を使った場合(ボタン1)、GDIのAPIを使った場合(ボタン2)、GDI+で作ったRegionの輪郭をFrameRgnで描こうとした場合(ボタン3)のソースを長くなりますが、載せておきます。

     

    ボタン1では領域が塗りつぶされます。

    ボタン2では領域が塗りつぶされ、輪郭が描かれます。

    ボタン3では,Regionオブジェクトのハンドルは得られているようですが、FrameRgn戻り値が0になって輪郭を描くことができません。GetHrgnメソッドの使い方が間違っているのでしょうか。(この辺り、詳しい方ご教授ください。)

     

    個人的に「ドローソフトもどき」を作っており、単体の図形と同じ仕様で合成図形も描画したいと思っています。

     

    正月休みにかけて IIJIMAS 様にお教えいただいた方法での一般化も考えてみたと思います。

     

    ' VB2005

    Public Class Form1

      '.NET FRAME WORK(GDI+) & GDI API
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim g As Graphics = Me.CreateGraphics()
        g.Clear(Color.White)

        Dim rect1 As New Rectangle(50, 50, 30, 90)
        Dim rect2 As New Rectangle(20, 80, 90, 30)
        Dim rgn1 As New Region(rect1)
        Dim rgn2 As New Region(rect2)
        rgn1.Union(rgn2)
        g.FillRegion(Brushes.Blue, rgn1)

        rgn1.Dispose()
        rgn2.Dispose()
        g.Dispose()
      End Sub

     

      ' API(GDI)
      Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim g As Graphics = Me.CreateGraphics()
        g.Clear(Color.White)

        Dim hDC As Integer = g.GetHdc.ToInt32
        Dim hRgn1 As Integer = CreateRectRgn(50, 50, 80, 140)
        Dim hRgn2 As Integer = CreateRectRgn(20, 80, 110, 110)
        Dim hBrush1 As Integer = CreateSolidBrush(RGB(0, 0, 255))
        Dim hBrush2 As Integer = CreateSolidBrush(RGB(255, 0, 0))
        CombineRgn(hRgn1, hRgn1, hRgn2, RGN_OR)
        FillRgn(hDC, hRgn1, hBrush1)
        Dim ret As Integer = FrameRgn(hDC, hRgn1, hBrush2, 1, 1)
        Debug.WriteLine("hRgn1=" & hRgn1 & ",ret=" & ret) ' ret=1 が戻る

        DeleteObject(hRgn1)
        DeleteObject(hRgn2)
        DeleteObject(hBrush1)
        DeleteObject(hBrush2)
        g.Dispose()
      End Sub

     

      '.NET FRAME WORK(GDI+) & API(GDI)
      Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        Dim g As Graphics = Me.CreateGraphics()
        g.Clear(Color.White)

        ' API(GDI)
        Dim hDC As Integer = g.GetHdc.ToInt32
        g.ReleaseHdc()
        Dim hBrush2 As Integer = CreateSolidBrush(RGB(255, 0, 0))

        '.NET FRAME WORK(GDI+)
        Dim rect1 As New Rectangle(50, 50, 30, 90)
        Dim rect2 As New Rectangle(20, 80, 90, 30)
        Dim rgn1 As New Region(rect1)
        Dim rgn2 As New Region(rect2)
        rgn1.Union(rgn2)
        g.FillRegion(Brushes.Blue, rgn1)

        '.NET FRAME WORK(GDI+) & GDI API
        Dim hRgn As Integer = rgn1.GetHrgn(g).ToInt32
        Dim ret As Integer = FrameRgn(hDC, hRgn, hBrush2, 1, 1) 'API(GDI)
        Debug.WriteLine("hRgn=" & hRgn & ",ret=" & ret) ' ret=0 が戻る

        rgn1.Dispose()
        rgn2.Dispose()
        DeleteObject(hBrush2)
        g.Dispose()
      End Sub

     

      '矩形の領域を作成する
      Declare Function CreateRectRgn Lib "GDI32.dll" _
          (ByVal nLeft As Integer, ByVal nTop As Integer, _
           ByVal nRight As Integer, ByVal nBotomm As Integer) As Integer
      '戻り値:成功=矩形領域のハンドル,失敗=0

     

      '既存の二つの領域を結合して新しい領域を作成する
      Declare Function CombineRgn Lib "GDI32.dll" _
          (ByVal hrgnDest As Integer, ByVal hrgnSrc1 As Integer, _
          ByVal hrgnSrc2 As Integer, ByVal nCombineMode As Integer) As Integer
      'nCombineModeの定数
      Const RGN_AND As Integer = 1   ' 2つの領域の重なっている部分のみを使う
      Const RGN_OR As Integer = 2    ' 2つの領域を結合する
      Const RGN_XOR As Integer = 3   ' 2つの領域を結合し、共通部分はのぞく
      Const RGN_DIFF As Integer = 4  ' hrgnSrc1の領域で、hRgnSrc2を除いた部分を使う
      Const RGN_COPY As Integer = 5  ' hrgnSrc1を新しい領域にする

     

      '指定のブラシで領域を塗りつぶす
      Declare Function FillRgn Lib "GDI32.dll" _
          (ByVal hdc As Integer, ByVal hrgn As Integer, ByVal hbr As Integer) As Integer

     

      '指定の領域の周囲に指定のブラシで境界線を書く
      Declare Function FrameRgn Lib "gdi32.dll" _
            (ByVal hDC As Integer, ByVal hrgn As Integer, ByVal hbr As Integer, _
             ByVal nWidth As Integer, ByVal nHeight As Integer) As Integer

     

      '純色(ソリッドカラー)で論理ブラシを作成する
      Declare Function CreateSolidBrush Lib "gdi32.dll" _
              (ByVal crColor As Integer) As Integer

     

      'オブジェクトを削除する
      Declare Function DeleteObject Lib "gdi32.dll" _
        (ByVal hObject As Integer) As Integer

     

    End Class

    2007年12月30日 9:24
  • g.ReleaseHdc()してるからではないでしょうか。>第三案

     

    私ならRegionよりGraphicsPathを主体にします。

    2007年12月30日 9:41
  • はなはなはな 様、レスをありがとうございます。

     

    > g.ReleaseHdc()してるからではないでしょうか。

    g.ReleaseHdc については、これをしないと次に g を使うときに

    「オブジェクトは現在他の場所で使用されています。GetHdcメソッドの後にグラフィックスオブジェクトを使用する場合、ReleaseHdcメソッドを呼び出してください」というエラーが出るためにこうしました。

     

    > 私ならRegionよりGraphicsPathを主体にします。

    現在考えている図形の合成は、GDIのCombineRgnでいう、OR,XOR,AND,DIFFのいずれかの方法で合成した図形の内部を塗りつぶし、輪郭(外周のみ)を別の色で描画しようとするものです。

    当初はGraphicsPathクラスにそのようなメソッドがあるだろうと思って調べたのですが分からず、RegionクラスにUnion,Xor,Intersect,Excludeなどの合成メソッドを見つけました。これで、4種類の合成図形の塗りつぶしは出来たのですが、輪郭が描けずに苦戦しているところです。

     

    何か,良いアイデアがあればご教授ください。

    2007年12月30日 15:08
  • 大晦日になってしまいましたね。

     

    >g.ReleaseHdc については、これをしないと次に g を使うときに

    >「オブジェクトは現在他の場所で使用されています。GetHdcメソッドの後にグラフィックスオブジェクトを使用する場合、>ReleaseHdcメソッドを呼び出してください」というエラーが出るためにこうしました。

     

    hdcは最後の方で一回使うだけなのに、最初に取得していること自体が問題です。

    使う直前に取得すればいいでしょう。

     

    >現在考えている図形の合成は、GDIのCombineRgnでいう、OR,XOR,AND,DIFFのいずれかの方法で合成した図形>の内部を塗りつぶし、輪郭(外周のみ)を別の色で描画しようとするものです。

     

    う~ん、簡単な方法はしりません。

    私が言ったのは自前でパスを作成する、という意味です。

    パスさえできれば、drawpath,fillpathがありますし、regionに楽に変換できますからfillregionも使えます。

     

    図形合成で楽をするか、描画で楽をするかの選択になるのではないでしょうか?

    2007年12月30日 15:27
  • >図形合成で楽をするか、描画で楽をするかの選択になるのではないでしょうか?

     

    この言は、API直呼び出しをしないとするならば、の話です。

    私はNETになってからAPIは呼ばないようになりました。

     

    案三でいくならば、それはそれでよいです。

    2007年12月30日 15:51
  • はなはなはな

     

    ありがとうございました。おかげさまで希望通りのクラスを作ることが出来ました。

    それでは、良いお年をお迎えください。

     

    Public Class MyRegion

      '2つの図形(path)を合成し,内部と輪郭を別の色で描く
      Public Shared Sub DrawRegion(ByVal g As Graphics, _
                             ByVal path1 As GraphicsPath, _
                             ByVal path2 As GraphicsPath, _
                             ByVal Mode As CombineMode, _
                             ByVal bCol As Color, _
                             ByVal pCol As Color, _
                             ByVal pWidth As Integer)

        Dim rgn1 As New Region(path1)
        Dim rgn2 As New Region(path2)
        Select Case Mode
          Case CombineMode.Union
            rgn1.Union(rgn2)
          Case CombineMode.Xor
            rgn1.Xor(rgn2)
          Case CombineMode.Intersect
            rgn1.Intersect(rgn2)
          Case Else 'CombineMode.Exclude
            rgn1.Exclude(rgn2)
        End Select
        g.FillRegion(New SolidBrush(bCol), rgn1) '合成図形の内部を塗りつぶす

        Dim hRgn As Integer = rgn1.GetHrgn(g).ToInt32
        Dim hBrush As Integer = CreateSolidBrush(RGB(pCol.R, pCol.G, pCol.B))
        Dim hDC As Integer = g.GetHdc.ToInt32
        FrameRgn(hDC, hRgn, hBrush, pWidth, pWidth) '合成図形の輪郭を描く

        rgn1.Dispose()
        rgn2.Dispose()
        DeleteObject(hRgn)
        DeleteObject(hBrush)
      End Sub

      '指定の領域の周囲に指定のブラシで境界線を書く
      Private Declare Function FrameRgn Lib "gdi32.dll" _
            (ByVal hDC As Integer, ByVal hrgn As Integer, ByVal hbr As Integer, _
             ByVal nWidth As Integer, ByVal nHeight As Integer) As Integer

      '純色(ソリッドカラー)で論理ブラシを作成する
      Private Declare Function CreateSolidBrush Lib "gdi32.dll" _
              (ByVal crColor As Integer) As Integer

      'オブジェクトを削除する
      Private Declare Function DeleteObject Lib "gdi32.dll" _
        (ByVal hObject As Integer) As Integer
    End Class

     

     

    2007年12月30日 18:07
  • 私が後始末にルーズなので、はっきりしたことは言えませんが、

    ReleaseHdcを呼んだほうがいい気がします。(使った後に)

     

    Frameworkの範疇を飛び出す時は、解放などにシビアにならないといけません。

     

    どなたかご助言くだされば幸いです。

    2007年12月31日 0:03