not depending on 3rd party dll's
Pentium 4 of 3 GHz takes
* about 4.5 seconds on a 1280*1024 desktop to find a 260*260 bitmap. Partial match 0.5 seconds
* about 1 seconds on a 1280*1024 desktop to find a 20*17 bitmap. Partial match 0.5 seconds
Some references I used to come up with this solution
* code to get a smaller bitmap: http://www.autoitscript.com/forum/index.ph...=pixelchecksum)
* Pixelchecksum seems not to do the full job but in combination with below it can become very fast: http://www.autoitscript.com/forum/index.ph...l=pixelchecksum
* A pixel by pixel searcher http://www.autoitscript.com/forum/index.ph...&hl=compare
* UDF Get or Read Pixel from Memory
* GetDIBits
Not fully finished but all comments/enhancements are appreciated
* Save to file or directly on special window description SCREEN, [ACTIVE] or name of picture file
* Searching is done on a line by line basis instead of pixel by pixel
* Bitmap to search in example program should be fully visible to work for first 3 examples as it are screenshots taken. Same applies for 3 in AUTOIT homepage
* Speed depends on the size of the bitmap you are searching for but is about linear and < 5 seconds
* Some doubts on left and top returned (sometimes seems to be off by 1 or several pixels)
* Transparency (when search letters on a different background) http://www.winprog.org/tutorial/transparency.html
* Match quicker by doing a bitblt with srcinvert when first line is found (instead of checking line by line)
* $BMP1 and $BMP2 to be HBITMAP handle as input instead of filenames (will make searching within partial screen
Sample to work
* Install pics.zip into %temp% / Save BMP attachments for example to @tempdir (%temp%) for example to work
Sample code
Opt('MustDeclareVars', 1) ;#include <GUIConstants.au3> #include <GDIPlus.au3> #Include <ScreenCapture.au3> #include <string.au3> ;~ If debugging output is wanted to understand and analyse the algorithm put this on true/false Const $bDebug=True ;~ Const $bDebug=False ;~ Const $cMatchLinePercentage=0.97 ;Minimum matching, put on 1 if exact matching is needed on all lines Const $cMatchLinePercentage=0.99 ;Minimum matching, put on 1 if exact matching is needed on all lines ;~ Constants for type of picture matching const $c24RGBFullMatch=1 ;Load as 24 bits and full match const $c24RGBPartialMatch=2 ;Load as 24 bits and partial match const $c16RGBFullMatch=3 ;Load as 16 bits and full match const $c16RGBPartialMatch=4 ;Load as 16 bits and partial match ; ** Example start ** ; Screen samples Global Const $Bitmap1Filename = @scriptDir & "\FULLSCREEN.BMP" Global Const $Bitmap2Filename = @scriptDir & "\CALCULATOR.BMP" Global Const $Bitmap3Filename = @scriptDir & "\BACKSPACE.BMP" ;BMP samples Global Const $Bitmap4Filename = @scriptDir & "\7WDS_BW.BMP" Global Const $Bitmap5Filename = @scriptDir & "\SEVEN_BW.BMP" Global Const $Bitmap6Filename = @scriptDir & "\CAT_BW.BMP" Global Const $Bitmap7Filename = @scriptDir & "\AUTOIT3.bmp";Make sure the homepage of autoit with 3 is visible local $calcHWND, $begin, $pos, $aWinPos, $aWinCSize, $start local $pBitmap, $BitmapData, $pixelFormat Opt("WinTitleMatchMode", 4);Matching windows by advanced options ; Initialize GDI+ library _GDIPlus_Startup () ;Calculator, make sure its started $calcHWND = WinGetHandle("[REGEXPCLASS:.*Calc.*]") If $calcHWND = "" Then Run("calc.exe") Sleep(2000) $calcHWND = WinGetHandle("[REGEXPCLASS:.*Calc.*]") EndIf ;Capture the calculator screen $begin = TimerInit() winactivate($calcHWND) Sleep(500) _ScreenCapture_CaptureWnd($Bitmap2Filename, $calcHWND,0,0,-1,-1,False) ConsoleWrite("Saving calculator window " & TimerDiff($begin) & " milliseconds " & @LF) winmove("[ACTIVE]","",223,341) Sleep(500) ;Capture an area within the calculator area backspace $begin = TimerInit() ;~ $pos=controlgetpos("","","[CLASS:Button; TEXT:BACKSPACE]") $pos=controlgetpos("","","[CLASS:Button; INSTANCE:2]") $aWinPos = WinGetPos($calchWnd) $aWinCSize = WinGetClientSize($calchWnd) ;Q&D calculation of offsets to capture _ScreenCapture_Capture($Bitmap3Filename, ($aWinPos[0]+$pos[0]) + ($aWinPos[2] - $aWinCSize[0])-3, ($awinpos[1]+$pos[1])+ ($aWinPos[3]-$aWinCSize[1])-3, $aWinPos[0]+$pos[0]+$pos[2]+3, ($awinpos[1]+$pos[1])+ ($aWinPos[3]-$aWinCSize[1])+$pos[3]-3,false) ConsoleWrite("Saving backspacebutton " & TimerDiff($begin) & " milliseconds " & @LF) ; Capture full screen $begin = TimerInit() _ScreenCapture_Capture($Bitmap1Filename,0,0,-1,-1,False) ConsoleWrite("Saving full screen took " & TimerDiff($begin) & " milliseconds " & @LF) func filegetname($f) local $i $i=stringinstr($f,"\",false,-1) if $i > 0 Then return stringmid($f,$i+1) Else return $f EndIf EndFunc ;Do the actual find Func FindTester($BMP1, $BMP2, $Bool) local $tResult $start = TimerInit() $tResult=findBMP($BMP1,$BMP2, $Bool) ConsoleWrite($tResult & " " & FileGetName($BMP2) & " in " & FileGetName($BMP1) & " ** matchtype " & $Bool & " time elapsed: " & TimerDiff($start) & " milliseconds" & @LF) EndFunc if not $bDebug=True Then ; Not very usefull to find full screen and tricky as screen is most likely changed (clock, output from application etc.) ; findTester("SCREEN",$Bitmap1Filename,TRUE);Find the full screen itself ; findTester($Bitmap1Filename,$Bitmap1Filename,$c24RGBFullMatch);Find the full screen itself 31 seconds findTester($Bitmap1Filename,$Bitmap1Filename,$c24RGBPartialMatch);Find the full screen itself 2.8529088255245 seconds ; findTester($Bitmap1Filename,$Bitmap1Filename,$c16RGBFullMatch);Find the full screen itself 21 seconds findTester($Bitmap1Filename,$Bitmap1Filename,$c16RGBPartialMatch);Find the full screen itself 1 seconds ; Be aware that overlapping windows, moving things on screen can make it difficult to find on full screen findTester("SCREEN",$Bitmap2Filename,$c24RGBFullMatch);Find the full calculatorscreen findTester("SCREEN",$Bitmap2Filename,$c24RGBPArtialMatch);Find the full calculatorscreen with partial match findTester("SCREEN",$Bitmap2Filename,$c16RGBFullMatch);Find the full calculatorscreen findTester("SCREEN",$Bitmap2Filename,$c16RGBPArtialMatch);Find the full calculatorscreen with partial match findTester($Bitmap1Filename,$Bitmap2Filename,$c16RGBFullMatch);Find the full calculatorscreen findTester($Bitmap1Filename,$Bitmap2Filename,$c16RGBPartialMatch);Find the full calculatorscreen with partial match findTester($Bitmap2Filename,$Bitmap3Filename,$c24RGBFullMatch);Find the backspace button findTester($Bitmap2Filename,$Bitmap3Filename,$c24RGBPartialMatch);Find the backspace button with partial match findTester($Bitmap2Filename,$Bitmap3Filename,$c16RGBFullMatch);Find the backspace button findTester($Bitmap2Filename,$Bitmap3Filename,$c16RGBPartialMatch);Find the backspace button with partial match winactivate($calcHWND);Make sure calculator is active on the screen findTester("[ACTIVE]",$Bitmap3Filename,$c24RGBFullMatch);Find the backspace button findTester("[ACTIVE]",$Bitmap3Filename,$c24RGBPartialMatch);Find the backspace button with partial match findTester("[ACTIVE]",$Bitmap3Filename,$c16RGBFullMatch);Find the backspace button on active screen findTester("[ACTIVE]",$Bitmap3Filename,$c16RGBPartialMatch);Find the backspace button with partial match findTester("SCREEN",$Bitmap3Filename,$c24RGBFullMatch);Find the backspace button findTester("SCREEN",$Bitmap3Filename,$c24RGBPartialMatch);Find the backspace button with partial match findTester("SCREEN",$Bitmap3Filename,$c16RGBFullMatch);Find the backspace button findTester("SCREEN",$Bitmap3Filename,$c16RGBPartialMatch);Find the backspace button with partial match findTester($Bitmap4Filename,$Bitmap5Filename,$c16RGBFullMatch);Find the seven findTester($Bitmap4Filename,$Bitmap5Filename,$c16RGBPartialMatch);Find the seven with partial match findTester($Bitmap4Filename,$Bitmap6Filename,$c16RGBFullMatch);Find the cat findTester($Bitmap4Filename,$Bitmap6Filename,$c16RGBPartialMatch);Find the cat with partial match findTester($Bitmap1Filename,$Bitmap7Filename,$c24RGBFullMatch);Find the 3 of Autoit Homepage findTester($Bitmap1Filename,$Bitmap7Filename,$c24RGBPartialMatch);Find the 3 of Autoit Homepage findTester($Bitmap1Filename,$Bitmap7Filename,$c16RGBFullMatch);Find the 3 of Autoit Homepage findTester($Bitmap1Filename,$Bitmap7Filename,$c16RGBPartialMatch);Find the 3 of Autoit Homepage Else ;~ findTester($Bitmap4Filename,$Bitmap6Filename,$c24RGBFullMatch);Find the cat ;~ findTester($Bitmap1Filename,$Bitmap2Filename,$c24RGBFullMatch);Find the full calculatorscreen findTester("SCREEN",$Bitmap2Filename,$c24RGBFullMatch);Find the full calculatorscreen EndIf _GDIPlus_Shutdown() ;** Example end **
Actual functions
;=============================================================================== ; Function Name: findBMP ; Description: Finds a bitmap (.BMP) in another BMP file (other formats PNG and TIFF work also other formats less usefull GIF, JPEG, Exif, WMF, and EMF should work) ; Syntax: findBMP($BMP1, $BMP2, $MatchType=TRUE) ; ; Parameter(s): $BMP1 = Filename of bitmap to search in ; $BMP2 = Filename of bitmap to search for ; $MatchType = c24RGBFullMatch, c24RGBPartialMatch, c16RGBFullMatch, c16RGBPartialMatch ; ; Return Value(s): On Success: = Returns Array List ; On Failure: = @error 1 (Control was found but there was an error with the DLLCall) ; ; Author(s): JunkEW ; ; Note(s): ; * Its never an exact match even with TRUE as last few bits are disposed in algorithm and lines below ; are not checked under assumption that those are 99.99% of the time correct ; * locking bits overview http://www.bobpowell.net/lockingbits.htm ; ToDo: ; * Transparency (when search letters on a different background) http://www.winprog.org/tutorial/transparency.html ; * Match quicker by doing a bitblt with srcinvert when first line is found (instead of checking line by line) ; * $BMP1 and $BMP2 to be HBITMAP handle as input instead of filenames (will make searching within partial screen easier) ; Example(s): ; ;=============================================================================== Func findBMP($BMP1, $BMP2, $MatchType=$c24RGBFullMatch) Dim $fLine[1];Line number of found line(s), redimmed when second picture size is known Dim $BMP1Data="", $BMP1Width=0, $BMP1Height=0, $BMP1LineWidth=0; Dim $BMP2Data="", $BMP2Width=0, $BMP2Height=0, $BMP2LineWidth=0 Dim $foundAt = "", $matchPossible=FALSE, $matchedLines=0, $foundAtLeft=-1, $foundAtTop=-1 Dim $bestMatchLine=-1, $HighestMatchingLines=-1; For knowing when no match is found where best area is Dim $iPos=1; dim $imgBytes, $avData; local $iFuzzyDist, $searchFor, $iAbove, $bMatchPossible, $aboveLine local $j, $imgBits local $tDebug if ($MatchType=$c24RGBFullMatch) or ($matchtype=$c24RGBPartialMatch) then $imgBytes=3 Else $imgBytes=2 endif ; Load the bitmap to search in getImage($BMP1, $BMP1Data, $BMP1Width, $BMP1Height, $BMP1LineWidth, $imgBytes) $BMP1Data = BinaryToString($BMP1Data) ; Load the bitmap to find getImage($BMP2, $BMP2Data, $BMP2Width, $BMP2Height, $BMP2LineWidth, $imgBytes) ;Make it strings to be able to use string functions for searching $BMP2Data = BinaryToString($BMP2Data) if $bDebug=True Then ;~ START debugging ;Get some debugging information FileDelete(@ScriptDir & "\BMP1DATA.TXT") FileDelete(@ScriptDir & "\BMP2DATA.TXT") FileDelete(@ScriptDir & "\COMPAREDLINES.TXT") consolewrite($BMP1Width & @crlf) consolewrite($BMP1Height & @crlf) consolewrite($BMP1LineWidth & @crlf) consolewrite(stringlen($BMP1Data) & @crlf) consolewrite(".{" & $BMP1LineWidth & "}"& @CRLF) consolewrite(".{" & $BMP2LineWidth & "}"& @CRLF) $tDebug=StringRegExpReplace(_stringtohex($BMP1Data),"(.{" & $BMP1LineWidth*2 & "})","$1" & @CRLF) consolewrite(@error & @extended & @CRLF) FileWrite (@ScriptDir & "\BMP1DATA.TXT" , $tdebug) $tDebug=StringRegExpReplace(_stringtohex($BMP2Data),"(.{" & $BMP2LineWidth*2 & "})","$1" & @CRLF) consolewrite(@error & @extended & @CRLF) FileWrite (@ScriptDir & "\BMP2DATA.TXT" , $tdebug) ;~ END debugging EndIf ;For reference of line where in BMP2FindIn a line of BMP2Find was found If $BMP2Height = 0 Then SetError(1,0,0) Return False EndIf ReDim $fline[$BMP2Height] ;If exact match check every 1 line else do it more fuzzy (as most likely other lines are unique) if ($MatchType=$c24RGBFullMatch) or ($matchtype=$c16RGBFullMatch) Then $iFuzzyDist = 1 Else ;Check fuzzy every 10% of lines $iFuzzyDist = ceiling(($bmp2height * 0.1)) endIf $begin = TimerInit() ;Look for each line of the bitmap if it exists in the bitmap to find in ;~ Split bitmap to search in lines ;~ $avData=stringregexp($BMP2Data, ".{1," & $BMP2lineWidth & "}+", 3) For $i = 0 To $BMP2Height - 1 ;Minus imgbytes as last bits are padded with unpredictable bytes (24 bits image assumption) or 2 when 16 bits $searchFor = StringMid($BMP2Data, 1 + ($i * $BMP2lineWidth), ($BMP2lineWidth - $imgBytes)) ;~ $searchfor=stringleft($avData[$i],$BMP2lineWidth - $imgBytes) $iPos = StringInStr($BMP1Data, $searchFor, 2, 1, $iPos) ; $iPos = StringInStr($BMP1Data, $searchFor) ;Look for all lines above if there is also a match ;Not doing it for the lines below as speed is more important and risk of mismatch on lines below is small $iAbove=1 if $iPos > 0 then $bMatchPossible=True $matchedLines=1;As first found line is matched we start counting ;Location of the match $foundAtTop = Int($iPos / $BMP1lineWidth) -$i $foundAtLeft = int(mod($iPos,$bmp1linewidth) / $imgBytes) Else ;~ No 1st line found so far nothing to start matching $bMatchPossible=false exitloop endif while (($i+$iAbove) <= ($BMP2Height -1)) and ($bMatchPossible=True) $searchFor = StringMid($BMP2Data, 1 + (($i + $iAbove) * $BMP2lineWidth), ($BMP2lineWidth - $imgBytes)) ;~ $searchfor = stringleft($avData[$i+$iAbove],$BMP2lineWidth - $imgBytes) $aboveLine = stringmid($BMP1Data,$iPos + ($iAbove * $BMP1LineWidth), ($BMP2LineWidth - $imgBytes)) if $bDebug=True Then ;~ START debugging $tDebug=StringRegExpReplace(_stringtohex($searchfor),"(.{8})","$1_") FileWrite (@ScriptDir & "\COMPAREDLINES.TXT" , $tDebug & @crlf) $tDebug=StringRegExpReplace(_stringtohex($aboveline),"(.{8})","$1_") FileWrite (@ScriptDir & "\COMPAREDLINES.TXT" , $tDebug & @crlf) If $aboveLine <> $searchFor Then FileWrite (@ScriptDir & "\COMPAREDLINES.TXT" , "** MISMATCH ** of line " & (1+$iAbove) & " in 2nd bitmap at line " & ($foundAtTop + $i) & @crlf) endIf ;~ END debugging EndIf if comparePicLine($aboveline,$searchfor) = false then $bMatchPossible=False ;To remember the area with the best match if $matchedLines >= $HighestMatchingLines Then $HighestMatchingLines = $matchedLines ;Best guess of location ;~ $foundAtTop = $fline[$i] + $i - $BMP2Height $foundAtTop = Int($iPos / $BMP1lineWidth);+ $i - $BMP2Height $bestMatchLine = Int($iPos / $BMP1lineWidth) EndIf ExitLoop EndIf $matchedLines=$matchedLines + 1 $iAbove=$iAbove+$iFuzzyDist WEnd ;If bMatchPossible is still true most likely we have found the bitmap if $bmatchPossible = True then ;~ ConsoleWrite("Could match top: " & $foundAtTop & " left: " & $foundAtLeft & " in " & TimerDiff($begin) / 1000 & " seconds" & @LF) ; MouseMove($foundatleft,$foundatTop) exitloop else ;~ consolewrite("i not matched " & $ipos & " " & $matchedlines & @crlf ) EndIf Next ;For some debugging of time ; if $bMatchPossible = True Then ; ConsoleWrite("Searching took " & TimerDiff($begin) / 1000 & " seconds " & @LF) ; Else ; ConsoleWrite("NOT FOUND Searching took " & TimerDiff($begin) / 1000 & " seconds" & @LF) ; endif ;Return an error if not found else return an array with all information if $bMatchPossible = False Then SetError(1, 0, 0) endif ; return stringsplit($bMatchPossible & ";" & $matchedLines & ";" & $foundAtLeft & ";" & $foundAtTop & ";" & $bmp2width & ";" & $BMP2Height & ";" & $HighestMatchingLines & ";" & $bestMatchLine,";") return $bMatchPossible & ";" & $matchedLines & ";" & $foundAtLeft & ";" & $foundAtTop & ";" & $bmp2width & ";" & $BMP2Height & ";" & $HighestMatchingLines & ";" & $bestMatchLine EndFunc;==>findBMP func comparePicLine($s1,$s2) local $sLen, $iMatched if $s1=$s2 Then return True Else $iMatched=0 $sLen=stringlen($s1) for $tJ=1 to $slen if stringmid($s1,$tJ,1)=stringmid($s2,$tJ,1) Then $iMatched=$imatched+1 Else if $bDebug=True Then ;~ START debugging FileWrite (@ScriptDir & "\COMPAREDLINES.TXT" , "Debug mismatch pixel " & $tj & "=" & int($tj/4) & @crlf) endif endif Next if ($iMatched / $sLen ) > $cMatchLinePercentage then return true Else return false endif EndIf endfunc Func GetImage($BMPFile, byref $BMPDataStart, byref $Width, byRef $Height, byref $Stride, $imgBytes=3) local $Scan0, $pixelData, $hbScreen, $pBitmap, $pBitmapCap, $handle ; Load the bitmap to search in 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 ;Get $tagGDIPBITMAPDATA structure ;~ ConsoleWrite("Bitmap Width: " & _GDIPlus_ImageGetWidth($pBitmap) & @CRLF ) ;~ ConsoleWrite("Bitmap Height: " & _GDIPlus_ImageGetHeight($pBitmap) & @CRLF) ;~ 24 bits (3 bytes) or 16 bits (2 bytes) comparison if ($imgBytes=2) then $BitmapData= _GDIPlus_BitmapLockBits($pBitmap, 0, 0, _GDIPlus_ImageGetWidth($pBitmap), _GDIPlus_ImageGetHeight($pBitmap), $GDIP_ILMREAD, $GDIP_PXF16RGB555) ;~ $BitmapData= _GDIPlus_BitmapLockBits($pBitmap, 0, 0, _GDIPlus_ImageGetWidth($pBitmap), _GDIPlus_ImageGetHeight($pBitmap), $GDIP_ILMREAD, $GDIP_PXF32ARGB) Else $BitmapData= _GDIPlus_BitmapLockBits($pBitmap, 0, 0, _GDIPlus_ImageGetWidth($pBitmap), _GDIPlus_ImageGetHeight($pBitmap), $GDIP_ILMREAD, $GDIP_PXF24RGB) ;~ $BitmapData= _GDIPlus_BitmapLockBits($pBitmap, 0, 0, _GDIPlus_ImageGetWidth($pBitmap), _GDIPlus_ImageGetHeight($pBitmap), $GDIP_ILMREAD, $GDIP_PXF32ARGB) endIf If @ERROR Then MsgBox(0,"","Error locking region " & @error) $Stride = DllStructGetData($BitmapData, "Stride");Stride - Offset, in bytes, between consecutive scan lines of the bitmap. If the stride is positive, the bitmap is top-down. If the stride is negative, the bitmap is bottom-up. $Width = DllStructGetData($BitmapData, "Width");Image width - Number of pixels in one scan line of the bitmap. $Height = DllStructGetData($BitmapData, "Height");Image height - Number of scan lines in the bitmap. $PixelFormat = DllStructGetData($BitmapData, "PixelFormat");Pixel format - Integer that specifies the pixel format of the bitmap $Scan0 = DllStructGetData($BitmapData, "Scan0");Scan0 - Pointer to the first (index 0) scan line of the bitmap. $pixelData = DllStructCreate("ubyte lData[" & (abs($Stride) * $Height-1) & "]", $Scan0) $BMPDataStart = $BMPDataStart & DllStructGetData($pixeldata,"lData") _GDIPlus_BitmapUnlockBits($pBitmap, $BitmapData) _GDIPlus_ImageDispose ($pBitmap) _WinAPI_DeleteObject ($pBitmap) EndFunc;==>GetImage
Edit: Speed and partial match enhancements. Search is within a split second especially when partial match is turned on
Edit2: Cleaned up code in a nicer way, returns best match results even if find is false
Edit 24 march 2008: Becomes dependent on beta v3.2.11.5 (beta)
;$iPos = StringInStr($BMP1Data, $searchFor)
$iPos = StringInStr($BMP1Data, $searchFor, 2, 1, $iPos+1)
Edit 13th june 2008 updated for new autoit version
Edit 31st august 2008 added some reference links
Edit 04 sept 2008 completely rewritten using GDI plus functions and speedenhancements by using different 24 bits and 16 bits pictures
Edit 23 nov 2008 small additional check on bmp2height beeing 0
Edit 1st dec 2008 fixed top/left, added [active] find on active screen, work with window special description
Edit 20st feb 2009 fixed Stringinstr to use additional parameters, now matchin b/w pictures better
$iPos = StringInStr($BMP1Data, $searchFor, 2, 1, $iPos)
Edit jan 5th 2013 full updated all working under Windows 7 / 64 bits (not tested under windows XP but no reason to be broken)
see also http://www.autoitscript.com/forum/topic/146996-find-image-in-image-loop/
works with BMP, PNG and TIFF
added threshold for small bits off on a single line (unclear why GDI writes those pixels/shade variation)
speed I assume is no issue for nowadays CPU power
Attached Files
Edited by junkew, 10 February 2013 - 04:01 PM.








