[Pixel überprüfen] via getPixel aus gdi32: Alternativen.

  • VB.NET

Es gibt 30 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

    [Pixel überprüfen] via getPixel aus gdi32: Alternativen.

    Hallo,

    ich benötige dringend Rat. Meine Anwendung ist dafür gedacht, einen Screenshot anzufertigen, sobald bei einer Drittanwendung (ein Spiel) ein bestimmtes Muster auftaucht (es handelt sich um eine Punktetabelle) - sprich sobald diese Tabelle erscheint, soll meine Anwendung einen Screenshot speichern. Dafür benutze ich die getPixel function.

    Hier der relevante Teil meiner Anwendung:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Declare Function GetForegroundWindow Lib "user32.dll" () As IntPtr
    3. Private Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
    4. Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer) As Integer
    5. Private Declare Function GetDC Lib "user32" Alias "GetDC" (ByVal hwnd As Integer) As Integer
    6. Dim intFenster_Vordergrund As IntPtr
    7. Dim windowTitle As String = "warrock"
    8. Public Sub PixelCheck()
    9. Dim hWnd = FindWindowByTitle(windowTitle)
    10. intFenster_Vordergrund = CType(GetForegroundWindow().ToString, IntPtr)
    11. If hWnd = intFenster_Vordergrund Then
    12. Dim lColor As Integer = GetPixel(GetDC(0), 563, 759)
    13. Dim lColor2 As Integer = GetPixel(GetDC(0), 599, 800)
    14. Dim lColor3 As Integer = GetPixel(GetDC(0), 1160, 831)
    15. Dim lColor4 As Integer = GetPixel(GetDC(0), 608, 827)
    16. Dim lColor5 As Integer = GetPixel(GetDC(0), 887, 760)
    17. If Not ((lColor <= 6000000) Or (lColor >= 10000000)) And Not ((lColor2 <= 12000000) Or (lColor2 >= 15000000)) And Not ((lColor3 <= 6000000) Or (lColor3 >= 9000000)) And Not ((lColor4 <= 500000) Or (lColor4 >= 2000000)) And Not ((lColor5 <= 9000000) Or (lColor5 >= 12000000)) Then
    18. EndscoreCapture() 'fertigt den Screenshot an.
    19. End If
    20. End If
    21. End Sub
    22. End Class


    Zunächst einmal überprüfe ich via GetForegroundWindow und FindWindow, ob die hwnd PID der Drittanwendung mit derjenigen PID übereinstimmt, in der sich der Benutzer momentan befindet (fokus). Das ist erforderlich, damit nicht von anderen Anwendungen Screenshots erstellt werden können, die eine ähnliche Anordnung von Pixeln haben (z.B. wenn man sich im Browser befindet usw.). Wenn die gewünschte Anwendung im Vordergrund ist, speichere ich in lColor bis lColor5 Farbwerte von den relevanten Koordinaten, und überprüfe, ob diese sich in einem bestimmten Farbspektrum befinden. Um es zu vereinfachen als Beispiel: Sobald Pixel 563, 759 Rot und sobald Pixel 599, 800 Blau sind, soll ein Screenshot angefertigt werden. Die GetPixel Funktion liefert einen Wert, der erst durch Hexadezimalumwandlung RGB ergibt, was ich mir aber erspare. Dadurch bedeuted z.B. 16.777.215 = weiß.

    VB.NET-Quellcode

    1. If Not ((lColor <= 6000000) Or (lColor >= 10000000)) And Not ((lColor2 <= 12000000) Or (lColor2 >= 15000000)) And Not ((lColor3 <= 6000000) Or (lColor3 >= 9000000)) And Not ((lColor4 <= 500000) Or (lColor4 >= 2000000)) And Not ((lColor5 <= 9000000) Or (lColor5 >= 12000000)) Then
    2. EndscoreCapture()
    3. End If

    Diese Abfrage schließt alle Szenarien aus, in denen die Drittanwendung nicht die Punktetabelle zeigt. Somit habe ich meine gewünschte Methode, die jede Punktetabelle als Screenshot abspeichert.

    Nun zu meinem Problem: Es handelt sich um ein Spiel (genauer gesagt um einen FPS). Wenn ich meine Sub PixelCheck() in einer While-Schleife ca. jede Millisekunde ausführe, stellt sich das als extrem unperformant heraus und es ruckelt im Spiel, weil meine Anwendung in Echtzeit immer wieder diese 5 Pixel abfragt. Selbst wenn ich meine Sub in einen Timer packe, der nur jede Sekunde abfragt, ruckelt es in jeder Sekunde. GetPixel ist demnach für mich unbrauchbar nehme ich an.

    Lohnt es sich alternativ 5 * 1x1 Bitmaps von diesen Positionen anzulegen und diese auf ihre Farbwerte zu überprüfen? Ist das "schneller"? Gibt es weitere Möglichkeiten oder Ideen, wie sich das optimieren lässt? Das einzige was ich bereits probiert habe, ist meine Sub in einen anderen Thread auszulagern, was allerdings wenig gebracht habe. Ich weiß auch leider nicht, wie GetPixel im Detail arbeitet. Ich erstelle jedes Mal einen device context über GetDC, wobei GetDC(0) den gesamten Bildschirm erfasst, liegt hierin vielleicht das Problem? Irgendwelche Vorschläge, um mein Vorhaben zu realisieren?

    Mit freundlichen Grüßen

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „getaim“ ()

    Ich benutze nun getPixel von der FastGrahpicsLib. Die LIB funktioniert über LockBits, was nach meiner Recherche die schnellste Möglichkeit ist. Allerdings brauche ich immer nur einen Pixel und allein das Erstellen einer Bitmap für einen blöden Pixel, nur damit ich die Farbe von diesem lesen kann kommt mir als die falsche Vorgehensweise vor. Ich glaube das Ruckeln kommt durchs permanente Erstellen von Bitmaps.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „getaim“ ()

    getaim schrieb:

    der erst durch Hexadezimalumwandlung RGB ergibt
    What :?:

    getaim schrieb:

    Ist das "schneller"?
    Was ergaben Deine experimentellen Untersuchungen?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    der erst durch Hexadezimalumwandlung RGB ergibt

    Auch wenn das mit meinem Problem wenig zu tun hat: Ich glaube Sie haben recht, ich habe da etwas durcheinandergebracht:

    VB.NET-Quellcode

    1. Dim strRgb As String = VB.Right("000000" & Hex(lColor), 6)

    Hiermit lässt sich der Wert, den ich durch getPixel bekomme in RGB umwandel und bevor ich mehr Müll von mir gebe, es handelt sich um COLORREF.
    EDIT:

    VB.NET-Quellcode

    1. "R:" & VB.Right(strRgb, 2) & " G:" & Mid(strRgb, 3, 2) & " B:" & VB.Left(strRgb, 2)

    Wäre dann F7 F7 F7 (RGB) für weiß.

    Die Variante mit Bitmaps über die FastGraphics Lib sieht nun (beispielhaft für einen Pixel) so aus:

    VB.NET-Quellcode

    1. Dim g As FastGraphicsLib.FastGraphics = FastGraphicsLib.FastGraphics.FromScreen(New Rectangle(886, 646, 1, 1))
    2. g.GetPixel(0, 0)


    Nur halt 5 Bitmaps für 5 Positionen. Das funktioniert recht gut, in Echtzeit ruckelt es aber trotzdem leicht - auf älteren System vermutlich erst recht.

    Gruß

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „getaim“ ()

    getaim schrieb:

    VB.NET-Quellcode

    1. Dim strRgb As String = VB.Right("000000" & Hex(lColor), 6)
    Hier verlierst Du die Zeit. Arbeite mit numerischen Werten, lColor ist doch einer.
    Bei dem andern: Mach einen Test mit beiden Lösungen, ruf die 1000 Mal auf und vergleiche die Laufzeit beider => "Benchmark".
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Nein, diese "Konvertierung" führe ich eben nicht durch. Ich werte diejenigen Werte aus, die mir lColor bis lColor5 liefert - und zwar direkt.

    Edit :Werde gleich eine Zeitmessung durchführen. Die FastGraphics Lib funktioniert über LockBits, was deutlich schneller sein dürfte. Mir geht es aber immer noch um den Ansatz, erst jedes Mal BitMaps zu erstellen, was mir falsch vorkommt, da es ja wirklich nur um jeweils einen Pixel geht.
    @getaim Na fein.
    Kannst Du bitte die Vollzitate sein lassen?
    Die nerven mächtig.
    ----
    Was machen die Laufzeittests?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    getaim schrieb:

    Das zeigt
    das es egal ist, mit welcher Methode Du arbeitest.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    mir scheint dein Ansatz aus post#1 schon korrekt. FastgraphicsLib kenne ich nicht genau, aber denke auch, die erstellt erstmal eine Bitmap, lockt dann die Bitmap-Daten, und dann ist sie schnell ja. Die Langsamkeit ist da aber bereits passiert.

    An post#1 fällt mir auf, dass du 5 mal GetDC(0) aufrufst - die Device-Handle des Bildschirms.
    Tatsächlich brauchst du das kein einziges Mal aufzurufen, in dieser Methode.
    Du kannst es einmalig im Form_Load oder Whatever abrufen und in einer Variablen speichern - die Handle ist ja immer dieselbe und kann wiederverwendet werden.

    Wäre imo einen Versuch wert.
    Ansonsten musst du erstmal weiter fleißig rumtesten, welches die Aufrufe sind, die die Zeit fressen.

    Sowas soll ja mit Profiler-Tools gehen - nur hab ich noch nie verstanden, son Profiler-Bericht richtig zu deuten.
    Ich kommentiere immer verdächtige Parts aus, und gucke, obs danach schneller geht.
    Denke auch, dass EDR da Recht hat. Falls dein GetDC() lange braucht, dann dauert deine fünf Aufrufe logischerweise auch fünf mal so lang.
    Was auch ein Problem sein könnte, dass du die Handles nicht wieder freigibst. Kannst du auch mal probieren.

    C#-Quellcode

    1. IntPtr hdc = GetDC(IntPtr.Zero);
    2. uint pixel = GetPixel(hdc, x, y);
    3. ReleaseDC(IntPtr.Zero, hdc);


    Alternativ vielleicht das hier testen: stackoverflow.com/questions/14…e-color-of-a-screen-pixel
    @ErfinderDesRades Jou.

    LaMiy schrieb:

    nicht wieder freigibst
    Das frisst Ressourcen, nicht aber Zeit.
    @getaim Hole den DC genau ein Mal und verwende ihn immer wieder.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    So hatte mich jetzt doch was gewurmt.
    Habe zwei Tests gemacht. 1x mit deiner Variante und 1x mit einer von Stackoverflow. Irgendwie war die zweite tatsächlich schneller. Probier einfach mal aus und sag ob es besser klappt.

    Code für 2.

    VB.NET-Quellcode

    1. Public Shared Function BitBlt(hDC As IntPtr, x As Integer, y As Integer, nWidth As Integer, nHeight As Integer, hSrcDC As IntPtr, _
    2. xSrc As Integer, ySrc As Integer, dwRop As Integer) As Integer
    3. End Function
    4. Private screenPixel As New Bitmap(1, 1, PixelFormat.Format32bppArgb)
    5. Public Function GetColorAt(location As Point) As Color
    6. Using gdest As Graphics = Graphics.FromImage(screenPixel)
    7. Using gsrc As Graphics = Graphics.FromHwnd(IntPtr.Zero)
    8. Dim hSrcDC As IntPtr = gsrc.GetHdc()
    9. Dim hDC As IntPtr = gdest.GetHdc()
    10. Dim retval As Integer = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, _
    11. location.X, location.Y, CInt(CopyPixelOperation.SourceCopy))
    12. gdest.ReleaseHdc()
    13. gsrc.ReleaseHdc()
    14. End Using
    15. End Using
    16. Return screenPixel.GetPixel(0, 0)
    17. End Function
    Bilder
    • zeit.png

      26,11 kB, 394×227, 118 mal angesehen
    • zeit2.png

      25,94 kB, 387×231, 119 mal angesehen

    getaim schrieb:

    Habe ich so umgesetzt
    Poste bitte mal den ganzen Code.

    getaim schrieb:

    RGB-String
    String :?:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Public Class Form1
    3. Public Declare Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
    4. <DllImport("user32.dll")> _
    5. Private Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    6. End Function
    7. <DllImport("user32.dll")> _
    8. Private Shared Function GetDC(ByVal hwnd As IntPtr) As IntPtr
    9. End Function
    10. Public Function GetScreenPixelColor(ByVal X As Long, ByVal Y As Long) As Integer
    11. Dim nDC As Long
    12. nDC = CLng(GetDC(CType(0, IntPtr)))
    13. GetScreenPixelColor = CInt(GetPixel(nDC, X, Y))
    14. ReleaseDC(CType(0, IntPtr), CType(nDC, IntPtr))
    15. End Function
    16. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    17. Dim startpt As Integer = Environment.TickCount
    18. For i = 1 To 100
    19. GetScreenPixelColor(100, 100)
    20. GetScreenPixelColor(520, 400)
    21. GetScreenPixelColor(730, 300)
    22. GetScreenPixelColor(940, 200)
    23. GetScreenPixelColor(150, 100)
    24. Next
    25. Dim stoppt As Integer = Environment.TickCount
    26. MsgBox(stoppt - startpt)
    27. End Sub
    28. End Class

    Das ist meine Testfunktion. Ich denke, dass der Vorschlag so richtig umgesetzt ist.
    EDIT: Ich habe gerade nDC = GetDC(0) ins Load-Ereignis gesetzt und das Ganze reduziert sich auf 15ms. Jetzt ist die Frage, ob sich die Pixel auch wirklich aktualisieren oder immer nur die gleichen Werte vom Zeitpunkt des FormLoads kommen. Edit2: Ja das ist leider so - schade!
    -------------------------------------

    VB.NET-Quellcode

    1. Dim punkt As New Point(500, 500)
    2. Dim farbcode = GetColorAt(punkt).ToString

    LaMiy's Funktion liefert mir nicht die richtige Farbe, sondern immer nur 0, 0 ,0.

    Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „getaim“ ()

    Erstmal sind deine Deklarationen komplett falsch, du brauchst Int und nicht long, und an mancher stelle wären auch IntPtr anstelle von Int werten logischer.
    Hol dir erst mal von dieser Seite die richtigen Deklerationen:
    pinvoke.net/
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    getaim schrieb:

    Ich denke, dass der Vorschlag so richtig umgesetzt ist.
    Nö.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Public Class Form1
    3. <DllImport("user32.dll")> _
    4. Private Shared Function GetDC(ByVal hwnd As IntPtr) As IntPtr
    5. End Function
    6. <DllImport("gdi32.dll")> _
    7. Public Shared Function GetPixel(hdc As IntPtr, _
    8. nXPos As Integer, _
    9. nYPos As Integer) As UInteger
    10. End Function
    11. <DllImport("user32.dll")> _
    12. Private Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    13. End Function
    14. Private nDC As IntPtr
    15. Public Function GetScreenPixelColor(ByVal X As Integer, ByVal Y As Integer) As UInteger
    16. Return GetPixel(nDC, X, Y)
    17. End Function
    18. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    19. Dim sw As New Stopwatch
    20. sw.Start()
    21. nDC = GetDC(IntPtr.Zero)
    22. For i = 1 To 100
    23. GetScreenPixelColor(100, 100)
    24. GetScreenPixelColor(120, 100)
    25. GetScreenPixelColor(140, 100)
    26. GetScreenPixelColor(150, 100)
    27. GetScreenPixelColor(160, 100)
    28. Next
    29. ReleaseDC(IntPtr.Zero, nDC)
    30. sw.Stop()
    31. MessageBox.Show(sw.ElapsedMilliseconds.ToString)
    32. End Sub
    33. End Class
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Dass meine Umsetzung gewisse Mängel hat, war mir schon klar. Danke für den Tipp mit dem Add-In, habe zumindest meine Version jetzt nach bestem Gewissen editiert.
    @RodFromGermany
    Danke für den Ansatz, an der Laufzeit tut sich leider trotzdem nichts. Es geht auch nicht einzig allein um die Geschwindigkeit, die wäre so ausreichend. Aber versuch mal während der Prozedur z.B. das Browserfenster zu bewegen, es ruckelt.

    edit:
    Habe nun auch LaMiy's Ansatz durch: Laufzeit und Ruckeln während der Prozedur sind nahezu identisch.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Drawing.Imaging
    2. Imports System.Runtime.InteropServices
    3. Public Class Form1
    4. Dim p1 As New Point(500, 500)
    5. Dim p2 As New Point(400, 100)
    6. Dim p3 As New Point(600, 300)
    7. Dim p4 As New Point(100, 100)
    8. Dim p5 As New Point(440, 100)
    9. <DllImport("gdi32.dll")> _
    10. Private Shared Function BitBlt(ByVal hdc As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As Long) As Boolean
    11. End Function
    12. Private screenPixel As New Bitmap(1, 1, PixelFormat.Format32bppArgb)
    13. Public Function GetColorAt(location As Point) As Color
    14. Using gdest As Graphics = Graphics.FromImage(screenPixel)
    15. Using gsrc As Graphics = Graphics.FromHwnd(IntPtr.Zero)
    16. Dim hSrcDC As IntPtr = gsrc.GetHdc()
    17. Dim hDC As IntPtr = gdest.GetHdc()
    18. Dim retval As Integer = CInt(BitBlt(hDC, 0, 0, 1, 1, hSrcDC, _
    19. location.X, location.Y, CInt(CopyPixelOperation.SourceCopy)))
    20. gdest.ReleaseHdc()
    21. gsrc.ReleaseHdc()
    22. End Using
    23. End Using
    24. Return screenPixel.GetPixel(0, 0)
    25. End Function
    26. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    27. Dim startpt As Integer = Environment.TickCount
    28. For i = 1 To 100
    29. GetColorAt(p1)
    30. GetColorAt(p2)
    31. GetColorAt(p3)
    32. GetColorAt(p4)
    33. GetColorAt(p5)
    34. Next
    35. Dim stoppt As Integer = Environment.TickCount
    36. MessageBox.Show(CStr(stoppt - startpt))
    37. End Sub
    38. End Class

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „getaim“ ()