Jump to content

Average colour of an area of the display


Melba23
 Share

Recommended Posts

  • Moderators

gil900,

However, the 2 methods produce significantly different answers when I test them:

#include <GUIConstantsEx.au3>
#include <ScreenCapture.au3>
#include <Color.au3>

_GDIPlus_Startup()

$hex_avColor_1 = Screen_Area_Average_Color(10,10,400,400)
ConsoleWrite($hex_avColor_1 & @CRLF)

$hex_avColor_2 = _Area_Average_Colour(10, 10, 10, 400, 400)
ConsoleWrite($hex_avColor_2 & @CRLF)

$hGUI = GUICreate("Test", 500, 500)

$cLabel_1 = GUICtrlCreateLabel("", 0, 0, 500, 250)
GUICtrlSetBkColor($cLabel_1, "0x" & $hex_avColor_1)

$cLabel_2 = GUICtrlCreateLabel("", 0, 250, 500, 250)
GUICtrlSetBkColor($cLabel_2, "0x" & $hex_avColor_2)

GUISetState()

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            Exit
    EndSwitch
WEnd

Func Screen_Area_Average_Color($x_pos,$y_pos,$x_size,$y_size,$bRetunAsHex = 1)
    Local $Output = -1, _
    $hHBmp = _ScreenCapture_Capture("", $x_pos, $y_pos, $x_size, $y_size) ;create a GDI bitmap by capturing full screen of the desktop
    If @error Then Return SetError(1)
    Local $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBmp) ;convert GDI bitmap to GDI+ bitmap
    If Not @error Then
        Local $hBitmap_Scaled = _GDIPlus_ImageResize($hBitmap, 1, 1) ;resize image to 1x1
        If Not @error Then
            $Output = _GDIPlus_BitmapGetPixel($hBitmap_Scaled,0,0) ; Get color of the 1x1 pixel
            If Not @error Then
                If $bRetunAsHex Then $Output = Hex($Output,6)
            EndIf
            _GDIPlus_BitmapDispose($hBitmap_Scaled) ; release $hBitmap_Scaled
        EndIf

         _GDIPlus_BitmapDispose($hBitmap) ; release $hBitmap
    EndIf
    _WinAPI_DeleteObject($hHBmp) ;release GDI bitmap resource because not needed anymore
    If $Output = -1 Then Return SetError(1)
    Return $Output
EndFunc

; #FUNCTION# =========================================================================================================
; Name...........: _Area_Average_Colour
; Description ...: Returns average colour within a defined are of the display
; Syntax ........: _Area_Average_Colour([$iStep [, $iLeft [, $iTop [, $iWidth [, $iHeight ]]]]])
; Parameters ....: $iStep   - Pixel resolution value.  Lower values give better resolution but take longer. (Default = 10)
;                  $iLeft   - X coordinate of top-left corner of area (Default = 0 - left edge of screen)
;                           - If this parameter holds a window HANDLE, the area is set to the client area of the window
;                  $iTop    - Y coordinate of top-left corner of area (Default = 0 - top edge of screen)
;                  $iWidth  - Width of the area (Default = @DesktopWidth)
;                  $iHeight - Height of the area (Default = @DesktopHeight)
; Requirement(s) : v3.3.0.0 or higher
; Return values .: Success - Returns six character string containing RGB hex values
;                  Failure - Returns 0 and sets @error:
;                  1 - Screen Capture failure
;                  2 - GDI function failure
; Author ........: Melba23.   Credit to Malkey for the basic GDI code
; Modified ......:
; Remarks .......:
; Example .......: Yes
;=====================================================================================================================
Func _Area_Average_Colour($iStep = 10, $iLeft = 0, $iTop = 0, $iWidth = @DesktopWidth, $iHeight = @DesktopHeight)

    Local $iBlue = 0, $iGreen = 0, $iRed = 0, $iInterim_Blue = 0, $iInterim_Green = 0, $iInterim_Red = 0, $iInner_Count = 0, $iOuter_Count = 0

    If IsHWnd($iLeft) Then
        $hWnd = $iLeft
        Local $tPoint = DllStructCreate("int X;int Y")
        DllStructSetData($tpoint, "X", 0)
        DllStructSetData($tpoint, "Y", 0)
        _WinAPI_ClientToScreen($hWnd, $tPoint)
        $iLeft = DllStructGetData($tPoint, "X")
        $iTop = DllStructGetData($tPoint, "Y")
        $aSize = WinGetClientSize($hWnd)
        $iWidth =  $aSize[0]
        $iHeight = $aSize[1]
    EndIf

    _GDIPlus_Startup()

    Local $hBMP = _ScreenCapture_Capture("", $iLeft, $iTop, $iLeft + $iWidth, $iTop + $iHeight)
    If $hBMP = 0 Then
        _GDIPlus_Shutdown()
        Return SetError(1, 0, 0)
    EndIf

    Local $hImage = _GDIPlus_BitmapCreateFromHBITMAP($hBMP)
    If $hImage = 0 Then
        _WinAPI_DeleteObject($hBMP) ; <<<<<<<<<<<<<
        _GDIPlus_Shutdown()
        Return SetError(2, 0, 0)
    EndIf

    Local $tRes = _GDIPlus_BitmapLockBits($hImage, 0, 0, $iWidth, $iHeight, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32ARGB)
    If @error Then
        _GDIPlus_BitmapDispose($hImage) ; <<<<<<<<<<<<<
        _WinAPI_DeleteObject($hBMP)     ; <<<<<<<<<<<<<
        _GDIPlus_Shutdown()
        Return SetError(2, 0, 0)
    EndIf

    ;Get the returned values of _GDIPlus_BitmapLockBits()
    Local $iLock_Width  = DllStructGetData($tRes, "width")
    Local $iLock_Height = DllStructGetData($tRes, "height")
    Local $iLock_Stride = DllStructGetData($tRes, "stride")
    Local $iLock_Scan0  = DllStructGetData($tRes, "Scan0")

    ; Run through the BitMap testing pixels at the step distance
    For $i = 0 To $iWidth - 1 Step $iStep
        For $j = 0 To $iHeight - 1 Step $iStep
            Local $v_Buffer = DllStructCreate("dword", $iLock_Scan0 + ($j * $iLock_Stride) + ($i * 4))
            ; Get colour value of pixel
            Local $v_Value = DllStructGetData($v_Buffer, 1)
            ; Add components
            $iBlue  += _ColorGetBlue($v_Value)
            $iGreen += _ColorGetGreen($v_Value)
            $iRed   += _ColorGetRed($v_Value)
            ; Adjust counter
            $iInner_Count += 1
        Next
        ; Determine average value so far - this prevents value becoming too large
        $iInterim_Blue  += $iBlue / $iInner_Count
        $iBlue = 0
        $iInterim_Green += $iGreen / $iInner_Count
        $iGreen = 0
        $iInterim_Red   += $iRed / $iInner_Count
        $iRed = 0
        ; Adjust counters
        $iInner_Count = 0
        $iOuter_Count += 1
    Next
    ; Determine final average
    Local $avBlue = Hex(Int(Round($iInterim_Blue / $iOuter_Count, 0)), 2)
    Local $avGreen = Hex(Int(Round($iInterim_Green / $iOuter_Count, 0)), 2)
    Local $avRed = Hex(Int(Round($iInterim_Red / $iOuter_Count, 0)), 2)

    ; Clear up

    _GDIPlus_BitmapUnlockBits($hImage, $tRes)
    _GDIPlus_BitmapDispose($hImage) ; <<<<<<<<<<<<<
    _WinAPI_DeleteObject($hBMP)     ; <<<<<<<<<<<<<
    _GDIPlus_Shutdown()

    Return ($avRed & $avGreen & $avBlue)

EndFunc   ;==>_Area_Average_Colour

I have no idea which is the more correct - but it is something to note.

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

M23,

I found bug in my code. I used _ScreenCapture_Capture worng in parameters

Quote

 ... $iRight = -1 [, $iBottom = -1

I'm fixing it now.

Edited by Guest
Link to comment
Share on other sites

Try this:

#include <GUIConstantsEx.au3>
#include <ScreenCapture.au3>

_GDIPlus_Startup()

$hex_avColor = Area_Average_Color(10,10,400,400)
ConsoleWrite($hex_avColor& @CRLF)

Func Area_Average_Color($x_pos,$y_pos,$x_size,$y_size,$hWnd = 0,$bRetunAsHex = 1)
    Local $Output = -1,$hHBmp
    If Not $hWnd Then
        $hHBmp = _ScreenCapture_Capture("", $x_pos, $y_pos, $x_pos+$x_size, $y_pos+$y_size,False) ;create a GDI bitmap by capturing full screen of the desktop
    Else
        $hHBmp = _ScreenCapture_CaptureWnd('',$hWnd,$x_pos,$y_pos,$x_pos+$x_size,$y_pos+$y_size,False)
    EndIf
    If @error Then Return SetError(1,0,0)
    Local $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBmp) ;convert GDI bitmap to GDI+ bitmap
    If Not @error Then
        $Output = GetAverageColorFromGDIPlusImage($hBitmap,$bRetunAsHex)
         _GDIPlus_BitmapDispose($hBitmap) ; release $hBitmap
    EndIf

    If $Output = -1 Then Return SetError(1,0,0)
    Return $Output
EndFunc


Func GetAverageColorFromGDIPlusImage($hBitmap,$bRetunAsHex = 1)
    Local $Output = -1, _
    $hBitmap_Scaled = _GDIPlus_ImageResize($hBitmap, 1, 1) ;resize image to 1x1
    If Not @error Then
        $Output = _GDIPlus_BitmapGetPixel($hBitmap_Scaled,0,0) ; Get color of the 1x1 pixel
        If Not @error Then
            If $bRetunAsHex Then $Output = '0x'&Hex($Output,6)
        EndIf
        _GDIPlus_BitmapDispose($hBitmap_Scaled) ; release $hBitmap_Scaled
    EndIf
    If $Output = -1 Then Return SetError(1,0,0)
    Return $Output
EndFunc
Link to comment
Share on other sites

  • Moderators

gil900,

I had already amended the test code I was using in a very similar manner and the results were still significantly different, no doubt because of the different methods of averaging the colour used by the 2 functions. As I said above, I have no idea (or even interest) in which one is more correct - all I wanted to point out was that the 2 functions produce different answers.

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

On 5.2.2016 at 5:15 PM, Melba23 said:

gil900,

I had already amended the test code I was using in a very similar manner and the results were still significantly different, no doubt because of the different methods of averaging the colour used by the 2 functions. As I said above, I have no idea (or even interest) in which one is more correct - all I wanted to point out was that the 2 functions produce different answers.

M23

I have not researched and checked it, but I thought about it logically.
In order to resize image to be smaller - the algorithm must calculate the average color of [X groups of pixels].
(If the target size is 1x1 then X is always 1 - there is only one group of pixels which is all the pixels in the image)

If the average color calculation is too much wrong, then you should see defect/s in the resized image.
So the result of the resize process is an ultimate test for the average color calculation algorithm(that  included in the resize function). 

And _GDIPlus_ImageResize() seems to produce very good looking result of smaller resized image.

Because of this, I think it is right to conclude that the result is very correct and also tested(Tested but not intentionally)

Edited by Guest
Link to comment
Share on other sites

Holy crap thank you guys for this really timely code example :D

I'm working on a project to gather my average screen color every second or so and spit it back to a DMX controller for "surround lighting" via some LED strips.

It's really close and here you guys just saved me a ton of time

Link to comment
Share on other sites

  • Moderators

pcguru000,

That was exactly the reason for the original thread that led to my code - nice to see it being used again.

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

Hi,

with a little help of AssembleIt64(), i wrote an ASM-function to calculate the average colour of the screen.

The function returns the same result as Melba23´s script, (no wonder, i am add up all single color components and devide this result by the sum of all pixel )

It´s a little bit faster than Melba23´s script...a little....(500-600 times).

90% of the runtime of this function takes the BitBlt() because of M$-AERO, this could be dramatically improved by switch off AERO (improvement factor 10!!! ) so the whole function takes only a handful milliseconds.

The script could be easily adapt to calculate parts of the screen or windows/controls, but I leave it to others to post scripts which can be easily copy&pasted :D

;#include <assembleit2_64.au3>

#include <WinAPI.au3>
#include <Memory.au3>

#AutoIt3Wrapper_UseX64=n


#cs _averagepixel
    Use32                            ;32Bit!

    mov edi,dword[esp+4]             ;pointer bitmap
    mov ecx,dword[esp+8]             ;width bitmap
    mov ebx,dword[esp+12]            ;height bitmap


    mov eax,ebx                      ;h
    mul ecx                          ;w*h
    mov ecx,eax                      ;w*h=number of pixel
    movd xmm3,ecx                    ;0,0,0,w*h
    pshufd xmm3,xmm3,0               ;w*h,w*h,w*h,w*h

    sub ecx,1                        ;from 0 to (w*h)-1
    shl ecx,2                        ;w*h*4 bytes

    mov edx,0
    movd xmm1,edx                    ;xmm1 ARGB every pixel
    movd xmm2,edx                    ;xmm2 sumn all A,R,G,B

    ;_asmdbg_()

    @pixel_count:                    ;loop all pixel
    movd xmm0,[edi+ecx]              ;pixel ARGB into xmm
    PUNPCKLBW xmm0,xmm1              ;000000000A0R0G0B
    PUNPCKLBW xmm0,xmm1              ;000A000R000G000B
    PADDD xmm2,xmm0                  ;add all A,R,G,B Integer
    sub ecx,4
    jnz @pixel_count                 ;loop all pixel

    movdqa xmm6,xmm2                 ;save xmm2 A,R,G,B

    PSRLDQ xmm2,8                    ;shift right 8 bytes = 0,0,A,R
    CVTDQ2PD xmm4,xmm2               ;convert int to double
    CVTDQ2PD xmm5,xmm3               ;convert int to double
    DIVPD xmm4,xmm5                  ;divide sum_all_pixel / number_all_pixel
    CVTPD2DQ xmm7,xmm4               ;convert double to integer
    PSLLDQ xmm7,8                    ;shift left 8 bytes = 000A000R00000000
    ;_asmdbg_()

    MOVDQA xmm2,xmm6                 ;restore xmm2 A,R,G,B
    CVTDQ2PD xmm4,xmm2               ;convert to double 0,0,G,B
    CVTDQ2PD xmm5,xmm3               ;convert to double
    DIVPD xmm4,xmm5                  ;divide sum_all_pixel / number_all_pixel
    CVTPD2DQ xmm6,xmm4               ;convert double to integer = 00000000000G000B
    ORPS xmm7,xmm6                   ;xmm7=000A000R000G000B =average colour all pixel

    PACKUSWB xmm7,xmm7               ;
    PACKUSWB xmm7,xmm7               ;ARGBARGBARGBARGB
    movd eax,xmm7                    ;return average ARGB

    ret

#ce



$t = TimerInit()
$average = Screen_Average_Colour()
$m = Int(1000 * TimerDiff($t) / 1000)
MsgBox(0, "Average Colour", "Average colour ARGB of screen = " & $average & @CRLF & "in " & $m & " milliseconds")



Func Screen_Average_Colour()
    ;get desktop width and height
    local $hDLL_User32 = DllOpen("user32.dll")
    local $DesktopWidth = DllCall($hDLL_User32, "int", "GetSystemMetrics", "int", 78) ;sm_virtualwidth
    $DesktopWidth = $DesktopWidth[0]
    local $DesktopHeight = DllCall($hDLL_User32, "int", "GetSystemMetrics", "int", 79) ;sm_virtualheight
    $DesktopHeight = $DesktopHeight[0]

    Local $ptr_bitmap, $hbmp         ;byref _CreateNewBmp32($iwidth, $iheight, ByRef $ptr, ByRef $hbmp)

    $hdc_bitmap = _CreateNewBmp32($DesktopWidth, $DesktopHeight, $ptr_bitmap, $hbmp) ;create empty bitmap
    $hdc_desktop = _WinAPI_GetDC(0)  ;hdc desktop (or window/control if you want)

    _WinAPI_BitBlt($hdc_bitmap, 0, 0, $DesktopWidth, $DesktopHeight, $hdc_desktop, 0, 0, 0xCC0020);bitblt desktop into bitmap

    ;$binarycode = _AssembleIt2("retbinary", "_averagepixel") ;gibt nur den assemblierten code zurück
    $binarycode = "0x8B7C24048B4C24088B5C240C89D8F7E189C1660F6ED9660F70DB0083E901C1E102BA00000000660F6ECA660F6ED2660F6E040F660F60C1660F60C1660FFED083E90475EA660F6FF2660F73DA08F30FE6E2F30FE6EB660F5EE5F20FE6FC660F73FF08660F6FD6F30FE6E2F30FE6EB660F5EE5F20FE6F40F56FE660F67FF660F67FF660F7EF8C3"

    ;nur für dllcalladdress() benötigt, den binarycode braucht man nur ein mal erstellen
    $tCodeBuffer = dllstructcreate("byte[" & StringLen($binarycode) / 2 - 1 & "]") ;reserve Memory for opcodes
    DllStructSetData($tCodeBuffer, 1, $binarycode) ;set asm-code into memory

    ;call asm-code
    $ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "ptr", $ptr_bitmap, "int_ptr", $DesktopWidth, "int_ptr", $DesktopHeight)

    _DeleteBitmap32($hdc_bitmap, $ptr_bitmap, $hbmp) ;Ressourcen freigeben

    Return Hex($ret[0], 8);get average colour
EndFunc                              ;==>Screen_Average_Colour




Func _CreateNewBmp32($iwidth, $iheight, ByRef $ptr, ByRef $hbmp) ;erstellt leere 32-bit-Bitmap; Rückgabe $HDC und $ptr und handle auf die Bitmapdaten
    $hcdc = _WinAPI_CreateCompatibleDC(0) ;Desktop-Kompatiblen DeviceContext erstellen lassen
    $tBMI = DllStructCreate($tagBITMAPINFO) ;Struktur der Bitmapinfo erstellen und Daten eintragen
    DllStructSetData($tBMI, 1, DllStructGetSize($tBMI) - 4);Structgröße abzüglich der Daten für die Palette
    DllStructSetData($tBMI, 2, $iwidth)
    DllStructSetData($tBMI, 3, -$iheight) ;minus =standard = bottomup
    DllStructSetData($tBMI, 4, 1)
    DllStructSetData($tBMI, 5, 32)   ;32 Bit = 4 Bytes => AABBGGRR
    $adib = DllCall('gdi32.dll', 'ptr', 'CreateDIBSection', 'hwnd', 0, 'ptr', DllStructGetPtr($tBMI), 'uint', 0, 'ptr*', 0, 'ptr', 0, 'uint', 0)
    $hbmp = $adib[0]                 ;hbitmap handle auf die Bitmap, auch per GDI+ zu verwenden
    $ptr = $adib[4]                  ;pointer auf den Anfang der Bitmapdaten, vom Assembler verwendet
    _WinAPI_SelectObject($hcdc, $hbmp) ;objekt hbitmap in DC
    Return $hcdc                     ;DC der Bitmap zurückgeben
EndFunc                              ;==>_CreateNewBmp32

Func _DeleteBitmap32($DC, $ptr, $hbmp)
    _WinAPI_DeleteDC($DC)
    _WinAPI_DeleteObject($hbmp)
    $ptr = 0
EndFunc                              ;==>_DeleteBitmap32

 

Link to comment
Share on other sites

I'm not to be able to hold a candle to AndyG but here we go. :baby:

My slower, not optimized / non MMX/SSE, ASM version.

#include <GDIPlus.au3>

$sFile = FileOpenDialog("Select an image", "", "Images (*.jpg;*.bmp;*.png;*.gif;*.tif)")
If @error Then Exit MsgBox(0, "Information", "Nothing selected")

_GDIPlus_Startup()
Global Const $hBitmap = _GDIPlus_BitmapCreateFromFile($sFile)

Global $fTimer = TimerInit()
Global $iResult = ASM_BitmapGetAverageColorValue($hBitmap)
ConsoleWrite(TimerDiff($fTimer) & " ms" & @CRLF)

MsgBox(0, "Test", "Average color value: 0x" & Hex($iResult, 8) & @CRLF)

_GDIPlus_ImageDispose($hBitmap)
_GDIPlus_Shutdown()


Func ASM_BitmapGetAverageColorValue($hBitmap) ;coded by UEZ build 2016-02-08
    Local $aDim = _GDIPlus_ImageGetDimension($hBitmap)
    If @error Then Return SetError(0, 0, 0)
    Local Const $iPixels = $aDim[0] * $aDim[1]
    Local $tBitmapData = _GDIPlus_BitmapLockBits($hBitmap, 0, 0, $aDim[0], $aDim[1], $GDIP_ILMREAD, $GDIP_PXF32ARGB)
    Local $pScan = $tBitmapData.Scan0
    Local $iStride = $tBitmapData.Stride * 4
    Local $tPixelData = DllStructCreate("dword[" & Abs($iStride * $aDim[1]) & "]", $pScan)
    Local $tStruct_ARGB = DllStructCreate("uint Alpha;uint Red;uint Green;uint Blue")

    Local $tCodeBuffer = DllStructCreate("byte ASM[75]")
    $tCodeBuffer.ASM = "0x8B7424048B7C24088B4C240C8B0689C381E3FF000000015F0C89C381E300FF0000C1EB08015F0889C381E30000FF00C1EB10015F0489C381E3000000FFC1EB18011F83C60483E90177C2C3"

    Local $aRet = DllCall("user32.dll", "none", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer), _
                          "ptr", DllStructGetPtr($tPixelData), _
                          "ptr", DllStructGetPtr($tStruct_ARGB), _
                          "uint", $iPixels, _
                          "int", Null)
    _GDIPlus_BitmapUnlockBits($hBitmap, $tBitmapData)
    Local $iResult = 0x1000000 * Int($tStruct_ARGB.Alpha / $iPixels) + 0x10000 * Int($tStruct_ARGB.Red / $iPixels) + 0x100 * Int($tStruct_ARGB.Green / $iPixels) + Int($tStruct_ARGB.Blue / $iPixels)
    Return $iResult
EndFunc

 

For a 3000x30002 image it takes ~200 ms.

 

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

@UEZ, you got my intention (and show it with your script ) how to use ASM to speed up AutoIt-Scripts dramatically.
After searching/finding the "inner loop", write a really short, simple, and easy to understand ASM-code with the usage of only a handful ASM-opcodes...

The rest should be done with Autoit. Very nice work, indeed!

Btw. it´s absolutely unimportant and it does not matter how long the execution of the ASM-code takes! I don´t use BitmapLockBits() because of the gruesome (or grisely? what would a native english speaker choose?) runtime of this function...
If the ASM-code is 500 to 1000 times faster than a loop in AutoIt, there is no need to discuss about some milliseconds. Therefore...thank you for reminding me my "intention" using ASM

Link to comment
Share on other sites

Link to comment
Share on other sites

@LarsJ, you could copy the "code" and insert it here...B) It´s an online disassembler, the right thing for such a piece of code...

 

//EDIT

He adds every A,R,G,B Colour component into the  via

DllStructCreate("uint Alpha;uint Red;uint Green;uint Blue")

created memory. So after the ASM-code he has to divide those sum by the number of pixel.

 

In my code, I did this too, but because of the need of precision, i store the sum of each component into a 64-bit-quadword. Then, because of the precision of division, these integer-quadwords have to transform into double-floatingpoint, then divide, and after that, transform the double into an 8-bit-integer....

UEZ´s version is so much easier....he did the "calculation" with AutoIt. But as mentioned above, nobody needs the complete function running within 3 milliseconds...i prefer his version:D

 

Edited by AndyG
Link to comment
Share on other sites

I prefer complete code from UEZ with lots of comments, optimization hints, hints about porting to 64 bit, and that kind of things as he usually adds.

Link to comment
Share on other sites

Here my version using Assembleit.

If you insert more 

_asmdbg_()

-lines into ASM-code, the debugger shows you how the code works. If you have the "Debugger Window" on the left side of the screen and the scite-Window on the right side, every time you will hit onto the "Next"-Button, in Scite window the line with the actual _asmdbg() will be marked...so you can step through your code

AssembleIt64.zip

#include <assembleit2_64.au3>
#include <WinAPI.au3>

#AutoIt3Wrapper_UseX64=n


#cs _averagepixel                        ;Pixel ((A+B+C+D)/4)
    Use32                                ;32Bit!

    mov edi,dword[esp+4]                 ;pointer bitmap
    mov ecx,dword[esp+8]                 ;width bitmap
    mov ebx,dword[esp+12]                ;height bitmap


    mov eax,ebx                          ;h
    mul ecx                              ;w*h
    mov ecx,eax                          ;w*h=number of pixel
    movd xmm3,ecx                        ;0,0,0,w*h
    pshufd xmm3,xmm3,0                   ;w*h,w*h,w*h,w*h

    sub ecx,1                            ;from 0 to (w*h)-1
    shl ecx,2                            ;w*h*4 bytes

    mov edx,0
    movd xmm1,edx                        ;xmm1 ARGB every pixel
    movd xmm2,edx                        ;xmm2 sumn all A,R,G,B

    _asmdbg_()

    @pixel_count:                        ;all pixel

    movd xmm0,[edi+ecx]                  ;pixel ARGB into xmm
    PUNPCKLBW xmm0,xmm1                  ;000000000A0R0G0B
    PUNPCKLBW xmm0,xmm1                  ;000A000R000G000B
    PADDD xmm2,xmm0                      ;add all A,R,G,B

    sub ecx,4
    jnz @pixel_count                     ;loop all pixel

    _asmdbg_()

    movdqa xmm6,xmm2                     ;save xmm2

    PSRLDQ xmm2,8                        ;shift right 8 bytes = 0,0,A,R
    CVTDQ2PD xmm4,xmm2                   ;convert to double
    CVTDQ2PD xmm5,xmm3                   ;convert to double
    DIVPD xmm4,xmm5                      ;divide sum_all_pixel / number_all_pixel
    CVTPD2DQ xmm7,xmm4                   ;convert double to integer
    PSLLDQ xmm7,8                        ;shift left 8 bytes = 000A000R00000000

   _asmdbg_()

    MOVDQA xmm2,xmm6                     ;restore xmm2 A,R,G,B
    CVTDQ2PD xmm4,xmm2                   ;convert to double 0,0,G,B
    CVTDQ2PD xmm5,xmm3                   ;convert to double
    DIVPD xmm4,xmm5                      ;divide sum_all_pixel / number_all_pixel
    CVTPD2DQ xmm6,xmm4                   ;convert double to integer = 00000000000G000B
    ORPS xmm7,xmm6                       ;xmm7=000A000R000G000B =average color all pixel

    PACKUSWB xmm7,xmm7                   ;
    PACKUSWB xmm7,xmm7                   ;ARGBARGBARGBARGB
    movd eax,xmm7                        ;return average ARGB

    ret

#ce



Global $hDLL_User32 = DllOpen("user32.dll")
$DesktopWidth = DllCall($hDLL_User32, "int", "GetSystemMetrics", "int", 78) ;sm_virtualwidth
$DesktopWidth = $DesktopWidth[0]
$DesktopHeight = DllCall($hDLL_User32, "int", "GetSystemMetrics", "int", 79) ;sm_virtualheight
$DesktopHeight = $DesktopHeight[0]

Global $ptr_bitmap, $hbmp                ;byref _CreateNewBmp32($iwidth, $iheight, ByRef $ptr, ByRef $hbmp)

$hdc_bitmap = _CreateNewBmp32($DesktopWidth, $DesktopHeight, $ptr_bitmap, $hbmp) ;hdc empty bitmap
$hdc_desktop = _WinAPI_GetDC(0)          ;hdc desktop

_WinAPI_BitBlt($hdc_bitmap, 0, 0, $DesktopWidth, $DesktopHeight, $hdc_desktop, 0, 0, $SRCCOPY);bitblt desktop into bitmap

;#############################################################################################
;if you need the assembled code, uncomment the following lines and use DllCallAddress()
;~ $binarycode = _AssembleIt2("retbinary", "_averagepixel") ;gibt nur den assemblierten code zurück
;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $binarycode = ' & $binarycode & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $binarycode = ' & StringLen($binarycode) & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
;nur für dllcalladdress() benötigt, den binarycode braucht man nur ein mal erstellen
;~ $tCodeBuffer = _dllstructcreate64("byte[" & StringLen($binarycode) / 2 - 1 & "]") ;reserve Memory for opcodes
;~ DllStructSetData($tCodeBuffer, 1, $binarycode)
;~     $ret = DllCallAddress("uint:cdecl", DllStructGetPtr($tCodeBuffer), "ptr", $ptr_bitmap, "int_ptr", $DesktopWidth, "int_ptr", $DesktopHeight)
;~     ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret[0] & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
;~     $ret = $ret[0]
;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret & @CRLF & '>Error code: ' & @error & @CRLF) ;### Debug Console
;#############################################################################################


$ret = _AssembleIt2("uint", "_averagepixel", "ptr", $ptr_bitmap, "int_ptr", $DesktopWidth, "int_ptr", $DesktopHeight)
$ret=hex($ret,8)
MsgBox(262144, 'Debug line ~' & @ScriptLineNumber, 'Selection:' & @CRLF & '$ret' & @CRLF & @CRLF & 'Return:' & @CRLF & $ret) ;### Debug MSGBOX


_DeleteBitmap32($hdc_bitmap, $ptr_bitmap, $hbmp) ;Ressourcen freigeben





Func _CreateNewBmp32($iwidth, $iheight, ByRef $ptr, ByRef $hbmp) ;erstellt leere 32-bit-Bitmap; Rückgabe $HDC und $ptr und handle auf die Bitmapdaten
    $hcdc = _WinAPI_CreateCompatibleDC(0) ;Desktop-Kompatiblen DeviceContext erstellen lassen
    $tBMI = DllStructCreate($tagBITMAPINFO) ;Struktur der Bitmapinfo erstellen und Daten eintragen
    DllStructSetData($tBMI, 1, DllStructGetSize($tBMI) - 4);Structgröße abzüglich der Daten für die Palette
    DllStructSetData($tBMI, 2, $iwidth)
    DllStructSetData($tBMI, 3, -$iheight) ;minus =standard = bottomup
    DllStructSetData($tBMI, 4, 1)
    DllStructSetData($tBMI, 5, 32)       ;32 Bit = 4 Bytes => AABBGGRR
    $adib = DllCall('gdi32.dll', 'ptr', 'CreateDIBSection', 'hwnd', 0, 'ptr', DllStructGetPtr($tBMI), 'uint', 0, 'ptr*', 0, 'ptr', 0, 'uint', 0)
    $hbmp = $adib[0]                     ;hbitmap handle auf die Bitmap, auch per GDI+ zu verwenden
    $ptr = $adib[4]                      ;pointer auf den Anfang der Bitmapdaten, vom Assembler verwendet
    _WinAPI_SelectObject($hcdc, $hbmp)   ;objekt hbitmap in DC
    Return $hcdc                         ;DC der Bitmap zurückgeben
EndFunc                                  ;==>_CreateNewBmp32

Func _DeleteBitmap32($DC, $ptr, $hbmp)
    _WinAPI_DeleteDC($DC)
    _WinAPI_DeleteObject($hbmp)
    $ptr = 0
EndFunc                                  ;==>_DeleteBitmap

 

Edited by AndyG
Link to comment
Share on other sites

@LarsJ I wanted to see who will be interested in the code and ask for it and as Andy said I used a very simple ASM version for the summation:

Func _ASM_BitmapCountARGBColors()
    _("use32") ;32Bit!
    _("mov esi, dword[esp+4]") ;points to beginning of the bitmap in the memory
    _("mov edi, dword[esp+8]") ;points to the beginning of the struct in the memory
    _("mov ecx, dword[esp+12]") ;amount of pixels to read (width * height)
    _("_loop1:")
        _("mov dword eax, [esi]") ;copy the current AARRGGBB value to eax

        _("mov ebx, eax")   ;make a copy of eax for "faster" access
        _("and ebx, 0x000000FF") ;mask the blue channel
        _("add dword[edi + 12], ebx") ;add blue value which is saved in the struct -> each struct value is 4 bytes long and blue starts at memory + 12

        _("mov dword ebx, eax") 
        _("and ebx, 0x0000FF00")    ;now the same with green channel
        _("shr ebx, 8")             ;shift the green value to the right -> 0000GG00 - > 000000GG
        _("add dword[edi + 8], ebx") ;add green value

        _("mov dword ebx, eax")
        _("and ebx, 0x00FF0000") ;and red channel
        _("shr ebx, 16")        ;-> 00RR0000 - > 000000RR
        _("add dword[edi + 4], ebx") ;add red value

        _("mov dword ebx, eax")
        _("and ebx, 0xFF000000")    ;and finally with alpha channel
        _("shr ebx, 24")            ;-> AA000000 - > 000000AA
        _("add dword[edi], ebx") ;add alpha value

        _("add esi, 4") ;get next color word from bitmap
        _("sub ecx, 1") ;decrease pixel count
        _("ja _loop1")  ;if pixel count = zero then exit loop
    _("ret") ;finished
EndFunc

The code is straightforward and doesn't need much comments. If you have any question then please ask.

I will try tomorrow to implement it as x64.

 

Well Andy, if I were able to code how you code in ASM I would do it regardless whether it makes sense for a particular case. My ASM code is the result being a ASM rookie.:baby:

Edited by UEZ
added more comments to the ASM rookie code

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...