Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[func]RegionGetColor - Average color a window - v3.8 +MCode!


  • Please log in to reply
15 replies to this topic
infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008
RegionGetColor() function gets the average color of a portion of the screen. RegionWaitColor() function waits either until the average color of a region matches a value, or until the region's current color changes. Included are some generally useful color functions such as InvertColor() and withinVariation()

Fixed! It now supports getting the average color of something not visible on the screen, i.e. if the window is overlapped. To do this pass the hwnd of the window (or control) to be checked. But it still doesn't (and most likely won't ever) work with minimized windows.

Machine code, WHOOOOO!! Added machine code to do the most time consuming and processor-intensive process: summing the pixels color. It now has OVER 3400% PERFORMANCE BOOST for large areas.

Using the AvgBitmap() func, you can get the average color of any bitmap in memory.

{Code}
;region ;Functions; ######################################################################
regionGetColor(x, y, w, h, hwnd=0) {
; created by Infogulch - thanks to Titan for much of this
; x, y, w, h  ~  coordinates of the region to average
; hwnd        ~  handle to the window that coords refers to
   DllCall("QueryPerformanceCounter", "Int64 *", Start1)
   If !hwnd, hdc := GetDC( hwnd )
      hcdc := hdc
   Else
   {
      WinGetPos, , , hwndW, hwndH, ahk_id %hwnd%
      hcdc := CreateCompatibleDC( hdc )
      , hbmp := CreateCompatibleBitmap( hdc, hwndW, hwndH )
      , hobj := SelectObject( hcdc, hbmp )
      , PrintWindow( hwnd, hcdc, 0 )
   }
   memdc := CreateCompatibleDC( hdc )
   , membmp := CreateCompatibleBitmap( hdc, w, h )
   , memobj := SelectObject( memdc, membmp )
   , BitBlt( memdc, 0, 0, w, h, hcdc, x, y, 0xCC0020 )
   , fmtI := A_FormatInteger
   SetFormat,    Integer, Hex
   retval := AvgBitmap(membmp, w * h) + 0
   SetFormat,    Integer, %fmtI%
   
   If hbmp
      DeleteObject(hbmp), SelectObject(hcdc, hobj), DeleteDC(hcdc)
   DeleteObject(membmp), SelectObject(memdc, memobj), DeleteDC(memdc)
   , ReleaseDC(hwnd, hdc)
   , DllCall("QueryPerformanceCounter", "Int64 *", End1), DllCall("QueryPerformanceFrequency", "Int64 *", f)
   return retval, ErrorLevel := (End1-Start1)/f
}

AvgBitmap(hbmp, pc) {
; by Infogulch
; hbmp  ~  handle to an existing bitmap
; pc    ~  size of the bitmap, should be w * h
; http://msdn.microsoft.com/en-us/library/dd144850(VS.85).aspx
   DllCall("GetBitmapBits", "UInt", hbmp, "UInt", VarSetCapacity(bits, pc*4, 0), "UInt", &bits)
   SumIntBytes(bits, pc, ca, cr, cg, cb)
   return ((Round(cr/pc) & 0xff) << 16) | ((Round(cg/pc) & 0xff) << 8) | (Round(cb/pc) & 0xff)
}

SumIntBytes( ByRef arr, len, ByRef a, ByRef r, ByRef g, ByRef b ) {
; by infogulch
; 32 bit:             16,843,009 px ||       4,104 x       4,104 px screen
; 64 bit: 72,340,172,838,076,673 px || 268,961,285 x 268,961,285 px screen
  static f, i
  if !i
  {
    t =
    (LTrim Join
    558bec81eccc0000005356578dbd34ffffffb933000000b8ccccccccf3abc745
    f800000000eb098b45f883c0018945f88b45f83b450c0f83940000008b45f88b
    4d088b1481c1ea1833c08b4d1003118b490413c88b451089108948048b45f88b
    4d088b1481c1ea1081e2ff00000033c08b4d1403118b490413c88b4514891089
    48048b45f88b4d088b1481c1ea0881e2ff00000033c08b4d1803118b490413c8
    8b451889108948048b45f88b4d088b148181e2ff00000033c08b4d1c03118b49
    0413c88b451c8910894804e957ffffff5f5e5b8be55dc3
    )
    VarSetCapacity(f, tl := StrLen(t)/2), i := 0
    While i < tl
      NumPut("0x" SubStr(t, i*2+1, 2), f, i, "UChar"), i++
  }
    VarSetCapacity(a, 8, 0), VarSetCapacity(r, 8, 0)
  , VarSetCapacity(g, 8, 0), VarSetCapacity(b, 8, 0)
  , DllCall( &f, "UInt", &arr, "UInt", len
    , "UInt", &a, "UInt", &r, "UInt", &g, "UInt", &b
    , "CDecl")
  , a := NumGet(a, 0, "UInt64"), r := NumGet(r, 0, "UInt64")
  , g := NumGet(g, 0, "UInt64"), b := NumGet(b, 0, "UInt64")
}

regionWaitColor(color, X, Y, W, H, hwnd=0, interval=100, timeout=5000, a="", b="", c="") {
   CompareColor := (color != "" ? color : regionGetColor(X, Y, W, H, hwnd))
   Start := A_TickCount
   while !(color ? retVal : !retVal) && !(timeout > 0 ? A_TickCount-Start > timeout : 0) 
   {
      retVal := regionCompareColor( CompareColor, x, y, w, h, hwnd, a, b, c)
      If interval
         Sleep interval
   }
   ErrorLevel := A_TickCount-Start
   Return (color ? retVal : !retVal)
}

regionCompareColor(color, x, y, w, h, hwnd=0, a="", b="", c="") {
   return withinVariation(regionGetColor(x, y, w, h, hwnd), color, a, b, c)
}

withinVariation( x, y, a, b="", c="") { 
; return wether x is within ±A ±B ±C of y
; if a is blank return wether x = y
; if b or c is blank, they are set equal to a
    If (a = "")
        return (x = y)
    v := Variation(x, y)
    return v >> 16 & 0xFF <= a
        && v >> 8  & 0xFF <= (b+0 ? b : a)
        && v       & 0xFF <= (c+0 ? c : a)
}

Variation( x, y ) {
    return Abs((x & 0xFF0000) - (y & 0xFF0000))
         | Abs((x & 0x00FF00) - (y & 0x00FF00))
         | Abs((x & 0x0000FF) - (y & 0x0000FF))
}

invertColor(x, a = "") {
; by Infogulch
; inverts the rgb/bgr hex color passed
; x: color to be inverted
; a: true to invert alpha as well
   return ~x & (a ? 0xFFFFFFFF : 0xFFFFFF)
}

/* Old version, if you want to compare performance
AvgBitmap(hbmp, pc) {
; by Infogulch
; hbmp  ~  handle to an existing bitmap
; pc    ~  size of the bitmap, should be w * h
; http://msdn.microsoft.com/en-us/library/dd144850(VS.85).aspx
   cb := cg := cr := 0
   DllCall("GetBitmapBits", "UInt", hbmp, "UInt", VarSetCapacity(bits, pc*4, 0), "UInt", &bits)
   Loop, % pc
   {
      a := NumGet(bits, A_Index*4-4)
      , cr += a >> 16 & 0xff
      , cg += (a >> 8) & 0xff
      , cb += a & 0xff
   }
   return ((Round(cr/pc) & 0xff) << 16) | ((Round(cg/pc) & 0xff) << 8) | (Round(cb/pc) & 0xff)
}
*/
;end_region

;region ;mfc wrapper;#####################################################################
CreateCompatibleDC(hdc=0) {
   return DllCall("CreateCompatibleDC", "UInt", hdc)
}     

CreateCompatibleBitmap(hdc, w, h) {
   return DllCall("CreateCompatibleBitmap", UInt, hdc, Int, w, Int, h)
}

SelectObject(hdc, hgdiobj) {
   return DllCall("SelectObject", "UInt", hdc, "UInt", hgdiobj)
}

GetDC(hwnd=0) {
   return DllCall("GetDC", "UInt", hwnd)
}

BitBlt( hdc_dest, x1, y1, w1, h1 , hdc_source, x2, y2 , mode ) {
   return DllCall("BitBlt"
          , UInt,hdc_dest   , Int, x1, Int, y1, Int, w1, Int, h1
          , UInt,hdc_source , Int, x2, Int, y2
          , UInt, mode) 
}

DeleteObject(hObject) {
   Return, DllCall("DeleteObject", "UInt", hObject)
}

DeleteDC(hdc) {
   Return, DllCall("DeleteDC", "UInt", hdc)
}

ReleaseDC(hwnd, hdc) {
   Return, DllCall("ReleaseDC", "UInt", hwnd, "UInt", hdc)
}

PrintWindow(hwnd, hdc, Flags=0) {
   return DllCall("PrintWindow", "UInt", hwnd, "UInt", hdc, "UInt", Flags)
}
;end_region

{Demostration}
;region ;AutoExec; #######################################################################
#NoEnv
#SingleInstance, Force
CoordMode, Mouse, Screen
RegionMain:
If !regionInit
{
   OnExit, Exit
   Gui, 1:+AlwaysOnTop +ToolWindow
   Gui, 1:Color, 0xffffff
   Gui, 1:Add, Edit, vGuiTextVar +ReadOnly h160 w180, Color: 0xffffff`nCount: `nTime: `n`n`n`n`n`n`n
   Gui, 1:Show, , regionColor
   Gui, 2:Color, 0xCCCCCC
   Gui, 2:+ToolWindow -Caption +Border +AlwaysOnTop +0x20 ; 0x20=click-thru
   Gui, 2:Add, Text, vGuiTextVar2 w80
   Gui, 2:+LastFound
   2GuiID := WinExist()
   Gui, 2:Show, X-2000 Y-2000 W1 H1
   WinSet, Trans, 150, ahk_id %2GuiID%
;   CoordMode, Mouse, Screen
   Process, Priority,, High
   SetBatchLines, -1
   SetWinDelay, -1
   RegionInit = 1
   GuiX := GuiY := 0
   GuiW := GuiH := 100
}
Gui, 1:Show
return
;end_region

;region ;Labels and Hotkeys; #############################################################
Esc::
Exit:
GuiClose:
   ExitApp

!LButton::
; use gui 2 to create a rectangle for area selection
   If !RegionInit
      GoSub RegionMain
   MouseGetPos, s_MSX, s_MSY, s_ID, s_CID, 2 ;start mouse X and Y
   WinSet, AlwaysOnTop, On, ahk_id %2GuiID%
   Loop
   {
      Sleep 20
      If !GetKeyState("LButton", "P")                  ;break if user releases the mouse
         Break   
      MouseGetPos, c_MSX, c_MSY                     ;current mouse X and Y
      GuiX := (s_MSX < c_MSX ? s_MSX : c_MSX)            ;use whichever smaller for X and Y
      GuiY := (s_MSY < c_MSY ? s_MSY : c_MSY)
      GuiW := Abs(Abs(s_MSX)-Abs(c_MSX))               ;doesn't matter which is bigger,
      GuiH := Abs(Abs(s_MSY)-Abs(c_MSY))               ;the absloute difference will be the same
      WinMove, ahk_id %2GuiID%,, GuiX, GuiY, GuiW, GuiH   ;move the window there
      GuiControl, 2:, GuiTextVar2, % GuiW ", " GuiH
   }
^!+r::               ;to retry at the last used coord.
   WinMove, ahk_id %2GuiID%,, GuiX, GuiY, GuiW, GuiH      ;to see where it's retrying
   Sleep 100
   WinMove, ahk_id %2GuiID%,, -2000,-2000, 2, 2          ;hide the window away
      WinGetPos, WinX, WinY, WinW, WinH, ahk_id %s_ID%
      ControlGetPos, CtrX, CtrY, CtrW, CtrH, , ahk_id %s_CID%
      regionInfo := "Relative to:`n   Screen: " GuiX "," GuiY
      regionInfo .= "`n   Window: " GuiX-WinX "," GuiY-WinY
      regionInfo .= "`n   Control: " GuiX-WinX-CtrX "," Guiy-WinY-CtrY
      regionInfo .= "`nWidth/Height: " GuiW "," GuiH
   Info1 := "RGB:`t"
   Color1 := regionGetColor(GuiX, GuiY, GuiW, GuiH) ;get the color of the region
   Time1 := "Time: " ErrorLevel
   Gui, 1:Color, %Color1%
   GuiControl, , GuiTextVar, % Info1 Color1 "`n`t" Time1 "`n`n" regionInfo
return
;end_region

I initially made it for automating a game, but I could think of other applications, e.g. an average color picker. But why not use ImageSearch for the game?
1. The game had different qualities, and I didn't want to have to have an image for each.
2. A hex color, e.g. 0x113355, is a lot more portable than a bunch of images.

Some links: My Ask for Help thread about this from some time ago.Thanks to:tic for most of the demonstration script
Special thanks to Titan for this new version, as most of the dllcall work was done by him in RegionWaitChange. I created regionWaitColor and regionGetColor with that function in mind.
I must thank ahklerner and Ice_Tea
laszlo for helping me figure out how to get the machine code out of a dll. (and Titan again for reigniting my interest in this topic)

polyethene
  • Members
  • 5519 posts
  • Last active: May 17 2015 06:39 AM
  • Joined: 26 Oct 2012
Good work. regionWaitColor compliments RegionWaitChange, both have practical uses for macroing.

Your method for sampling is random which could be acceptable for some cases but will often produce biased results. To illustrate:

Posted Image Shaded squares represent pixels sampled by your random algorithm.

Posted Image For more consistent result use a method like this one:

SetBatchLines, -1

x = 10
y = 25
w = 60
h = 45
s = .4 ; only sample 40%

dx := w / (xc := w * s), dy := h / (hc := h * s), k := 0xff, cr := cb := cg := cx := 0

Loop, %hc% {
	xi = %x%
	Loop, %xc% {
		PixelGetColor, c, xi += dx, y, ;RGB
		cb += c >> 16, cg += (c >> 8) & k, cr += c & k, cx++
	}
	y += dy
}

cb //= cx, cg //= cx, cr //= cx
cb &= k, cg &= k, cr &= k
MsgBox, Sampled %cx% pixels`, average: rgb(%cr%`, %cg%`, %cb%)
Or for a faster way:
x = 100
y = 250
w = 10
h = 15
s = .4 ; only sample 40%

c := w * h * s, i := w * h / c, j := 0

Loop, %c% {
	xi := x + Mod(j, w), yi := y + j // h, j += i
	MsgBox, Get colour of (%xi%`, %yi%)
}

autohotkey.com/net Site Manager

 

Contact me by email (polyethene at autohotkey.net) or message tidbit


infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008
Updated! it is much faster for larger areas now.

It supports averaging the color of your own bitmap, just pass the handle to the AvgBitmap().

It also supports getting the average color of something not visible on the screen.

:)

nanochip
  • Members
  • 12 posts
  • Last active: Apr 28 2009 03:47 AM
  • Joined: 23 Oct 2006
Thanks for the cool routines infogulch. They work really well.

I haven't been able to make the routines work for windows that are hidden (overlapped) or minimized. I supply the hwnd for the desired window, but I only get the average color for the window that is on top of the desired window.

Is there something else I can try for this case?

thanks.

TheSource
  • Guests
  • Last active:
  • Joined: --
I had trouble training a game wich used lots of colouranimation of the sprites, thanks to this routine I can pinpoint them anyway...

infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008
Thanks guys, I'm glad you like them. :)

Yes, nanochip, there does seem to be a bug with overlapping or minimized windows. I'm researching it now. I've heard reports that it works properly on vista, so it may be an xp only problem. So, unfortunately, the "overlapping / minimized" claim is not valid atm. Hopefully it can be resolved.

I've been wanting to compile the loop in avgBitmap() into machine code so it would finish very quickly no matter what size area you check. I've worked on it a little but have hit a roadblock (i.e. I don't know C :p ) so it probably won't come for some time.

:)

infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008
Updated! Now it works correctly with windows that are overlapped. It will not work with minimized windows.

I hope to compile the loop in AvgBitmap() making it much faster for any size region. If someone wants to help, that would be great! PM me cause I've got some code started.

Thanks for you patience guys.

:D

infogulch
  • Moderators
  • 717 posts
  • Last active: Jul 31 2014 08:27 PM
  • Joined: 27 Mar 2008
I just added MCode for a part of the process and it's now over 3400% faster than before! Yay! Check it out!

Tuncay
  • Members
  • 1945 posts
  • Last active: Feb 08 2015 03:49 PM
  • Joined: 07 Nov 2006
Thx for this function. I am going to add this to my library collection for republishing. I have changed all function names, with adding prefix regionGetColor_, besides regionGetColor() itself. Other than this there is no change, it is to use it safely with the stdlib and to make it compliant.

No signature.


ddk_
  • Guests
  • Last active:
  • Joined: --
This is cool, but some could make a version which tells the most common color instead of showing the avarage 8)

silkcom
  • Members
  • 162 posts
  • Last active: Jul 31 2015 10:57 PM
  • Joined: 23 Jan 2008
I'm having trouble getting this to work using HWND. Can someone give me an example? I have a simple gui that is often below others or off screen some. I just need the HWND of that window and searching regions in the window.

tic
  • Members
  • 1934 posts
  • Last active: May 30 2018 08:13 PM
  • Joined: 22 Apr 2007
Heres code that can be used with the gdi+ library to get the average colour from a bitmap (from the screen or file or whatever) and only uses integers and has one return value. The ARGB can be separated if needed using Gdip_FromARGB

__declspec(dllexport) int Gdip_AverageColour(unsigned char * Scan0, int x, int y, int w, int h, int Stride)
{
    int o, A, R, G, B, tA, tR, tG, tB;
    tA = tR = tG = tB = 0;
    for (int y1 = y; y1 < h; ++y1)
    {
        A = R = G = B = 0;
        for (int x1 = x; x1 < w; ++x1)
        {
            o = (4*x1)+(y1*Stride);
            A += Scan0[3+o];
            R += Scan0[2+o];
            G += Scan0[1+o];
            B += Scan0[o];
        }
        tA += A/w;
        tR += R/w;
        tG += G/w;
        tB += B/w;
    }
    return (tA/h << 24) | (tR/h << 16) | (tG/h << 8) | tB/h;
}


guest3456
  • Guests
  • Last active:
  • Joined: --

I'm having trouble getting this to work using HWND.


i'm having the same problems. its just reverting to the screen instead of the window.. :(

guest3456
  • Guests
  • Last active:
  • Joined: --

I'm having trouble getting this to work using HWND.


i'm having the same problems. its just reverting to the screen instead of the window.. :(



nevermind. heres the similar test code to use the window under mouse when the alt+lbutton drag starts


;region ;AutoExec; #######################################################################
#NoEnv
#SingleInstance, Force

#include RegionGetColor.ahk

CoordMode, Mouse, relative
RegionMain:
If !regionInit
{
   OnExit, Exit
   Gui, 1:+AlwaysOnTop +ToolWindow
   Gui, 1:Color, 0xffffff
   Gui, 1:Add, Edit, vGuiTextVar +ReadOnly h160 w180, Color: 0xffffff`nCount: `nTime: `n`n`n`n`n`n`n
   Gui, 1:Show, , regionColor
   Gui, 2:Color, 0xCCCCCC
   Gui, 2:+ToolWindow -Caption +Border +AlwaysOnTop +0x20 ; 0x20=click-thru
   Gui, 2:Add, Text, vGuiTextVar2 w80
   Gui, 2:+LastFound
   2GuiID := WinExist()
   Gui, 2:Show, X-2000 Y-2000 W1 H1
   WinSet, Trans, 150, ahk_id %2GuiID%
;   CoordMode, Mouse, Screen
   Process, Priority,, High
   SetBatchLines, -1
   SetWinDelay, -1
   RegionInit = 1
   GuiX := GuiY := 0
   GuiW := GuiH := 100
}
Gui, 1:Show
return
;end_region

;region ;Labels and Hotkeys; #############################################################
Esc::
Exit:
GuiClose:
   ExitApp

!LButton::
; use gui 2 to create a rectangle for area selection
   If !RegionInit
      GoSub RegionMain
   MouseGetPos, s_MSX, s_MSY, s_ID, s_CID, 2 ;start mouse X and Y
   ;msgbox, %s_msx% x %s_msy%
   WinSet, AlwaysOnTop, On, ahk_id %2GuiID%
   Loop
   {
      Sleep 20
      If !GetKeyState("LButton", "P")                  ;break if user releases the mouse
         Break   
      MouseGetPos, c_MSX, c_MSY                     ;current mouse X and Y
      tooltip, %c_msx% x %c_msy%
      GuiX := (s_MSX < c_MSX ? s_MSX : c_MSX)            ;use whichever smaller for X and Y
      GuiY := (s_MSY < c_MSY ? s_MSY : c_MSY)
      GuiW := Abs(Abs(s_MSX)-Abs(c_MSX))               ;doesn't matter which is bigger,
      GuiH := Abs(Abs(s_MSY)-Abs(c_MSY))               ;the absloute difference will be the same
      WinGetPos, WinX, WinY, WinW, WinH, ahk_id %s_ID%
      WinMove, ahk_id %2GuiID%,, % WinX+GuiX, % WinY+GuiY, GuiW, GuiH   ;move the window there
      GuiControl, 2:, GuiTextVar2, % "w:" GuiW ", h:" GuiH
   }
^!+r::               ;to retry at the last used coord.
   WinGetPos, WinX, WinY, WinW, WinH, ahk_id %s_ID%
   
   WinMove, ahk_id %2GuiID%,, % WinX+GuiX, % WinY+GuiY, GuiW, GuiH      ;to see where it's retrying
   Sleep 100
   WinMove, ahk_id %2GuiID%,, -2000,-2000, 2, 2          ;hide the window away
      
      WinGetTitle, WinTitle, ahk_id %s_ID%
      ControlGetPos, CtrX, CtrY, CtrW, CtrH, , ahk_id %s_CID%
      regionInfo := "Relative to:`n   Screen: " WinX+GuiX "," WinY+GuiY
      regionInfo .= "`n   Window: " GuiX "," GuiY
      ;regionInfo .= "`n   WinTitle: " . WinTitle
      regionInfo .= "`n   Control: " GuiX-WinX-CtrX "," Guiy-WinY-CtrY
      regionInfo .= "`nWidth/Height: " GuiW "," GuiH
   Info1 := "RGB:`t"
   new1 := regionCompareColor(Color1, GuiX, GuiY, GuiW, GuiH, s_ID) ? "Changed:`tno" : "Changed:`tyes"
   ;Color1 := regionGetColor(GuiX, GuiY, GuiW, GuiH) ;get the color of the region
   Color1 := regionGetColor(GuiX, GuiY, GuiW, GuiH, s_ID) ;get the color of the region
   Time1 := "Time:`t" ErrorLevel
   Gui, 1:Color, %Color1%
   GuiControl, , GuiTextVar, % Info1 Color1 "`n" new1 "`n" Time1 "`n`n" regionInfo
   tooltip,
return


;end_region 



NoobinTraining
  • Guests
  • Last active:
  • Joined: --
im surprized of all this time no one have not notices some bugs in his code...

CreateCompatibleBitmap(hdc, w, h) {
return DllCall("CreateCompatibleBitmap", UInt, hdc, Int, w, Int, w)
}

lol, sorry to pull up this topic, but i can help to notices this as im using it myself for refferrence purpose to make my own code.