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:
#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.autohotke...topic35992.html)
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:
#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:
#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.