【问题标题】:Reversed FloodFill: test if a point is inside an areaReversed FloodFill:测试一个点是否在一个区域内
【发布时间】:2014-09-09 14:29:41
【问题描述】:

我有一个带有大图片的图片框:

在此图片框中,使用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

FloodFills 开始的 X 和 Y 坐标位于必须填充的区域中的某个位置,但不在精确的中心,或者所有区域的偏移量相同,并且这些区域的形状或形状也不相同尺寸。 (这就是我们喜欢 FloodFill 的原因)

我现在希望用户通过点击图片并使用与他们点击的区域相对应的特定操作的坐标来与图片进行交互。

为此,我使用_MouseDown() 事件:

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

调用CheckClick() 来确定点击的区域:

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

此时我在原始 X 和 Y 坐标的右下角使用一个 15x15 像素的正方形,如果用户单击这些坐标,它就可以正常工作。

如果 FloodFill 的 X 和 Y 坐标位于小方块的左上角,则 15x15 方块适用于图片中较小的方块,但情况并非如此,您可以看到还有其他形状以及小方块。

我想做的是:

  • 使用来自_MouseDown() 事件的 X 和 Y 坐标
  • 从 FloodFill 开始循环遍历我的 X 和 Y 坐标列表
  • 确定哪个 FloodFill 开始对应于 MouseDown 事件的 X 和 Y 坐标

[编辑]

例如:

  • 我图片中黄色填充的中心是 500,160(在白色正方形和矩形之间的黄色中)。它的坐标是mobjDet(6).sngXmobjDet(6).sngY
  • 用户单击白色矩形上方黄色区域的某处,例如 495,53
  • 如何将 _MouseDown() 事件的 X 和 Y 与 FloodFill udt 的索引 (6) 结合起来?

这样我就可以知道用户点击了图片中的哪个区域。

【问题讨论】:

    标签: vb6 gdi flood-fill


    【解决方案1】:

    当我过去做过这样的事情时,最适合我的方法是创建一个 UDT 数组。每个 UDT 都包含调用我的绘图 API(如 FloodFill)所需的所有信息(例如,colorx1x2y1y2)。当我不得不渲染我的屏幕时,我只是遍历我的数组并绘制了我所有的项目。当用户点击我的 hdc 时,我会再次循环以使用相同的坐标找到匹配的 UDT,其中还包含诸如id 等附加信息。

    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
    

    另外,由于 FloodFill 几乎不可能跟踪填充的内容,我建议您使用更严格的 API 手动绘制图形,例如 FillRect。 这意味着您将在 UDT 中拥有所绘制内容的完整边界(顶部、底部、左侧和右侧),这样您就可以查看鼠标单击是否在那个矩形内。

    这里是绘制矩形的 API。您还需要使用 CreateSolidBrush 函数创建一个“画笔”。

    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
    

    此外,我喜欢将我正在绘制的任何东西(例如 PictureBox)的 ScaleMode1 - Twip(无用)更改为 3 - Pixel,这样我就不必经常乘以或除以 15。

    希望这会有所帮助。


    最后一点,如果您真的与 FloodFill 相关,我可以推荐的最好的方法是使用...

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

    ...它可以返回鼠标单击的 x 和 y 坐标处的像素颜色。然后您可以测试颜色以(希望)匹配您需要的颜色。

    【讨论】:

    • 感谢您的回答。我确实拥有 udt 中的 Floodfill 所需的所有信息(在我的示例代码中为 mobjDet)。洪水填充工作正常,我有问题虽然找到用户点击的x和y坐标的对应(项目)udt(数组)......我将用一个示例用户操作更新我的原始帖子
    • 好的,我已经更新了答案。希望它有帮助。不要忽视最后的解决方案。
    • 感谢您的更新。可悲的是,我有许多区域具有相同的颜色,所以颜色不是用户单击的坐标所独有的......我知道基本的绘图功能和 api,事实上,如果我用它们来填充我的(不规则的)形状,那么我可以很容易地确定我的坐标属于哪个区域,只需使用该代码两次,看看中心坐标和点击的坐标是否在同一个区域......现在我正在考虑复制我的图片,并填充该区域点击的坐标,看看中心点的哪个区域发生了变化..不确定它是否会起作用:)
    • 您是否有机会使用稍微不同的颜色(相差一个十六进制数字)。眼睛无法分辨,但颜色可能是独一无二的。
    • 我可以,但我想我宁愿让它不可见..图片是动态的,虽然用户可以点击它,但图片中的颜色可能会改变
    【解决方案2】:

    使用 GDI,您可以使用Regions。这些将要求您在代码中定义区域(或位图以外的其他结构,可能是点轮廓数组),但允许您:

    1. 使用CreatePolygonRgn()CreatePolyPolygonRgn() 创建一个多边形区域
    2. FrameRgn()画出边框
    3. FillRgn()填充区域
    4. 点击测试点击查看他们在哪个区域PtInRgn()

    【讨论】:

    • 问题将是创建多边形...现在我创建位图和洪水填充坐标,但在(不久的)将来,用户将导入自己的 bmp 并使用洪水填充创建自己的 xml坐标..所以似乎主要问题是找到点击点周围的边界..我可能必须通过代码而不是api创建一个填充,这样当我遇到另一个点时我会注意到
    • 我不知道有一种 GDI 方法可以获取一个本来是 FloodFilled 的区域。确定区域的一种巧妙方法......从您的洪水填充点开始,向外创建Rectangles(或向区域添加点),直到您达到边界颜色。相关问题列表中的可能解决方案:Floodfill implementetionFloodfill part of an Image
    • @Hrqls 也可用于自动生成区域:QuickFill/flood fill algorithm in C#/VB.NET
    • 谢谢! ..我在这个应用程序的java实现中使用了一个floodfill算法..我现在正在考虑在vb6中重现它(或者至少是我需要的部分)......虽然我可能会更快更容易地使用资源将位图复制到内存,填充其中的区域,并查看所需坐标何时改变颜色..我想这将是这些解决方案之一:)
    • 怎么样,ss a off(在启动时),用独特的颜色填充每个区域,然后扫描生成的图像,检查颜色,创建哪些区域在哪些点的地图.点击后,您可以将坐标用作该地图的索引,并立即获取区域信息。
    【解决方案3】:

    感谢 LimaNightHawk 和 Deanna 告诉我如何解决这个问题 :)

    在我的图片框中加载位图后,我在内存中复制它。 然后,在该副本中,我用自己的颜色填充每个重要区域:

    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
    

    为了简单起见,我使用区域的索引号作为颜色。

    然后我可以通过读取内存副本中该区域的颜色来获取索引:

    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
    

    最后我从 MouseDown 事件中调用上面的方法来使用用户点击的区域的索引:

    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
    

    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
    

    【讨论】:

    • 我在 Form_Unload 中删除对象 mlngCopy,并在过程本身中删除 lngBrush .. 我不确定我是否应该 DeleteObject 其他任何东西?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-04
    • 2015-02-06
    • 1970-01-01
    • 2017-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多