Jump to content

Average Color Value


Recommended Posts

Hey guys,

Basically, I'm trying to calculate the average color value of a JPG/JPEG image.

I'm having the most difficulty with the JPEG format, since I don't really know how to derive color data from it, however, also, I'm still trying to figure out how to find the average of all the colors. For example:

0xFF0000 = Red

0x0000FF = Blue

Added, they would equal 0xFF00FF

Divide that by two, and i get 0x7F807F

Shouldn't it be somewhere between Red and Blue?

Wish I understood this code here..

http://www.empiregpservices.com/blog/post....lor-of-an-image

Link to comment
Share on other sites

The color data in jpeg files are compressed so you cannot just read it directly.

Easiest way to do what you want would be to use _GDIPlus_BitmapLockBits(), do a search there are good examples floating around ^_^

As for the avarage color calculation, the code on the page you linked used this:

this.averageColor = r «« 16 | g «« 8 | b;

Which would be equal to:

$avarageColor = BitOR(BitShift($red, -16), BitShift($green, -8), $blue)

In autoit ;)

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

This reminds me of the new taskbar in Windows 7. When you hover over an icon the most frequent color is found to create a glowing background. Do you want the average color or the most frequent?

Link to comment
Share on other sites

This reminds me of the new taskbar in Windows 7. When you hover over an icon the most frequent color is found to create a glowing background. Do you want the average color or the most frequent?

Probably Average.

My goal is to do something basically similar to what that page I linked to did. Find a good color to use as an accent to a desktop image.

My concern now, it that will it take very long to do that with all the pixels in a 1280x1024 pixel image?

Link to comment
Share on other sites

My concern now, it that will it take very long to do that with all the pixels in a 1280x1024 pixel image?

Yes it will be very slow in autoit. But it's not like you need to use all pixels, skip every fifth pixel or so, I would be surprised if the result would be much different.

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

Found this snippet a while ago to create an "eye-friendly" contrast...

Func _CalcContrastColor($crBg)
; http://www.codeproject.com/KB/tips/JbColorContrast.aspx
    Local Const $TOLERANCE = 30

    If ( _
            Abs(BitAND($crBg, 0xFF) - 0x80) <= $TOLERANCE And _
            Abs(BitAND(BitShift($crBg, 8), 0xFF) - 0x80) <= $TOLERANCE And _
            Abs(BitAND(BitShift($crBg, 16), 0xFF) - 0x80) <= $TOLERANCE _
            ) Then Return BitAND((0x7F7F7F + $crBg), 0xFFFFFF);

    Return BitXOR($crBg, 0xFFFFFF);
EndFunc ;==>_CalcContrastColor

Edit: Ups, a little of topic ^_^, would also recommend to try with ImageMagick...

Edited by KaFu
Link to comment
Share on other sites

Ok I think i got it to work, but i'm not sure if there is a better way to do this... I don't really fully understand what the GDI+ functions are doing

But i think it turned out fairly well with a small test image.

However there is one problem and that is that it doesn't work on images 1280x1024 ^_^

Heres what I got, I'm sure it can be optomized

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


_GDIPlus_Startup()
$fname1 = FileOpenDialog("First image", "", "All images(*.bmp;*.jpg;*.png;)")
If $fname1 = "" Then Exit

$bm1 = _GDIPlus_ImageLoadFromFile($fname1)


$Result = AverageColor($bm1)
MsgBox(0, "", $Result)
GUICreate("")
GUISetBkColor(Number($Result))
GUISetState()

While 1
    If GUIGetMsg() = -3 Then Exit
WEnd


_GDIPlus_ImageDispose($bm1)
_GDIPlus_Shutdown()

Func AverageColor($bm1)

    $Width = _GDIPlus_ImageGetWidth($bm1)
    $Height = _GDIPlus_ImageGetHeight($bm1)
    $Count = $Width * $Height
    $BitmapData1 = _GDIPlus_BitmapLockBits($bm1, 0, 0, $Width, $Height, $GDIP_ILMREAD, $GDIP_PXF32RGB)
    $Stride = DllStructGetData($BitmapData1, "Stride")
    $Scan0 = DllStructGetData($BitmapData1, "Scan0")

    Dim $Color[$Width][$Height], $i = 0
    For $row = 0 To $Width - 1
        For $col = 0 To $Height - 1
            $i += 1
            ConsoleWrite($i & "/" & $Count & @CRLF)
            $pixel = DllStructCreate("dword", $Scan0 + $row * $Stride + $col * 4)
            $Color[$row][$col] = Hex(DllStructGetData($pixel, 1), 6)
        Next
    Next
    ;_ArrayDisplay($Color)
    Dim $SumRed = 0, $SumGreen = 0, $SumBlue = 0
    For $x = 0 To $Width - 1
        For $y = 0 To $Height - 1
            $SumRed += _ColorGetRed($Color[$x][$y])
            $SumGreen += _ColorGetGreen($Color[$x][$y])
            $SumBlue += _ColorGetBlue($Color[$x][$y])
        Next
    Next
    $NewR = $SumRed / $Count
    $NewG = $SumGreen / $Count
    $NewB = $SumBlue / $Count
    $new = BitOR(BitShift($NewR, -16), BitShift($NewG, -8), $NewB)

    _GDIPlus_BitmapUnlockBits($bm1, $BitmapData1)
    Return "0x" & $new
EndFunc   ;==>AverageColor
Edited by Paulie
Link to comment
Share on other sites

Hey guys,

Basically, I'm trying to calculate the average color value of a JPG/JPEG image.

I'm having the most difficulty with the JPEG format, since I don't really know how to derive color data from it, however, also, I'm still trying to figure out how to find the average of all the colors. For example:

0xFF0000 = Red

0x0000FF = Blue

Added, they would equal 0xFF00FF

Divide that by two, and i get 0x7F807F

Shouldn't it be somewhere between Red and Blue?

Wish I understood this code here..

http://www.empiregpservices.com/blog/post....lor-of-an-image

I agree. Make use of _GDIPlus_BitmapLockBits().

I played around with an existing example script.

http://www.autoitscript.com/forum/index.ph...st&p=663576

Added average colour.

At line #50, is the command:-

$hImage2 = _ImageColorRegExpReplace($hImage, "av", "FF0000FF"); Change Average colour to blue in BBGGRRAA hex colour format.

The "av" in the search colour parameter enabled the average colour routine.

Added range so that colours close to average colour will change to the new colour entered.

It looks like you have solved your problem, almost.

I hope this helps.

;
#include <GuiConstantsEx.au3>
#include <GDIPlus.au3>
#include <ScreenCapture.au3>
#include <Misc.au3>
; http://www.autoitscript.com/forum/index.php?s=&showtopic=91755&view=findpost&p=663576
Opt('MustDeclareVars', 1)

Global $width, $height, $hGUI1, $hGUI2, $hImage, $hImage2, $hGraphic1, $hGraphic2, $iX, $iY
Local $PathFile = FileOpenDialog("Choose a File", @ScriptDir & "\", "Images (*.jpg;*.bmp;*.png;*.gif)|All Files (*.*)", 3)
If Not @error Then _Main($PathFile)

Func _Main($PathFile)
    ; Capture top left corner of the screen
    ;_ScreenCapture_Capture(@MyDocumentsDir & "\GDIPlus_Image.png", 0, 0, 400, 300)
    _GDIPlus_Startup()
    $hImage = _GDIPlus_ImageLoadFromFile($PathFile)
    ;$hImage = _GDIPlus_ImageLoadFromFile(@MyDocumentsDir & "\GDIPlus_Image.png")
    $iX = _GDIPlus_ImageGetWidth($hImage)
    $iY = _GDIPlus_ImageGetHeight($hImage)

    ; Create a GUI for the original image
    $hGUI1 = GUICreate("Original", $iX, $iY, 0, 0)
    GUISetState()

    ; Create a GUI for the zoomed image
    $hGUI2 = GUICreate("Ctrl + Left click to save image", $iX, $iY, 0, $iY + 30)
    GUISetState()
    GUIRegisterMsg(0xF, "MY_PAINT"); Register PAINT-Event 0x000F = $WM_PAINT (WindowsConstants.au3)
    ; Initialize GDI+ library and load image
    _GDIPlus_Startup()

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

    ; ====== Examples calling _ImageColorRegExpReplace() =========================
    ; Note colour format hex 0xBBGGRRAA - Blue, Green, Red, Alpha (transparency)

    ;$hImage2 = _ImageColorRegExpReplace($hImage, "(000000FF)" , "FFFFFFFF" ); Change black to white
    ;$hImage2 = _ImageColorRegExpReplace($hImage, "(FFFFFFFF)" , "FFFFFF00" ); Change white to transparent
    ;$hImage2 = _ImageColorRegExpReplace($hImage, "(([E-F][0-9A-F]){3})(FF)" , "${1}00" ); Change near to white to transparent
    ;$hImage2 = _ImageColorRegExpReplace($hImage, "(FF0000FF|0000AAFF|FFFFFFFF)", "000000FF"); Change blue, off red, white to black

    ;Swap red and blue channels on half the image. Image is 400x300 = 120,000 pixels. Number of pixels to replace is 60,000.
    ;$hImage2 = _ImageColorRegExpReplace($hImage, "([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})(FF)", "${3}${2}${1}${4}", 60000)

    ;$hImage2 = _ImageColorRegExpReplace($hImage, "(([0-9E-F]){6})(FF)", "0000FFFF"); Change near to white to red

    $hImage2 = _ImageColorRegExpReplace($hImage, "av", "FF0000FF"); Change Average colour to blue  in BBGGRRAA hex colour format.
    ;==============================================================================================

    ; Draw RegExpReplace image
    $hGraphic2 = _GDIPlus_GraphicsCreateFromHWND($hGUI2)
    _GDIPlus_GraphicsDrawImage($hGraphic2, $hImage2, 0, 0)
    ;$hImage3 = _GDIPlus_BitmapCreateFromHBITMAP ($hImage2)
    ConsoleWrite("$hImage2   " & $hImage2 & @CRLF)

    ; Clean up screen shot file
    FileDelete(@MyDocumentsDir & "\GDIPlus_Image.jpg")

    ; Loop until user exits
    Do
        If _IsPressed("01") And _IsPressed("11") Then ;Left click + Ctrl key to save image
            Do
            Until Not _IsPressed("01")
            Local $PathFile1 = FileSaveDialog("Choose a name.", @ScriptDir & "\", _
                    "Images (*.bmp;*.jpg;*.png;*.gif)|All files (*.*)", 16, "ColourModify.jpg")
            If Not @error Then
                _GDIPlus_ImageSaveToFile($hImage2, $PathFile1)
                ShellExecute($PathFile1)
            EndIf
        EndIf
        Sleep(10)
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    ; Release resources
    _GDIPlus_GraphicsDispose($hGraphic1)
    _GDIPlus_GraphicsDispose($hGraphic2)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_ImageDispose($hImage2)
    _GDIPlus_Shutdown()
EndFunc   ;==>_Main

; ========= _ImageColorRegExpReplace($hImage, $iColSrch, $iColNew,$iCount = 0) =====================
;Paramerers:-
; $hImage   - Handle to a Bitmap object
; $iColSrch - A regular expression pattern to compare the colours to in 0xBBGGRRAA hex colour format.
; $iColNew  - The colour which will replace the regular expression matching colour.Also in 0xBBGGRRAA hex colour format.
; $iCount - [optional] The number of times to execute the replacement in the image. The default is 0. Use 0 for global replacement.
; Note :- Colour format in hex 0xBBGGRRAA
;
Func _ImageColorRegExpReplace($hImage, $iColSrch, $iColNew, $iCount = 0)
    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, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32ARGB)

    ;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("$sREResult1 first 9 colours = " & StringRegExpReplace($sREResult1, "(.{81})(.*)", "\1") & @CRLF)

    ;======================  Average colour ===============================
    If StringInStr($iColSrch, "av") > 0 Then
        Local $iBlue, $iGreen, $iRed, $iAlpha, $avBlue, $avGreen, $avRed, $aviAlpha
        For $x = 1 To StringLen($sREResult1) Step 9
            $iBlue += Dec(Hex("0x" & StringMid($sREResult1, $x, 2)))
            $iGreen += Dec(Hex("0x" & StringMid($sREResult1, $x + 2, 2)))
            $iRed += Dec(Hex("0x" & StringMid($sREResult1, $x + 4, 2)))
            $iAlpha += Dec(Hex("0x" & StringMid($sREResult1, $x + 6, 2)))
            ;MsgBox(0,"","0x" & Hex($iAlpha,2) & Hex($iRed,2)  & Hex($iGreen,2)  &  Hex($iBlue,2) )
        Next
        $avBlue = Hex(Round($iBlue / ($height * $width), 0), 2)
        $avGreen = Hex(Round($iGreen / ($height * $width), 0), 2)
        $avRed = Hex(Round($iRed / ($height * $width), 0), 2)
        $aviAlpha = Hex(Round($iAlpha / ($height * $width), 0), 2)

        MsgBox(0, "", "$width = " & $width & @CRLF & _
                "$height = " & $height & @CRLF & _
                "Blue = " & $avBlue & @CRLF & _
                "$iGreen = " & $avGreen & @CRLF & _
                "$iRed = " & $avRed & @CRLF & _
                "$iAlpha = " & $aviAlpha & @CRLF & _
                "Av.Col ARGB = " & "0x" & $aviAlpha & $avRed & $avGreen & $avBlue)

        Local $iRnge = 30 ; Range 30 is plus or minus 15 for each colour channel, max 0xFF and min 0x00.
        $iColSrch = StringRERange("0x" & $avBlue, $iRnge) & StringRERange("0x" & $avGreen, $iRnge) & _
                StringRERange("0x" & $avRed, $iRnge) & StringRERange("0x" & $aviAlpha, $iRnge)
        ;ConsoleWrite("$iColSrch " & $iColSrch & @CRLF)
    EndIf
    ;=================> End of Average colour ===============================

    If StringInStr($iColNew, "0x") > 0 Then $iColNew = StringReplace($iColNew, "0x", ""); Remove "0x" not needed

    ; StringRegExpReplace performed and white spaces removed
    $sResult = StringStripWS(StringRegExpReplace($sREResult1, $iColSrch, $iColNew, $iCount), 8)

    ; Replace "0x" prefix and set modified data back to DLL structure, $v_BufferA
    DllStructSetData($v_BufferA, 1, "0x" & $sResult)
    _GDIPlus_BitmapUnlockBits($hBitmap1, $Reslt) ; releases the locked region

    Return $hBitmap1
EndFunc   ;==>_ImageColorRegExpReplace

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

Func StringRERange($iColSrch, $iRnge)
    Local $str = "(", $iColSrchMin, $iColSrchMax
    $iColSrchMin = "0x" & Hex((Dec(Hex($iColSrch)) - $iRnge) * ((Dec(Hex($iColSrch)) - $iRnge) > 0), 2)
    $iColSrchMax = "0x" & Hex((Dec(Hex($iColSrch)) + $iRnge) * ((Dec(Hex($iColSrch)) + $iRnge) < 255) + 255, 2)
    For $n = $iColSrchMin To $iColSrchMax
        $str &= Hex($n, 2) & "|"
    Next
    $str = StringTrimRight($str, 1) & ")"
    Return $str
EndFunc   ;==>StringRERange
;
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...