【问题标题】:Getting a pixel color at screen coordinates and detecting for color change在屏幕坐标处获取像素颜色并检测颜色变化
【发布时间】:2019-04-11 20:13:48
【问题描述】:

我正在尝试创建一个应用程序来为我执行一些任务。基本上,我希望能够使用热键 (F12) 在我的屏幕上分配一个点,这将保存该点的颜色。

这种像素颜色经常发生变化,然后又恢复为原来的颜色。

在应用程序的整个运行过程中,每次颜色偏离其原始颜色时,它都会打开一个消息框,让我知道颜色再次发生了变化。

我已经做了很多谷歌搜索,但对于我的生活,我无法弄清楚这一点。

这是我目前所拥有的。

我有一个 timer2 可以检测我的热键 (F12),抓取颜色,甚至抓取鼠标的坐标:

Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
    For i = 1 To 255
        result = 0
        result = GetAsyncKeyState(i)
        If result = -32767 Then
            If i = 123 Then
                Dim myBmp As New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
                Dim g As Graphics = Graphics.FromImage(myBmp)
                g.CopyFromScreen(Point.Empty, Point.Empty, myBmp.Size)
                g.Dispose()
                Label1.Text = MousePosition.X.ToString & "," & MousePosition.Y.ToString
                PictureBox1.BackColor = myBmp.GetPixel(MousePosition.X, MousePosition.Y)
                'Label1.BackColor = myBmp.GetPixel(MousePosition.X, MousePosition.Y)
                myBmp.Dispose()

            End If
        End If
    Next i
End Sub

现在,如果我尝试在我的其他一台显示器(我有超过 1 台显示器)上捕获像素颜色,则该代码会出错:
“在 System.Drawing.dll 中发生了“System.ArgumentOutOfRangeException”类型的未处理异常 附加信息:参数必须为正且
在这一行: PictureBox1.BackColor = myBmp.GetPixel(MousePosition.X, MousePosition.Y)

另外,鉴于我能够在一定程度上获得鼠标的坐标,我不知道如何让我的应用程序以资源轻的方式监视这些坐标(鼠标不必停留在该像素位置或焦点)用于颜色变化。有人可以帮忙吗?

【问题讨论】:

    标签: .net vb.net


    【解决方案1】:

    这是我的多显示器设置中的 F12 部分以及计时器

    <DllImport("user32.dll")>
    Shared Function GetAsyncKeyState(ByVal vKey As Keys) As Short
    End Function
    
    Private savedColor As Color
    Private savedPosition As Point
    Private timer As New Threading.Timer(AddressOf timerCallback)
    Private timerInterval As Integer = 100
    
    Private Sub setPositionAndColor(ByRef position As Point, ByRef color As Color)
        Using myBmp As New Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height)
            Using g As Graphics = Graphics.FromImage(myBmp)
                g.CopyFromScreen(Point.Empty, Point.Empty, myBmp.Size)
            End Using
            position = Cursor.Position
            color = myBmp.GetPixel(position.X, position.Y)
        End Using
    End Sub
    
    Private Sub timerCallback(state As Object)
        If GetAsyncKeyState(Keys.F12) <> 0 Then
            setPositionAndColor(savedPosition, savedColor)
            Me.Invoke(
                Sub()
                    Label1.Text = $"{savedPosition.X}, {savedPosition.Y}"
                    PictureBox1.BackColor = savedColor
                End Sub)
        Else
            Dim currentColor As Color
            Dim currentPosition As Point
            setPositionAndColor(currentPosition, currentColor)
            If currentPosition = savedPosition AndAlso currentColor <> savedColor Then
                MessageBox.Show("!")
            End If
        End If
        timer.Change(timerInterval, -1)
    End Sub
    
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        timer.Change(timerInterval, -1)
    End Sub
    

    运行后,我发现它有点波涛汹涌。但它似乎做你想做的事。

    【讨论】:

    • 只复制你需要的一个像素而不是每次都在内存中创建这么大的位图不是更好吗? :) - 毕竟Graphics.CopyFromScreen() 允许您指定要捕获的区域的坐标和大小。
    • @VisualVincent 是的。我刚刚复制了 OP 代码并使其工作。 OP可以这样优化它。我所做的使其能够工作的主要更改是 SystemInformation.VirtualScreen.WidthCursor.Position
    • 看到了。只是建议它作为额外的改进。顺便说一句,我忘了说“很好的答案!” :)
    • 谢谢!将此标记为答案。不幸的是,我不知道 Graphics.CopyFromScreen() 更加优化。我会尝试用谷歌搜索如何使用它:)。
    【解决方案2】:

    使用 GDI 时,您尝试执行的操作可能会非常缓慢。实现这种东西的一个好方法是研究 SharpDX 之类的东西,并利用 DirectX 为您捕获屏幕,然后循环遍历数组中的像素以获取它们的颜色。

    https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/Direct3D11.1/ScreenCapture/Program.cs

    我知道代码是用 C# 编写的,但它与 VB 非常相似,我相信转换它不会很困难。

    【讨论】:

      【解决方案3】:

      一种使用RegisterHotkey 安装系统范围的热键的方法,该热键激活一个程序,该程序在任何屏幕中捕获光标下的颜色。

      当注册为热键的键盘键被按下时,将通知注册它的窗口(或线程),无论窗口是否具有焦点、最小化或在另一个屏幕中。

      按下热键时,使用Screen.FromPoint()方法识别光标所在的屏幕。
      有关与 Screen 对象和 Screens/VirtualScreen 坐标相关的方法的更多信息,请参阅Using SetWindowPos with multiple monitors 中的答案。

      然后使用CreateDC函数获取Cursor位置对应的Screen的设备上下文,并使用GetPixel函数获取Cursor下像素的Color。

      在任何时候都不会创建位图。

      注意事项:

      • 必须使用UnregisterHotKey 函数取消注册已注册的热键。
      • CreateDC 函数创建的句柄必须使用DeleteDC 函数释放。
      • 按下热键时,WM_HOTKEY 消息将发送到注册它的窗口(或线程)。我们需要重写 WndProc 以接收通知。请参阅 MSDN 文档中与 WM_HOTKEY 消息相关的注释,了解由 wParamlParam 表示的值的解释。
      • 如果热键对应于我们注册的热键,则会调用一个动作(此处名为 HotkeyPressed)。光标下的颜色设置为 PicureBox 控件的背景颜色(此处命名为PicureBox1)。
      • 由于提到了 F12 键,请注意此键由调试器(当前调试器或内核模式调试器)保留,因此不应使用它。当然,它可以与键修饰符(Control、Alt、Shift)相关联。
      • 如果注册了更多热键,则必须增加/更改相应的标识符(此处为名为 myHotkeyID 的字段)。


      在选择的表单中插入以下代码。
      热键 (F11) 在 Form.Load() 事件中注册,在 Form.FormClosed() 事件中取消注册。

      Imports System.Runtime.InteropServices
      Imports System.Security
      Imports System.Security.Permissions
      
      Private Const WM_HOTKEY As Integer = &H312
      Private myHotkeyID As Integer = 327680
      Private myHotkeyModifiers As RegHotkeyModifiers = 0
      Private currentScreen As Screen
      Private cursorPosition As Point = Point.Empty
      Friend Shared HotkeyPressed As Action
      
      Public Sub New()
          InitializeComponent()
          HotkeyPressed =
          Sub()
              currentScreen = Screen.FromPoint(cursorPosition)
              Me.PictureBox1.BackColor = PixelColorFromScreen(currentScreen, cursorPosition)
          End Sub
      End Sub
      
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
          Dim result = RegisterHotKey(Me.Handle, myHotkeyID, myHotkeyModifiers, Keys.F11)
      End Sub
      
      Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
          UnregisterHotKey(Me.Handle, myHotkeyID)
      End Sub
      
      <SecurityCritical>
      <SecurityPermission(SecurityAction.Demand, Flags:=SecurityPermissionFlag.UnmanagedCode)>
      Protected Overrides Sub WndProc(ByRef m As Message)
          Select Case m.Msg
              Case WM_HOTKEY
                  If CType(m.WParam, Integer) > 0 Then
                      Dim keyPressed As Keys = CType(CType(m.LParam, Integer) >> 16, Keys)
                      Dim modifiers = CInt(m.LParam) And &HFF
                      If myHotkeyID = CInt(m.WParam) AndAlso CInt(myHotkeyModifiers) = modifiers Then
                          Select Case keyPressed
                              Case Keys.F11
                                  cursorPosition = Cursor.Position
                                  HotkeyPressed()
                          End Select
                      End If
                  End If
                  Exit Select
          End Select
          MyBase.WndProc(m)
      End Sub
      

      Win32 函数声明和辅助方法:

      <Flags>
      Friend Enum RegHotkeyModifiers As UInteger
          MOD_ALT = &H1
          MOD_CONTROL = &H2
          MOD_SHIFT = &H4
          MOD_WIN = &H8         ' WINDOWS key was held down. Reserved for use by the operating system.
          MOD_NOREPEAT = &H4000 ' Keyboard auto-repeat does not yield multiple hotkey notifications.
      End Enum
      
      <DllImport("user32.dll")>
      Friend Shared Function RegisterHotKey(hWnd As IntPtr, ActionHotkeyId As Integer, fsModifiers As RegHotkeyModifiers, VirtualKey As Integer) As Boolean
      End Function
      <DllImport("user32.dll")>
      Friend Shared Function UnregisterHotKey(hWnd As IntPtr, ActionHotkeyId As Integer) As Boolean
      End Function
      
      <DllImport("gdi32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
      Friend Shared Function CreateDC(lpszDriver As String, lpszDevice As String, lpszOutput As String, lpInitData As IntPtr) As IntPtr
      End Function
      
      <DllImport("gdi32.dll", SetLastError:=True, EntryPoint:="DeleteDC")>
      Friend Shared Function DeleteDC(<[In]> hdc As IntPtr) As Boolean
      End Function
      
      <DllImport("gdi32.dll", SetLastError:=True)>
      Friend Shared Function GetPixel(hdc As IntPtr, nXPos As Integer, nYPos As Integer) As UInteger
      End Function
      
      Friend Function CreateDCFromDeviceName(deviceName As String) As IntPtr
          Return CreateDC(deviceName, Nothing, Nothing, IntPtr.Zero)
      End Function
      
      Private Function PixelColorFromScreen(screen As Screen, position As Point) As Color
          Dim screenDC As IntPtr = CreateDCFromDeviceName(screen.DeviceName)
          Dim pixel As UInteger = GetPixel(screenDC, position.X, position.Y)
          Try
              Return Color.FromArgb(CType(pixel And &HFF, Integer),
                                    CType(pixel And &HFF00, Integer) >> 8,
                                    CType(pixel And &HFF0000, Integer) >> 16)
          Finally
              DeleteDC(screenDC)
          End Try
      End Function
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-08
        • 1970-01-01
        • 2014-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-08
        相关资源
        最近更新 更多