Hallo,
Ich habe folgendes Problem: Ich möchte eine Reihe von Pixelfarben in einem beliebigen DC auslesen. Das ganze soll aber ohne die API-Funktion 'GetPixel' funktionieren:
Mein Plan war es, den Inhalt des DC in eine HBitmap zu BitBlt'en. Das funktioniert auch super.
Allerdings komme ich dann nur an die rohen Farbdaten, wenn ich diese entweder mit Hilfe von
- LockBits() (was vorher eine Umwandlung der HBitmap in ein GDI+-Bitmap-Objekt erfordern würde),
- oder CreateDIBSection auslese.
BitmapLockBits ist für mich ein alter Hut - mich stört lediglich die unnötige Verschwendung von Performance, da die Bitmap-Bits erst in einen Buffer kopiert werden - weswegen ich mich für CreateDIBSection entschieden habe. Laut MSDN wird hier direkter zugriff auf die DIBits ermöglicht. Klingt ja sehr performant!
Jetzt kommen wir zum eigentlichen Problem:
Bei der Verwendung von CreateDIBSection ist BitBlt extrem(!) langsam - mehrere hundert millisekunden (600+ ms) ist für mich absolut nicht tragbar. Kann mir jemand sagen, warum die performance hier so mies ist? Oder am besten mit einem Verbesserungsvorschlag um die Ecke kommen? Mir ist ja bewusst, dass BitBlt hier eventuell einige kleine Konversionen durchführen muss, aber die DC's sind doch untereinander kompatibel und die DIB hat die gleiche Farbtiefe, wie der Screen-DC. Ich versteh's nicht.
/Edit: Neuer, Kommentierter Code.
Spoiler anzeigen
#include <Memory.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>
Global $_SGP2_hDC_Screen, $_SGP2_hDC_Memory, $_SGP2_pBitmapBits, $_SGP2_hMem_Alloc, $_SGP2_aArea[4]
[/autoit] [autoit][/autoit] [autoit]_ScreenGetPixel_2_Startup(100, 100) ; 100x100 großer DIB-Bereich
[/autoit] [autoit][/autoit] [autoit][/autoit] [autoit][/autoit] [autoit]$iSum = 0
For $i = 1 To 10
$iTimer = TimerInit()
_ScreenGetPixel_2_Refresh(Random(0, 300, 1), Random(0, 300, 1)) ; einen 100x100 Bereich irgendwo ab X: 0-300, Y: 0-300 übertragen
For $y = 0 To 99
For $x = 0 To 99
_ScreenGetPixel_2_GetPixel($x, $y)
Next
Next
$iTimer = TimerDiff($iTimer)
$iSum += $iTimer
ConsoleWrite('Durchgang #' & $i & ': ' & $iTimer & @CRLF)
Next
ConsoleWrite('Gesamt: ' & $iSum & @CRLF)
ConsoleWrite('Durchschnitt: ' & $iSum / $i & @CRLF)
_ScreenGetPixel_2_Shutdown()
;==========================================================
; Übergeben werden muss die Größe des Bildschirmbereiches,
; der später übertragen werden soll. Je größer der Bereich, desto
; länger dauert das BitBlt beim _ScreenGetPixel_2_Refresh()
;==========================================================
Func _ScreenGetPixel_2_Startup($iWidth, $iHeight, $iX = 0, $iY = 0)
Local Const $tagBITMAPINFOHEADER = 'dword biSize;long biWidth;long biHeight;ushort biPlanes;ushort biBitCount;dword biCompression;dword biSizeImage;long biXPelsPerMeter;long biYPelsPerMeter;dword biClrUsed;dword biClrImportant;'
Local $pBitmapHeader, $tBitmapHeader
[/autoit] [autoit][/autoit] [autoit]; Wir holen uns zuerst den DC vom Desktop.
$_SGP2_hDC_Screen = _WinAPI_GetDC(0)
; Dazu erstellen wir uns noch einen kompatiblen DC im Memory, der
; mit der DIB verknüpft wird. Wenn wir ein BitBlt in diesen DC machen,
; und die Farbinformationen vom Desktop-DC übertragen, enthält die DIB
; unsere Farbdaten.
$_SGP2_hDC_Memory = _WinAPI_CreateCompatibleDC($_SGP2_hDC_Screen)
; Speicher für Bitmap-Header reservieren
$_SGP2_hMem_Alloc = _MemGlobalAlloc(40, $GMEM_FIXED)
If Not @error Then
; Pointer des reservierten Speichers holen
$pBitmapHeader = _MemGlobalLock($_SGP2_hMem_Alloc)
; BitmapHeader in reserviertem Speicherbereich erstellen
$tBitmapHeader = DllStructCreate($tagBITMAPINFOHEADER, $pBitmapHeader)
; Bitmapinfos ausfüllen
DllStructSetData($tBitmapHeader, 'biSize', DllStructGetSize($tBitmapHeader))
DllStructSetData($tBitmapHeader, 'biWidth', $iWidth)
DllStructSetData($tBitmapHeader, 'biHeight', $iHeight)
DllStructSetData($tBitmapHeader, 'biPlanes', 1)
DllStructSetData($tBitmapHeader, 'biBitCount', @DesktopDepth)
DllStructSetData($tBitmapHeader, 'biCompression', 0)
DllStructSetData($tBitmapHeader, 'biSizeImage', 0)
DllStructSetData($tBitmapHeader, 'biXPelsPerMeter', 0)
DllStructSetData($tBitmapHeader, 'biYPelsPerMeter', 0)
DllStructSetData($tBitmapHeader, 'biClrUsed', 0)
DllStructSetData($tBitmapHeader, 'biClrImportant', 0)
; Globale Daten füllen
$_SGP2_aArea[0] = $iX
$_SGP2_aArea[1] = $iY
$_SGP2_aArea[2] = $iWidth
$_SGP2_aArea[3] = $iHeight
; DIB-Section erstellen. $_SGP2_pBitmapBits ist ein Pointer direkt auf die
; Farbinformationen der DIB.
$hBitmap = _WinAPI_CreateDIBSection(0, $tBitmapHeader, 0, $_SGP2_pBitmapBits)
; DIB in den Device Context laden.
_WinAPI_SelectObject($_SGP2_hDC_Memory, $hBitmap)
_ScreenGetPixel_2_Refresh()
[/autoit] [autoit][/autoit] [autoit]EndIf
EndFunc ;==>_ScreenGetPixel_2_Startup
;==========================================================
; Aktualisiert die Pixelfarben, die in zwischen-
; gespeicherter Form vorliegen.
;==========================================================
Func _ScreenGetPixel_2_Refresh($iX = -1, $iY = -1)
Local Const $SRCCOPY = 0x00CC0020 ; WindowsConstants.au3
; Wenn X oder Y Koordinate nicht mitgegeben wurde,
; verwende die bei StartUp angegebenen Koordinaten als
; linke obere Ecke.
If $iX = -1 Then $iX = $_SGP2_aArea[0]
If $iY = -1 Then $iY = $_SGP2_aArea[1]
; Hier passiert die Maagie!
_WinAPI_BitBlt($_SGP2_hDC_Memory, 0, 0, $_SGP2_aArea[2], $_SGP2_aArea[3], $_SGP2_hDC_Screen, $iX, $iY, $SRCCOPY)
EndFunc ;==>_ScreenGetPixel_2_Refresh
;==========================================================
; Holt eine Pixelfarbe aus den Bitmap Bits der DIB
;==========================================================
Func _ScreenGetPixel_2_GetPixel($iX = 0, $iY = 0)
Local $tStruct, $iReturn
; Struktur für Pixelfarbe erstellen
$tStruct = DllStructCreate("dword", $_SGP2_pBitmapBits + ($iX * 4) + ($iY * $_SGP2_aArea[2] * 4))
; Farbe auslesen
$iReturn = DllStructGetData($tStruct, 1)
; Struktur freigeben
$tStruct = 0
Return $iReturn
EndFunc ;==>_ScreenGetPixel_2_GetPixel
;==========================================================
; Aufräumarbeiten. Sollte vor einem erneuten
; _ScreenGetPixel_2_Startup IMMER durchgeführt werden!
;==========================================================
Func _ScreenGetPixel_2_Shutdown()
; DCs freigeben.
_WinAPI_ReleaseDC(0, $_SGP2_hDC_Screen)
_WinAPI_DeleteDC($_SGP2_hDC_Memory)
; Speicher für den Bitmapheader freigeben .
_MemGlobalFree($_SGP2_hMem_Alloc)
$_SGP2_hDC_Screen = 0
$_SGP2_hDC_Memory = 0
$_SGP2_pBitmapBits = 0
$_SGP2_aArea[0] = 0
$_SGP2_aArea[1] = 0
$_SGP2_aArea[2] = 0
$_SGP2_aArea[3] = 0
EndFunc ;==>_ScreenGetPixel_2_Shutdown
; From WinAPIEx.au3
;
; #FUNCTION# ====================================================================================================================
; Name...........: _WinAPI_CreateDIBSection
; Description....: Creates a DIB that applications can write to directly.
; Syntax.........: _WinAPI_CreateDIBSection ( $hDC, $tBITMAPINFO, $iUsage, ByRef $pBits [, $hSection [, $iOffset]] )
; Parameters.....: $hDC - Handle to a device context. If the value of $iUsage is $DIB_PAL_COLORS, the function uses this
; device context's logical palette to initialize the DIB colors.
; $tBITMAPINFO - $tagBITMAPINFO structure that specifies various attributes of the DIB, including the bitmap
; dimensions and colors.
; $iUsage - The type of data contained in the $pBits array. (either logical palette indexes or literal RGB values).
; The following values are defined.
;
; $DIB_PAL_COLORS
; $DIB_RGB_COLORS
;
; $pBits - A pointer to the location of the DIB bit values.
; $hSection - Handle to a file-mapping object that the function will use to create the DIB.
; $iOffset - The offset from the beginning of the file-mapping object referenced by $hSection where storage
; for the bitmap bit values is to begin. This value is ignored if $hSection is 0.
; Return values..: Success - Handle to the newly created DIB, and $pBits points to the bitmap bit values. You can create the
; structure by using $pBits pointer to further its filling. For example,
; DllStructCreate('dword[4]', $pBits).
; Failure - 0 and sets the @error flag to non-zero, $pBits also is 0.
; Author.........: Yashied
; Modified.......:
; Remarks........: When you are finished using the icon, destroy it using the _WinAPI_DestroyIcon() function.
; Related........:
; Link...........: @@MsdnLink@@ CreateDIBSection
; Example........: Yes
; ===============================================================================================================================
Func _WinAPI_CreateDIBSection($hDC, $tBITMAPINFO, $iUsage, ByRef $pBits, $hSection = 0, $iOffset = 0)
Local $Ret = DllCall('gdi32.dll', 'ptr', 'CreateDIBSection', 'hwnd', $hDC, 'ptr', DllStructGetPtr($tBITMAPINFO), 'uint', $iUsage, 'ptr*', 0, 'ptr', $hSection, 'dword', $iOffset)
[/autoit] [autoit][/autoit] [autoit]If (@error) Or (Not $Ret[0]) Then
$pBits = 0
Else
$pBits = $Ret[4]
EndIf
Return SetError(Number(Not $Ret[0]), 0, $Ret[0])
EndFunc ;==>_WinAPI_CreateDIBSection
Alter Code, den ich ursprünglich gepostet hatte:
Spoiler anzeigen
Spoiler anzeigen
[autoit]#include <Memory.au3>
#include <WinAPIEx.au3>
#include <WindowsConstants.au3>
Global $_SGP2_hDC_Screen, $_SGP2_hDC_Memory, $_SGP2_pBitmapBits, $_SGP2_hMem_Alloc, $_SGP2_aArea[4]
[/autoit] [autoit][/autoit] [autoit];==========================================================
; Übergeben werden muss der Bildschirmbereich, in dem
; später gesucht werden soll. Je größer der bereich, desto
; länger dauert das BitBlt beim _ScreenGetPixel_2_Refresh()
;==========================================================
Func _ScreenGetPixel_2_Startup($iX, $iY, $iWidth, $iHeight)
Local $pBitmapHeader, $tBitmapHeader
$_SGP2_hDC_Screen = _WinAPI_GetDC(0)
$_SGP2_hDC_Memory = _WinAPI_CreateCompatibleDC($_SGP2_hDC_Screen)
$_SGP2_hMem_Alloc = _MemGlobalAlloc(40, $GMEM_FIXED)
If Not @error Then
$pBitmapHeader = _MemGlobalLock($_SGP2_hMem_Alloc)
$tBitmapHeader = DllStructCreate($tagBITMAPINFOHEADER, $pBitmapHeader)
DllStructSetData($tBitmapHeader, 'biSize', DllStructGetSize($tBitmapHeader))
DllStructSetData($tBitmapHeader, 'biWidth', Abs($iWidth))
DllStructSetData($tBitmapHeader, 'biHeight', Abs($iHeight))
DllStructSetData($tBitmapHeader, 'biPlanes', 1)
DllStructSetData($tBitmapHeader, 'biBitCount', @DesktopDepth)
DllStructSetData($tBitmapHeader, 'biCompression', 0)
DllStructSetData($tBitmapHeader, 'biSizeImage', 0)
DllStructSetData($tBitmapHeader, 'biXPelsPerMeter', 0)
DllStructSetData($tBitmapHeader, 'biYPelsPerMeter', 0)
DllStructSetData($tBitmapHeader, 'biClrUsed', 0)
DllStructSetData($tBitmapHeader, 'biClrImportant', 0)
$_SGP2_aArea[0] = $iX
$_SGP2_aArea[1] = $iY
$_SGP2_aArea[2] = $iWidth
$_SGP2_aArea[3] = $iHeight
$hBitmap = _WinAPI_CreateDIBSection(0, $tBitmapHeader, 0, $_SGP2_pBitmapBits)
_WinAPI_SelectObject($_SGP2_hDC_Memory, $hBitmap)
_MemGlobalFree($_SGP2_hMem_Alloc)
_ScreenGetPixel_2_Refresh()
[/autoit] [autoit][/autoit] [autoit]EndIf
EndFunc ;==>_ScreenGetPixel_2_Startup
;==========================================================
; Aktualisiert die Pixelfarben, die in zwischen-
; gespeicherter Form vorliegen.
;==========================================================
Func _ScreenGetPixel_2_Refresh($iX = -1, $iY = -1)
Local Const $SRCCOPY = 0x00CC0020 ; WindowsConstants.au3
If $iX = -1 Then $iX = $_SGP2_aArea[0]
If $iY = -1 Then $iY = $_SGP2_aArea[1]
_WinAPI_BitBlt($_SGP2_hDC_Memory, 0, 0, $_SGP2_aArea[2], $_SGP2_aArea[3], $_SGP2_hDC_Screen, $iX, $iY, $SRCCOPY)
EndFunc ;==>_ScreenGetPixel_2_Refresh
;==========================================================
; Holt eine Pixelfarbe aus dem Zwischenspeicher
;==========================================================
Func _ScreenGetPixel_2_GetPixel($iX, $iY)
Local $tStruct, $iReturn
$tStruct = DllStructCreate("dword", $_SGP2_pBitmapBits + ($iX * 4) + ($iY * $_SGP2_aArea[2] * 4))
$iReturn = DllStructGetData($tStruct, 1)
$tStruct = 0
Return $iReturn
EndFunc ;==>_ScreenGetPixel_2_GetPixel
;==========================================================
; Aufräumarbeiten. Sollte vor einem erneuten
; _ScreenGetPixel_2_Startup IMMER durchgeführt werden!
;==========================================================
Func _ScreenGetPixel_2_Shutdown()
_WinAPI_ReleaseDC(0, $_SGP2_hDC_Screen)
_WinAPI_DeleteDC($_SGP2_hDC_Memory)
$_SGP2_hDC_Screen = 0
$_SGP2_hDC_Memory = 0
$_SGP2_pBitmapBits = 0
$_SGP2_aArea[0] = 0
$_SGP2_aArea[1] = 0
$_SGP2_aArea[2] = 0
$_SGP2_aArea[3] = 0
EndFunc ;==>_ScreenGetPixel_2_Shutdown
Gruß, SEuBo