Hey people,
having recently run into the "
slow PixelGetColor problem" I wasn't satisfied with the solution of disabling the compositing (Aero Glass) everytime I needed the script in question. So I went on to develop an alternative.
The whole thing is based on the idea that it's quite stupid to ask the grapics card for every single pixel. If the script needs a very large quantity of pixels in a very small amount of time (which is true for my scripts) it is much more efficient to grab the whole screen into memory once and then read the relevant pixels from there.
How to use it:
Code:
#include fastPixelGetColor.ahk
updateFastPixelGetColor() ; grab the screen into memory
c := fastPixelGetColor(4, 73) ; get the color at x=4, y=73 at the last call of updateFastPixelGetColor()
And this is
fastPixelGetColor.ahk: (you may notice it's based on
http://www.autohotkey.com/forum/topic35992.html)
Code:
fastPixelGetColorBufferDC = 0
fastPixelGetColorReady = 0
fastPixelGetColorWait = 1
fastPixelGetColor(x, y) {
global fastPixelGetColorBufferDC, fastPixelGetColorReady, fastPixelGetColorWait
global fastPixelGetColorScreenLeft, fastPixelGetColorScreenTop
; check if there is a valid data buffer
if (!fastPixelGetColorReady) {
if (fastPixelGetColorWait) {
Start := A_TickCount
While !fastPixelGetColorReady {
Sleep, 10
if (A_TickCount - Start > 5000)
return -3 ; time out if data is not ready after 5 seconds
}
}
else
return -2 ; return an invalid color if waiting is disabled
}
return DllCall("GetPixel", "Uint", fastPixelGetColorBufferDC, "int", x - fastPixelGetColorScreenLeft, "int", y - fastPixelGetColorScreenTop)
}
updateFastPixelGetColor() {
global fastPixelGetColorReady, fastPixelGetColorBufferDC
static oldObject = 0, hBuffer = 0
static screenWOld = 0, screenHOld = 0
; get screen dimensions
global fastPixelGetColorScreenLeft, fastPixelGetColorScreenTop
SysGet, fastPixelGetColorScreenLeft, 76
SysGet, fastPixelGetColorScreenTop, 77
SysGet, screenW, 78
SysGet, screenH, 79
fastPixelGetColorReady = 0
; determine whether the old buffer can be reused
bufferInvalid := screenW <> screenWOld OR screenH <> screenHOld OR fastPixelGetColorBufferDC = 0 OR hBuffer = 0
screenWOld := screenW
screenHOld := screenH
if (bufferInvalid) {
; cleanly discard the old buffer
DllCall("SelectObject", "Uint", fastPixelGetColorBufferDC, "Uint", oldObject)
DllCall("DeleteDC", "Uint", fastPixelGetColorBufferDC)
DllCall("DeleteObject", "Uint", hBuffer)
; create a new empty buffer
fastPixelGetColorBufferDC := DllCall("CreateCompatibleDC", "Uint", 0)
hBuffer := CreateDIBSection(fastPixelGetColorBufferDC, screenW, screenH)
oldObject := DllCall("SelectObject", "Uint", fastPixelGetColorBufferDC, "Uint", hBuffer)
}
screenDC := DllCall("GetDC", "Uint", 0)
; retrieve the whole screen into the newly created buffer
DllCall("BitBlt", "Uint", fastPixelGetColorBufferDC, "int", 0, "int", 0, "int", screenW, "int", screenH, "Uint", screenDC, "int", fastPixelGetColorScreenLeft, "int", fastPixelGetColorScreenTop, "Uint", 0x40000000 | 0x00CC0020)
; important: release the DC of the screen
DllCall("ReleaseDC", "Uint", 0, "Uint", screenDC)
fastPixelGetColorReady = 1
}
CreateDIBSection(hDC, nW, nH, bpp = 32, ByRef pBits = "") {
NumPut(VarSetCapacity(bi, 40, 0), bi)
NumPut(nW, bi, 4)
NumPut(nH, bi, 8)
NumPut(bpp, NumPut(1, bi, 12, "UShort"), 0, "Ushort")
NumPut(0, bi,16)
Return DllCall("gdi32\CreateDIBSection", "Uint", hDC, "Uint", &bi, "Uint", 0, "UintP", pBits, "Uint", 0, "Uint", 0)
}
For your convenience here's some toy code to try it:
Code:
#include fastPixelGetColor.ahk
CoordMode, Mouse, Screen
CoordMode, Pixel, Screen
Gui, Add, Text, vMyLabel, This is some stupid and (nearly) useless dummy text that makes the window wider!
Gui, Show
SetTimer, updateLabel, 500
updateLabel:
updateFastPixelGetColor()
SetFormat, Integer, d
MouseGetPos, x, y
t = ( %x%, %y% )
SetFormat, Integer, h
c1 := fastPixelGetColor(x, y)
PixelGetColor, c2, x, y
t := t . " -- FAST: " . c1 . " -- SLOW: " . c2
; convert BGR to RGB for "Gui, Color"
c3 := (c1 & 0xFF) * 0x10000 | (c1 & 0xFF00) | (c1 & 0xFF0000) // 0x10000
Gui, Color, %c3%
GuiControl, Text, MyLabel, %t%
return
And for your reading pleasure here's some benchmarks:
Code:
#include fastPixelGetColor.ahk
count = 30
Start := A_TickCount
Loop, %count%
PixelGetColor, var, 1, 1, alt
end := A_TickCount
MsgBox % count " PixelGetColor calls took " (end - Start) "ms (" (end - start)/count "ms each)"
Start := A_TickCount
Loop, %count%
updateFastPixelGetColor()
end := A_TickCount
MsgBox % count " updateFastPixelGetColor calls took " (end - Start) "ms (" (end - start)/count "ms each)"
count := count * 1000
Start := A_TickCount
Loop, %count%
var := fastPixelGetColor(1, 1)
end := A_TickCount
MsgBox % count " fastPixelGetColor calls took " (end - Start) "ms (" (end - start)/count "ms each)"
exitapp
On my machine (Core 2 Duo 2 GHz, Radeon x1700 Mobility) the results look somewhat like this:
With Aero Glass (compositing)
enabled:
300 PixelGetColor calls took 8328ms (27.760000ms each)
30 updateFastPixelGetColor calls took 4047ms (134.900000ms each)
90000 fastPixelGetColor calls took 2625ms (0.029167ms each)
With Aero Glass (compositing)
disabled:
3000 PixelGetColor calls took 313ms (0.104333ms each)
300 updateFastPixelGetColor calls took 5719ms (19.063333ms each)
30000 fastPixelGetColor calls took 813ms (0.027100ms each)
Note that with Aero Glass the use of updateFastPixelGetColor() pays off if you need more than 4 pixels at a time. In fact, even without Aero Glass the use of updateFastPixelGetColor() may still be economical if you need more than 250 pixels at once. Of course your mileage may vary.
Also, please note that this code is
not extensively tested! So please do not be upset if it
grows an arm and stabs you in the face.
