Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

Fast PixelGetColor workaround for Aero (Windows 7 and Vista)


  • Please log in to reply
10 replies to this topic
blamestar.de
  • Guests
  • Last active:
  • Joined: --
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:
#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. ;)

blamestar.de
  • Guests
  • Last active:
  • Joined: --
One thing I forgot to add: Currently it only works in screen coordinate mode!

So if you want to do something like
MouseGetPos, x, y
c := fastPixelGetColor(x, y)
you need to add the following line at the beginning of your script:
CoordMode, Mouse, Screen
Feel free to modify this script to support window coordinate mode as well. I probably won't.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Searching for a single pixel is not the point of this function.

Actually, PixelSearch does something similar: it copies the entire region to be searched into system memory, then searches the copy rather than requesting each pixel from the video device one at a time. Of course, PixelSearch repeats the process each time it is called, so performing multiple searches might be faster using blamestar.de's method.

Nyl
  • Guests
  • Last active:
  • Joined: --
Sorry for the necro but I can't find any workarounds for pixelsearch with aero glass.

I know it was mentioned in this post that .de's method would work, but I'm not experienced enough to convert his .ahk

Anyone know of a solid workaround for this? For now I just have aero glass disabled.

Thanks,

Nyl

noise
  • Members
  • 57 posts
  • Last active: Aug 31 2018 10:16 AM
  • Joined: 14 May 2009
I haven't tested or used the script but you should be able to do the following.

instead of using PixelGetColor

Save fastPixelGetColor.ahk into your script directory and add this line to your script:
#include fastPixelGetColor.ahk

Then when you want to get the pixel details you enter:
updateFastPixelGetColor()
to grab the screenshot into memory

and then do a series of:
c := fastPixelGetColor(4, 73)
to get the colour of the pixels from the screenshot taken.

Nyl
  • Guests
  • Last active:
  • Joined: --
Hello and thank you for the reply.

I was looking to do it with pixelsearch, not PixelGetColor.

noise
  • Members
  • 57 posts
  • Last active: Aug 31 2018 10:16 AM
  • Joined: 14 May 2009
ah, well you wont need this script then ;)

gpel
  • Members
  • 3 posts
  • Last active: May 02 2011 08:28 PM
  • Joined: 07 Sep 2010
I have the problem of slow PixelGetColor since upgrading to Windows 7. I was quite happy to see a resolution. However, I have a few issues with implementing the solution.

- First is that I am using 1.0.46 because I need the low level __expr functionality. This script uses the "while" which best I can tell is not available in 1.0.46. This was easy to solve and work around.
- Secondly I was using PixelGetColor to return RGB color values. However fastPixelGetColor does not return RGB (or the PixelGetColor default of hexidecimal BGR), but rather integer/decimal equivilent. Is there a way to have return or convert to RGB? I did look into the "BitBlt" DllCall, but didn't see any options and quickly got lost in the Microsoft fog.

Thanks in advance!

G

tic
  • Members
  • 1934 posts
  • Last active: May 30 2018 08:13 PM
  • Joined: 22 Apr 2007
Using the gdi+ library you can get all the pixels on the screen 3 times faster than the top code:

SetBatchLines, -1
CoordMode, Mouse, Screen
CoordMode, Pixel, Screen

#Include fastPixelGetColor.ahk
;#Include Gdip.ahk

pToken := Gdip_Startup()

Time1 := A_TickCount
updateFastPixelGetColor()
Loop, %A_ScreenHeight%
{
	y := A_Index-1
	Loop, %A_ScreenWidth%
		c := fastPixelGetColor(A_Index-1, y)
}
Time2 := A_TickCount

Time3 := A_TickCount
pBitmap := Gdip_BitmapFromScreen()
E1 := Gdip_LockBits(pBitmap, 0, 0, Gdip_GetImageWidth(pBitmap), Gdip_GetImageHeight(pBitmap), Stride, Scan0, BitmapData)
Loop, %A_ScreenHeight%
{
	y := A_Index-1
	Loop, %A_ScreenWidth%
		c := Gdip_GetLockBitPixel(Scan0, A_Index-1, y, Stride)
}
Gdip_UnlockBits(pBitmap, BitmapData)
Time4 := A_TickCount

MsgBox, % Time2-Time1 "`n" Time4-Time3		;%

Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
return

This is without using machine code. If I needed to get a number of pixels consecutively then machine code would be able to do it a few thousand times faster. You need version 1.39 of the Gdip library though. You would need to rename the CreateDIBSection in fastPixelGetColor.ahk though as it conflicts with the one in the gdi+ library

Le____
  • Members
  • 4 posts
  • Last active: Feb 09 2019 03:19 PM
  • Joined: 17 Oct 2014

you just saved my life guys, thank you so much!



ilikescripting
  • Members
  • 2 posts
  • Last active: Dec 07 2014 04:18 AM
  • Joined: 05 Dec 2014

I used this script and it works great! I have the same problem with very slow PixelGetColor on Aero. I haven't dug through the code just yet but I am wondering if you could tell me how I could modify updateFastPixelGetColor() so that it only gets pixel data for a subset of the screen. I am assuming if we get pixel data for a small subset of the screen that updateFastPixelGetColor() would run even faster. My current need is to only query pixel colors from within a small rectangular region of the screen.

 

Thanks for sharing such a great script!