Sign in to follow this  
Followers 0
nekkutta

[semi-Solved] Need help with DllCall() GDI+ function

6 posts in this topic

#1 ·  Posted (edited)

I'm trying to figure out how to get the average RGB of a jpg file, I've already loaded the GDI+ UDF to get the X x Y of the file is there anyway to get something like PixelGetColor without actually displaying the image?

EDIT: partially solved, func works: returns a 4 element array for the alpha, red, green, blue decimal values

Edit #2: ok, I think I am going to scrap this, while it may work for a 4bpp bmp file, it totally blunders with a jpeg file that has alpha blending, may be able to figure out a math format to construe the REAL RGB from the returned values by setting the level of the BG color( more than likely 0xFFFFFF ) off of the alpha channel but I haven't really figured out how one goes about getting the BG color of an image when loaded in a buffer. If anyone could tell me how you find the BG color, I'll probably give it a whirl. This is driving me absolutely nuts. For what its worth, if bitwise operations are wanted, or the full hex, $str can be "dword argb"

microsoft can't make anything simple.{/rant}

Thanks!

Edited by nekkutta

[size="2"] "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian Kernighan[/size]

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

OK, since I can't Edit, my previous post, I was digging around in the MSDN and I found this:

GpStatus WINGDIPAPI GdipBitmapGetPixel(GpBitmap* bitmap, INT x, INT y, ARGB *color) Bitmap::GetPixel    The GetPixel method gets the color of a specified pixel in this bitmap.

so from what I can tell I'm gonna have to pass this through autoit like so: (copied from the GDI+ Include)

Func _GDIPlus_BitmapGetPixel($hBmp, $iX, $iY)
    Local $aResult

    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY)
    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult
EndFunc  ;==>_GDIPlus_BitmapGetPixel

Someone want to take a quick look at this and let me know if it's correct or have I got something wrong...

Edited by nekkutta

[size="2"] "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian Kernighan[/size]

Share this post


Link to post
Share on other sites

I'm trying to figure out how to get the average RGB of a jpg file, I've already loaded the GDI+ UDF to get the X x Y of the file is there anyway to get something like PixelGetColor without actually displaying the image?

OK, since I can't Edit, my previous post, I was digging around in the MSDN and I found this:

GpStatus WINGDIPAPI GdipBitmapGetPixel(GpBitmap* bitmap, INT x, INT y, ARGB *color) Bitmap::GetPixel    The GetPixel method gets the color of a specified pixel in this bitmap.

so from what I can tell I'm gonna have to pass this through autoit like so: (copied from the GDI+ Include)

Func _GDIPlus_BitmapGetPixel($hBmp, $iX, $iY)
    Local $aResult

    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY)
    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult
EndFunc  ;==>_GDIPlus_BitmapGetPixel

Someone want to take a quick look at this and let me know if it's correct or have I got something wrong...

About your DllCall, I searched the forums for GdipBitmapGetPixel. The twenty odd "finds" all showed an extra last parameter for the colour, ARGB.

Using GetPixel or GdipBitmapGetPixel is a valid method to obtain the pixel colour values to calculate the average colour of an image. But, I believe using _GDIPlus_BitmapLockBits is a faster method to obtain all pixel values.

I assume the way to get the average colour is to add the individual colour channels of the pixels together. Divide the total of each channel by the number of pixels. Then, join the resulting averages of each channel together giving an average colour.

This example does this to the four channels of each pixel. RGB, (Red, Green, Blue) and A, (Alpha) channels. Output is to the console.

This is just one of many ways to do it.

;

#include <WinAPI.au3>
#include <GuiConstantsEx.au3>
#include <GDIPlus.au3>
#include <ScreenCapture.au3>
#include <Misc.au3>

; Modified from:-
; http://www.autoitscript.com/forum/index.php?s=&showtopic=94738&view=findpost&p=681193
Opt('MustDeclareVars', 1)

Global $width, $height, $hGUI1, $hImage, $hGraphic1, $hBitmap1
Global $iX, $iY, $sAllPixels, $GuiX = 320, $GuiY = 240, $init, $sStr

; Search colours in hex BBGGRRAA format are white, black,      yellow,         blue, and       red in order.
Global $aSearchCols[5][2] = [["FFFFFFFF", 0],["000000FF", 0],["00FFFFFF", 0],["FF0000FF", 0],["0000FFFF", 0]]

_Main()

Func _Main()
    ; Capture top left corner of the screen
    _ScreenCapture_Capture(@MyDocumentsDir & "\GDIPlus_Image.png", 0, 0, $GuiX, $GuiY)
    _GDIPlus_Startup()
    ;$hImage = _GDIPlus_ImageLoadFromFile( @MyDocumentsDir & "\GDIPlus_Image.png") ; OR next line
    $hImage = _GDIPlus_ImageLoadFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\Blue.bmp")
    $iX = _GDIPlus_ImageGetWidth($hImage)
    $iY = _GDIPlus_ImageGetHeight($hImage)
    $GuiX = $iX
    $GuiY = $iY
    ; Create a GUI for the original image
    $hGUI1 = GUICreate("Original", $GuiX, $GuiY, 0, 0)
    GUISetState()
    GUIRegisterMsg(0xF, "MY_PAINT"); Register PAINT-Event 0x000F = $WM_PAINT (WindowsConstants.au3)
    ; Initialize GDI+ library and load image


    ; Draw original image
    $hGraphic1 = _GDIPlus_GraphicsCreateFromHWND($hGUI1)
    _GDIPlus_GraphicsDrawImage($hGraphic1, $hImage, 0, 0)

    $init = TimerInit()

    ; All pixels of image are in variable, $sAllPixels, inthe format:-
    ; BBGGRRAA BBGGRRAA BBGGRRAA ...
    $sAllPixels = _ImageGetPixels($hImage)
    ;LocalConsoleWrite("$sAllPixels " & $sAllPixels & @CRLF)
    Local $TimeDiff = TimerDiff($init)
    ConsoleWrite("Time taken with _ImageGetPixels (All Pixels)" & $GuiX & "x" & $GuiY & @CRLF & "100%: " & _
            $TimeDiff & @CRLF & "Average (Time per pixel): " & $TimeDiff / ($GuiX * $GuiY) & @CRLF & @CRLF)

    $init = TimerInit()

    ; Find specific colours and the number of times each colour is occurring in image
    Local $sStr = ""
    For $x = 0 To UBound($aSearchCols) - 1
        StringReplace($sAllPixels, $aSearchCols[$x][0], $aSearchCols[$x][0])
        $aSearchCols[$x][1] = @extended
        $sStr &= "0x" & $aSearchCols[$x][0] & " appears " & $aSearchCols[$x][1] & " times " & @CRLF
    Next

    ; Finds the RGB colour at X, Y position. This example $x,$y position are desktop coordinates.
    Local $x = Int($iX * 3 / 4), $y = Int($iY / 2), $col, $ColRGB
    $col = StringMid($sAllPixels, ($y * $GuiX + $x) * 9 + 1, 8);
    $ColRGB = "0x" & StringRegExpReplace($col, "(.{2})(.{2})(.{2})(.{2})", "\3\2\1")
    ;ConsoleWrite($x & ", " & $y & " $col (0xRRGGBB format) = " & $ColRGB &  @CRLF)

    ;Average Colour
    Local $sREBlue = Hex(Execute(StringTrimRight(StringRegExpReplace($sAllPixels & " ", "(.{2})(.{2})(.{2})(.{2}) ", '0x\1 + '), 3)) / ($iX * $iY), 2)
    Local $sREGreen = Hex(Execute(StringTrimRight(StringRegExpReplace($sAllPixels & " ", "(.{2})(.{2})(.{2})(.{2}) ", '0x\2 + '), 3)) / ($iX * $iY), 2)
    Local $sRERed = Hex(Execute(StringTrimRight(StringRegExpReplace($sAllPixels & " ", "(.{2})(.{2})(.{2})(.{2}) ", '0x\3 + '), 3)) / ($iX * $iY), 2)
    Local $sREAlpha = Hex(Execute(StringTrimRight(StringRegExpReplace($sAllPixels & " ", "(.{2})(.{2})(.{2})(.{2}) ", '0x\4 + '), 3)) / ($iX * $iY), 2)


    ConsoleWrite("Image size (Width x Height) = " & $GuiX & " x " & $GuiY & " = " & $GuiX * $GuiY & " pixels" & @CRLF & _
            "Time taken getting all pixel info. : " & TimerDiff($init) & @CRLF & _
            "coordinates (x, y) " & $x & ", " & $y & " $col (0xRRGGBB format)  = " & $ColRGB & @CRLF & @CRLF & _
            " The following colours are in 0xBBGGRRAA format :- " & @CRLF & $sStr & @CRLF & _
            "Average colour in image (0xAARRGGBB format) = " & "0x" & $sREAlpha & $sRERed & $sREGreen & $sREBlue & @CRLF)

    ; Loop until user exits
    Do

    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    ; Release resources
    _WinAPI_DeleteObject($hBitmap1)
    _GDIPlus_GraphicsDispose($hGraphic1)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_Shutdown()
EndFunc   ;==>_Main

Func _ImageGetPixels($hImage)
    Local $Reslt, $stride, $format, $Scan0, $iIW, $iIH, $hBitmap1
    Local $v_BufferA, $AllPixels, $sREResult1, $sResult

    $iIW = _GDIPlus_ImageGetWidth($hImage)
    $iIH = _GDIPlus_ImageGetHeight($hImage)

    $hBitmap1 = _GDIPlus_BitmapCloneArea($hImage, 0, 0, $iIW, $iIH, $GDIP_PXF32ARGB)

    ; Locks a portion of a bitmap for reading or writing
    $Reslt = _GDIPlus_BitmapLockBits($hBitmap1, 0, 0, $iIW, $iIH, $GDIP_ILMREAD, $GDIP_PXF32ARGB)
    If @error Then MsgBox(0, "", "_GDIPlus_BitmapLockBits Error")
    ;Get the returned values of _GDIPlus_BitmapLockBits ()
    $width = DllStructGetData($Reslt, "Width")
    $height = DllStructGetData($Reslt, "Height")
    $stride = DllStructGetData($Reslt, "Stride")
    $format = DllStructGetData($Reslt, "Format")
    $Scan0 = DllStructGetData($Reslt, "Scan0")

    $v_BufferA = DllStructCreate("byte[" & $height * $width * 4 & "]", $Scan0) ; Create DLL structure for all pixels
    $AllPixels = DllStructGetData($v_BufferA, 1)
    ;ConsoleWrite("$AllPixels, raw data, first 9 colours = " & StringRegExpReplace($AllPixels, "(.{98})(.*)", "\1") & @CRLF)

    ; Searches on this string - $sREResult1 whch has the prefix "0x"  removed, and a space put between pixels 8 characters long.
    $sREResult1 = StringRegExpReplace(StringTrimLeft($AllPixels, 2), "(.{8})", "\1 ")
    ;ConsoleWrite("$AllPixels, raw data, first 9 colours = " & StringRegExpReplace($sREResult1, "(.{98})(.*)", "\1") & @CRLF)

    _GDIPlus_BitmapUnlockBits($hBitmap1, $Reslt) ; releases the locked region
    Return $sREResult1
EndFunc   ;==>_ImageGetPixels

Func MY_PAINT($hWnd, $msg, $wParam, $lParam)
    If $hWnd = $hGUI1 Then _GDIPlus_GraphicsDrawImageRect($hGraphic1, $hImage, 0, 0, $width, $height)
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MY_PAINT
;

Share this post


Link to post
Share on other sites

I figured out the color ARGB, and I finally got it to work, although I'm getting quite a bit of negative values for the red and blue channels, not too concerned with the alpha channel.

Turns out I had to _GDIPlus_BitmapLockBits in a read mode to get the _GDIPlus_BitmapGetPixel to return anything (duh) so for what its worth, here is my _GDIPlus_BitmapGetPixel()

Func _GDIPlus_BitmapGetPixel($hBitmap, $iX, $iY)
    #cs
    Color(
        BYTE a,
        BYTE r,
        BYTE g,
        BYTE b
        )
    #ce
    $str = "byte a;byte r;byte g;byte b"
    $color = DllStructCreate($str)
    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, 'ptr', DllStructGetPtr($color))
    If @error Then
        MsgBox( 0 , 'error', @error & ' ' & @extended)
        Return SetError(@error, @extended, 0)
    EndIf
    $a = DllStructGetData($color, 'a')
    $r = DllStructGetData($color, 'r')
    $g = DllStructGetData($color, 'g')
    $b = DllStructGetData($color, 'b')
    Local $retValues[4] = [$a , $r , $g , $b]
    ;MsgBox(0,'dllstructgetdata', 'alpha: ' & $a & ' red: ' & $r & @CRLF & 'green: ' & $g & ' blue: ' & $B)
    Return $retValues
EndFunc   ;==>_GDIPlus_BitmapGetPixel

I know its pretty rough, and not really commented, but the general idea is there. Maybe when the next version of the GDI+ udf comes out, someone will have taken this and actually made it pretty.

If anyone has any ideas on the common occurance of negative values in the red and blue channels, I'm pretty sure it has something to do with the pixel format of the bitmap, maybe next I will create a GDI+ bitmap format conversion function, although looking at the function at the msdn, it'll be a lot more of a headache than this was :)

Thanks


[size="2"] "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian Kernighan[/size]

Share this post


Link to post
Share on other sites

I figured out the color ARGB, and I finally got it to work, although I'm getting quite a bit of negative values for the red and blue channels, not too concerned with the alpha channel.

Turns out I had to _GDIPlus_BitmapLockBits in a read mode to get the _GDIPlus_BitmapGetPixel to return anything (duh) so for what its worth, here is my _GDIPlus_BitmapGetPixel()

Func _GDIPlus_BitmapGetPixel($hBitmap, $iX, $iY)
    #cs
    Color(
        BYTE a,
        BYTE r,
        BYTE g,
        BYTE b
        )
    #ce
    $str = "byte a;byte r;byte g;byte b"
    $color = DllStructCreate($str)
    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, 'ptr', DllStructGetPtr($color))
    If @error Then
        MsgBox( 0 , 'error', @error & ' ' & @extended)
        Return SetError(@error, @extended, 0)
    EndIf
    $a = DllStructGetData($color, 'a')
    $r = DllStructGetData($color, 'r')
    $g = DllStructGetData($color, 'g')
    $b = DllStructGetData($color, 'b')
    Local $retValues[4] = [$a , $r , $g , $b]
    ;MsgBox(0,'dllstructgetdata', 'alpha: ' & $a & ' red: ' & $r & @CRLF & 'green: ' & $g & ' blue: ' & $ B) 
    Return $retValues
EndFunc   ;==>_GDIPlus_BitmapGetPixel

I know its pretty rough, and not really commented, but the general idea is there. Maybe when the next version of the GDI+ udf comes out, someone will have taken this and actually made it pretty.

If anyone has any ideas on the common occurance of negative values in the red and blue channels, I'm pretty sure it has something to do with the pixel format of the bitmap, maybe next I will create a GDI+ bitmap format conversion function, although looking at the function at the msdn, it'll be a lot more of a headache than this was :)

Thanks

I believe the negative values in the colour channels is caused by interpreting hex 0xFF as -1.

The raw data of the pixels in BGRA sequence. Your $str structure needs to be re-arranged.

Other than that, your function looks pretty good.

I had never thought of dissecting out the colour channels at the DllCall level.

;
#include <GDIPlus.au3>
#include <Array.au3>

Opt('MustDeclareVars', 1)
Local $hBitmap, $aArr

; Example showing negative value in hexadecimal.
MsgBox(0, "", "Hex(-1, 2) = " & Hex(-1, 2) & @CRLF & "Hex(-2, 2) = " & Hex(-2, 2))

_GDIPlus_Startup()
;$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\red.bmp")
;$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\green.bmp")
$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\blue.bmp")
$aArr = _GDIPlus_BitmapGetPixel($hBitmap, 21, 21)
_ArrayDisplay($aArr)
;MsgBox(0, "", ); 0,0 is top left corner pixel of image.

; Clean up resources
_GDIPlus_ImageDispose($hBitmap)

; Shut down GDI+ library
_GDIPlus_Shutdown()

Func _GDIPlus_BitmapGetPixel($hBitmap, $iX, $iY)
    #cs
        Color(
        BYTE a,
        BYTE r,
        BYTE g,
        BYTE b
        )
    #ce
    Local $str, $color, $aResult, $a, $r, $g, $b
    $str = "byte b;byte g;byte r;byte a"
    $color = DllStructCreate($str)
    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, 'ptr', DllStructGetPtr($color))
    If @error Then
        MsgBox(0, 'error', @error & ' ' & @extended)
        Return SetError(@error, @extended, 0)
    EndIf
    $a = Hex(DllStructGetData($color, 'a'), 2)
    $r = Hex(DllStructGetData($color, 'r'), 2)
    $g = Hex(DllStructGetData($color, 'g'), 2)
    $b = Hex(DllStructGetData($color, 'b'), 2)
    Local $retValues[4] = [$a, $r, $g, $b]  
    Return $retValues
EndFunc   ;==>_GDIPlus_BitmapGetPixel
;

Share this post


Link to post
Share on other sites

I believe the negative values in the colour channels is caused by interpreting hex 0xFF as -1.

The raw data of the pixels in BGRA sequence. Your $str structure needs to be re-arranged.

Other than that, your function looks pretty good.

I had never thought of dissecting out the colour channels at the DllCall level.

;
#include <GDIPlus.au3>
#include <Array.au3>

Opt('MustDeclareVars', 1)
Local $hBitmap, $aArr

; Example showing negative value in hexadecimal.
MsgBox(0, "", "Hex(-1, 2) = " & Hex(-1, 2) & @CRLF & "Hex(-2, 2) = " & Hex(-2, 2))

_GDIPlus_Startup()
;$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\red.bmp")
;$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\green.bmp")
$hBitmap = _GDIPlus_BitmapCreateFromFile("C:\Program Files\AutoIt3\Examples\GUI\Advanced\Images\blue.bmp")
$aArr = _GDIPlus_BitmapGetPixel($hBitmap, 21, 21)
_ArrayDisplay($aArr)
;MsgBox(0, "", ); 0,0 is top left corner pixel of image.

; Clean up resources
_GDIPlus_ImageDispose($hBitmap)

; Shut down GDI+ library
_GDIPlus_Shutdown()

Func _GDIPlus_BitmapGetPixel($hBitmap, $iX, $iY)
    #cs
        Color(
        BYTE a,
        BYTE r,
        BYTE g,
        BYTE b
        )
    #ce
    Local $str, $color, $aResult, $a, $r, $g, $b
    $str = "byte b;byte g;byte r;byte a"
    $color = DllStructCreate($str)
    $aResult = DllCall($ghGDIPDll, "int", "GdipBitmapGetPixel", "hwnd", $hBitmap, "int", $iX, "int", $iY, 'ptr', DllStructGetPtr($color))
    If @error Then
        MsgBox(0, 'error', @error & ' ' & @extended)
        Return SetError(@error, @extended, 0)
    EndIf
    $a = Hex(DllStructGetData($color, 'a'), 2)
    $r = Hex(DllStructGetData($color, 'r'), 2)
    $g = Hex(DllStructGetData($color, 'g'), 2)
    $b = Hex(DllStructGetData($color, 'b'), 2)
    Local $retValues[4] = [$a, $r, $g, $b]  
    Return $retValues
EndFunc   ;==>_GDIPlus_BitmapGetPixel
;

thanks for the help malkey I tried looking over your code in the third post but I couldn't really follow it, I really have a hard time wrapping my head around bitwise operators. :) Someday something in my head will snap into place and I will understand them but for now... well, I'm doing my best to avoid them, having enough trouble doing the mental debugging without adding in stuff I can't comprehend.

You say that the data is in BGRA, but according to the msdn the color constuctor is just as I had in the cs-ce section of the function (argb) unless C loads that kind of stuff in reverse. actually on the color class () if you construct with only 3 bytes, it will force the alpha 0xFF and just point to the RGB values.

actually, I think the reason I'm getting neg values is how I'm passing $hBitmap.

$bgFileImage = _GDIPlus_ImageLoadFromFile($bgdir & $bgfile)
$bgimageY = _GDIPlus_ImageGetHeight($bgFileImage)
$bgimageX = _GDIPlus_ImageGetWidth($bgFileImage)
;change to graphic
$gcbkFile = _GDIPlus_ImageGetGraphicsContext($bgFileImage)
;Change to bitmap
$btmpFile = _GDIPlus_BitmapCreateFromGraphics($bgimageX, $bgimageY, $gcbkFile)

I think that may be causing my problem, mainly I didn't want to load the file just to get the X x Y and then reload the file as a bitmap, since I am really running this from a USB flash drive, I wanted to keep File I/O to a minimum. I'll try to load the bitmap from the file and see what I get. ok, just tried, after redoing the color struc and trying both ways (passing from image and loading from file) and it is kinda working both ways. still getting neg values, how would those come from 0xFF?? most of the coloring is working right but for a really light background image I'm getting a light blue for the text color, why would I be getting negative values, shouldn't the function be returning an int 0-255? Thanks again with the struc, still curious as to why it's reversed. but I am making progress again, now I can kinda debug myself, Whoo-Hoo!!


[size="2"] "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian Kernighan[/size]

Share this post


Link to post
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
Sign in to follow this  
Followers 0