Sign in to follow this  
Followers 0
Zhelkus

Check if an image is grayscale or not

10 posts in this topic

I want to make a simple script that checks if an image is grayscale (black and white) or not. I was thinking of scanning each image with PixelSearch and then check if amongst the whole image there's a pixel that isn't a varying shade of black or white... the thing is that I don't know how to exactly process this. I had it panned out in my head to use PixelSearch for red, green and blue. If result came out positive then it wasn't B&W. But um... what about pictures that have more than 16k colours? Then I found this link: http://www.autoitscript.com/forum/index.php?showtopic=95357. And I wasn't sure how to use it.

By any change is there already a script that does this? Any help plz? ;)

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

Hi, have a look in GDIPlus in AutoIt Help file under User Defined Functions -> GDIPlus Management.

_GDIPlus_ImageLoadFromFile() to load your image.

Then use

_GDIPlus_ImageGetPixelFormat() will return the pixel format of the image.

1 bit-per-pixel = monochrome.

Cheers

Edit: NVM, I read again op post after Ascend4nt post..

I'm totally of track to what's wanted.

Edited by smashly

Share this post


Link to post
Share on other sites

smashly, that's black and white, not greyscale.

Zhelkus, pure black-and-white will have pixel colors of the same value for RGB, if 24 or 32-bit color is used (or the image is converted to that format).

For simple tests, PixelGetColor() can be used with BitAnd and BitShift to grab individual components, but for a larger image I'm not sure... You can certainly grab the image into memory and scan it more quickly using a DLLStruct, but that still will take some time. Maybe you'll get lucky and find someone with a DLL for this. (my own code to read images is a bit messy right now, sorry)

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

Let me see if I got this: there's a function in autoit that already does this... but it's slow? Hence I should search for a DLL to call and make it faster? Sorry if I'm too lame at this, I'm not really well experienced at coding or scripting...

Basically what I want the script for is to check if a GIF or PNG has any colours besides shades of gray. I've found that many people save images as PNGs with 256 colours (or less) in grayscale, and some of them might have a colour that isn't a shade of gray (which isn't grayscale any longer but still has only 256 colours). Also, quite strange, some people save JPEGs to PNG in grayscale but the image has much more than 256 colours due to previously being JPEG or some other odd reason.

I want to build a script that checks for that. Weird, I know. :)

I just thought of something more practical: (hypothetically) I got an image with a palette of 256 (8bit) but only has 32 colours (grayscale). If I convert it to 16 colours (4bit) and perform a PixelChecksum on both, before and after, will I get different results or is it not that precise?

And what parameter dictates whether an image is grayscale when using _GDIPlus_ImageGetPixelFormat. The palette size? Or does it actually to check the colours of every pixel?

EDIT: Smashly if you wrote a script leave it. I'm pretty sure I could use anything as an aid or guideline atm. ;)

Edited by Zhelkus

Share this post


Link to post
Share on other sites

This works on my xp.

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

Opt("WinTitleMatchMode", 2) ;1=start, 2=subStr, 3=exact, 4=advanced, -1 to -4=Nocase

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

ShellExecute($sImageIn)

Local $begin = TimerInit()
If _IsImageGrayScale($sImageIn, 1) Then
    ConsoleWrite("Time: " & Round(TimerDiff($begin) / 1000, 3) & " secs" & @CRLF)
    MsgBox(0, "Greyscale", 'Image "' & $sImageIn & '" is all greyscale.')
Else
    ConsoleWrite("Time: " & TimerDiff($begin) & @CRLF)
    MsgBox(0, "NOT Greyscale", 'Image "' & $sImageIn & '" is NOT all greyscale.')
EndIf

WinClose(StringRegExpReplace($sImageIn, "^.*\\|\..*$", ""))


;Parameters:-
;       $sInFile - Full path and name of image file.
;       $iEveryNthPixel - A number indicating which pixels to test for being grey. Default, 1, means
;                        test every pixel. A "2" would test every 2nd pixel, "3" every 3rd pixel, etc.
; Returns true if image is greyscale. Otherwise, returns false.
Func _IsImageGrayScale($sInFile, $iEveryNthPixel = 1)
    Local $hImage, $iW, $iH, $tBitmapData, $iStride, $iScan0, $sRet
    Local $hBmp, $hBitmap, $hGraphic
    _GDIPlus_Startup()
    $hImage = _GDIPlus_ImageLoadFromFile($sInFile)
    $iW = _GDIPlus_ImageGetWidth($hImage)
    $iH = _GDIPlus_ImageGetHeight($hImage)

    $hBmp = _WinAPI_CreateBitmap($iW, $iH, 1, 32)
    $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hBmp)
    _WinAPI_DeleteObject($hBmp)
    $hGraphic = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    _GDIPlus_GraphicsDrawImage($hGraphic, $hImage, 0, 0)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_GraphicsDispose($hGraphic)
    $hImage = _GDIPlus_BitmapCloneArea($hBitmap, 0, 0, $iW, $iH, $GDIP_PXF32ARGB)
    _GDIPlus_BitmapDispose($hBitmap)

    $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $iW, $iH, $GDIP_ILMWRITE, $GDIP_PXF32ARGB)
    $iStride = DllStructGetData($tBitmapData, "stride")
    $iScan0 = DllStructGetData($tBitmapData, "Scan0")

    Local $v_BufferA = DllStructCreate("byte[" & $iH * $iW * 4 & "]", $iScan0) ; Create DLL structure for all pixels
    Local $AllPixels = DllStructGetData($v_BufferA, 1)
    Local $sPix1 = StringRegExpReplace(StringTrimLeft($AllPixels, 2), "(.{8}){" & $iEveryNthPixel & "}", "\1 ")
    ;ConsoleWrite($sPix1 & @CRLF)
    $sRet = (StringRegExpReplace($sPix1, "(..)\1\1FF ?", "") == "") ; Either True or False
    ConsoleWrite("$sRet " & $sRet & @CRLF)
    _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_Shutdown()
    Return $sRet
EndFunc ;==>_IsImageGrayScale

#cs
    The following completion times obtained from a 39.4KB greyscale JPEG image, 590x629 = 371,110 pixels.
    Time: 5.129 secs every pixel
    Time: 2.868 secs every 2nd pixel
    Time: 2.35 secs every 3rd pixel
    Time: 1.801 secs every 4th pixel
    Time: 1.123 secs every 10th pixel
    Time: 0.898 secs every 20th pixel
#ce

To help you understand and to experiment with some test data and/or the regular expression pattern, I have included the following script for your convenience.

; Note RGB shades of grey in 0xRRGGBB hex colour format has 0xRR = 0xGG = 0xBB.
; Black, 0x000000, and white, 0xFFFFFF, could be considered part of the greyscale
; because the three (3) colour channels are equal. The two extreme shades of grey.

; $AllPixels has 21 pixels in raw data form in "BBGGRRAA" hex colour format i.e. Blue, Green, Red, Alpha channels.
; Where the Alpha channel of each pixel colour is 0xFF (255) as "AA" (fully opaque)
Local $AllPixels = "0x929292FF909090FF8D8D8DFF8A8A8AFF888888FF8E8E8EFF8F8F8FFF909090FF929292FF929292FF" & _
        "919191FF919191FF919191FF939393FF939393FF929292FF939393FF959595FF969696FF909090FF929292FF"

; Return every 3rd pixel for greyscale testing.
Local $sPix3rd = StringRegExpReplace(StringTrimLeft($AllPixels, 2), "(.{8}){3}", "\1 ")
ConsoleWrite("$sPix3rd Starts with 3rd pixel>" & $sPix3rd & @CRLF)

Local $iEveryNthPixel = 1

; Will return only every nth pixel for greyscale testing purposes.
Local $sPix1 = StringRegExpReplace(StringTrimLeft($AllPixels, 2), "(.{8}){" & $iEveryNthPixel & "}", "\1 ")
ConsoleWrite("$sPix1 >" & $sPix1 & @CRLF)

ConsoleWrite("Is greyscale: " & (StringRegExpReplace($sPix1, "(..)\1\1FF ?", "") == "") & @CRLF)

ConsoleWrite("Returns non-grey colours >" & (StringRegExpReplace($sPix1, "(..)\1\1FF ?", "")) & @CRLF)
ConsoleWrite('Back reference "\1 " only >' & (StringRegExpReplace($sPix1, "(..)\1\1FF ?", "\1 ")) & @CRLF)

Share this post


Link to post
Share on other sites

#6 ·  Posted (edited)

Hi, 

i took a little bit assistance by Wards Fasm.au3 and the result are those some bytes where are able to check a 1600x1000 pixel bitmap in 1-2ms   ;)

;32 Bit only!

#include <GDIPlus.au3>

;you can find the embedded FASM-assembler by Ward at
;http://www.autoitscript.com/forum/index.php?showtopic=111613&view=findpost&p=782727
;
;a "How to use Assembler with AutoIt"-Tutorial with many examples(in german ^^) at
;http://www.autoitscript.com/forum/index.php?showtopic=111613&view=findpost&p=782727
;give AssembleIt a try :o)

;#include <AssembleIt.au3>    ;executes the Assemblercode  


Func _is_pic_grayscale() ;function to use with AssembleIt.au3
    _("use32") ;32Bit Asssembler
    _("mov esi,dword[esp+4]") ;Startaddress Bitmapdata (Pixel)
    _("mov ecx,dword[esp+8]") ;number of Pixels 32Bpp

    ;compare the 3 Bytes of RR,GG,BB
    ;if all 3 Bytes are equal, Picture is grayscale
    _("_loop1:") ;
    _("mov eax,[esi]") ;load pixel into Register eax=AARRGGBB
    _("cmp al,ah") ;is GG=BB?
    _("jne _nogray") ;Jump if Not Equal to _nogray (no gray)
    _("shr eax,8") ;shift RR into ah, al=GG now
    _("cmp al,ah") ;is RR=GG?
    _("jne _nogray") ;Jump if Not Equal to _nogray  (no gray)
    _("add esi,4")    ;esi=esi+4 next 4 Bytes = next Pixel
    _("loop _loop1") ;ecx=ecx-1    Jump, if ecx is above 0 to _loop1
    _("xor eax,eax") ;if all pixels are gray, eax=0     xor eax,eax -> eax=0
    _("_nogray:") ;if eax<>0 then one of the pixels is not gray
    _("ret 8") ;back to AutoIt,  result is in eax;  remember ret 8 when use AssembleIt because cdecl
EndFunc   ;==>_is_pic_gray

Func _($a) ;remove if AssembleIt is used
EndFunc   ;==>_




;code by Malkey ^^
Opt("WinTitleMatchMode", 2) ;1=start, 2=subStr, 3=exact, 4=advanced, -1 to -4=Nocase

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

ShellExecute($sImageIn)
sleep(500)
Local $begin = TimerInit()
If _IsImageGrayScaleAsm32($sImageIn) Then
    ConsoleWrite("Time: " & Round(TimerDiff($begin) / 1000, 3) & " secs" & @CRLF)
    MsgBox(0, "Greyscale", 'Image "' & $sImageIn & '" is all greyscale.')
Else
    ConsoleWrite("Time: " & TimerDiff($begin) & @CRLF)
    MsgBox(0, "NOT Greyscale", 'Image "' & $sImageIn & '" is NOT all greyscale.')
EndIf

WinClose(StringRegExpReplace($sImageIn, "^.*\\|\..*{:content:}quot;, ""))




Func _IsImageGrayScaleAsm32($sImageIn) ;
if @AutoItX64 then
    Msgbox(0,"Function _IsImageGrayScaleAsm32()","is only working with a 32Bit-AutoIt Script!")
    Exit
endif
    _GDIPlus_Startup()
    $hBitmap = _GDIPlus_BitmapCreateFromFile($sImageIn)
    Local $iWidth, $iHeight, $hBitmapData, $Scan, $Stride, $tPixelData, $pPixelStruct
    $iWidth = _GDIPlus_ImageGetWidth($hBitmap)
    $iHeight = _GDIPlus_ImageGetHeight($hBitmap)

    $hBitmapData = _GDIPlus_BitmapLockBits($hBitmap, 0, 0, $iWidth, $iHeight, BitOR($GDIP_ILMREAD, $GDIP_ILMWRITE), $GDIP_PXF32RGB)
    $Scan = DllStructGetData($hBitmapData, "Scan0")
    $Stride = DllStructGetData($hBitmapData, "Stride")
    $tPixelData = DllStructCreate("dword[" & (Abs($Stride * $iHeight)) & "]", $Scan)

;
;~ $ret=_AssembleIt("ptr","_is_pic_grayscale","ptr", DllStructGetPtr($tPixelData), "int", $iWidth * $iHeight)  ;returns eax in $ret, no array!
;~ ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
;~ MsgBox(262144,'Debug line ~' & @ScriptLineNumber,'Selection:' & @lf & '$ret' & @lf & @lf & 'Return:' & @lf & $ret) ;### Debug MSGBOX


    $bytecode = "0x8B7424048B4C24088B0638E0750EC1E80838E0750783C604E2EE31C0C3" ;Fasm did the work ;)
    Global $tCodeBuffer = DllStructCreate("byte[" & StringLen($bytecode) / 2 - 1 & "]") ;alloc some memory
    DllStructSetData($tCodeBuffer, 1, $bytecode) ;write bytecode into struct
    $t=timerinit()
    $ret = DllCall("user32.dll", "ptr", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer), "ptr", DllStructGetPtr($tPixelData), "int", $iWidth * $iHeight, "int", 0, "int", 0);returns eax in ret[0]
    $m=timerdiff($t)
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $ret = ' & $ret[0] & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console
    ConsoleWrite('@@ Debug(' & @ScriptLineNumber & ') : $m = ' & $m & @crlf & '>Error code: ' & @error & @crlf) ;### Debug Console


    _GDIPlus_BitmapUnlockBits($hBitmap, $hBitmapData)
    _GDIPlus_BitmapDispose($hBitmap)
     _GDIPlus_Shutdown()
    If $ret[0] = 0 Then  ;if eax=0 all pixels are grey   replace ret[0] with ret if AssembleIt is used
        Return True
    Else
        Return False
    EndIf
EndFunc   ;==>_IsImageGrayScaleAsm32
The Assemblerpart could be easily transfered into a dll....

Have Fun!

/EDIT/ unfortunately i have no 64Bit Windows, so this code runs only with 32Bit-AutoIt!

Edited by AndyG

Share this post


Link to post
Share on other sites

That's not fair comparison AndyG. ;)

Br,

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!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Share this post


Link to post
Share on other sites

That's not fair comparison AndyG.

Should it be fair or should it be fast?  ;) There were no "fair" requirements in the startpost  :)

But, hey, it has lasted one minute to write the assemblerpart, but one hour to understand, how Malkey´s RegExp´s work. And I'm still not sure if I really understood it! 

Share this post


Link to post
Share on other sites

#9 ·  Posted (edited)

AndyG

The speed difference is very, very impressive.

I am getting the compiled assembly code to be about 1,000 times faster than the string regular expression method. That is, from 5 secs down to 5 millisecs.

Note: The two links at the top of your code post #6 are the same. "AssembleIt.au3" could not be found.

I managed to duplicate your $opcode by putting your _is_pic_grayscale() function contents into the Demo7() function here.

#include "FASM.au3"

Dim $Fasm = FasmInit()

MsgBox(0, "Byte Code", _IsPicGrayscale())

; http://www.autoitscript.com/forum/index.php?showtopic=111613&view=findpost&p=783151
Func _IsPicGrayscale()
    FasmReset($Fasm)
    FasmAdd($Fasm, "use32") ;32Bit Asssembler
    FasmAdd($Fasm, "mov esi,dword[esp+4]") ;Startaddress Bitmapdata (Pixel)
    FasmAdd($Fasm, "mov ecx,dword[esp+8]") ;number of Pixels 32Bpp

    ;compare the 3 Bytes of RR,GG,BB
    ;if all 3 Bytes are equal, Picture is grayscale
    FasmAdd($Fasm, "_loop1:")
    FasmAdd($Fasm, "mov eax,[esi]") ;load pixel into Register eax=AARRGGBB
    FasmAdd($Fasm, "cmp al,ah") ;is GG=BB?
    FasmAdd($Fasm, "jne _nogray") ;Jump if Not Equal to _nogray (no gray)
    FasmAdd($Fasm, "shr eax,8") ;shift RR into ah, al=GG now
    FasmAdd($Fasm, "cmp al,ah") ;is RR=GG?

    FasmAdd($Fasm, "jne _nogray") ;Jump if Not Equal to _nogray (no gray)
    FasmAdd($Fasm, "add esi,4") ;esi=esi+4 next 4 Bytes = next Pixel
    FasmAdd($Fasm, "loop _loop1") ;ecx=ecx-1    Jump, if ecx is above 0 to _loop1
    FasmAdd($Fasm, "xor eax,eax") ;if all pixels are gray, eax=0    xor eax,eax -> eax=0
    FasmAdd($Fasm, "_nogray:") ;if eax<>0 then one of the pixels is not gray
    FasmAdd($Fasm, "ret") ;back to AutoIt, result is in eax
    $bytecode = String(FasmGetBinary($Fasm))
    ClipPut('$bytecode = "' & $bytecode & '"')
    ConsoleWrite('$bytecode = "' & $bytecode & '"' & @CRLF)
    Return $bytecode
EndFunc ;==>_IsPicGrayscale

Here is the modified script.

#include <GDIPlus.au3>
; http://www.autoitscript.com/forum/index.php?showtopic=120313&view=findpost&p=836303
; Incorporating AndyG opcode from
; http://www.autoitscript.com/forum/index.php?showtopic=120313&view=findpost&p=836173
; And smashly code from
; http://www.autoitscript.com/forum/index.php?showtopic=102626&view=findpost&p=728034

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

ShellExecute($sImageIn)

Local $begin = TimerInit()
If _IsImageGrayScale($sImageIn) Then
    MsgBox(0, "Greyscale", 'Image "' & $sImageIn & '" is all greyscale.' & @CRLF & _
    "Time: " & Round(TimerDiff($begin) / 1000, 3) & " secs")
Else
    MsgBox(0, "NOT Greyscale", 'Image "' & $sImageIn & '" is NOT all greyscale.' & @CRLF & _
    "Time: " & Round(TimerDiff($begin) / 1000, 3) & " secs")
EndIf

Opt("WinTitleMatchMode", 2) ;1=start, 2=subStr, 3=exact, 4=advanced, -1 to -4=Nocase
WinClose(StringRegExpReplace($sImageIn, "^.*\\|\..*$", ""))


;Parameters:-
; $sInFile - Full path and name of image file.
; Returns true if image is greyscale. Otherwise, returns false.
Func _IsImageGrayScale($sInFile)
    Local $hImage, $iW, $iH, $tBitmapData, $iStride, $iScan0, $sRet
    Local $hBmp, $hBitmap, $hGraphic, $tCodeBuffer, $bytecode, $tPixelData
    _GDIPlus_Startup()
    $hImage = _GDIPlus_ImageLoadFromFile($sInFile)
    $iW = _GDIPlus_ImageGetWidth($hImage)
    $iH = _GDIPlus_ImageGetHeight($hImage)

    ;=> Start Work around For XP, GDIPBitmapLockBits() seem to hard crash autoit When using images that are less then 24bpp
    ; If your using Vista or Newer OS then this won't be called or needed.
    ; http://www.autoitscript.com/forum/index.php?showtopic=102626&view=findpost&p=728034
    If StringInStr("WIN_2003,WIN_XP,WIN_2000", @OSVersion) Then
    Local $aRet, $hBmp, $hBitmap, $hGraphic
    $aRet = _GDIPlus_ImageGetPixelFormat($hImage)
    If Int(StringRegExpReplace($aRet[1], "\D+", "")) < 24 Then
    $hBmp = _WinAPI_CreateBitmap($iW, $iH, 1, 32)
    $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hBmp)
    _WinAPI_DeleteObject($hBmp)
    $hGraphic = _GDIPlus_ImageGetGraphicsContext($hBitmap)
    _GDIPlus_GraphicsDrawImage($hGraphic, $hImage, 0, 0)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_GraphicsDispose($hGraphic)
    $hImage = _GDIPlus_BitmapCloneArea($hBitmap, 0, 0, $iW, $iH, $GDIP_PXF32ARGB)
    _GDIPlus_BitmapDispose($hBitmap)
    EndIf
    EndIf
    ;=> End Work around

    $tBitmapData = _GDIPlus_BitmapLockBits($hImage, 0, 0, $iW, $iH, $GDIP_ILMWRITE, $GDIP_PXF32ARGB)
    $iStride = DllStructGetData($tBitmapData, "stride")
    $iScan0 = DllStructGetData($tBitmapData, "Scan0")
    $tPixelData = DllStructCreate("dword[" & (Abs($iStride * $iH)) & "]", $iScan0)
    $bytecode = "0x8B7424048B4C24088B0638E0750EC1E80838E0750783C604E2EE31C0C3"
    $tCodeBuffer = DllStructCreate("byte[" & StringLen($bytecode) / 2 - 1 & "]") ;alloc some memory
    DllStructSetData($tCodeBuffer, 1, $bytecode) ;write bytecode into struct
    $sRet = DllCall("user32.dll", "ptr", "CallWindowProcW", "ptr", DllStructGetPtr($tCodeBuffer), "ptr", DllStructGetPtr($tPixelData), "int", $iW * $iH, "int", 0, "int", 0);returns eax in ret[0]

    _GDIPlus_BitmapUnlockBits($hImage, $tBitmapData)
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_Shutdown()
    Return ($sRet[0] = 0)
EndFunc ;==>_IsImageGrayScale

Edit: Fixed typo.

Edited by Malkey

Share this post


Link to post
Share on other sites

#10 ·  Posted (edited)

Sorry, i did a c&p error with the AssembleIt Download-Page! Thank you for the hint!

AssembleIt is something like a Wrapper for the whole Fasmxx()&Fasmyy()-stuff.

I tried to make Wards "embedded" Assembler more embedded. 

See Examples in the zip-file for details!

Edited by AndyG

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