AutoHotkey Community

It is currently May 26th, 2012, 8:23 pm

All times are UTC [ DST ]




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: August 15th, 2009, 11:04 pm 
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. ;)


Report this post
Top
  
Reply with quote  
 Post subject: Note
PostPosted: August 16th, 2009, 11:14 am 
One thing I forgot to add: Currently it only works in screen coordinate mode!

So if you want to do something like
Code:
MouseGetPos, x, y
c := fastPixelGetColor(x, y)

you need to add the following line at the beginning of your script:
Code:
CoordMode, Mouse, Screen

Feel free to modify this script to support window coordinate mode as well. I probably won't.


Report this post
Top
  
Reply with quote  
 Post subject:
PostPosted: September 8th, 2009, 8:05 pm 
Offline

Joined: April 10th, 2009, 1:46 am
Posts: 139
Location: Wichita,Kansas
theres a better way to do a fast pixelsearch, put , fast after the full syntax of pixelsearch.

_________________
Drainx1
Your favorite.

What says that we aren't a visualization of someones dream?
And that person could change the laws of physics without thinking a thing about it.
They could recreate the world, and not know what happened.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 9th, 2009, 10:20 am 
Offline

Joined: October 17th, 2006, 4:15 pm
Posts: 7502
Location: Australia
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 9th, 2010, 6:19 pm 
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


Report this post
Top
  
Reply with quote  
 Post subject:
PostPosted: March 9th, 2010, 7:39 pm 
Offline

Joined: May 14th, 2009, 12:43 pm
Posts: 57
Location: UK
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.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: March 9th, 2010, 7:57 pm 
Hello and thank you for the reply.

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


Report this post
Top
  
Reply with quote  
 Post subject:
PostPosted: March 9th, 2010, 8:33 pm 
Offline

Joined: May 14th, 2009, 12:43 pm
Posts: 57
Location: UK
ah, well you wont need this script then ;)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 7th, 2010, 10:52 pm 
Offline

Joined: September 7th, 2010, 10:16 pm
Posts: 3
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


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 9th, 2010, 3:58 pm 
Offline

Joined: April 22nd, 2007, 6:33 pm
Posts: 1833
Using the gdi+ library you can get all the pixels on the screen 3 times faster than the top code:

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


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: Google Feedfetcher, notsoobvious and 19 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group