Search code examples
backgroundautoit

AutoIt PixelGetColor from background/inactive/unfocused window


PixelGetColor has an optional parameter hwnd (handle of window the pixel is read from). Therefore I assume it is possible to read from unfocused windows (i.e not minimized, but behind another window); but I can't get it to work like that.

  • Is my assumption wrong? If not, how would this be done? If so;

    • why the hwnd parameter?
    • is there another method involving pixel recognition?

Solution

  • Abstract

    You want to create a simple empty bitmap and transfer the DeviceContext content of the hidden window into it. Then you can read any value at any position.

    Creating an empty bitmap

    Includes

    We will need to include the WinAPI definitions and constants.

    #include <WinAPI.au3>
    #include <WindowsConstants.au3>
    

    And that's about it.

    Initial struct

    Now we have to create a new DLL struct from the $tagBITMAPINFO template. We need to fill the struct with the bitmaps parameters. To ensure compatibility across all AutoIt version, I will access the struct items by index.

    1) Create compatible Device Context:

    Local $hCompDC = _WinAPI_CreateCompatibleDC(0)
    

    2) Create struct from template, fill with data:

    Local $tBMI = DllStructCreate($tagBITMAPINFO)
    DllStructSetData($tBMI, 1, DllStructGetSize($tBMI) - 4) ; size of struct
    DllStructSetData($tBMI, 2, 400) ; width
    DllStructSetData($tBMI, 3, 400) ; height
    DllStructSetData($tBMI, 4, 1)
    DllStructSetData($tBMI, 5, 32)  ; bits per pixel
    

    You need to adjust the width and height parameters to match the region you want to transfer. In your case, I guess the size of the window would be a good choice, though the smaller the region, the faster.

    3) Create GDI Object

    Create a CreateDIBSection and save the important variables (object handle and struct pointer):

    $aDIB = DllCall('gdi32.dll', 'ptr', 'CreateDIBSection', 'hwnd', 0, 'ptr', DllStructGetPtr($tBMI), 'uint', 0, 'ptr*', 0, 'ptr', 0, 'uint', 0)
    $hGDIObj = $aDIB[0]
    $hPtr = $aDIB[4]
    

    4) Activate

    Select the object to use it:

    _WinAPI_SelectObject($hCompDC, $hGDIObj)
    

    5) Create a pixel-map

    This is a dword-array of color values. Replace 160000 with width*height of your region:

    $hPixelStruct = DllStructCreate("dword[160000]", $hPtr)
    

    Capture window

    Now we need to transfer the hidden window DeviceContext into our "virtual" context, but first the DC of the target (I'm using Paint as an example):

    $hWnd = WinGetHandle("Paint")
    $hWndDC = _WinAPI_GetDC($hWnd)
    

    Let's transfer the DC into our DC, using PrintWindow:

    Local $iX = 20 ; x coord of pixel in window DC (incl. title bar)
    Local $iY = 20 ; y coord
    DllCall("User32.dll", "int", "PrintWindow", "hwnd", $hWnd, "ptr", $hCompDC, "int", 0)
    

    Reading single pixels

    Since $hPixelStruct is a continuous stream of values, we have to do a little bit of math to point at the right pixel:

    $iColor = MsgBox(0, "Color", '0x' & Hex(DllStructGetData($hPixelStruct, 1, $iY * 400 + $iX + 1), 6))
    

    Cleaning up

    Lastly, destroy the resources:

    _WinAPI_ReleaseDC(0, $hCompDC)
    _WinAPI_ReleaseDC($hWnd, $hWndDC)
    

    Results

    Works perfectly. Though this is only valid for hidden windows, NOT minimized, as minimized windows do not have any drawn context.

    screenshot

    Script

    Here's a Gist containing the script: minxomat/readcolor.au3