在屏幕坐标处获取像素颜色并检测颜色变化

时间:2019-04-11 20:13:49

标签: .net vb.net

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

此像素颜色会更改,然后经常重置为原始颜色。

在应用程序的整个运行过程中,每次颜色从原始颜色更改为红色时,都会打开一个msgbox,让我知道颜色再次更改。

我做了很多谷歌搜索,但是对于我一生来说,我无法弄清楚。

这是我到目前为止所拥有的。

我有一个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)

此外,鉴于我能够某种程度地获取鼠标的坐标,所以我不知道如何让我的应用程序以资源节约的方式监视这些坐标(而鼠标不必停留在该像素位置或表格聚焦)以改变颜色。有人可以帮忙吗?

3 个答案:

答案 0 :(得分: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

运行它后,我发现它有点断断续续。但这似乎可以满足您的要求。

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

使用RegisterHotkey安装系统范围内的热键的方法,该键可激活在任何屏幕中的“光标”下捕获颜色的过程。

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

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

然后使用CreateDC函数来获取与光标位置相对应的Screen的设备上下文,并使用GetPixel函数来捕获光标下的像素的颜色。

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

注意:

  • 必须使用UnregisterHotKey功能取消注册的热键。
  • 必须使用DeleteDC函数释放由CreateDC函数创建的句柄。
  • 按下热键时,会向注册它的窗口(或线程)发送WM_HOTKEY消息。我们需要重写WndProc以接收通知。有关由WM_HOTKEYwParam表示的值的解释,请参见MSDN Docs中与lParam消息有关的注释。
  • 如果热键与我们注册的热键相对应,则将调用一个动作(在这里称为 HotkeyPressed )。光标下的颜色设置为PicureBox控件的BackColor(在这里,名为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