argumentum Posted November 7 Share Posted November 7 5 minutes ago, ioa747 said: Here it is the edition with a single column patern ..the memory use increase is slower in this 5.0.1 version. ioa747 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
ioa747 Posted November 7 Share Posted November 7 i make the aNumber array Local Static I did a test on a sample of 1700 images (1700 * 3 = 5100 numbers) from: 103.986 seconds to: 102.365 seconds update the script Hashim 1 I know that I know nothing Link to comment Share on other sites More sharing options...
argumentum Posted November 7 Share Posted November 7 4 minutes ago, ioa747 said: from: 103.986 seconds to: 102.365 seconds ... ; Generate patterns for each found number For $i = 1 To $aNum[0][0] Local $sPattern = "" For $y = $firstRow To $lastRow For $x = $aNum[$i][0] To $aNum[$i][1] - 1 $sPattern &= ($aColorMap[$x][$y] = $hColor ? "1" : ".") ; shave a nanosecond ? Next $sPattern &= @CRLF Next $sNumber &= _GetPattern($sPattern) Next ... ioa747 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
argumentum Posted November 7 Share Posted November 7 Quote lol. It is faster I feel that we all in this thread are getting off course. The next problem the OP presented, was not the speed, but the crash. Hashim 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Werty Posted November 7 Share Posted November 7 (edited) 6 minutes ago, argumentum said: The next problem the OP presented, was not the speed, but the crash. @Nine fixed that earlier, now we are just playing around, OP has gotten above 15k, and is probably having his computer running for days now with no time to try out our new examples, we may probably come up with a solution that would finish faster than the script he has running now. Just like Voyager 1 will be surpassed by spaceships we build later. Edited November 7 by Werty argumentum, ioa747 and Hashim 2 1 Some guy's script + some other guy's script = my script! Link to comment Share on other sites More sharing options...
junkew Posted November 7 Share Posted November 7 (edited) Wouldn't it be easier to see if it can be read by uiautomation or other accessibility tools. try inspect.exe or simplespy (see faq in signature) It seems to be a certain Delphi version that the tool.exe was build in (https://www.embarcadero.com/products/delphi/starter) This imagerecognition is probably much faster in Delphi or C# I remembered I build this 15 years ago 😉 for a similar purpose. And for fun some reference on analog digits on screen for having fun to recognize with pixel checking 😉 expandcollapse popup#include <GDIPlus.au3> #include <WindowsConstants.au3> #include <GUIConstantsEx.au3> Dim $ws[12] = [0, 0, 10, -7, 50, -7, 60, 0, 50, 7, 10, 7] ;Koordinaten Polygonzug: von links nach rechts WAAGRECHTER Balken, von rechts nach links SENKRECHTER Balken Dim $ziffer[10] = [239, 10, 118, 94, 154, 220, 253, 14, 254, 222] ;alle gesetzten bits des Indexes in der 7-segmentanzeige ergeben die Ziffer Dim $balkenpos[8] = [0, "60.0", "0.0", "60.60", "0.60", "0.60", "0.120", "0.0"] ;position der Leuchtbalken der 7-Segmentanzeige innerhalb der Ziffer Dim $p[7][2] ;nimmt die polygonzug-koordinaten zum Zeichnen auf $p[0][0] = 6 ;6 Punkte im Polygonzug _GDIPlus_Startup() Global $hgui = GUICreate('Uhr', 620, 175, -1, -1, $WS_POPUP) ;GUI erstellen ohne Rahmen Global $hWnd = WinGetHandle($hgui) ;Handle holen Global $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hWnd) ;"Leinwand" erstellen, auf der gezeichnet werden kann GUICtrlCreateLabel("", 0, 0, 620, 175, Default, $GUI_WS_EX_PARENTDRAG) ;verschieben des Fensters möglich machen durch 2. Fenster GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT) ;das 2. Fenster transparent machen GUISetBkColor(0x000000) ;Hintergrund der GUI schwarz GUISetState() ;GUI anzeigen AdlibRegister("_time", 1000) ;jede Zehntelsekunde ein Refresh der Zeitanzeige Do ;Endlosschleife, solange bis.. Until GUIGetMsg() = -3 ;..ESC gedrückt wird _GDIPlus_GraphicsDispose($hGraphic) ;freigeben _GDIPlus_Shutdown() Func _time() ;Uhrzeit anzeigen :o) For $k = 1 To 6 ;die 6 Ziffern der Uhrzeit $setbits = $ziffer[StringMid(String(@HOUR & @MIN & @SEC), $k, 1)] ;gesetzte Bits in der siebensegmentanzeige anhand der Ziffer in der Uhrzeit holen For $bitnr = 7 To 1 Step -1 ;alle Bits durchlaufen _drawpolygon(BitAND($setbits, 128), $k * 100 - 80 + ($k = 1 Or $k = 3 Or $k = 5) * 20, $bitnr) ;parameter: bit gesetzt ja/nein, position der gesamten ziffer,nummer des bits(gerade=waagrechter balken, ungerade=senkrechter balken) $setbits = BitShift($setbits, -1) ;nächstes Bit holen Next Next $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + (@SEC / 2 = Int(@SEC / 2)) * 0xBB0000) ;Pinsel erstellen, wenn Ziffer gerade, dann farbig, ansonsten schwarz _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 55, 15, 15, $brush) ;Punkte zeichnen _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 55, 15, 15, $brush) _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 105, 15, 15, $brush) _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 105, 15, 15, $brush) _GDIPlus_BrushDispose($brush) ;Pinsel auflösen EndFunc ;==>_time Func _drawpolygon($bit, $xpos, $bitnr) ;zeichnet einen polygonzug ("Balken") an die entsprechende Position $split = StringSplit($balkenpos[$bitnr], ".", 3) ;x- und y-koordinaten des Balkens innerhalb der Ziffer holen $b = (($bitnr / 2) = Int($bitnr / 2)) ;$bit gerade => $b = true => Balken waagrecht, ansonsten Balken senkrecht $step = -1 + 2 * $b ;schrittweite durch das $WS-Array For $i = 11 - 11 * $b To 11 * $b Step 2 * $step ;array mit waagrechten (bit=gerade) oder (Bit=ungerade) senkrechten Balken füllen $r = Int(Abs((11 * (Not ($b))) - $i) / 2) + 1 ;abhängig von der Reihenfolge, egal ob $i von 0 bis 11 oder von 11 bis 0, $r muss immer 1,2,3,4,5,6 $p[$r][0] = $ws[$i] + $split[0] + $xpos ;x- und $p[$r][1] = $ws[$i + $step] + $split[1] + 30 ;y-position in das polygonarray schreiben Next $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + ($bit <> 0) * 0xBB0000) ;wenn bit gesetzt, dann farbig, ansonsten schwarz _GDIPlus_GraphicsFillPolygon($hGraphic, $p, $brush) ;Balken zeichnen _GDIPlus_BrushDispose($brush) EndFunc ;==>_drawpolygon Edited November 7 by junkew Clock reference analog digits Hashim 1 FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
argumentum Posted November 8 Share Posted November 8 (edited) 7 hours ago, junkew said: Clock reference analog digits Spoiler expandcollapse popup#include <GDIPlus.au3> #include <WindowsConstants.au3> #include <GUIConstantsEx.au3> ;~ Global Const $iColorBright = 0xFF000099, $iColorDimmed = 0xFF000044 ; blue Global Const $iColorBright = 0xFF006600, $iColorDimmed = 0xFF002200 ; green ;~ Global Const $iColorBright = 0xFF880000, $iColorDimmed = 0xFF330000 ; red Dim $ws[12] = [0, 0, 10, -7, 50, -7, 60, 0, 50, 7, 10, 7] ;Coordinates of polyline: from left to right HORIZONTAL bar, from right to left VERTICAL beam Dim $digit[10] = [239, 10, 118, 94, 154, 220, 253, 14, 254, 222] ;all set bits of the index in the 7-segment display result in the digit Dim $barkenpos[8] = [0, "60.0", "0.0", "60.60", "0.60", "0.60", "0.120", "0.0"] ;position of the light bars of the 7-segment display within the number Dim $p[7][2] ;takes the polygon coordinates for drawing $p[0][0] = 6 ;6 points in the polyline _GDIPlus_Startup() Global $hgui = GUICreate('Clock', 620, 175, -1, -1, $WS_POPUP) ;Create GUI without frame Global $hWnd = WinGetHandle($hgui) ;Get handle Global $hGraphic = _GDIPlus_GraphicsCreateFromHWND($hWnd) ;Create a "canvas" to draw on GUICtrlCreateLabel("", 0, 0, 620, 175, Default, $GUI_WS_EX_PARENTDRAG) ;move the window made possible by 2nd window GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT) ;make the 2nd window transparent GUISetBkColor(0x000000) ;Background of the GUI black GUISetState() ;Show GUI AdlibRegister("_time", 1000) ;a refresh of the time display every tenth of a second Do ;infinite loop until... Until GUIGetMsg() = -3 ;..ESC is pressed _GDIPlus_GraphicsDispose($hGraphic) ;release _GDIPlus_Shutdown() Func _time() ;Show time :o) For $k = 1 To 6 ;the 6 digits of the time $setbits = $digit[StringMid(String(@HOUR & @MIN & @SEC), $k, 1)] ;get set bits in the seven-segment display based on the digit in the time For $bitnr = 7 To 1 Step -1 ;go through all bits _drawpolygon(BitAND($setbits, 128), $k * 100 - 80 + ($k = 1 Or $k = 3 Or $k = 5) * 20, $bitnr) ;parameter: bit set yes/no, position of the entire digit, number of the bit (even=horizontal bar, odd=vertical bar) $setbits = BitShift($setbits, -1) ;get next bit Next Next ;~ $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + (@SEC / 2 = Int(@SEC / 2)) * 0xBB0000) ;Create a brush if the number is even, then colored, otherwise black $brush = _GDIPlus_BrushCreateSolid( (@SEC / 2 = Int(@SEC / 2)) ? $iColorBright : $iColorDimmed) ;Create a brush if the number is even, then colored, otherwise black _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 55, 15, 15, $brush) ;Draw points _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 55, 15, 15, $brush) _GDIPlus_GraphicsFillEllipse($hGraphic, 202, 105, 15, 15, $brush) _GDIPlus_GraphicsFillEllipse($hGraphic, 402, 105, 15, 15, $brush) _GDIPlus_BrushDispose($brush) ;Dispose brush EndFunc ;==>_time Func _drawpolygon($bit, $xpos, $bitnr) ;draws a polygon ("bar") at the corresponding position $split = StringSplit($barkenpos[$bitnr], ".", 3) ;get x and y coordinates of the bar within the digit $b = (($bitnr / 2) = Int($bitnr / 2)) ;$bit even => $b = true => Bar horizontal, otherwise bar vertical $step = -1 + 2 * $b ;step through the $WS array For $i = 11 - 11 * $b To 11 * $b Step 2 * $step ;fill array with horizontal (bit=even) or (bit=odd) vertical bars $r = Int(Abs((11 * (Not ($b))) - $i) / 2) + 1 ;depending on the order, whether $i is from 0 to 11 or from 11 to 0, $r must always 1,2,3,4,5,6 $p[$r][0] = $ws[$i] + $split[0] + $xpos ;x- and $p[$r][1] = $ws[$i + $step] + $split[1] + 30 ;write y-position into the polygon array Next ;~ $brush = _GDIPlus_BrushCreateSolid(0xFF440000 + ($bit <> 0) * 0xBB0000) ;if bit set, then colored, otherwise black $brush = _GDIPlus_BrushCreateSolid(($bit <> 0) ? $iColorBright : $iColorDimmed) ;if bit set, then colored, otherwise black _GDIPlus_GraphicsFillPolygon($hGraphic, $p, $brush) ;Draw bars _GDIPlus_BrushDispose($brush) EndFunc ;==>_drawpolygon Greenfied ? Thank you ( and AndyG ) for the code Edited November 8 by argumentum forgot to mention AndyG Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Hashim Posted November 8 Author Share Posted November 8 (edited) On 11/7/2024 at 5:20 AM, ioa747 said: I modified the script so that it calls _GDIPlus_Startup() once at the beginning, and _GDIPlus_Shutdown() once at the end instead of calling her for every number I replaced the _ScreenCapture function (although I believe I fixed it after @Nine 's comments), with _ScreenCapture_Capture (since you didn't need the extras staff of _ScreenCapture) Great minds! I came up with the same ideas last night, I also modified your script to expect a bitmap so I can just pass the bitmap directly instead of capturing a bitmap then saving that to a file then converting that to a bitmap and then creating a bitmap from the file. That said it's actually not as fast an improvement as I thought it would be considering I'm not doing all that or saving a million files to the filesystem anymore. 20 hours ago, Werty said: You dont need to check the whole pattern, the first column in each digit is unique... 11 hours ago, ioa747 said: Here it is the edition with a single column patern expandcollapse popup; https://www.autoitscript.com/forum/topic/211521-ocr-from-a-small-area/?do=findComment&comment=1538057 ; Version: 5.0.2 #include <GDIPlus.au3> #include <Array.au3> #include <File.au3> #include <WinAPIProc.au3> ;~ Local $Result = _GetNumber(@ScriptDir & "\100num\040.png") ;~ MsgBox($MB_SYSTEMMODAL, "_GetNumber", $Result) Test("M:\TEMP\100num") ;-------------------------------------------------------------------------------------------------------------------------------- Func _GetNumber($sFileName, $hColor = 0x00FF00) ; Main Program ; Initialize GDI+ to work with bitmaps _GDIPlus_Startup() ; Capture the color map for the specified area Local $aColorMap = CaptureAreaColorMap($sFileName) If @error = 1 Then MsgBox($MB_SYSTEMMODAL, "LineNumber:" & @ScriptLineNumber, "Path was invalid.") Exit EndIf ;~ _ArrayDisplay($aColorMap) Local $Result = FindNumberUsingColorMap($aColorMap, $hColor) ;~ MsgBox($MB_SYSTEMMODAL, "$Result", $Result) ; Cleanup resources _GDIPlus_Shutdown() Return $Result EndFunc ;==>_GetNumber ;-------------------------------------------------------------------------------------------------------------------------------- Func CaptureAreaColorMap($sFileName) ; create color map array ; Initialize GDI+ to work with bitmaps ;_GDIPlus_Startup() ; Capture the screen area as a bitmap Local $hBitmap = _GDIPlus_BitmapCreateFromFile($sFileName) If @error Then Return SetError(1, 0, "") ; because the user ( me ) might do the unexpected ;) ; Get the width and height of the captured area Local $width = _GDIPlus_ImageGetWidth($hBitmap) Local $height = _GDIPlus_ImageGetHeight($hBitmap) ;~ ConsoleWrite("Image: $width=" & $width & ", $height=" & $height & @CRLF) ; Create an array to store color values Local $aColorMap[$width][$height] ; Loop through each pixel in the bitmap and retrieve its color For $y = 0 To $height - 1 For $x = 0 To $width - 1 ; Get the pixel color from the bitmap in ARGB format Local $argbColor = _GDIPlus_BitmapGetPixel($hBitmap, $x, $y) ; Convert ARGB to BGR for comparison (ignore the alpha channel) Local $bgrColor = BitAND($argbColor, 0x00FFFFFF) $aColorMap[$x][$y] = $bgrColor Next Next ; Cleanup resources _GDIPlus_BitmapDispose($hBitmap) ;_GDIPlus_Shutdown() Return $aColorMap EndFunc ;==>CaptureAreaColorMap ;-------------------------------------------------------------------------------------------------------------------------------- Func FindNumberUsingColorMap($aColorMap, $hColor = 0x00FF00) ; find number in color map array Local $width = UBound($aColorMap, 1) Local $height = UBound($aColorMap, 2) Local $firstRow = -1, $lastRow = -1, $firstCol = -1, $lastCol = -1 ; Scan for the first and last rows and columns with $hColor pixels For $y = 0 To $height - 1 For $x = 0 To $width - 1 If $aColorMap[$x][$y] = $hColor Then If $firstRow = -1 Then $firstRow = $y $lastRow = $y If $firstCol = -1 Or $x < $firstCol Then $firstCol = $x If $lastCol = -1 Or $x > $lastCol Then $lastCol = $x EndIf Next Next If $firstRow = -1 Or $lastRow = -1 Or $firstCol = -1 Or $lastCol = -1 Then MsgBox($MB_SYSTEMMODAL, "LineNumber:" & @ScriptLineNumber, "No color found" & @TAB & @TAB) Exit EndIf ; each number Display is a matrix of 14x20 pixels Local $numberWidth = 14 ; set number width per digit Local $numberSpace = 2 ; set Space between digits Local $Empty = 0, $Corection = 0 ; Corection for boundaries if 1st digit = 1 For $x = $firstCol To $lastCol - 1 If $aColorMap[$x][$firstRow] = 0x000000 Then $Empty += 1 If $Empty = 3 Then ;found 1st Space between digits $Corection = $firstCol - 1 - Abs($x - $numberWidth - $numberSpace) ExitLoop EndIf Else $Empty = 0 EndIf Next If $Corection > 0 Then $firstCol = $firstCol - $Corection Local $numberStart = $firstCol ;~ ConsoleWrite("$Corection=" & $Corection & @CRLF) ;~ ConsoleWrite("Display_boundaries firstRow:" & $firstRow & " lastRow:" & $lastRow & " firstCol:" & $firstCol & " lastCol:" & $lastCol & @CRLF) ;~ ConsoleWrite("" & @CRLF) Local $aNum[11][3] ; Define boundaries for each number position For $i = 1 To 10 $aNum[$i][0] = $numberStart $aNum[$i][1] = $numberStart + $numberWidth If $numberStart + $numberWidth + $numberSpace > $lastCol Then ExitLoop $numberStart += $numberWidth + $numberSpace $aNum[0][0] = $i + 1 Next ;~ _ArrayDisplay($aNum) Local $sNumber ; Generate patterns for each found number For $i = 1 To $aNum[0][0] Local $sPattern = "" For $y = $firstRow To $lastRow $x = $aNum[$i][0] If $aColorMap[$x][$y] = $hColor Then $sPattern &= "1" Else $sPattern &= "." EndIf $sPattern &= @CRLF Next $sNumber &= _GetPattern($sPattern) Next Return $sNumber EndFunc ;==>FindNumberUsingColorMap ;-------------------------------------------------------------------------------------------------------------------------------- Func _GetPattern($sPattern) ; patterns for each digit ;~ ConsoleWrite("**************" & @CRLF & $sPattern & @CRLF) Local Static $aPNum ; Define simplified patterns for each digit If Not IsArray($aPNum) Then Local $aNumber[10] $aNumber[0] = "" ; Pattern for '0' $aNumber[0] &= "." & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "1" & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[0] &= "." & @CRLF $aNumber[1] = "" ; Pattern for '1' $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[1] &= "." & @CRLF $aNumber[2] = "" ; Pattern for '2' $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[2] &= "." & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[2] &= "1" & @CRLF $aNumber[3] = "" ; Pattern for '3' $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "1" & @CRLF $aNumber[3] &= "1" & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "1" & @CRLF $aNumber[3] &= "1" & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[3] &= "." & @CRLF $aNumber[4] = "" ; Pattern for '4' $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "1" & @CRLF $aNumber[4] &= "1" & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "1" & @CRLF $aNumber[4] &= "1" & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[4] &= "." & @CRLF $aNumber[5] = "" ; Pattern for '5' $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "1" & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[5] &= "." & @CRLF $aNumber[6] = "" ; Pattern for '6' $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "1" & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[6] &= "." & @CRLF $aNumber[7] = "" ; Pattern for '7' $aNumber[7] &= "1" & @CRLF $aNumber[7] &= "1" & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[7] &= "." & @CRLF $aNumber[8] = "" ; Pattern for '8' $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "1" & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[8] &= "." & @CRLF $aNumber[9] = "" ; Pattern for '9' $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "1" & @CRLF $aNumber[9] &= "1" & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "1" & @CRLF $aNumber[9] &= "1" & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aNumber[9] &= "." & @CRLF $aPNum = $aNumber EndIf Switch $sPattern Case $aPNum[0] Return 0 Case $aPNum[1] Return 1 Case $aPNum[2] Return 2 Case $aPNum[3] Return 3 Case $aPNum[4] Return 4 Case $aPNum[5] Return 5 Case $aPNum[6] Return 6 Case $aPNum[7] Return 7 Case $aPNum[8] Return 8 Case $aPNum[9] Return 9 Case Else Return SetError(1, 0, "") EndSwitch EndFunc ;==>_GetPattern ;-------------------------------------------------------------------------------------------------------------------------------- Func Test($sFolderPath, $hColor = 0x00FF00) ; _GetNumber for all the *.png files in $sFolderPath directory ; List all the *.png files in $sFolderPath directory Local $aFileList = _FileListToArray($sFolderPath, "*.png", $FLTA_FILES) If @error = 1 Then MsgBox($MB_SYSTEMMODAL, "LineNumber:" & @ScriptLineNumber, "Path was invalid.") Exit EndIf If @error = 4 Then MsgBox($MB_SYSTEMMODAL, "LineNumber:" & @ScriptLineNumber, "No file(s) were found.") Exit EndIf _ArrayColInsert($aFileList, 1) ; add 1 column to hold the numbers ; Initialize GDI+ to work with bitmaps _GDIPlus_Startup() Local $cnt = 0 Local $CleanTime = 0 Local $hTimer = TimerInit() For $j = 1 To 100 For $i = 1 To $aFileList[0][0] $cnt += 1 ConsoleWrite("$cnt: " & $cnt & @CRLF) ; Capture the color map for the specified area Local $aColorMap = CaptureAreaColorMap($sFolderPath & "\" & $aFileList[$i][0]) If @error = 1 Then MsgBox($MB_SYSTEMMODAL, "LineNumber:" & @ScriptLineNumber, "Path was invalid.") Exit EndIf $aFileList[$i][1] = FindNumberUsingColorMap($aColorMap, $hColor) ; Rename a file using FileMove and overwrite the new file if it exists. ;~ FileMove($sFolderPath & "\" & $aFileList[$i][0], $sFolderPath & "\" & $aFileList[$i][1] & ".png", $FC_OVERWRITE) ;~ $aProcessMemoryInfo = _WinAPI_GetProcessMemoryInfo() ;~ ConsoleWrite('The peak working set: ' & $aProcessMemoryInfo[1] / 1024 / 1014 & ' MB' & @CRLF) Next Next ConsoleWrite("processed in: " & Round(TimerDiff($hTimer) / 1000, 3) & " seconds " & @LF) _ArrayDisplay($aFileList, "$aFileList") ; Cleanup resources _GDIPlus_Shutdown() EndFunc ;==>Test ;-------------------------------------------------------------------------------------------------------------------------------- I see what you mean now by only capturing part of the pattern rather than all of it. It's a genius idea for anyone who needs it, but unless it saves a serious amount of time I probably won't implement it in my own script - I like the readability of the original bitmap ID script, it's much easier to understand what's going on with the digits and be able to modify it for another bitmap font if I ever need to do anything like this again 10 years from now when I have early onset dementia. 10 hours ago, argumentum said: lol. It is faster I feel that we all in this thread are getting off course. The next problem the OP presented, was not the speed, but the crash. Thanks for the laserlike focus, I appreciate it! It was a weird bug and definitely caused by something in the _GetCapture function, but probably just a system memory issue caused by the lack of disposal that Nines pointed out. Either way I've now managed to pass over 300,000 outputs in a single run (took about 12 hours) and I only interrupted it because I had to use my computer for something else, so I think it's safe to say the issue is solved. The only problem with it now is that my error handling doesn't appear to be working for the few hundred serial numbers where no output is recognised. I'm guessing these are because the script is now too fast for the application and needs a few sleep calls somewhere, and I'm fine with just adding those few hundred manually instead of slowing down the whole script - the problem is I have to use Bash/GNU tools to find them in the file instead of the images saving to Bitmap ID Failed like they should be. Error-handling is harder than it looks, I suppose. Here's the current script for anyone interested: expandcollapse popup#ce ---------------------------------------------------------------------------- #include <GDIPlus.au3> #include <ScreenCapture.au3> #include <WinAPIFiles.au3> #include "Bitmap Digit Identifier.au3" ; https://www.autoitscript.com/forum/topic/211521-ocr-from-a-small-area/?do=findComment&comment=1538057 Run("Tool.exe"); Local $tool = WinWaitActive("Tool", "", 3) ControlClick($tool, "", "TTabSheet1", "primary", 1, 210, 50) ControlCommand($tool, "" , "TComboBox3" , "SelectString" , "Open") $db = FileOpen(@ScriptDir & "\tool-db.txt", 1) DirRemove ("Bitmap ID Failed", 1) DirCreate ("Bitmap ID Failed") _GDIPlus_Startup() For $loop = 0 To 999999 $serial = StringFormat('%06i', $loop) ControlSetText($tool, "", "TMaskEdit3", "S.N: 9" & $serial) ControlClick($tool, "", "TTabSheet3", "primary", 1, 69, 50) $hHBitmap = _ScreenCapture_Capture("", 792, 450, 927, 480) $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBitmap) Local $output = _GetNumber($hBitmap) If @error Then MsgBox(0, "Encountered fatal error", @error) ExitLoop EndIf If ((StringLen($output) <> 3) or not StringIsDigit($output)) Then _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\Bitmap ID Failed\" & $serial & ".png") EndIf _WinAPI_DeleteObject($hHBitmap) _GDIPlus_BitmapDispose($hBitmap) FileWrite($db, $serial & "," & $output & @LF) Next _GDIPlus_Shutdown() FileClose($db) Exit Edited November 8 by Hashim argumentum and ioa747 2 Link to comment Share on other sites More sharing options...
argumentum Posted November 8 Share Posted November 8 6 minutes ago, Hashim said: The only problem with it now is that my error handling doesn't appear to be working for the few hundred serial numbers where no output is recognized. I'm guessing these are because the script is now too fast for the application and needs a few sleep calls somewhere, well, you ControlClick() and immediately _ScreenCapture_Capture(). I would given that it seems to be that fast, and only so often slightly slower, move the ScreenCap() and _GetNumber() into a function that will retry. Something like this: Spoiler expandcollapse popup#ce ---------------------------------------------------------------------------- #include <GDIPlus.au3> #include <ScreenCapture.au3> #include <WinAPIFiles.au3> #include "Bitmap Digit Identifier.au3" ; https://www.autoitscript.com/forum/topic/211521-ocr-from-a-small-area/?do=findComment&comment=1538057 Run("Tool.exe"); Local $tool = WinWaitActive("Tool", "", 3) ControlClick($tool, "", "TTabSheet1", "primary", 1, 210, 50) ControlCommand($tool, "" , "TComboBox3" , "SelectString" , "Open") $db = FileOpen(@ScriptDir & "\tool-db.txt", 1) DirRemove ("Bitmap ID Failed", 1) DirCreate ("Bitmap ID Failed") _GDIPlus_Startup() For $loop = 0 To 999999 $serial = StringFormat('%06i', $loop) ControlSetText($tool, "", "TMaskEdit3", "S.N: 9" & $serial) ControlClick($tool, "", "TTabSheet3", "primary", 1, 69, 50) Local $output = _GetNumberOrGetNumberNoMatterWhat() ;~ $hHBitmap = _ScreenCapture_Capture("", 792, 450, 927, 480) ;~ $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBitmap) ;~ Local $output = _GetNumber($hBitmap) ;~ If @error Then ;~ MsgBox(0, "Encountered fatal error", @error) ;~ ExitLoop ;~ EndIf ;~ If ((StringLen($output) <> 3) or not StringIsDigit($output)) Then ;~ _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\Bitmap ID Failed\" & $serial & ".png") ;~ EndIf ;~ _WinAPI_DeleteObject($hHBitmap) ;~ _GDIPlus_BitmapDispose($hBitmap) FileWrite($db, $serial & "," & $output & @LF) Next _GDIPlus_Shutdown() FileClose($db) Func _GetNumberOrGetNumberNoMatterWhat() Local $hHBitmap, $hBitmap, $output Do $hHBitmap = _ScreenCapture_Capture("", 792, 450, 927, 480) $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBitmap) $output = _GetNumber($hBitmap) If @error Then Sleep(10) ContinueLoop ;~ MsgBox(0, "Encountered fatal error", @error) ;~ ExitLoop EndIf If ((StringLen($output) <> 3) or not StringIsDigit($output)) Then _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\Bitmap ID Failed\" & $serial & ".png") EndIf _WinAPI_DeleteObject($hHBitmap) _GDIPlus_BitmapDispose($hBitmap) Until $output <> "" Return $output EndFunc Exit Untested but that's the idea. Hashim 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
junkew Posted November 8 Share Posted November 8 Check the findbmp code func GetImage on lockbits _GDIPlus_BitmapLockBits and search the forum on bitblt and lockbits. Probably (much) faster then getting the color per pixel with _GDIPlus_BitmapGetPixel($hBitmap, $x, $y) And rotating can help: Function _GDIPlus_ImageRotateFlip as you then have directly the pixels as sequence instead of calculating x,y and iterating x, y 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 Hashim and ioa747 2 FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
Hashim Posted November 8 Author Share Posted November 8 (edited) 12 hours ago, argumentum said: well, you ControlClick() and immediately _ScreenCapture_Capture(). I would given that it seems to be that fast, and only so often slightly slower, move the ScreenCap() and _GetNumber() into a function that will retry. Something like this: Hide contents expandcollapse popup#ce ---------------------------------------------------------------------------- #include <GDIPlus.au3> #include <ScreenCapture.au3> #include <WinAPIFiles.au3> #include "Bitmap Digit Identifier.au3" ; https://www.autoitscript.com/forum/topic/211521-ocr-from-a-small-area/?do=findComment&comment=1538057 Run("Tool.exe"); Local $tool = WinWaitActive("Tool", "", 3) ControlClick($tool, "", "TTabSheet1", "primary", 1, 210, 50) ControlCommand($tool, "" , "TComboBox3" , "SelectString" , "Open") $db = FileOpen(@ScriptDir & "\tool-db.txt", 1) DirRemove ("Bitmap ID Failed", 1) DirCreate ("Bitmap ID Failed") _GDIPlus_Startup() For $loop = 0 To 999999 $serial = StringFormat('%06i', $loop) ControlSetText($tool, "", "TMaskEdit3", "S.N: 9" & $serial) ControlClick($tool, "", "TTabSheet3", "primary", 1, 69, 50) Local $output = _GetNumberOrGetNumberNoMatterWhat() ;~ $hHBitmap = _ScreenCapture_Capture("", 792, 450, 927, 480) ;~ $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBitmap) ;~ Local $output = _GetNumber($hBitmap) ;~ If @error Then ;~ MsgBox(0, "Encountered fatal error", @error) ;~ ExitLoop ;~ EndIf ;~ If ((StringLen($output) <> 3) or not StringIsDigit($output)) Then ;~ _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\Bitmap ID Failed\" & $serial & ".png") ;~ EndIf ;~ _WinAPI_DeleteObject($hHBitmap) ;~ _GDIPlus_BitmapDispose($hBitmap) FileWrite($db, $serial & "," & $output & @LF) Next _GDIPlus_Shutdown() FileClose($db) Func _GetNumberOrGetNumberNoMatterWhat() Local $hHBitmap, $hBitmap, $output Do $hHBitmap = _ScreenCapture_Capture("", 792, 450, 927, 480) $hBitmap = _GDIPlus_BitmapCreateFromHBITMAP($hHBitmap) $output = _GetNumber($hBitmap) If @error Then Sleep(10) ContinueLoop ;~ MsgBox(0, "Encountered fatal error", @error) ;~ ExitLoop EndIf If ((StringLen($output) <> 3) or not StringIsDigit($output)) Then _GDIPlus_ImageSaveToFile($hBitmap, @ScriptDir & "\Bitmap ID Failed\" & $serial & ".png") EndIf _WinAPI_DeleteObject($hHBitmap) _GDIPlus_BitmapDispose($hBitmap) Until $output <> "" Return $output EndFunc Exit Untested but that's the idea. Wow, the Do while/until loops to retry the numbers is clever, something like that wouldn't have even occurred to me. Just finished testing over another ~300,000 runs and didn't see a single empty output (and I trust no false positives either) so it clearly works, thank you! One question, what's the purpose of the sleep and ContinueLoop? In my tests I replaced this with my original MsgBox, as if there's a significant failure I'd probably want to know about it instead of skipping the output check and going to the next loop, or am I misunderstanding how it works? 3 hours ago, junkew said: Check the findbmp code func GetImage on lockbits _GDIPlus_BitmapLockBits and search the forum on bitblt and lockbits. Probably (much) faster then getting the color per pixel with _GDIPlus_BitmapGetPixel($hBitmap, $x, $y) And rotating can help: Function _GDIPlus_ImageRotateFlip as you then have directly the pixels as sequence instead of calculating x,y and iterating x, y 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 It's mind-boggling to me how people keep coming up with ways to make this faster, suppose that's the difference between being a computer scientist and a simple programmer like myself. I no longer have the time to implement something like this and I've already spent too long writing the code instead of just doing the work I should be doing, but this is definitely appreciated and if I start needing to use this script more regularly I'll definitely be benchmarking Werty/ioa747's first-column solution against this one. Edited November 8 by Hashim Link to comment Share on other sites More sharing options...
argumentum Posted November 8 Share Posted November 8 20 minutes ago, Hashim said: ...what's the purpose of the sleep and ContinueLoop? In my tests I replaced this with my original MsgBox, as if... If the error is fatal, quit. If the error is circumstantial, retry. If the expected circumstances never come, ... then is a good idea to add a retry counter and a MsgBox() with "Retry, continue, cancel options", with a timeout of a certain default, to accommodate all foreseeable circumstances and desired default option, in case the operator/user is not at the keyboard. Look at it like a game of chess, where you are coding in advance, a reaction to any of the opponent's ( the darn PC ) possible moves. Once you become more familiar with this adventure of coding, forking opens a world of possibilities. But that is 3D chess. Hashim 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
AndyG Posted November 9 Share Posted November 9 On 11/8/2024 at 6:41 AM, argumentum said: Greenfied ? Thank you ( and AndyG ) for the code Haha...thx, i made this centuries ago...if I remember right, it was a contribution to some other "digital desktop clock" scripts which took some hundret lines of code...my intention was to write a script in less than 50 lines of code. The "bitbanging" aka producing a polygon, means the position and orientation of an "equal shape", I taught myself about 40 years ago when I programmed "graphical" games for a calculator (sharp PC1401) with a 7-segment LC display in machine code. 15 hours ago, junkew said: Check the findbmp code func GetImage on lockbits _GDIPlus_BitmapLockBits and search the forum on bitblt and lockbits. Probably (much) faster then getting the color per pixel with _GDIPlus_BitmapGetPixel($hBitmap, $x, $y) And rotating can help: Function _GDIPlus_ImageRotateFlip as you then have directly the pixels as sequence instead of calculating x,y and iterating x, y yepp, rotation is the key...and it is not necessary to use "graphical stuff" like GDI(+) or something like that to read or write "pixels"! A bitmap is an array of bytes/words/dwords representing the "pixels". If you put an AutoIt dllstruct "over" a bitmap (at the bitmap's starting address in memory), you can read or write "pixels" with a simple DllStructGetData() / DllStructSetData(), or, much faster, define/read the dllstruct as a "string" (characters) and use the super-fast AutoIt string commands like StringInstr() to locate a specific sequence of "bytes" (characters). As Werty mentioned, the first column of the digit is unique. This requires seven "bit tests" due to the way the pixels are arranged in memory, as each "pixel" is in a different area of the bitmap. After rotating the image, only one test is required, as the "pixels" of the first column are now in a row, one after the other. So only one read from memory is required to get the "number". And since the position of the next "number" on the screen is known in the bitmap's memory, a search for a sequence of 4 consecutive "numbers" would only require 4 reads from memory. This means that the first step is to look for the first "digit", then look at the (known) next position in memory and check if the byte at that position is the second "digit", and so on... With this technique the search of a sequence of "numbers" on a screenshot is possible in a few (milli)-Seconds. In native AutoIt code.... Hashim and argumentum 2 Link to comment Share on other sites More sharing options...
Werty Posted November 10 Share Posted November 10 (edited) On 11/9/2024 at 8:30 AM, AndyG said: a few (milli)-Seconds Yup, ~2 milliseconds on my pc, and that's without the fancy rotating stuff. The basics #include <GDIPlus.au3> #include <ScreenCapture.au3> ;Be sure $posx and $posy is pointing at the correct spot! Global $posx = 1718, $posy = 699, $result = "" ;Lookup table to avoid searching Global $digit[115] = [1,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,6,0,0,0,3,2,0,0,0,0, _ 0,0,0,0,0,0,0,0,9,0,0,0,0,0,8,0,0,0,0,0, _ 0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,5] $capture = _ScreenCapture_Capture("", $posx, $posy, $posx+34, $posy+20) $bitmap = _GDIPlus_BitmapCreateFromHBITMAP($capture); for some reason createfromhbitmap is not a fully compatible gdiplus bitmap $clone = _GDIPlus_BitmapCloneArea($bitmap, 0, 0, 34, 20, $GDIP_PXF32ARGB); so we have to do this before it works correctly _GDIPlus_BitmapDispose($bitmap) Func Getnumber($result) Local $code[3] = [0,0,0], $value = 64 $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) For $loop = 1 To 680 Step 102 $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 $value /= 2 Next _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) EndFunc And a runable script (zipped folder with script and test images attached) expandcollapse popup#include <GDIPlus.au3> #include <ScreenCapture.au3> HotKeySet("{ESC}", "_exit") ;Be sure $posx and $posy is pointing at the correct spot! Global $posx = 1718, $posy = 699, $result = "" ;Lookup table to avoid searching Global $digit[115] = [1,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,6,0,0,0,3,2,0,0,0,0, _ 0,0,0,0,0,0,0,0,9,0,0,0,0,0,8,0,0,0,0,0, _ 0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,5] ;Test section ;-------------------------------------------------------------- $gui = GUICreate("Getnumber", 640, 480, -1, -1) GUISetState() WinWaitActive($gui); <--- necessary or screencapture fails _GDIPlus_Startup() $graphics = _GDIPlus_GraphicsCreateFromHWND($gui) $image = _GDIPlus_BitmapCreateFromFile("518.png") _GDIPlus_GraphicsDrawImageRect($graphics, $image, 280, 200, 136, 31) Sleep(10); <------- necessary because drawimagerect() is too slow, script occasionally fails without ;-------------------------------------------------------------- ;/Test section $time = TimerInit() $capture = _ScreenCapture_Capture("", $posx, $posy, $posx+34, $posy+20) $bitmap = _GDIPlus_BitmapCreateFromHBITMAP($capture); for some reason createfromhbitmap is not a fully compatible gdiplus bitmap $clone = _GDIPlus_BitmapCloneArea($bitmap, 0, 0, 34, 20, $GDIP_PXF32ARGB); so we have to do this before it works correctly _GDIPlus_BitmapDispose($bitmap) $result = Getnumber() Consolewrite("Result: " & $result & " Time: " & TimerDiff($time)/1000 & @crlf) While 1 Sleep(10) WEnd Func Getnumber() Local $code[3] = [0,0,0] $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) $value = 64 For $loop = 1 To 680 Step 102 $code[0] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop ), 8), 1) = 1 ? $value:0 $code[1] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop+16), 8), 1) = 1 ? $value:0 $code[2] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop+32), 8), 1) = 1 ? $value:0 $value /= 2 Next $result = String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) $code = 0 _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return $result EndFunc Func _exit() _GDIPlus_Shutdown() Exit EndFunc Not much code it takes. I havent checked for memoryleaks and stuff, too tired now, if you spot anyting please tell. Be sure to change the $posx and $posy to point at the correct spot, as it is now it fits a 3440x1440 monitor. (you can comment out line 56 that gives error so you can take a screenshot to find the correct spot) /edit Changed, zip not updated Changed again, $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 getnumber.zip Edited November 12 by Werty Hashim and ioa747 2 Some guy's script + some other guy's script = my script! Link to comment Share on other sites More sharing options...
ioa747 Posted November 10 Share Posted November 10 (edited) 1st try (58) : ==> Array variable has incorrect number of subscripts or subscript dimension range exceeded. do i have to do something? Edit: ok now it works. Now I have to understand how it works? 1 hour ago, Werty said: When you found the spot and got it running please post your screen resolution and your posx posy result so others with same resolution can use your result so they dont have to do the same @DesktopWidth x @DesktopHeight = 1920x1080 Global $posx = 958, $posy = 499, $result = "" Edited November 10 by ioa747 Hashim and Werty 2 I know that I know nothing Link to comment Share on other sites More sharing options...
Werty Posted November 10 Share Posted November 10 (edited) Do what i wrote, find the correct spot, comment out that line that gives error and run the script again, then you'll see the test image, and while the script is running press printscreen on your keyboard to take a screenshot, open that screenshot in an image editor and point the cursor at the spot on the picture i posted and note down the coords, those are the coords for $posx and $posy. When you found the spot and got it running please post your screen resolution and your posx posy result so others with same resolution can use your result so they dont have to do the same. It could ofcourse be made so it checks automatically, it's autoit, but not now, sleep now, yes. 🥱 Edited November 10 by Werty Some guy's script + some other guy's script = my script! Link to comment Share on other sites More sharing options...
Hashim Posted November 13 Author Share Posted November 13 (edited) On 11/10/2024 at 10:42 AM, Werty said: Yup, ~2 milliseconds on my pc, and that's without the fancy rotating stuff. The basics #include <GDIPlus.au3> #include <ScreenCapture.au3> ;Be sure $posx and $posy is pointing at the correct spot! Global $posx = 1718, $posy = 699, $result = "" ;Lookup table to avoid searching Global $digit[115] = [1,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,6,0,0,0,3,2,0,0,0,0, _ 0,0,0,0,0,0,0,0,9,0,0,0,0,0,8,0,0,0,0,0, _ 0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,5] $capture = _ScreenCapture_Capture("", $posx, $posy, $posx+34, $posy+20) $bitmap = _GDIPlus_BitmapCreateFromHBITMAP($capture); for some reason createfromhbitmap is not a fully compatible gdiplus bitmap $clone = _GDIPlus_BitmapCloneArea($bitmap, 0, 0, 34, 20, $GDIP_PXF32ARGB); so we have to do this before it works correctly _GDIPlus_BitmapDispose($bitmap) Func Getnumber() Local $code[3] = [0,0,0], $value = 64 $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) For $loop = 1 To 680 Step 102 $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 $value /= 2 Next $result = String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) $code = 0 _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return $result EndFunc And a runable script (zipped folder with script and test images attached) expandcollapse popup#include <GDIPlus.au3> #include <ScreenCapture.au3> HotKeySet("{ESC}", "_exit") ;Be sure $posx and $posy is pointing at the correct spot! Global $posx = 1718, $posy = 699, $result = "" ;Lookup table to avoid searching Global $digit[115] = [1,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,6,0,0,0,3,2,0,0,0,0, _ 0,0,0,0,0,0,0,0,9,0,0,0,0,0,8,0,0,0,0,0, _ 0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, _ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,5] ;Test section ;-------------------------------------------------------------- $gui = GUICreate("Getnumber", 640, 480, -1, -1) GUISetState() WinWaitActive($gui); <--- necessary or screencapture fails _GDIPlus_Startup() $graphics = _GDIPlus_GraphicsCreateFromHWND($gui) $image = _GDIPlus_BitmapCreateFromFile("518.png") _GDIPlus_GraphicsDrawImageRect($graphics, $image, 280, 200, 136, 31) Sleep(10); <------- necessary because drawimagerect() is too slow, script occasionally fails without ;-------------------------------------------------------------- ;/Test section $time = TimerInit() $capture = _ScreenCapture_Capture("", $posx, $posy, $posx+34, $posy+20) $bitmap = _GDIPlus_BitmapCreateFromHBITMAP($capture); for some reason createfromhbitmap is not a fully compatible gdiplus bitmap $clone = _GDIPlus_BitmapCloneArea($bitmap, 0, 0, 34, 20, $GDIP_PXF32ARGB); so we have to do this before it works correctly _GDIPlus_BitmapDispose($bitmap) $result = Getnumber() Consolewrite("Result: " & $result & " Time: " & TimerDiff($time)/1000 & @crlf) While 1 Sleep(10) WEnd Func Getnumber() Local $code[3] = [0,0,0] $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) $value = 64 For $loop = 1 To 680 Step 102 $code[0] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop ), 8), 1) = 1 ? $value:0 $code[1] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop+16), 8), 1) = 1 ? $value:0 $code[2] += BitAnd(BitShift(DllStructGetData($pixels, 1, $loop+32), 8), 1) = 1 ? $value:0 $value /= 2 Next $result = String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) $code = 0 _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return $result EndFunc Func _exit() _GDIPlus_Shutdown() Exit EndFunc Not much code it takes. I havent checked for memoryleaks and stuff, too tired now, if you spot anyting please tell. Be sure to change the $posx and $posy to point at the correct spot, as it is now it fits a 3440x1440 monitor. (you can comment out line 56 that gives error so you can take a screenshot to find the correct spot) /edit Changed, zip not updated $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 getnumber.zip 8.66 kB · 5 downloads Since you were kind enough to write the code I decided to go ahead and waste some more time by testing this, and HOLY SHIT is this solution fast. I also tested the first-column bitmap solution a few days ago and didn't personally notice a massive improvement - both take at least 24 hours to output the whole 1M dataset. However, this genius solution blows them out of the water, doing the whole thing in 6-8 hours - 4 times as fast! Combined with a modified version of argumentum's Do-Until loop to retry outputs until they're in valid format, I got less than 40 false positives out of a million - not as effective as the bitmap solutions but still low enough to manually correct them without issue, and this is without my version having any sleep calls at all. I've also noticed that without any sleep calls the first code is almost always a false positive, which may be something to do with the start-up time of LockBits. This is almost definitely the solution I'm going to be using going forward because it's too genius not to, but I'd love to understand more about the maths behind it and how exactly it does what it does, especially so much faster than the bitmap solutions, if either you or @AndyG would care to explain. I'm guessing 4278255360 is a uint32, but what exactly do the numbers in the lookup table represent? What's the significance of 680 and 102? And how exactly does the ternary: $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? 64 : 0 ...work to recognise the first digit? Edited November 13 by Hashim argumentum 1 Link to comment Share on other sites More sharing options...
Werty Posted November 13 Share Posted November 13 (edited) I'll write up a walkthrough later, meanwhile, have you seen the update of the getnumber() func I made, it's shorter and slightly faster now... Func Getnumber($result) Local $code[3] = [0,0,0], $value = 64 $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) For $loop = 1 To 680 Step 102 $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 $value /= 2 Next _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) EndFunc Edit, doh, I see in your quote that you've seen it. Edited November 13 by Werty Hashim 1 Some guy's script + some other guy's script = my script! Link to comment Share on other sites More sharing options...
Hashim Posted November 13 Author Share Posted November 13 5 minutes ago, Werty said: I'll write up a walkthrough later, meanwhile, have you seen the update of the getnumber() func I made, it's shorter and slightly faster now... Func Getnumber($result) Local $code[3] = [0,0,0], $value = 64 $bitmap1 = _GDIPlus_BitmapLockBits($clone, 0, 0, 34, 20, BitOR($GDIP_ILMWRITE, $GDIP_ILMREAD), $GDIP_PXF32ARGB) $pixels = DllStructCreate("dword[" & 680 & "];", DllStructGetData($bitmap1, "Scan0")) For $loop = 1 To 680 Step 102 $code[0] += DllStructGetData($pixels, 1, $loop ) = 4278255360 ? $value:0 $code[1] += DllStructGetData($pixels, 1, $loop+16) = 4278255360 ? $value:0 $code[2] += DllStructGetData($pixels, 1, $loop+32) = 4278255360 ? $value:0 $value /= 2 Next _GDIPlus_BitmapUnlockBits($clone, $bitmap1) Return String($digit[$code[0]]) & String($digit[$code[1]]) & String($digit[$code[2]]) EndFunc Edit, doh, I see in your quote that you've seen it. I noticed the removal of the bitwise operations but not the rest of your edits to Getnumber(), I'll update them now. I look forward to the walkthrough, and thanks so much for the solution! Link to comment Share on other sites More sharing options...
junkew Posted November 13 Share Posted November 13 Did you try to create the 1 million screenshots and then run 10 au3 exe files to analyse 100.000 bitmaps? In theory it can be ten times faster. FAQ 31 How to click some elements, FAQ 40 Test automation with AutoIt, Multithreading CLR .NET Powershell CMDLets Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now