1

I have a picturebox with a large picture:

enter image description here

In this picturebox various areas are colored using the ExtFloodFill() API

Private Declare Function ExtFloodFill Lib "GDI32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long, ByVal wFillType As Long) As Long

The X and Y coordinates from which the FloodFills are started are somewhere in the area which has to be filled, but not in the exact center, or with the same offset for all areas, and neither are the areas the same shape or size. (That's why we love FloodFill)

I now want the users to interact with the picture by clicking on it and using the coordinates for specific actions corresponding to the area they clicked on.

For this I use the _MouseDown() event:

Private Sub picfat_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
  Dim lngIndex As Long
  lngIndex = CheckClick(mobjDet, x, y)
  If lngIndex > -1 Then
    With mobjDet(lngIndex)
      .lngStat = 2 - .lngStat
      SendCmd "det swico " & CStr(lngIndex) & "=" & CStr(.lngStat), wskDet
    End With 'mobjDet(lngIndex)
  End If
End Sub

Which calls CheckClick() to determine which area the click was on:

Private Function CheckClick(obj() As FC_DET, sngX As Single, sngY As Single) As Long
  Dim lngObj As Long
  Dim lngIndex As Long
  Dim sngWidth As Single, sngHeight As Single
  sngWidth = 15
  sngHeight = 15
  lngIndex = -1
  For lngObj = 0 To UBound(obj)
    With obj(lngObj)
      If sngWidth > 0 Then
        If sngX > .sngX Then
          If sngX < .sngX + sngWidth Then
            If sngY > .sngY Then
              If sngY < .sngY + sngHeight Then
                lngIndex = .lngIndex
                Exit For
              End If
            End If
          End If
        End If
      End If
    End With 'obj(lngObj)
  Next lngObj
  CheckClick = lngIndex
End Function

At this moment I use a square of 15x15 pixels to the bottom-right of the original X and Y coordinates, which works correctly if the user clicks on those coordinates.

The 15x15 square would work for the smallers squares in the picture if the X and Y coordinates of the FloodFill would be the upperleft corner of the small squares, but this is not the case, and as you can see there are other shapes as well as the small squares.

What I want to do is:

  • Use the X and Y coordinates from the _MouseDown() event
  • Loop through my list of X and Y coordinates from the FloodFill starts
  • Determine which FloodFill start corresponds to the X and Y coordinates from the MouseDown event

[EDIT]

For example:

  • The center of the yellow floodfill in my picture is 500,160 (in the yellow between the white square and rectangle). The coordinates of this are mobjDet(6).sngX and mobjDet(6).sngY
  • The user clicks somewhere in the yellow area above the white rectangle, for example at 495,53
  • How can I couple the X and Y of the _MouseDown() event with the index (6) of my FloodFill udt ?

That way I can find out which area the user clicked on in the picture.

Deanna
  • 23,876
  • 7
  • 71
  • 156
Hrqls
  • 2,944
  • 4
  • 34
  • 54

3 Answers3

2

When I've done things like this in the past, the approach that worked best for me was to create an array of UDTs. Each UDT held all the information (e.g., color, x1, x2, y1, y2) needed to call my draw APIs (like FloodFill). When I had to render my screen I just looped through my array an drew all my items. When the user clicked on my hdc, I would loop again to find the matching UDT using the same coordinates, which also contained additional information like id, etc.

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type DrawItem
    Id As Long
    Name As String
    ' etc...

    tRect As RECT
End Type

Private DrawItems() As DrawItem

Also, because FloodFill is virtually impossible to track what was filled, I recommend that you manually draw your graphics using more ridgid APIs, like FillRect. This will mean that you will have the full boundries (Top, Bottom, Left, and Right) of what you're drawing in your UDT so that you can then see if the mouse click was inside that rectangle.

Here's the API for drawing a rectangle. You'll also need to create a "brush" with the CreateSolidBrush function.

Private Declare Function FillRect Lib "user32" (ByVal hdc As Long, lpRect As RECT, ByVal hBrush As Long) As Long
Private Declare Function CreateSolidBrush Lib "gdi32" (ByVal crColor As Long) As Long

Also, I like to change the ScaleMode of whatever I was drawing on (e.g., PictureBox) from 1 - Twip (useless) to 3 - Pixel so I didn't constantly have to multiply or divide by 15.

Hope this helps.


As a final note, if you're really tied to the FloodFill, the best I can recommend is using...

Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long

...which could return the color of a pixel at your mouse click's x, and y coordinates. Then you could test the color to (hopefully) match what you needed.

LimaNightHawk
  • 6,613
  • 3
  • 41
  • 60
  • thanks for your answer. i have all the required information for the floodfill in an udt indeed (mobjDet in my example code). the floodfill is working fine, i have problems though to find the corresponding (item in the) udt (array) to the x and y coordinates of where the user clicks .... i will update my original post with an example user action – Hrqls Sep 10 '14 at 05:18
  • Ok, I've updated the answer. Hope it's helpful. Don't overlook the last ditch solution at the end. – LimaNightHawk Sep 10 '14 at 12:28
  • thanks for the update. sadly i have many areas with the same color, so color isnt exclusive to the coordinates where the user clicks.... i know of the basic drawing functions and api, in fact if i use them to fill my (irregular) shapes, then i can easily determine which area my coordinates belong to by simply using that code twice and see if the center coordinates and the clicked coordinates are in the same area ..... for now i am thinking of copying my picture, and floodfilling the area of the clicked coordinates and see which area of the center points changed .. not sure if it will work :) – Hrqls Sep 10 '14 at 12:32
  • Is there any chance you could use a _slightly_ different color (off by one hex digit). The eye wouldn't be able to tell, but the color could be unique. – LimaNightHawk Sep 10 '14 at 12:37
  • i could, but i think i will rather do it invisible .. the picture is dynamic, and while the user can click on it, the colors in the picture might change – Hrqls Sep 10 '14 at 13:02
  • You could still use the 2 least significant bits of each colour component as the ID. This would give you 64 unique areas. Using 3 bits will give a very slight colour variance but expands the ID space to 1024 areas. This is VERY hacky though :) I assume the shapes are pre drawn and you're relying on those boundaries to constraing the flood fill? – Deanna Sep 11 '14 at 08:33
  • the shapes are pre drawn indeed. it is a bmp of an intersection. the user can then fill in coordinates in an xml which are used for the flood fills. the shapes are filled with colors corresponding to the colors of the trafficlights, and the induction loops in the road, this is realtime data so the colors change all the time ... the boundaries remain the same though, so i might work with that ... – Hrqls Sep 11 '14 at 09:50
1

Using GDI, you can make use of Regions. These will require you to define the areas in code (or some other structure other than a bitmap, maybe an point outline array), but will allow you to:

  1. Create a polygon region with CreatePolygonRgn() or CreatePolyPolygonRgn()
  2. Draw the border with FrameRgn()
  3. Fill the region with FillRgn()
  4. Hit test clicks to see which area they're in with PtInRgn()
Deanna
  • 23,876
  • 7
  • 71
  • 156
  • the problem will be creating the polygons ... right now i create the bitmap and the floodfill coordinates, but in the (near) future the user will import their own bmp and create their own xml with floodfill coordinates .. so it seems the main problem is to find the boundaries surrounding a point click .. i might have to create a floodfill by code instead of the api, that way i will notice when i encounter another point – Hrqls Sep 11 '14 at 11:56
  • I'm not aware of a GDI method to get a region that would have been `FloodFill`ed. A hacky way to determine the region... starting from your flood fill point, work outwards creating `Rectangle`s (or adding points to a region) until you hit your bounding colour. Possible solutions from the Related questions list: [Floodfill implementetion](http://stackoverflow.com/q/24315491/588306), [Floodfill part of an Image](http://stackoverflow.com/q/23767303/588306). – Deanna Sep 11 '14 at 12:27
  • 1
    @Hrqls Also useful for auto generating a region: [QuickFill/flood fill algorithm in C#/VB.NET](http://stackoverflow.com/q/4230764/588306) – Deanna Sep 11 '14 at 12:33
  • thanks! .. i am using a floodfill algorithm in my java implementation of this application .. i am now looking into reproducing this in vb6 (or at least the parts that i need for this) ... i might be faster and easier on resources though to copy the bitmap to memory, floodfill the regions in there, and see when the required coordinate changes color .. i guess it will be one of those solutions :) – Hrqls Sep 11 '14 at 12:57
  • 1
    How about, ss a one off (on startup), flood fill each area with a unique colour, then scan through the resulting image, checking the colour, creating a map of which areas are at what points. On click, you can then use the coordinates as indexes into that map and immediately get the area info back. – Deanna Sep 11 '14 at 13:32
  • thats a nice idea ... i will load the background bmp in memory, color each area with its own unique color (calculated from its index, so i can calculate the index back from the color) and will just keep that in memory to check when the user clicks on the picturebox – Hrqls Sep 11 '14 at 14:56
  • sorry for the late reply ... flu intervention ... I got it working and posted it as answer .. thanks for thinking along with me :) – Hrqls Sep 25 '14 at 14:07
1

Thanks to LimaNightHawk and Deanna for giving me the idea how to solve this :)

After loading the bitmap in my picturebox I make a copy of it in memory. In that copy I then fill each important area with its own color :

Private Sub CopyView()
  Dim lngIndex As Long
  Dim sngWidth As Single, sngHeight As Single
  Dim lngBrush As Long
  Dim lngBmp As Long
  sngWidth = ScaleWidth
  sngHeight = ScaleHeight
  lngBrush = CreateSolidBrush(vbYellow)
  With picFAT
    mlngCopy = CreateCompatibleDC(.hdc)
    SelectObject mlngCopy, lngBrush
    If mlngCopy <> 0 Then
      lngBmp = CreateCompatibleBitmap(.hdc, sngWidth, sngHeight)
      If lngBmp <> 0 Then
        SelectObject mlngCopy, lngBmp
        BitBlt mlngCopy, 0, 0, sngWidth, sngHeight, .hdc, 0, 0, SRCCOPY
        For lngIndex = 0 To UBound(mobjDet)
          With mobjDet(lngIndex)
            lngBrush = CreateSolidBrush(lngIndex + 1)
            SelectObject mlngCopy, lngBrush
            ExtFloodFill mlngCopy, .sngX, .sngY, GetPixel(mlngCopy, .sngX, .sngY), 1
          End With 'mobjDet(lngIndex)
        Next lngIndex
      End If
    End If
  End With 'picFAT
  DeleteObject lngBrush
End Sub

To make it very simple I use the index number of the area as the color.

I can then obtain the index by reading the color of that area in the memory copy :

Private Function FindIndex(sngX As Single, sngY As Single) As Long
  Dim lngColor As Long
  lngColor = GetPixel(mlngCopy, sngX, sngY)
  FindIndex = lngColor - 1
End Function

And finally I call the above from the MouseDown event to use the index of the area where the user clicked:

Private Sub picfat_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
  Dim lngIndex As Long
  lngIndex = FindIndex(x, y)
  If lngIndex > -1 Then
    If lngIndex <= UBound(mobjDet) Then
      With mobjDet(lngIndex)
        .lngStat = 2 - .lngStat
        SendCmd "det swico " & CStr(.lngIndex) & "=" & CStr(.lngStat), wskDet
      End With 'mobjDet(lngIndex)
    End If
  End If
End Sub

See below for the declarations of the API :

'API
Private Const SRCCOPY = &HCC0020

Private Declare Function ExtFloodFill Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal crColor As Long, ByVal wFillType As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function CreateSolidBrush Lib "gdi32" (ByVal crColor As Long) As Long
Hrqls
  • 2,944
  • 4
  • 34
  • 54
  • I DeleteObject mlngCopy in Form_Unload, and lngBrush in the procedure itself .. I am not sure wether I should DeleteObject anything else? – Hrqls Sep 25 '14 at 14:09