DHL

Problems comparing two (in memory) bitmaps using memcmp

8 posts in this topic

Hi, 

I'm trying to compare two bitmaps (screenshots of the desktop) that I create using BitBlt. The bitmaps can be successfully saved to disk, but when I run the two bitmaps through a CompareBitmaps()-function I found in this thread, which calls memcmp, the script crashes with "Exit code: -1073741819". 

This crash only happens when I try to compare bitmaps that I got by using BitBlt. If I use the CompareBitmaps()-function on bitmaps created by _GDIPlus_ImageLoadFromFile, everything works fine. (I'm using AutoIT 3.3.6.1)

I'll attach my script that crashes, and the  CompareBitmaps() example script that works (which I got from the thread previosly mentioned)

My script, that for some reason crashes : 

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

_GDIPlus_Startup()

Local $hDesktop = _WinAPI_GetDesktopWindow()
$clientS = WinGetClientSize($hDesktop)
Local $Width = $clientS[0]
Local $Height = $clientS[1]

Local $hDC = _WinAPI_GetDC($hDesktop)
Local $memDC = _WinAPI_CreateCompatibleDC($hDC)
Local $memBmp = _WinAPI_CreateCompatibleBitmap($hDC, $Width, $Height)
_WinAPI_SelectObject ($memDC, $memBmp)
_WinAPI_BitBlt($memDC, 0, 0, $Width, $Height, $hDC, 0,0, $__SCREENCAPTURECONSTANT_SRCCOPY)
_WinAPI_DeleteDC($memDC)
_WinAPI_ReleaseDC($hDesktop, $hDC)

Local $hImage1 = _GDIPlus_BitmapCreateFromHBITMAP($memBmp)
_WinAPI_DeleteObject($memBmp)

_GDIPlus_ImageSaveToFile($hImage1, @ScriptDir & "\GDIPlus_Image1.jpg")

MsgBox(0, "Debug", "Screenshot taken & saved to disk. Ready to take new screenshot" )

Local $hDC2 = _WinAPI_GetDC($hDesktop)
Local $memDC2 = _WinAPI_CreateCompatibleDC($hDC2)
Local $memBmp2 = _WinAPI_CreateCompatibleBitmap($hDC2, $Width, $Height)
_WinAPI_SelectObject ($memDC2, $memBmp2)
_WinAPI_BitBlt($memDC2, 0, 0, $Width, $Height, $hDC2, 0,0, $__SCREENCAPTURECONSTANT_SRCCOPY)
_WinAPI_DeleteDC($memDC2)
_WinAPI_ReleaseDC($hDesktop, $hDC2)

Local $hImage2 =_GDIPlus_BitmapCreateFromHBITMAP($memBmp2)
_WinAPI_DeleteObject($memBmp2)

_GDIPlus_ImageSaveToFile($hImage2, @ScriptDir & "\GDIPlus_Image2.jpg")

MsgBox(0, "Debug", "Screenshot2 taken & saved to disk. Ready to compare images (from memory)" )

MsgBox(0, "bm1==bm2", CompareBitmaps($hImage1, $hImage2) )

; Clean up resources
_GDIPlus_ImageDispose($hImage1)
_GDIPlus_ImageDispose($hImage2)

; Shut down GDI+ library
_GDIPlus_ShutDown ()

Func CompareBitmaps($bm1, $bm2)

    $Bm1W = _GDIPlus_ImageGetWidth($bm1)
    $Bm1H = _GDIPlus_ImageGetHeight($bm1)

    $BitmapData1 = _GDIPlus_BitmapLockBits($bm1, 0, 0, $Bm1W, $Bm1H, $GDIP_ILMREAD, $GDIP_PXF32RGB)
    $Stride = DllStructGetData($BitmapData1, "Stride")
    $Scan0 = DllStructGetData($BitmapData1, "Scan0")

    $ptr1 = $Scan0
    $size1 = ($Bm1H - 1) * $Stride + ($Bm1W - 1) * 4
    $Bm2W = _GDIPlus_ImageGetWidth($bm2)
    $Bm2H = _GDIPlus_ImageGetHeight($bm2)
    $BitmapData2 = _GDIPlus_BitmapLockBits($bm2, 0, 0, $Bm2W, $Bm2H, $GDIP_ILMREAD, $GDIP_PXF32RGB)
    $Stride = DllStructGetData($BitmapData2, "Stride")
    $Scan0 = DllStructGetData($BitmapData2, "Scan0")
    $ptr2 = $Scan0
    $size2 = ($Bm2H - 1) * $Stride + ($Bm2W - 1) * 4

    $smallest = $size1
    If $size2 < $smallest Then $smallest = $size2
    $call = DllCall("msvcrt.dll", "int:cdecl", "memcmp", "ptr", $ptr1, "ptr", $ptr2, "int", $smallest)

    _GDIPlus_BitmapUnlockBits($bm1, $BitmapData1)
    _GDIPlus_BitmapUnlockBits($bm2, $BitmapData2)
    Return ($call[0]=0)
EndFunc  ;==>CompareBitmaps

 

Example Script (that basically does the same thing, but works fine)

#include <GDIPlus.au3>

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

MsgBox(0, "bm1==bm2", CompareBitmaps($bm1, $bm2))
_GDIPlus_ImageDispose($bm1)
_GDIPlus_ImageDispose($bm2)
_GDIPlus_Shutdown()

Func CompareBitmaps($bm1, $bm2)

    $Bm1W = _GDIPlus_ImageGetWidth($bm1)
    $Bm1H = _GDIPlus_ImageGetHeight($bm1)
    $BitmapData1 = _GDIPlus_BitmapLockBits($bm1, 0, 0, $Bm1W, $Bm1H, $GDIP_ILMREAD, $GDIP_PXF32RGB)
    $Stride = DllStructGetData($BitmapData1, "Stride")
    $Scan0 = DllStructGetData($BitmapData1, "Scan0")

    $ptr1 = $Scan0
    $size1 = ($Bm1H - 1) * $Stride + ($Bm1W - 1) * 4


    $Bm2W = _GDIPlus_ImageGetWidth($bm2)
    $Bm2H = _GDIPlus_ImageGetHeight($bm2)
    $BitmapData2 = _GDIPlus_BitmapLockBits($bm2, 0, 0, $Bm2W, $Bm2H, $GDIP_ILMREAD, $GDIP_PXF32RGB)
    $Stride = DllStructGetData($BitmapData2, "Stride")
    $Scan0 = DllStructGetData($BitmapData2, "Scan0")

    $ptr2 = $Scan0
    $size2 = ($Bm2H - 1) * $Stride + ($Bm2W - 1) * 4

    $smallest = $size1
    If $size2 < $smallest Then $smallest = $size2
    $call = DllCall("msvcrt.dll", "int:cdecl", "memcmp", "ptr", $ptr1, "ptr", $ptr2, "int", $smallest)



    _GDIPlus_BitmapUnlockBits($bm1, $BitmapData1)
    _GDIPlus_BitmapUnlockBits($bm2, $BitmapData2)

    Return ($call[0]=0)


EndFunc  ;==>CompareBitmaps

 

Any one got a clue why my script crashes? 

Share this post


Link to post
Share on other sites



Trying a little bump. 

I still haven't figured out why the second script works fine and the first doesn't.

They both call 

DllCall("msvcrt.dll", "int:cdecl", "memcmp", "ptr", $ptr1, "ptr", $ptr2, "int", $smallest)

The main difference is that the second script get the bitmap-handles from

$bm1 = _GDIPlus_ImageLoadFromFile($fname1)

while the first (which crashes) acquires the bitmap-handles using BitBlt and these lines:

Local $hDC = _WinAPI_GetDC($hDesktop)
Local $memDC = _WinAPI_CreateCompatibleDC($hDC)
Local $memBmp = _WinAPI_CreateCompatibleBitmap($hDC, $Width, $Height)
_WinAPI_SelectObject ($memDC, $memBmp)
_WinAPI_BitBlt($memDC, 0, 0, $Width, $Height, $hDC, 0,0, $__SCREENCAPTURECONSTANT_SRCCOPY)
_WinAPI_DeleteDC($memDC)
_WinAPI_ReleaseDC($hDesktop, $hDC)

Local $hImage1 = _GDIPlus_BitmapCreateFromHBITMAP($memBmp)

Anyone who can point me in the right direction on how to solve this problem? 

Share this post


Link to post
Share on other sites

One is a string of bytes and the other a handle?

 

1 person likes this

Share this post


Link to post
Share on other sites

Yeah, I agree it must be crashing because of some type mismatch.

However, shouldn't 

_GDIPlus_BitmapCreateFromHBITMAP($memBmp)

and 

_GDIPlus_ImageLoadFromFile($fname1)

both return a handle to a bitmap object ?

It seems so by the docs. What am I missing?

https://www.autoitscript.com/autoit3/docs/libfunctions/_GDIPlus_BitmapCreateFromHBITMAP.htm

https://www.autoitscript.com/autoit3/docs/libfunctions/_GDIPlus_ImageLoadFromFile.htm

Share this post


Link to post
Share on other sites

#6 ·  Posted (edited)

Its a while ago but maybe this one helps

https://www.autoitscript.com/autoit3/docs/libfunctions/_GDIPlus_BitmapCreateFromMemory.htm

and long ago I made this one

 

in that reference you see

If $BMPFile="SCREEN" Then
        $hbScreen=_ScreenCapture_Capture("",0,0,-1,-1,False)
        $pBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hbScreen); returns memory bitmap
    Else
;try to get a handle
        $handle = WinGetHandle($BMPFile)
        If @error Then
;Assume its an unknown handle so correct filename should be given
            $pBitmap = _GDIPlus_BitmapCreateFromFile($BMPFile)
        Else
            $hbScreen=_ScreenCapture_CaptureWnd("",$handle,0,0,-1,-1,False)
            $pBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hbScreen); returns memory bitmap
        EndIf
    EndIf

 

Edited by junkew

Share this post


Link to post
Share on other sites

Try this function instead:

Func _GDIPlus_ImageCompare($hImage1, $hImage2, $bFastCmp = True)
    Local Const $iW = _GDIPlus_ImageGetWidth($hImage1), $iH = _GDIPlus_ImageGetHeight($hImage1)
    If ($iW <> _GDIPlus_ImageGetWidth($hImage2)) Then Return SetError(1, 0, 0)
    If ($iH <> _GDIPlus_ImageGetHeight($hImage2)) Then Return SetError(2, 0, 0)
    Local $t = TimerInit()
    Local $tBitmapData1 = _GDIPlus_BitmapLockBits($hImage1, 0, 0, $iW, $iH, $GDIP_ILMREAD, $GDIP_PXF32ARGB)
    Local $tBitmapData2 = _GDIPlus_BitmapLockBits($hImage2, 0, 0, $iW, $iH, $GDIP_ILMREAD, $GDIP_PXF32ARGB)

    Local $iScan1 = DllStructGetData($tBitmapData1, "Scan0")
    Local $tPixel1 = DllStructCreate("int[" & $iW * $iH & "];", $iScan1)
    Local $iStride = Abs(DllStructGetData($tBitmapData1, "Stride"))

    Local $iScan2 = DllStructGetData($tBitmapData2, "Scan0")
    Local $tPixel2 = DllStructCreate("int[" & $iW * $iH & "];", $iScan2)

    If $bFastCmp Then
        $iResult = DllCall("msvcrt.dll", "int:cdecl", "memcmp", "ptr", $iScan1, "ptr", $iScan2, "int", ($iH - 1) * $iStride + ($iW - 1) * 4)[0]
    Else
        Local $iX, $iY, $iRowOffset, $iPixel1, $iPixel2, $aDiff[$iW * $iH + 1][3], $c = 1
        For $iY = 0 To $iH - 1
            $iRowOffset = $iY * $iW + 1
            For $iX = 0 To $iW - 1
                $iPixel1 = DllStructGetData($tPixel1, 1, $iRowOffset + $iX) ;get pixel color
                $iPixel2 = DllStructGetData($tPixel2, 1, $iRowOffset + $iX) ;get pixel color
                If $iPixel1 <> $iPixel2 Then
                    $aDiff[$c][0] = $iX & ", " & $iY
                    $aDiff[$c][1] = "0x" & Hex($iPixel1, 8)
                    $aDiff[$c][2] = "0x" & Hex($iPixel2, 8)
                    $c += 1
                EndIf
            Next
        Next
        $aDiff[0][0] = TimerDiff($t)
        $aDiff[0][1] = $iW
        $aDiff[0][2] = $iH
    EndIf

    _GDIPlus_BitmapUnlockBits($hImage1, $tBitmapData1)
    _GDIPlus_BitmapUnlockBits($hImage2, $tBitmapData2)

    If $bFastCmp Then Return SetError(0, Int(TimerDiff($t)), $iResult = 0)

    ReDim $aDiff[$c][3]
    Return $aDiff
EndFunc

 

1 person likes this

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

Thanks for pointing out that one returns a bitmap object and the other an image object, @junkew. I'm not really sure what the difference between those two are, but I guess they are different GDI+ objects.

I tried replacing the compare function in my code with @UEZ's _GDIPlus_ImageCompare and it just worked. I didn't have to do any "bitmap-object to image-object"-juggling. Amazing stuff and lightning fast. Thank you so much.

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

  • Similar Content

    • ogloed
      By ogloed
      Again, I'm struggling with DllCall(). So I have this MS C++ 6.0 compiled DLL and a manual for it. There's a function:
       
      Get information of disk arrays Declaration: VINT vr_get_array_info (VINT array_index, vr_array_info_t* pinfo); Description: Application can fetch the information of one specific disk array, which is located by index of all disk arrays in current system. Input parameters: VINT array_index : Index to all disk arrays in system, specify which disk array. vr_array_info_t *pinfo : 14 Pointer to a vr_array_info_t data structure to get the information Return value: VR_SUCCESS : Get the information successfully. VR_ERR_NOT_INITED : Raid lib hasn’t been initialized. VR_ERR_INVALID_INDEX : The input index is invalid. VR_ERR_INVALID_PARAM : Input parameter is invalid: the pointer is NULL. Here's what DLL Export Viewer says:
       
      Function Name     : int __cdecl vr_get_array_info(int,struct _vr_array_info *)
      Here's what is this _vr_array_info:
       
      typedef struct _vr_array_info { VWORD status; // current status of disk array VBYTE raidType;// same as Disk_Array.raidType, but value 0xFF means // a stand-alone disk. When it's a stand-alone disk, // only arDevices[0] and diskNum has meaning, and diskNum should // always be 1 . VBYTE diskNum;// count of valid arDevices[] members. // Note: disk array maybe incomplete, i.e. , some disk in the array maybe missing, // corresponding device ptr arDevices[i]->pRealDevice should be NULL. VDWORD capacityLow;// (Unit: sector) VDWORD capacityHigh;// (Unit: sector) // following 8 bytes define the real-capcity (in sector) of every disk in array VDWORD realCapacityLow; // (Unit: sector) VDWORD realCapacityHigh; // (Unit: sector) VDWORD stripeSize; // valid when raid is raid0, raid5 or raid01, in Kbytes VDWORD blockSize; // valid when raid is RAID5, in Kbytes VBOOL bNeedMigration; // the raid need migration // only valid when raid0/raid5/matrixRaid VBOOL bNeedInit; // the raid need initialization, only valid for RAID5 VBOOL bOptimized; // only for RAID5, this RAID5 access was optimized VBYTE systemDisk; /* does the devices within this disk array contain system files of current running OS ? the probably value are: VR_DEVICE_NOT_SYS_DISK VR_DEVICE_MAYBE_SYS_DISK VR_DEVICE_SYS_DISK they are defined in this file */ VWORD raid_index;// only raid index, no meaning with stand-alone disk VINT index; // all device index, including all raid and stand-alone disk } vr_array_info_t;

      Here's my code (function names are actually decorated, so):
       
      Local $pTest $hDLL = DllOpen(@ScriptDir & "\drvInterface.dll") ;~ VINT vr_init (void); ConsoleWrite("vr_init..." & @CRLF) $sTest = DllCall($hDLL, "int:cdecl", "?vr_init@@YAHXZ") ;~ VINT vr_get_controller_num (VINT *pnumber); ConsoleWrite("vr_get_controller_num..." & @CRLF) $sTest = DllCall($hDLL, "int:cdecl", "?vr_get_controller_num@@YAHPAH@Z", "int*", "$pTest") $iControllerNumber = $sTest[1] ConsoleWrite("$iControllerNumber = " & $iControllerNumber & @CRLF) ;~ VINT vr_get_device_num (VINT *pnumber); ConsoleWrite("vr_get_device_num..." & @CRLF) $sTest = DllCall($hDLL, "int:cdecl", "?vr_get_device_num@@YAHPAH@Z", "int*", "$pTest") $iDeviceNumber = $sTest[1] ConsoleWrite("$iDeviceNumber = " & $iDeviceNumber & @CRLF) ;~ VINT vr_get_array_num (VINT only_raid, VINT *pnumber); ConsoleWrite("vr_get_array_num..." & @CRLF) $sTest = DllCall($hDLL, "int:cdecl", "?vr_get_array_num@@YAHHPAH@Z", "int", 0, "int*", "$pTest") $iArrayNumber = $sTest[2] ConsoleWrite("$iArrayNumber = " & $iArrayNumber & @CRLF) $vr_array_info = DllStructCreate("ushort status;byte raidType;byte diskNum;dword capacityLow;dword capacityHigh;dword stripeSize;dword blockSize;boolean bNeedMigration;boolean bNeedInit;boolean bOptimized;byte systemDisk;byte raid_index;int index") ;~ VINT vr_get_array_info (VINT array_index, vr_array_info_t* pinfo); ConsoleWrite("vr_get_array_info..." & @CRLF) $sTest = DllCall($hDLL, "int:cdecl", "?vr_get_array_info@@YAHHPAU_vr_array_info@@@Z", "int", 0, "struct*", $vr_array_info) ;~ void vr_exit (void); ConsoleWrite("vr_exit..." & @CRLF) $sTest = DllCall($hDLL, "none", "?vr_exit@@YAXXZ") DllClose($hDLL) Exit Everything works fine up to vr_get_array_info part. This is where I get a "memory cannot be 'read'" Windows error ("Instruction at 0x7c93a514 referenced memory at 0x00000000").

      What am I doing wrong? Please help.
      drvInterface.dll
      ProgGuide.pdf
    • ogloed
      By ogloed
      Hello!
      There's a DLL, which I want to use in my script. There's the manual for that DLL. I'm trying to use any function from the manual in DllCall() and I get @error=3 ("function" not found in the DLL file). Why is that? How do I fix it?
      PEiD says that DLL is "Microsoft Visual C++ 6.0 DLL". Is that compatible with AutoIt? How do I use it? Please help.
       
      ProgGuide.pdf
      drvInterface.dll
    • timmalos
      By timmalos
      Hello all . i'm trying to build a tool to generate some pictures/graphs automatically based on some variables. I have completed the first part and I'm struggling now on what I thought would be the easiest part :
      In my tool there is a "preview" button, which helps to display on a new children GUI the generated picture. What I do in simplified :
      Global $g_hGraphics = _GDIPlus_GraphicsCreateFromHWND($g_hGUI) ;Then, I'll add multiple objects to this graphic, for example $Temp = _GDIPlus_ImageLoadFromFile($temp[1]) _GDIPlus_GraphicsDrawImage($g_hGraphics,$temp)  
      I'm then able to have a preview GUI showing all my elements perfectly. This works but now I want to be able to really generate it and have a ".png" file written to disk.
      What I tried without success:
      $g_hBitmap = _GDIPlus_BitmapCreateFromGraphics($previewWidth, $previewHeight, $g_hGraphics) _GDIPlus_ImageSaveToFile($g_hBitmap,$OUTPUTDIR & "\generated\test-"&@HOUR&@MIN&@SEC&".png") But what I write to file is a transparent PNG without anything.
      I'm pretty sure I'm missing something obvious there, I need your help
       
       
      NB : If this change your answer, my goal will be to generate transparent png, meaning I'll add multiple objects/pictures/texts on a transparent png background and if there are some "transparent holes" I need them to be transparent in the generated png.
       
      Thanks a lot for any help !
      Tim
    • Hammerfist
      By Hammerfist
      Hi guys.
      I try to choose an algorithm to compress some string data in-memory, without file medium to store in sqlite as blob. I plan to store about 100k blobs each 80k symbols (uncompressed). First opinion was LZMA udf by Ward, but udf file doesn't exist.
      Then i tried to use BZIP2 dll in DllCall, but it simply doesn't work and throws @error = 1.
       
      Here is an example:
       
      What should I do with this error? Where am I wrong?
      Thanks.
    • timmalos
      By timmalos
      Hello all.
      I'm trying to do something simple, but I can't manage to find where to start since I can have multiple possibilities and not sure what is the best one :
      Taking screen capture (I can do that) On this capture, I'll look for 10 particular regions that will give me 10 images of 40px * 40px (I can do that) Then what I want is for each of these regions, find on my library of ~140 images which one is the closest one to my region (= the match)  
      What do you think is the best approach ? Ican easily do step 1 and 2, however for the third step :
      I can't use memcmp because they won't be exactly the same (original library can differ a very little bit from what I'll get from my screen capture) There is no text, only drawing Should I do a pixelSum of what I get on screen and do the same via GDIPlus for each of my images in the library?  
      Thanks a lot,
      Tim