Jump to content
Xtreme .Net Talk

Recommended Posts

Posted

Hey all you GDI+ gurus,

 

I'm trying to define a region based on the results of a floodfill. The user draws freeform on a picturebox, then clicks in any enclosed area. I can fill the enclosed area rapidly just fine. However, I now want to define a region based on what was filled.

 

One idea was to collect the points during the floodfill that are on the original path. While this is slow, it's even slower trying to rearrange the resulting collection of points into a new path (the collection is in a pseudo-random order). Another idea is to collect the points in the path that enclose the selected point, define that as a region, and just do a FillRegion. However, sometimes the region is not bordered by a path but by another region of another color.

 

Is it possible to collect all points that are filled, then define a region that encloses all those points? I don't think RegionData works for this.

 

Any help (in VB.Net) would be greatly appreciated!

Larry

Posted

Optimizing the region?

 

I found a way to rapidly create a region from the pixels that are filled by creating a rectangle for each pixel, and then unioning that with a region. It's actually fairly simple, and here's the heart of the code:

 

points = New Stack

Dim rc As Rectangle

Dim reg As New Region

reg.MakeEmpty()

 

points.Push(New Point(X, Y))

 

Do

Dim p As Point = New Point

p = points.Pop

 

If bmp.GetPixel(p.X, p.Y).ToArgb <> fillColor.ToArgb Then

bmp.SetPixel(p.X, p.Y, fillColor)

rc = New Rectangle(p.X, p.Y, 1, 1)

reg.Union(rc)

 

If (CanUp(p.X, p.Y)) Then

points.Push(New Point(p.X, p.Y - 1))

End If

If (CanRight(p.X, p.Y)) Then

points.Push(New Point(p.X + 1, p.Y))

End If

If (CanDown(p.X, p.Y)) Then

points.Push(New Point(p.X, p.Y + 1))

End If

If (CanLeft(p.X, p.Y)) Then

points.Push(New Point(p.X - 1, p.Y))

End If

End If

 

Loop While (points.Count > 0)

 

 

However, if the region is larger than about 25,000 pixels, I get a stack overflow when I try to do anything with it, like FillRegion(Brushes.Blue, reg).

 

Is there some way to optimize the region so only the necessary datapoints are left in it?

 

Larry

Posted

Here's a fuller version of the code:

 

   Private bmp As Bitmap
   Private clrFill As Color
   Private clrOrig As Color
   Private reg As Region

   Private Sub panel1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles panel1.MouseDown
       bmp = panel1.Image
       FloodFill(e.X, e.Y, clrFill)
       panel1.Invalidate()
   End Sub

   Private Function FloodFill(ByVal X As Integer, ByVal Y As Integer, ByVal fillColor As Color) As Boolean
       If ((X < 0) Or (X >= bmp.Width) Or (Y < 0) Or (Y >= bmp.Height)) Then
           Return False
       End If

       clrOrig = bmp.GetPixel(X, Y)
       If clrOrig.ToArgb = fillColor.ToArgb Then
           Return False
       End If

       points = New Stack
       Dim rc As Rectangle
       reg = New Region
       reg.MakeEmpty()

       points.Push(New Point(X, Y))

       Do
           Dim p As Point = New Point
           p = points.Pop

           If bmp.GetPixel(p.X, p.Y).ToArgb <> fillColor.ToArgb Then
               bmp.SetPixel(p.X, p.Y, fillColor)

               rc = New Rectangle(p.X, p.Y, 1, 1)
               reg.Union(rc)

               If (CanUp(p.X, p.Y)) Then
                   points.Push(New Point(p.X, p.Y - 1))
               End If
               If (CanRight(p.X, p.Y)) Then
                   points.Push(New Point(p.X + 1, p.Y))
               End If
               If (CanDown(p.X, p.Y)) Then
                   points.Push(New Point(p.X, p.Y + 1))
               End If
               If (CanLeft(p.X, p.Y)) Then
                   points.Push(New Point(p.X - 1, p.Y))
               End If
           End If
       Loop While (points.Count > 0)

       Return True
   End Function

   Private Function CanUp(ByVal X As Integer, ByVal Y As Integer) As Boolean
       Return ((Y > 0) AndAlso bmp.GetPixel(X, Y - 1).ToArgb = clrOrig.ToArgb)
   End Function

   Private Function CanRight(ByVal X As Integer, ByVal Y As Integer) As Boolean
       Return ((X < bmp.Width - 1) AndAlso bmp.GetPixel(X + 1, Y).ToArgb = clrOrig.ToArgb)
   End Function

   Private Function CanDown(ByVal X As Integer, ByVal Y As Integer) As Boolean
       Return ((Y < bmp.Height - 1) AndAlso bmp.GetPixel(X, Y + 1).ToArgb = clrOrig.ToArgb)
   End Function

   Private Function CanLeft(ByVal X As Integer, ByVal Y As Integer) As Boolean
       Return ((X > 0) AndAlso bmp.GetPixel(X - 1, Y).ToArgb = clrOrig.ToArgb)
   End Function

 

Before this section of code I'm defining the bitmap from a picturebox, and drawing a freeform path on the picture box that can have many intersections, thus many small potential regions (think of a figure 8 - it has two regions). I'd like to fill each enclosed section of the path and create a region based on it. I'm also selecting the fill color - clrFill before this section of code.

 

When I click on a point inside an enclosed loop, the bitmap bmp takes a snapshot of the picturebox, and calls the FloodFill routine. The FloodFill pushes the first clicked point on the stack, then starts the loop by popping that point and checking if it's already been changed. If not, then it changes it to the fill color and checks the points above, to the right, below, and to the left to see if any need to be changed. If they do, they're pushed on the stack. The routine continues popping the points off the stack, checking, pushing new ones on the stack, until all points have been popped off the stack.

 

Whenever a new point is changed, I create a rectangle 1 pixel in size and union it with the growing region reg.

 

This is all fine and works fast. The stack overflow is not occuring with the points stack, but with the region. After the FloodFill routine finishes, panel1 is invalidated, calling the Paint routine, which tries to paint the region yellow.

 

e.Graphics.FillRegion(Brushes.Yellow, reg)

 

If the region has too many datapoints, this (or any other operation with reg such as reg.GetRegionData) causes the stack overflow.

 

The reason I say that there's unnecessary data points, is because I don't think you need all the interior datapoints to define a region, or we'd be seeing stack overflows all over the place. Since I can't do anything with the region after the FloodFill is finished, I have to find some way to limit the datapoints while the region is being built. OR, I have to find an entirely different way of doing this.

 

Thanks (and Merry Christmas!),

Larry

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...