FindText - Capture screen image into text and then find it

Post your working scripts, libraries and tools
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

FindText - Capture screen image into text and then find it

14 May 2016, 12:10

I wrote a useful function to share with you.
It is used to find text or images on the screen.
Hope you like it ! :)

Detailed usage can refer to:
1、The introduction by ed1chandler.
2、The introduction by c4p (latest).

Code: Select all

;/*
;===========================================
;  FindText - Capture screen image into text and then find it
;  https://autohotkey.com/boards/viewtopic.php?f=6&t=17834
;
;  Author  :  FeiYue
;  Version :  7.0
;  Date    :  2019-11-30
;
;  Usage:
;  1. Capture the image to text string.
;  2. Test find the text string on full Screen.
;  3. When test is successful, you may copy the code
;     and paste it into your own script.
;     Note: Copy the "FindText()" function and the following
;     functions and paste it into your own script Just once.
;  4. The more recommended way is to save the script as
;     "FindText.ahk" and copy it to the "Lib" subdirectory
;     of AHK program, instead of copying the "FindText()"
;     function and the following functions, add a line to
;     the beginning of your script: #Include <FindText>
;
;  Note:
;     After upgrading to v7.0, the search scope using
;     the upper left  corner coordinates (X1, Y1)
;     and lower right corner coordinates (X2, Y2), similar to ImageSearch.
;     This makes it easier for novices to understand and use.
;
;===========================================
;  Introduction of function parameters:
;
;  returnArray := FindText(
;      X1 --> the search scope's upper left corner X coordinates
;    , Y1 --> the search scope's upper left corner Y coordinates
;    , X2 --> the search scope's lower right corner X coordinates
;    , Y2 --> the search scope's lower right corner Y coordinates
;    , err1 --> Character "0" fault-tolerant in percentage(0.1=10%)
;    , err0 --> Character "_" fault-tolerant in percentage(0.1=10%)
;    , Text --> can be a lot of text parsed into images, separated by "|"
;    , ScreenShot --> if the value is 0, the last screenshot will be used
;    , FindAll --> if the value is 0, Just find one result and return
;    , JoinText --> if the value is 1, Join all Text for combination lookup
;    , offsetX --> Set the max text offset for combination lookup
;    , offsetY --> Set the max text offset for combination lookup
;  )
;
;  The function returns a second-order array containing
;  all lookup results, Any result is an associative array
;  {1:X, 2:Y, 3:W, 4:H, x:X+W//2, y:Y+H//2, id:Comment}
;  if no image is found, the function returns 0.
;
;  If the return variable is set to "ok", ok.1 is the first result found.
;  Where ok.1.1 is the X coordinate of the upper left corner of the found image,
;  and ok.1.2 is the Y coordinate of the upper left corner of the found image,
;  ok.1.3 is the width of the found image, and ok.1.4 is the height of the found image,
;  ok.1.x <==> ok.1.1+ok.1.3//2 ( is the Center X coordinate of the found image ),
;  ok.1.y <==> ok.1.2+ok.1.4//2 ( is the Center Y coordinate of the found image ),
;  ok.1.id is the comment text, which is included in the <> of its parameter.
;  ok.1.x can also be written as ok[1].x, which supports variables. (eg: ok[A_Index].x)
;===========================================
;*/

if (!A_IsCompiled and A_LineFile=A_ScriptFullPath)
  ft_Gui("Show")

ft_Gui(cmd)
{
  static
  if (cmd="Show")
  {
    Gui, ft_Main:+LastFoundExist
    IfWinExist
    {
      Gui, ft_Main:Show, Center
      return
    }
    if (!ft_FuncBind1)
      ft_FuncBind1:=Func("ft_Gui").Bind("Show")
    #NoEnv
    Menu, Tray, Add
    Menu, Tray, Add, FindText, %ft_FuncBind1%
    if (!A_IsCompiled and A_LineFile=A_ScriptFullPath)
    {
      Menu, Tray, Default, FindText
      Menu, Tray, Click, 1
      Menu, Tray, Icon, Shell32.dll, 23
    }
    ft_BatchLines:=A_BatchLines
    ft_IsCritical:=A_IsCritical
    Critical
    ww:=35, hh:=12, WindowColor:="0xDDEEFF"
    ft_Gui("MakeCaptureWindow")
    ft_Gui("MakeSubPicWindow")
    ft_Gui("MakeMainWindow")
    OnMessage(0x100, Func("ft_EditEvents1"))  ; WM_KEYDOWN
    OnMessage(0x201, Func("ft_EditEvents2"))  ; WM_LBUTTONDOWN
    OnMessage(0x200, Func("ft_ShowToolTip"))  ; WM_MOUSEMOVE
    Gui, ft_Main:Show, Center
    Critical, %ft_IsCritical%
    SetBatchLines, %ft_BatchLines%
    return
    ;-------------------
    ft_Run:
    Critical
    ft_Gui(Trim(A_GuiControl))
    return
  }
  if (cmd="MakeCaptureWindow")
  {
    Gui, ft_Capture:New
    Gui, +AlwaysOnTop -DPIScale
    Gui, Margin, 15, 15
    Gui, Color, %WindowColor%
    Gui, Font, s12, Verdana
    Gui, Add, Text, xm w855 h315 +HwndhPic
    Gui, Add, Slider, ym h315 vMySlider2 gft_Run
      +Center Page20 Line20 NoTicks AltSubmit +Vertical
    Gui, Add, Slider, xm w855 vMySlider1 gft_Run
      +Center Page20 Line20 NoTicks AltSubmit
    GuiControlGet, Pic, Pos, %hPic%
    PicW:=Round(PicW), PicH:=Round(PicH), MySlider1:=MySlider2:=0
    Gui, Add, Button, xm+125 w50 vRepU  gft_Run, -U
    Gui, Add, Button, x+0    wp  vCutU  gft_Run, U
    Gui, Add, Button, x+0    wp  vCutU3 gft_Run, U3
    ;--------------
    Gui, Add, Text,   x+50 yp+3 Section, Gray
    Gui, Add, Edit,   x+3 yp-3 w60 vSelGray ReadOnly
    Gui, Add, Text,   x+15 ys, Color
    Gui, Add, Edit,   x+3 yp-3 w120 vSelColor ReadOnly
    Gui, Add, Text,   x+15 ys, R
    Gui, Add, Edit,   x+3 yp-3 w60 vSelR ReadOnly
    Gui, Add, Text,   x+5 ys, G
    Gui, Add, Edit,   x+3 yp-3 w60 vSelG ReadOnly
    Gui, Add, Text,   x+5 ys, B
    Gui, Add, Edit,   x+3 yp-3 w60 vSelB ReadOnly
    ;--------------
    Gui, Add, Button, xm     w50 vRepL  gft_Run, -L
    Gui, Add, Button, x+0    wp  vCutL  gft_Run, L
    Gui, Add, Button, x+0    wp  vCutL3 gft_Run, L3
    Gui, Add, Button, x+15   w70 vAuto  gft_Run, Auto
    Gui, Add, Button, x+15   w50 vRepR  gft_Run, -R
    Gui, Add, Button, x+0    wp  vCutR  gft_Run, R
    Gui, Add, Button, x+0    wp  vCutR3 gft_Run Section, R3
    Gui, Add, Button, xm+125 w50 vRepD  gft_Run, -D
    Gui, Add, Button, x+0    wp  vCutD  gft_Run, D
    Gui, Add, Button, x+0    wp  vCutD3 gft_Run, D3
    ;--------------
    Gui, Add, Tab3,   ys-8 -Wrap, Gray|GrayDiff|Color|ColorPos|ColorDiff
    Gui, Tab, 1
    Gui, Add, Text,   x+15 y+15, Gray Threshold
    Gui, Add, Edit,   x+15 w100 vThreshold
    Gui, Add, Button, x+15 yp-3 vGray2Two gft_Run, Gray2Two
    Gui, Tab, 2
    Gui, Add, Text,   x+15 y+15, Gray Difference
    Gui, Add, Edit,   x+15 w100 vGrayDiff, 50
    Gui, Add, Button, x+15 yp-3 vGrayDiff2Two gft_Run, GrayDiff2Two
    Gui, Tab, 3
    Gui, Add, Text,   x+15 y+15, Similarity 0
    Gui, Add, Slider, x+0 w100 vSimilar1 gft_Run
      +Center Page1 NoTicks ToolTip, 100
    Gui, Add, Text,   x+0, 100
    Gui, Add, Button, x+15 yp-3 vColor2Two gft_Run, Color2Two
    Gui, Tab, 4
    Gui, Add, Text,   x+15 y+15, Similarity 0
    Gui, Add, Slider, x+0 w100 vSimilar2 gft_Run
      +Center Page1 NoTicks ToolTip, 100
    Gui, Add, Text,   x+0, 100
    Gui, Add, Button, x+15 yp-3 vColorPos2Two gft_Run, ColorPos2Two
    Gui, Tab, 5
    Gui, Add, Text,   x+10 y+15, R
    Gui, Add, Edit,   x+2 w70 vDiffR Limit3
    Gui, Add, UpDown, vdR Range0-255
    Gui, Add, Text,   x+5, G
    Gui, Add, Edit,   x+2 w70 vDiffG Limit3
    Gui, Add, UpDown, vdG Range0-255
    Gui, Add, Text,   x+5, B
    Gui, Add, Edit,   x+2 w70 vDiffB Limit3
    Gui, Add, UpDown, vdB Range0-255
    Gui, Add, Button, x+5 yp-3 vColorDiff2Two gft_Run, ColorDiff2Two
    Gui, Tab
    ;--------------
    Gui, Add, Button, xm vReset gft_Run, Reset
    Gui, Add, Checkbox, x+15 yp+5 vModify gft_Run, Modify
    Gui, Add, Text,   x+30, Comment
    Gui, Add, Edit,   x+5 yp-2 w150 vComment
    Gui, Add, Button, x+30 yp-3 vSplitAdd gft_Run, SplitAdd
    Gui, Add, Button, x+10 vAllAdd gft_Run, AllAdd
    Gui, Add, Button, x+10 w80 vButtonOK gft_Run, OK
    Gui, Add, Button, x+10 wp vClose gCancel, Close
    Gui, Show, Hide, Capture Image To Text
    return
  }
  if (cmd="MakeSubPicWindow")
  {
    Gui, ft_SubPic:New
    Gui, +AlwaysOnTop -Caption +ToolWindow -DPIScale +Parent%hPic%
    Gui, Margin, 0, 0
    Gui, Color, %WindowColor%
    Gui, -Theme
    nW:=2*ww+1, nH:=2*hh+1, C_:=[], w:=11
    Loop, % nW*(nH+1)
    {
      i:=A_Index, j:=i=1 ? "x0 y0" : Mod(i,nW)=1 ? "x0 y+1" : "x+1"
      j.=i>nW*nH ? " cRed BackgroundFFFFAA" : ""
      Gui, Add, Progress, w%w% h%w% %j% +Hwndid -E0x20000
      C_[i]:=id
    }
    Gui, +Theme
    GuiControlGet, SubPic, Pos, %id%
    SubPicW:=Round(SubPicX+SubPicW), SubPicH:=Round(SubPicY+SubPicH)
    Gui, Show, NA x0 y0 w%SubPicW% h%SubPicH%, SubPic
    i:=(SubPicW>PicW), j:=(SubPicH>PicH)
    Gui, ft_Capture:Default
    GuiControl, Enable%i%, MySlider1
    GuiControl, Enable%j%, MySlider2
    GuiControl,, MySlider1, % MySlider1:=0
    GuiControl,, MySlider2, % MySlider2:=0
    return
  }
  if (cmd="MakeMainWindow")
  {
    Gui, ft_Main:New
    Gui, +AlwaysOnTop -DPIScale
    Gui, Margin, 15, 15
    Gui, Color, %WindowColor%
    Gui, Font, s12 cBlack, Verdana
    Gui, Add, GroupBox, xm y10 w250 h45 vMyGroup
    Gui, Add, Text, xp+10 yp+18, % "Width: "
    Gui, Add, Text, x+0 yp w50, %ww%
    Gui, Add, UpDown, vMyww Range1-50, %ww%
    Gui, Add, Text, x+15 yp, % "Height: "
    Gui, Add, Text, x+0 yp w50, %hh%
    Gui, Add, UpDown, vMyhh Range1-40, %hh%
    GuiControlGet, p, Pos, Myhh
    GuiControl, Move, MyGroup, % "w" px+pw " h" py+ph
    Gui, Add, Button, x+30 yp-8 w120 vUpdate gft_Run, Update
    Gui, Add, Checkbox, x+40 yp+8 r1 -Wrap Checked vAddFunc
      , Additional FindText()
    Gui, Add, Text, xm, Hotkey
    Gui, Add, Edit, x+5 w120 vNowHotkey ReadOnly
    Gui, Add, Hotkey, x+5 w140 vSetHotkey1
    Gui, Add, DDL, x+5 w140 vSetHotkey2
      , % "||F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|MButton"
      . "|ScrollLock|CapsLock|Ins|Esc|BS|Del|Tab|Home|End|PgUp|PgDn"
      . "|NumpadDot|NumpadSub|NumpadAdd|NumpadDiv|NumpadMult"
    Gui, Add, Button, x+5 yp-3 w80 vApply gft_Run, Apply
    GuiControlGet, p, Pos, Apply
    w:=720-(px+pw)
    Gui, Add, Button, x+15 w%w% r1 -Wrap vTestClip gft_Run, TestClipboard
    Gui, Font, s6 bold, Verdana
    Gui, Add, Edit, xm w720 r25 vMyPic -Wrap
    Gui, Font, s12 norm, Verdana
    Gui, Add, Button, w180 vCapture gft_Run, Capture
    Gui, Add, Button, x+0 wp vCaptureS gft_Run, CaptureS
    Gui, Add, Button, x+0 wp vTest gft_Run, Test
    Gui, Add, Button, x+0 wp vCopy gft_Run Section, Copy
    Gui, Font, s12 cBlue, Verdana
    Gui, Add, Edit, xm w720 h350 vscr Hwndhscr -Wrap HScroll
    Gui, Show, Hide, Capture Image To Text And Find Text Tool
    return
  }
  if (cmd="Update")
  {
    Gui, ft_Main:Default
    GuiControlGet, Myww
    GuiControlGet, Myhh
    Gui, Hide
    ww:=Myww, hh:=Myhh, ft_Gui("MakeSubPicWindow")
    Gui, ft_Main:Show, Center
    return
  }
  if (cmd="Capture") or (cmd="CaptureS")
  {
    Gui, ft_Main:Default
    Gui, +LastFound
    WinMinimize
    Gui, Hide
    ShowScreenShot:=(cmd="CaptureS")
    if (ShowScreenShot)
      ft_ShowScreenShot(1)
    ;----------------------
    Gui, ft_Mini:New
    Gui, +LastFound +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
    Gui, Color, Red
    d:=2, w:=nW+2*d, h:=nH+2*d, i:=w-d, j:=h-d
    Gui, Show, Hide w%w% h%h%
    s=0-0 %w%-0 %w%-%h% 0-%h% 0-0
    s=%s%  %d%-%d% %i%-%d% %i%-%j% %d%-%j% %d%-%d%
    WinSet, Region, %s%
    ;------------------------------
    Hotkey, $*RButton, ft_RButton_Off, On
    lls:=A_ListLines=0 ? "Off" : "On"
    ListLines, Off
    CoordMode, Mouse
    KeyWait, RButton
    KeyWait, Ctrl
    oldx:=oldy:=""
    Loop
    {
      Sleep, 50
      MouseGetPos, x, y
      if (oldx=x and oldy=y)
        Continue
      oldx:=x, oldy:=y
      ;---------------
      Gui, Show, % "NA x" (x-w//2) " y" (y-h//2)
      ToolTip, % "The Capture Position : " x "," y
        . "`nFirst click RButton to start capturing"
        . "`nMove the mouse to avoid response"
        . "`nSecond click RButton to end capture"
    }
    Until GetKeyState("RButton","P") or GetKeyState("Ctrl","P")
    KeyWait, RButton
    KeyWait, Ctrl
    px:=x, py:=y, oldx:=oldy:=""
    Loop
    {
      Sleep, 50
      MouseGetPos, x, y
      if (oldx=x and oldy=y)
        Continue
      oldx:=x, oldy:=y
      ;---------------
      ToolTip, % "The Capture Position : " px "," py
        . "`nFirst click RButton to start capturing"
        . "`nMove the mouse to avoid response"
        . "`nSecond click RButton to end capture"
    }
    Until GetKeyState("RButton","P") or GetKeyState("Ctrl","P")
    KeyWait, RButton
    KeyWait, Ctrl
    ToolTip
    ListLines, %lls%
    Gui, Destroy
    WinWaitClose,,, 10
    cors:=ft_getc(px,py,ww,hh,!ShowScreenShot)
    Hotkey, $*RButton, ft_RButton_Off, Off
    if (ShowScreenShot)
      ft_ShowScreenShot(0)
    ;--------------------------------
    Gui, ft_Capture:Default
    k:=nW*nH+1
    Loop, % nW
      GuiControl,, % C_[k++], 0
    Loop, 6
      GuiControl,, Edit%A_Index%
    GuiControl,, Modify, % Modify:=0
    GuiControl,, GrayDiff, 50
    GuiControl, Focus, Gray2Two
    GuiControl, +Default, Gray2Two
    ft_Gui("Reset")
    Gui, Show, Center
    Event:=Result:=""
    DetectHiddenWindows, Off
    Gui, +LastFound
    Critical, Off
    WinWaitClose, % "ahk_id " WinExist()
    Gui, ft_Main:Default
    ;--------------------------------
    if (Event="ButtonOK")
    {
      if (!A_IsCompiled)
      {
        FileRead, s, %A_LineFile%
        s:=SubStr(s, s~="i)\n[;=]+ Copy The")
      }
      else s:=""
      GuiControl,, scr, % Result "`n" s
      GuiControl,, MyPic, % Trim(ASCII(Result),"`n")
      Result:=s:=""
    }
    else if (Event="SplitAdd") or (Event="AllAdd")
    {
      GuiControlGet, s,, scr
      i:=j:=0, r:="\|<[^>\n]*>[^$\n]+\$\d+\.[\w+/]+"
      While j:=RegExMatch(s,r,"",j+1)
        i:=InStr(s,"`n",0,j)
      GuiControl,, scr, % SubStr(s,1,i-1) . Result . SubStr(s,i+1)
      GuiControl,, MyPic, % Trim(ASCII(Result),"`n")
      Result:=s:=""
    }
    ;----------------------
    Gui, Show
    GuiControl, Focus, scr
    ft_RButton_Off:
    return
  }
  if (cmd="Test") or (cmd="TestClip")
  {
    Critical, Off
    Gui, ft_Main:Default
    Gui, +LastFound
    WinMinimize
    Gui, Hide
    DetectHiddenWindows, Off
    WinWaitClose, % "ahk_id " WinExist()
    Sleep, 100
    ;----------------------
    if (cmd="Test")
      GuiControlGet, s,, scr
    else
      s:=Clipboard
    if (!A_IsCompiled) and InStr(s,"MCode(") and (cmd="Test")
    {
      s:="`n#NoEnv`nMenu, Tray, Click, 1`n"
        . "Gui, ft_ok_:Show, Hide, ft_ok_`n"
        . s "`nExitApp`n"
      ft_Exec(s)
      DetectHiddenWindows, On
      WinWait, ft_ok_ ahk_class AutoHotkeyGUI,, 3
      if (!ErrorLevel)
        WinWaitClose,,, 30
    }
    else
    {
      Gui, +OwnDialogs
      t:=A_TickCount, n:=150000
      , RegExMatch(s,"\|<[^>\n]*>[^$\n]+\$\d+\.[\w+/]+",v)
      , ok:=FindText(-n, -n, n, n, 0, 0, v)
      , X:=ok.1.x, Y:=ok.1.y, Comment:=ok.1.id
      MsgBox, 4096, Tip, % "Found :`t" Round(ok.MaxIndex()) "`n`n"
        . "Time  :`t" (A_TickCount-t) " ms`n`n"
        . "Pos   :`t" X ", " Y "`n`n"
        . "Result:`t" (ok ? "Success ! " Comment : "Failed !"), 3
      for i,v in ok
        if (i<=2)
          MouseTip(ok[i].x, ok[i].y)
      ok:=""
    }
    ;----------------------
    Gui, Show
    GuiControl, Focus, scr
    return
  }
  if (cmd="Copy")
  {
    Gui, ft_Main:Default
    ControlGet, s, Selected,,, ahk_id %hscr%
    if (s="")
    {
      GuiControlGet, s,, scr
      GuiControlGet, r,, AddFunc
      if (r != 1)
        s:=RegExReplace(s,"\n\K[\s;=]+ Copy The[\s\S]*")
    }
    Clipboard:=RegExReplace(s,"\R","`r`n")
    ;----------------------
    Gui, Hide
    Sleep, 100
    Gui, Show
    GuiControl, Focus, scr
    return
  }
  if (cmd="MySlider1") or (cmd="MySlider2")
  {
    x:=SubPicW>PicW ? -(SubPicW-PicW)*MySlider1//100 : 0
    y:=SubPicH>PicH ? -(SubPicH-PicH)*MySlider2//100 : 0
    Gui, ft_SubPic:Show, NA x%x% y%y%
    return
  }
  if (cmd="Reset")
  {
    if !IsObject(ascii)
      ascii:=[], gray:=[], show:=[]
    CutLeft:=CutRight:=CutUp:=CutDown:=k:=0, bg:=""
    Loop, % nW*nH
    {
      show[++k]:=1, c:=cors[k]
      gray[k]:=(((c>>16)&0xFF)*38+((c>>8)&0xFF)*75+(c&0xFF)*15)>>7
      ft_Gui("SetColor")
    }
    Loop, % cors.CutLeft
      ft_Gui("CutL")
    Loop, % cors.CutRight
      ft_Gui("CutR")
    Loop, % cors.CutUp
      ft_Gui("CutU")
    Loop, % cors.CutDown
      ft_Gui("CutD")
    return
  }
  if (cmd="SetColor")
  {
    c:=c="Black" ? 0x000000 : c="White" ? 0xFFFFFF
      : ((c&0xFF)<<16)|(c&0xFF00)|((c&0xFF0000)>>16)
    SendMessage, 0x2001, 0, c,, % "ahk_id " . C_[k]
    return
  }
  if (cmd="RepColor")
  {
    show[k]:=1, c:=(bg="" ? cors[k] : ascii[k]
      ? "Black":"White"), ft_Gui("SetColor")
    return
  }
  if (cmd="CutColor")
  {
    show[k]:=0, c:=WindowColor, ft_Gui("SetColor")
    return
  }
  if (cmd="RepL")
  {
    if (CutLeft<=cors.CutLeft)
      return
    k:=CutLeft-nW, CutLeft--
    Loop, %nH%
      k+=nW, (A_Index>CutUp and A_Index<nH+1-CutDown
        ? ft_Gui("RepColor") : "")
    return
  }
  if (cmd="CutL")
  {
    if (CutLeft+CutRight>=nW)
      return
    CutLeft++, k:=CutLeft-nW
    Loop, %nH%
      k+=nW, (A_Index>CutUp and A_Index<nH+1-CutDown
        ? ft_Gui("CutColor") : "")
    return
  }
  if (cmd="CutL3")
  {
    Loop, 3
      ft_Gui("CutL")
    return
  }
  if (cmd="RepR")
  {
    if (CutRight<=cors.CutRight)
      return
    k:=1-CutRight, CutRight--
    Loop, %nH%
      k+=nW, (A_Index>CutUp and A_Index<nH+1-CutDown
        ? ft_Gui("RepColor") : "")
    return
  }
  if (cmd="CutR")
  {
    if (CutLeft+CutRight>=nW)
      return
    CutRight++, k:=1-CutRight
    Loop, %nH%
      k+=nW, (A_Index>CutUp and A_Index<nH+1-CutDown
        ? ft_Gui("CutColor") : "")
    return
  }
  if (cmd="CutR3")
  {
    Loop, 3
      ft_Gui("CutR")
    return
  }
  if (cmd="RepU")
  {
    if (CutUp<=cors.CutUp)
      return
    k:=(CutUp-1)*nW, CutUp--
    Loop, %nW%
      k++, (A_Index>CutLeft and A_Index<nW+1-CutRight
        ? ft_Gui("RepColor") : "")
    return
  }
  if (cmd="CutU")
  {
    if (CutUp+CutDown>=nH)
      return
    CutUp++, k:=(CutUp-1)*nW
    Loop, %nW%
      k++, (A_Index>CutLeft and A_Index<nW+1-CutRight
        ? ft_Gui("CutColor") : "")
    return
  }
  if (cmd="CutU3")
  {
    Loop, 3
      ft_Gui("CutU")
    return
  }
  if (cmd="RepD")
  {
    if (CutDown<=cors.CutDown)
      return
    k:=(nH-CutDown)*nW, CutDown--
    Loop, %nW%
      k++, (A_Index>CutLeft and A_Index<nW+1-CutRight
        ? ft_Gui("RepColor") : "")
    return
  }
  if (cmd="CutD")
  {
    if (CutUp+CutDown>=nH)
      return
    CutDown++, k:=(nH-CutDown)*nW
    Loop, %nW%
      k++, (A_Index>CutLeft and A_Index<nW+1-CutRight
        ? ft_Gui("CutColor") : "")
    return
  }
  if (cmd="CutD3")
  {
    Loop, 3
      ft_Gui("CutD")
    return
  }
  if (cmd="Gray2Two")
  {
    Gui, ft_Capture:Default
    GuiControl, Focus, Threshold
    GuiControlGet, Threshold
    if (Threshold="")
    {
      pp:=[]
      Loop, 256
        pp[A_Index-1]:=0
      Loop, % nW*nH
        if (show[A_Index])
          pp[gray[A_Index]]++
      IP:=IS:=0
      Loop, 256
        k:=A_Index-1, IP+=k*pp[k], IS+=pp[k]
      Threshold:=Floor(IP/IS)
      Loop, 20
      {
        LastThreshold:=Threshold
        IP1:=IS1:=0
        Loop, % LastThreshold+1
          k:=A_Index-1, IP1+=k*pp[k], IS1+=pp[k]
        IP2:=IP-IP1, IS2:=IS-IS1
        if (IS1!=0 and IS2!=0)
          Threshold:=Floor((IP1/IS1+IP2/IS2)/2)
        if (Threshold=LastThreshold)
          Break
      }
      GuiControl,, Threshold, %Threshold%
    }
    Threshold:=Round(Threshold)
    color:="*" Threshold, k:=i:=0
    Loop, % nW*nH
    {
      ascii[++k]:=v:=(gray[k]<=Threshold)
      if (show[k])
        i:=(v?i+1:i-1), c:=(v?"Black":"White"), ft_Gui("SetColor")
    }
    bg:=i>0 ? "1":"0"
    return
  }
  if (cmd="GrayDiff2Two")
  {
    Gui, ft_Capture:Default
    GuiControlGet, GrayDiff
    if (GrayDiff="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, `n  Please Set Gray Difference First !  `n, 1
      return
    }
    if (CutLeft=cors.CutLeft)
      ft_Gui("CutL")
    if (CutRight=cors.CutRight)
      ft_Gui("CutR")
    if (CutUp=cors.CutUp)
      ft_Gui("CutU")
    if (CutDown=cors.CutDown)
      ft_Gui("CutD")
    GrayDiff:=Round(GrayDiff)
    color:="**" GrayDiff, k:=i:=0
    Loop, % nW*nH
    {
      j:=gray[++k]+GrayDiff
      , ascii[k]:=v:=( gray[k-1]>j or gray[k+1]>j
      or gray[k-nW]>j or gray[k+nW]>j
      or gray[k-nW-1]>j or gray[k-nW+1]>j
      or gray[k+nW-1]>j or gray[k+nW+1]>j )
      if (show[k])
        i:=(v?i+1:i-1), c:=(v?"Black":"White"), ft_Gui("SetColor")
    }
    bg:=i>0 ? "1":"0"
    return
  }
  if (cmd="Color2Two") or (cmd="ColorPos2Two")
  {
    Gui, ft_Capture:Default
    GuiControlGet, c,, SelColor
    if (c="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, `n  Please Select a Color First !  `n, 1
      return
    }
    UsePos:=(cmd="ColorPos2Two") ? 1:0
    GuiControlGet, n,, Similar1
    n:=Round(n/100,2), color:=c "@" n
    , n:=Floor(9*255*255*(1-n)*(1-n)), k:=i:=0
    , rr:=(c>>16)&0xFF, gg:=(c>>8)&0xFF, bb:=c&0xFF
    Loop, % nW*nH
    {
      c:=cors[++k], r:=((c>>16)&0xFF)-rr
      , g:=((c>>8)&0xFF)-gg, b:=(c&0xFF)-bb
      , ascii[k]:=v:=(3*r*r+4*g*g+2*b*b<=n)
      if (show[k])
        i:=(v?i+1:i-1), c:=(v?"Black":"White"), ft_Gui("SetColor")
    }
    bg:=i>0 ? "1":"0"
    return
  }
  if (cmd="ColorDiff2Two")
  {
    Gui, ft_Capture:Default
    GuiControlGet, c,, SelColor
    if (c="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, `n  Please Select a Color First !  `n, 1
      return
    }
    GuiControlGet, dR
    GuiControlGet, dG
    GuiControlGet, dB
    rr:=(c>>16)&0xFF, gg:=(c>>8)&0xFF, bb:=c&0xFF
    , n:=Format("{:06X}",(dR<<16)|(dG<<8)|dB)
    , color:=StrReplace(c "-" n,"0x"), k:=i:=0
    Loop, % nW*nH
    {
      c:=cors[++k], r:=(c>>16)&0xFF, g:=(c>>8)&0xFF
      , b:=c&0xFF, ascii[k]:=v:=(Abs(r-rr)<=dR
      and Abs(g-gg)<=dG and Abs(b-bb)<=dB)
      if (show[k])
        i:=(v?i+1:i-1), c:=(v?"Black":"White"), ft_Gui("SetColor")
    }
    bg:=i>0 ? "1":"0"
    return
  }
  if (cmd="Modify")
  {
    GuiControlGet, Modify, ft_Capture:, Modify
    return
  }
  if (cmd="Similar1")
  {
    GuiControl, ft_Capture:, Similar2, %Similar1%
    return
  }
  if (cmd="Similar2")
  {
    GuiControl, ft_Capture:, Similar1, %Similar2%
    return
  }
  if (cmd="getwz")
  {
    wz:=""
    if (bg="")
      return
    k:=0
    Loop, %nH%
    {
      v:=""
      Loop, %nW%
        v.=!show[++k] ? "" : ascii[k] ? "1":"0"
      wz.=v="" ? "" : v "`n"
    }
    return
  }
  if (cmd="Auto")
  {
    ft_Gui("getwz")
    if (wz="")
    {
      Gui, ft_Capture:+OwnDialogs
      MsgBox, 4096, Tip, `nPlease Click Color2Two or Gray2Two First !, 1
      return
    }
    While InStr(wz,bg)
    {
      if (wz~="^" bg "+\n")
        wz:=RegExReplace(wz,"^" bg "+\n"), ft_Gui("CutU")
      else if !(wz~="m`n)[^\n" bg "]$")
        wz:=RegExReplace(wz,"m`n)" bg "$"), ft_Gui("CutR")
      else if (wz~="\n" bg "+\n$")
        wz:=RegExReplace(wz,"\n\K" bg "+\n$"), ft_Gui("CutD")
      else if !(wz~="m`n)^[^\n" bg "]")
        wz:=RegExReplace(wz,"m`n)^" bg), ft_Gui("CutL")
      else Break
    }
    wz:=""
    return
  }
  if (cmd="ButtonOK") or (cmd="SplitAdd") or (cmd="AllAdd")
  {
    Gui, ft_Capture:Default
    Gui, +OwnDialogs
    ft_Gui("getwz")
    if (wz="")
    {
      MsgBox, 4096, Tip, `nPlease Click Color2Two or Gray2Two First !, 1
      return
    }
    if InStr(color,"@") and (UsePos)
    {
      StringSplit, r, color, @
      k:=i:=j:=0
      Loop, % nW*nH
      {
        if (!show[++k])
          Continue
        i++
        if (k=cors.SelPos)
        {
          j:=i
          Break
        }
      }
      if (j=0)
      {
        MsgBox, 4096, Tip, Please select the core color again !, 3
        return
      }
      color:="#" . j . "@" . r2
    }
    GuiControlGet, Comment
    if (cmd="SplitAdd")
    {
      if InStr(color,"#")
      {
        MsgBox, 4096, Tip
          , % "Can't be used in ColorPos mode, "
          . "because it can cause position errors", 3
        return
      }
      SetFormat, IntegerFast, d
      bg:=StrLen(StrReplace(wz,"0"))
        > StrLen(StrReplace(wz,"1")) ? "1":"0"
      s:="", i:=0, k:=nW*nH+1+CutLeft
      Loop, % w:=nW-CutLeft-CutRight
      {
        i++
        GuiControlGet, j,, % C_[k++]
        if (j=0 and A_Index<w)
          Continue
        v:=RegExReplace(wz,"m`n)^(.{" i "}).*","$1")
        wz:=RegExReplace(wz,"m`n)^.{" i "}"), i:=0
        While InStr(v,bg)
        {
          if (v~="^" bg "+\n")
            v:=RegExReplace(v,"^" bg "+\n")
          else if !(v~="m`n)[^\n" bg "]$")
            v:=RegExReplace(v,"m`n)" bg "$")
          else if (v~="\n" bg "+\n$")
            v:=RegExReplace(v,"\n\K" bg "+\n$")
          else if !(v~="m`n)^[^\n" bg "]")
            v:=RegExReplace(v,"m`n)^" bg)
          else Break
        }
        if (v!="")
        {
          v:=Format("{:d}",InStr(v,"`n")-1) "." bit2base64(v)
          s.="`nText.=""|<" SubStr(Comment,1,1) ">" color "$" v """`n"
          Comment:=SubStr(Comment, 2)
        }
      }
      Event:=cmd, Result:=s
      Gui, Hide
      return
    }
    wz:=Format("{:d}",InStr(wz,"`n")-1) "." bit2base64(wz)
    s:="`nText.=""|<" Comment ">" color "$" wz """`n"
    if (cmd="AllAdd")
    {
      Event:=cmd, Result:=s
      Gui, Hide
      return
    }
    x:=px-ww+CutLeft+(nW-CutLeft-CutRight)//2
    y:=py-hh+CutUp+(nH-CutUp-CutDown)//2
    s:=StrReplace(s, "Text.=", "Text:=")
    s=
    (
`; #Include <FindText>

t1:=A_TickCount
%s%
if (ok:=FindText(%x%-150000, %y%-150000, 150000, 150000, 0, 0, Text))
{
  CoordMode, Mouse
  X:=ok.1.x, Y:=ok.1.y, Comment:=ok.1.id
  ; Click, `%X`%, `%Y`%
}

MsgBox, 4096, Tip, `% "Found :``t" Round(ok.MaxIndex()) "``n``n"
  . "Time  :``t" (A_TickCount-t1) " ms``n``n"
  . "Pos   :``t" X ", " Y "``n``n"
  . "Result:``t" (ok ? "Success ! " Comment : "Failed !")

for i,v in ok
  if (i<=2)
    MouseTip(ok[i].x, ok[i].y)

)
    Event:=cmd, Result:=s
    Gui, Hide
    return
  }
  if (cmd="ShowPic")
  {
    Critical
    ControlGet, i, CurrentLine,,, ahk_id %hscr%
    ControlGet, s, Line, %i%,, ahk_id %hscr%
    GuiControl, ft_Main:, MyPic, % Trim(ASCII(s),"`n")
    return
  }
  if (cmd="WM_LBUTTONDOWN")
  {
    Critical
    MouseGetPos,,,, j
    IfNotInString, j, progress
      return
    MouseGetPos,,,, j, 2
    Gui, ft_Capture:Default
    For k,v in C_
    {
      if (v!=j)
        Continue
      if (k>nW*nH)
      {
        GuiControlGet, i,, %v%
        GuiControl,, %v%, % i ? 0:100
      }
      else if (Modify and bg!="" and show[k])
      {
        ascii[k]:=!ascii[k]
        , c:=(ascii[k] ? "Black":"White")
        , ft_Gui("SetColor")
      }
      else
      {
        c:=cors[k], cors.SelPos:=k
        r:=(c>>16)&0xFF, g:=(c>>8)&0xFF, b:=c&0xFF
        GuiControl,, SelGray, % gray[k]
        GuiControl,, SelColor, %c%
        GuiControl,, SelR, %r%
        GuiControl,, SelG, %g%
        GuiControl,, SelB, %b%
      }
      Break
    }
    return
  }
  if (cmd="Apply")
  {
    if (!ft_FuncBind2)
      ft_FuncBind2:=Func("ft_Gui").Bind("ScreenShot")
    Gui, ft_Main:Default
    GuiControlGet, NowHotkey
    GuiControlGet, SetHotkey1
    GuiControlGet, SetHotkey2
    if (NowHotkey!="")
      Hotkey, *%NowHotkey%,, Off UseErrorLevel
    k:=SetHotkey1!="" ? SetHotkey1 : SetHotkey2
    if (k!="")
      Hotkey, *%k%, %ft_FuncBind2%, On UseErrorLevel
    GuiControl,, NowHotkey, %k%
    GuiControl,, SetHotkey1
    GuiControl, Choose, SetHotkey2, 0
    return
  }
  if (cmd="ScreenShot")
  {
    Critical
    ScreenShot()
    Gui, ft_Tip:New
    ; WS_EX_NOACTIVATE:=0x08000000, WS_EX_TRANSPARENT:=0x20
    Gui, +LastFound +AlwaysOnTop +ToolWindow -Caption -DPIScale +E0x08000020
    Gui, Color, Yellow
    Gui, Font, cRed s48 bold
    Gui, Add, Text,, Success
    WinSet, Transparent, 200
    Gui, Show, NA y0, ScreenShot Tip
    Sleep, 1000
    Gui, Destroy
    return
  }
}

ft_Load_ToolTip_Text()
{
  s=
(LTrim
Update     = Update the capture range by adjusting the numbers
AddFunc    = Additional FindText() in Copy
NowHotkey  = Current screenshot hotkey
SetHotkey1 = First sequence Screenshot hotkey
SetHotkey2 = Second sequence Screenshot hotkey
Apply      = Clear old screenshot hotkey and apply a new hotkey
TestClip   = Test the Text data in the clipboard for searching images
Capture    = Initiate Image Capture Sequence
CaptureS   = Restore the last screenshot and then start capturing
Test       = Test Results of Code
Copy       = Copy Code to Clipboard
--------------------
Reset      = Reset to Original Captured Image
SplitAdd   = Using Markup Segmentation to Generate Text Library
AllAdd     = Append Another FindText Search Text into Previously Generated Code
ButtonOK   = Create New FindText Code for Testing
Close      = Close the Window Don't Do Anything
Gray2Two      = Converts Image Pixels from Gray Threshold to Black or White
GrayDiff2Two  = Converts Image Pixels from Gray Difference to Black or White
Color2Two     = Converts Image Pixels from Color Similar to Black or White
ColorPos2Two  = Converts Image Pixels from Color Position to Black or White
ColorDiff2Two = Converts Image Pixels from Color Difference to Black or White
SelGray    = Gray value of the selected color
SelColor   = The selected color
SelR       = Red component of the selected color
SelG       = Green component of the selected color
SelB       = Blue component of the selected color
RepU       = Undo Cut the Upper Edge by 1
CutU       = Cut the Upper Edge by 1
CutU3      = Cut the Upper Edge by 3
RepL       = Undo Cut the Left Edge by 1
CutL       = Cut the Left Edge by 1
CutL3      = Cut the Left Edge by 3
Auto       = Automatic Cutting Edge
RepR       = Undo Cut the Right Edge by 1
CutR       = Cut the Right Edge by 1
CutR3      = Cut the Right Edge by 3
RepD       = Undo Cut the Lower Edge by 1
CutD       = Cut the Lower Edge by 1
CutD3      = Cut the Lower Edge by 3
Modify     = Allows Modify the Black and White Image
Comment    = Optional Comment used to Label Code ( Within <> )
Threshold  = Gray Threshold which Determines Black or White Pixel Conversion (0-255)
GrayDiff   = Gray Difference which Determines Black or White Pixel Conversion (0-255)
Similar1   = Adjust color similarity as Equivalent to The Selected Color
Similar2   = Adjust color similarity as Equivalent to The Selected Color
DiffR      = Red Difference which Determines Black or White Pixel Conversion (0-255)
DiffG      = Green Difference which Determines Black or White Pixel Conversion (0-255)
DiffB      = Blue Difference which Determines Black or White Pixel Conversion (0-255)
)
  return, s
}

ft_EditEvents1()
{
  static ft_FuncBind3:=Func("ft_Gui").Bind("ShowPic")
  ListLines, Off
  if (A_Gui="ft_Main" && A_GuiControl="scr")
    SetTimer, %ft_FuncBind3%, -150
}

ft_EditEvents2()
{
  ListLines, Off
  if (A_Gui="ft_SubPic")
    ft_Gui("WM_LBUTTONDOWN")
  else
    ft_EditEvents1()
}

ft_ShowToolTip(cmd:="")
{
  static
  ListLines, Off
  if (!ToolTip_Text)
    ToolTip_Text:=ft_Load_ToolTip_Text()
  if (!ft_FuncBind4)
    ft_FuncBind4:=Func("ft_ShowToolTip").Bind("ToolTip")
  if (!ft_FuncBind5)
    ft_FuncBind5:=Func("ft_ShowToolTip").Bind("ToolTipOff")
  if (cmd="ToolTip")
  {
    MouseGetPos,,, _TT
    WinGetClass, _TT, ahk_id %_TT%
    if (_TT = "AutoHotkeyGUI")
      ToolTip, % RegExMatch(ToolTip_Text
      , "im`n)^" CurrControl "\K\s*=.*", _TT)
      ? StrReplace(Trim(_TT,"`t ="),"\n","`n") : ""
    return
  }
  if (cmd="ToolTipOff")
  {
    ToolTip
    return
  }
  CurrControl:=A_GuiControl
  if (CurrControl!=PrevControl)
  {
    PrevControl:=CurrControl, _TT:=(CurrControl!="")
    SetTimer, %ft_FuncBind4%, % _TT ? -500  : "Off"
    SetTimer, %ft_FuncBind5%, % _TT ? -5500 : "Off"
    ToolTip
  }
}

ft_getc(px, py, ww, hh, ScreenShot:=1)
{
  local  ; Unaffected by Super-global variables
  xywh2xywh(px-ww,py-hh,2*ww+1,2*hh+1,x,y,w,h)
  if (w<1 or h<1)
    return
  bch:=A_BatchLines
  SetBatchLines, -1
  if (ScreenShot)
    ScreenShot()
  ;--------------------------------------
  GetBitsFromScreen(x,y,w,h,Scan0,Stride,0,zx,zy,zw,zh)
  ;--------------------------------------
  cors:=[], k:=0
  lls:=A_ListLines=0 ? "Off" : "On"
  ListLines, Off
  Loop, % 2*hh+1
  {
    j:=py-hh-zy+A_Index-1
    Loop, % 2*ww+1
      i:=px-ww-zx+A_Index-1
      , cors[++k]:=(i<0 or i>zw-1 or j<0 or j>zh-1)
      ? "0xFFFFFF" : Format("0x{:06X}"
      ,NumGet(Scan0+j*Stride+i*4,"uint")&0xFFFFFF)
  }
  ListLines, %lls%
  cors.CutLeft:=Abs(px-ww-x)
  cors.CutRight:=Abs(px+ww-(x+w-1))
  cors.CutUp:=Abs(py-hh-y)
  cors.CutDown:=Abs(py+hh-(y+h-1))
  SetBatchLines, %bch%
  return, cors
}

ft_ShowScreenShot(Show:=1) {
  local  ; Unaffected by Super-global variables
  static hBM, Ptr:=A_PtrSize ? "UPtr" : "UInt"
  Gui, ft_ScreenShot:Destroy
  if (hBM)
    DllCall("DeleteObject",Ptr,hBM), hBM:=""
  if (!Show)
    return
  n:=150000, xywh2xywh(-n,-n,2*n,2*n,x1,y1,w1,h1)
  GetBitsFromScreen(x1,y1,w1,h1,Scan0,Stride,0,x,y,w,h)
  ;---------------------
  VarSetCapacity(bi, 40, 0), NumPut(40, bi, 0, "int")
  NumPut(w, bi, 4, "int"), NumPut(-h, bi, 8, "int")
  NumPut(1, bi, 12, "short"), NumPut(32, bi, 14, "short")
  if (hBM:=DllCall("CreateDIBSection", Ptr,0, Ptr,&bi
    , "int",0, Ptr "*",ppvBits, Ptr,0, "int",0, Ptr))
      DllCall("RtlMoveMemory", Ptr,ppvBits, Ptr,Scan0, Ptr,Stride*h)
  ;-------------------------
  win:=DllCall("GetDesktopWindow", Ptr)
  hDC:=DllCall("GetWindowDC", Ptr,win, Ptr)
  mDC:=DllCall("CreateCompatibleDC", Ptr,hDC, Ptr)
  oBM:=DllCall("SelectObject", Ptr,mDC, Ptr,hBM, Ptr)
  hBrush:=DllCall("CreateSolidBrush", "uint",0xFFFFFF, Ptr)
  oBrush:=DllCall("SelectObject", Ptr,mDC, Ptr,hBrush, Ptr)
  DllCall("BitBlt", Ptr,mDC, "int",0, "int",0, "int",w, "int",h
    , Ptr,mDC, "int",0, "int",0, "uint",0xC000CA)
  DllCall("SelectObject", Ptr,mDC, Ptr,oBrush)
  DllCall("DeleteObject", Ptr,hBrush)
  DllCall("SelectObject", Ptr,mDC, Ptr,oBM)
  DllCall("DeleteDC", Ptr,mDC)
  DllCall("ReleaseDC", Ptr,win, Ptr,hDC)
  ;-------------------------
  Gui, ft_ScreenShot:+AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
  Gui, ft_ScreenShot:Margin, 0, 0
  Gui, ft_ScreenShot:Add, Picture, x0 y0 w%w% h%h% +HwndhPic +0xE
  SendMessage, 0x172, 0, hBM,, ahk_id %hPic%
  Gui, ft_ScreenShot:Show, NA x%x% y%y% w%w% h%h%, Show ScreenShot
}

ft_Exec(s)
{
  Ahk:=A_IsCompiled ? A_ScriptDir "\AutoHotkey.exe":A_AhkPath
  s:=RegExReplace(s, "\R", "`r`n")
  Try
  {
    shell:=ComObjCreate("WScript.Shell")
    oExec:=shell.Exec(Ahk " /f /ErrorStdOut *")
    oExec.StdIn.Write(s)
    oExec.StdIn.Close()
  }
  catch
  {
    f:=A_Temp "\~test1.tmp"
    s:="`r`n FileDelete, " f "`r`n" s
    FileDelete, %f%
    FileAppend, %s%, %f%
    Run, %Ahk% /f "%f%",, UseErrorLevel
  }
}


;===== Copy The Following Functions To Your Own Code Just once =====


;--------------------------------
; FindText - Capture screen image into text and then find it
;--------------------------------
; X1, Y1 --> the search scope's upper left corner coordinates
; X2, Y2 --> the search scope's lower right corner coordinates
; err1, err0 --> character "0" or "_" fault-tolerant in percentage
; Text --> can be a lot of text parsed into images, separated by "|"
; ScreenShot --> if the value is 0, the last screenshot will be used
; FindAll --> if the value is 0, Just find one result and return
; JoinText --> if the value is 1, Join all Text for combination lookup
; offsetX, offsetY --> Set the Max text offset for combination lookup
; ruturn --> the function returns a second-order array
; containing all lookup results, Any result is an associative array
; {1:X, 2:Y, 3:W, 4:H, x:X+W//2, y:Y+H//2, id:Comment},
; if no image is found, the function returns 0.
;--------------------------------

FindText( x1, y1, x2, y2, err1, err0, text, ScreenShot:=1
  , FindAll:=1, JoinText:=0, offsetX:=20, offsetY:=10 )
{
  local  ; Unaffected by Super-global variables
  bch:=A_BatchLines
  SetBatchLines, -1
  x:=(x1<x2 ? x1:x2), y:=(y1<y2 ? y1:y2)
  , w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  , xywh2xywh(x,y,w,h,x,y,w,h)
  if (w<1 or h<1)
  {
    SetBatchLines, %bch%
    return, 0
  }
  ;-------------------------------
  GetBitsFromScreen(x,y,w,h,Scan0,Stride,ScreenShot,zx,zy)
  ;-------------------------------
  sx:=x-zx, sy:=y-zy, sw:=w, sh:=h
  , arr:=[], info:=[], allv:=""
  Loop, Parse, text, |
  {
    v:=A_LoopField
    IfNotInString, v, $, Continue
    comment:="", e1:=err1, e0:=err0, set_e1_e0:=0
    ; You Can Add Comment Text within The <>
    if RegExMatch(v,"<([^>]*)>",r)
      v:=StrReplace(v,r), comment:=Trim(r1)
    ; You can Add two fault-tolerant in the [], separated by commas
    if RegExMatch(v,"\[([^\]]*)]",r)
    {
      v:=StrReplace(v,r), r1.=","
      StringSplit, r, r1, `,
      e1:=r1, e0:=r2, set_e1_e0:=1
    }
    StringSplit, r, v, $
    color:=r1, v:=r2
    StringSplit, r, v, .
    w1:=r1, v:=base64tobit(r2), h1:=StrLen(v)//w1
    if (r0<2 or h1<1 or w1>sw or h1>sh or StrLen(v)!=w1*h1)
      Continue
    mode:=InStr(color,"-") ? 4 : InStr(color,"#") ? 3
      : InStr(color,"**") ? 2 : InStr(color,"*") ? 1 : 0
    if (mode=4)
    {
      color:=StrReplace(color,"0x")
      StringSplit, r, color, -
      color:="0x" . r1, n:="0x" . r2
    }
    else
    {
      color:=RegExReplace(color,"[*#]") . "@"
      StringSplit, r, color, @
      color:=(mode=3 ? ((r1-1)//w1)*Stride+Mod(r1-1,w1)*4 : r1)
      , n:=Round(r2,2)+(!r2), n:=Floor(9*255*255*(1-n)*(1-n))
    }
    StrReplace(v,"1","",len1), len0:=StrLen(v)-len1
    , e1:=Round(len1*e1), e0:=Round(len0*e0)
    , info.Push( [StrLen(allv),w1,h1,len1,len0,e1,e0
    , mode,color,n,comment,set_e1_e0] ), allv.=v
  }
  if (allv="" or !Stride)
  {
    SetBatchLines, %bch%
    return, 0
  }
  num:=info.MaxIndex(), VarSetCapacity(input, num*7*4)
  , VarSetCapacity(gs, sw*sh)
  , VarSetCapacity(ss, sw*sh), k:=StrLen(allv)*4
  , VarSetCapacity(s1, k), VarSetCapacity(s0, k)
  , allpos_max:=(FindAll ? 1024 : 1)
  , VarSetCapacity(allpos, allpos_max*4)
  ;-------------------------------------
  Loop, 2
  {
  if (num>1 and err1=0 and err0=0)
  {
    err1:=err0:=0.05
    For i,j in info
      if (!j.12)
        j.6:=Round(j.4*err1), j.7:=Round(j.5*err0)
  }
  if (JoinText)
  {
    j:=info[1], mode:=j.8, color:=j.9, n:=j.10
    , w1:=-1, h1:=j.3, comment:="", k:=0
    Loop, % num
    {
      j:=info[A_Index], w1+=j.2+1, comment.=j.11
      Loop, 7
        NumPut(j[A_Index], input, 4*(k++), "int")
    }
    ok:=PicFind( mode,color,n,offsetX,offsetY
      ,Scan0,Stride,sx,sy,sw,sh,gs,ss,allv,s1,s0
      ,input,num*7,allpos,allpos_max )
    Loop, % ok
      pos:=NumGet(allpos, 4*(A_Index-1), "uint")
      , rx:=(pos&0xFFFF)+zx, ry:=(pos>>16)+zy
      , arr.Push( {1:rx, 2:ry, 3:w1, 4:h1
      , x:rx+w1//2, y:ry+h1//2, id:comment} )
  }
  else
  {
    For i,j in info
    {
      mode:=j.8, color:=j.9, n:=j.10, comment:=j.11
      , w1:=j.2, h1:=j.3, v:=SubStr(allv, j.1+1, w1*h1)
      Loop, 7
        NumPut(j[A_Index], input, 4*(A_Index-1), "int")
      NumPut(0, input, "int")
      ok:=PicFind( mode,color,n,offsetX,offsetY
        ,Scan0,Stride,sx,sy,sw,sh,gs,ss,v,s1,s0
        ,input,7,allpos,allpos_max )
      Loop, % ok
        pos:=NumGet(allpos, 4*(A_Index-1), "uint")
        , rx:=(pos&0xFFFF)+zx, ry:=(pos>>16)+zy
        , arr.Push( {1:rx, 2:ry, 3:w1, 4:h1
        , x:rx+w1//2, y:ry+h1//2, id:comment} )
      if (ok and !FindAll)
        Break
    }
  }
  if (num=1 and err1=0 and err0=0 and !arr.MaxIndex())
  {
    err1:=err0:=0.05, k:=0
    For i,j in info
      if (!j.12)
        j.6:=Round(j.4*err1), j.7:=Round(j.5*err0), k:=1
    IfEqual, k, 0, Break
  }
  else Break
  }
  SetBatchLines, %bch%
  return, arr.MaxIndex() ? arr:0
}

PicFind(mode, color, n, offsetX, offsetY
  , Scan0, Stride, sx, sy, sw, sh
  , ByRef gs, ByRef ss, ByRef text, ByRef s1, ByRef s0
  , ByRef input, num, ByRef allpos, allpos_max)
{
  static MyFunc, Ptr:=A_PtrSize ? "UPtr" : "UInt"
  static init:=PicFind(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
  if (!MyFunc)
  {
    x32:="5557565383EC788B8424CC0000008BBC24CC000000C7442"
    . "424000000008B40048B7F148944243C8B8424CC000000897C2"
    . "42C8BBC24CC0000008B40088B7F18894424348B8424CC00000"
    . "0897C24308B400C89C6894424288B8424CC0000008B401039C"
    . "6894424200F4DC68944241C8B8424D000000085C00F8E15010"
    . "0008BB424CC0000008B44242489F78B0C868B7486048B44870"
    . "88974241085C0894424180F8ED700000089CD894C2414C7442"
    . "40C00000000C744240800000000C744240400000000890C248"
    . "D76008DBC27000000008B5C24108B7424088B4C24148B54240"
    . "C89DF89F029F101F78BB424C000000001CE85DB7E5E8B0C248"
    . "9EB893C2489D7EB198BAC24C800000083C70483C00189548D0"
    . "083C101390424742C83BC248C0000000389FA0F45D0803C063"
    . "175D48BAC24C400000083C70483C00189549D0083C30139042"
    . "475D48B7424100174241489DD890C2483442404018BB424B00"
    . "000008B442404017424088BBC24A4000000017C240C3944241"
    . "80F8554FFFFFF83442424078B442424398424D00000000F8FE"
    . "BFEFFFF83BC248C000000030F84A00600008B8424A40000008"
    . "BB424A80000000FAF8424AC0000008BBC248C0000008D2CB08"
    . "B8424B00000008BB424A4000000F7D885FF8D0486894424100"
    . "F84F702000083BC248C000000010F845F08000083BC248C000"
    . "000020F84130900008B8424900000008B9C24940000000FB6B"
    . "C24940000000FB6B42490000000C744241800000000C744242"
    . "400000000C1E8100FB6DF0FB6D08B84249000000089D10FB6C"
    . "4894424088B842494000000C1E8100FB6C029C101D08904248"
    . "B442408894C24408B4C240801D829D9894424088D043E894C2"
    . "40489F129F9894424148BBC24B40000008B8424B0000000894"
    . "C240C89E98B6C2440C1E00285FF894424380F8EBA0000008BB"
    . "424B000000085F60F8E910000008B8424A00000008B5424240"
    . "39424BC00000001C8034C243889CF894C244003BC24A000000"
    . "0EB3D8D76008DBC2700000000391C247C3D394C24047F37394"
    . "C24087C3189F30FB6F33974240C0F9EC3397424140F9DC183C"
    . "00483C20121D9884AFF39F8741E0FB658020FB648010FB6303"
    . "9DD7EBE31C983C00483C201884AFF39F875E28BBC24B000000"
    . "0017C24248B4C24408344241801034C24108B442418398424B"
    . "40000000F8546FFFFFF8B8424B00000002B44243C8944240C8"
    . "B8424B40000002B442434894424600F884D0900008B4424288"
    . "BBC24C40000008B74243CC744241000000000C744243800000"
    . "000C7442434000000008D3C8789C583EE01897C246C8974247"
    . "48B44240C85C00F88E70000008B7C24388B8424AC000000BE0"
    . "0000000C704240000000001F8C1E0108944246889F82B84249"
    . "C0000000F49F08B84249C000000897424640FAFB424B000000"
    . "001F8894424708974245C8DB6000000008B04240344241089C"
    . "1894424088B442430394424200F84AA0100008B5C241C89C60"
    . "38C24BC00000031C08B54242C85DB0F8EC8010000897424048"
    . "B7C2420EB2D39C77E1C8BB424C80000008B1C8601CB803B007"
    . "40B836C240401782B8D74260083C0013944241C0F849101000"
    . "039C57ECF8BB424C40000008B1C8601CB803B0174BE83EA017"
    . "9B9830424018B04243944240C0F8D68FFFFFF83442438018BB"
    . "424B00000008B44243801742410394424600F8DEFFEFFFF8B4"
    . "C243483C47889C85B5E5F5DC250008B8424900000008BB424B"
    . "4000000C744240C00000000C744241400000000C1E8100FB6C"
    . "08904248B8424900000000FB6C4894424040FB684249000000"
    . "0894424088B8424B0000000C1E00285F68944242489E88BAC2"
    . "4940000000F8E24FEFFFF8B9C24B000000085DB7E758B9C24A"
    . "00000008B7424148BBC24A000000003B424BC00000001C3034"
    . "424248944241801C78D76008DBC27000000000FB643020FB64"
    . "B012B04242B4C24040FB6132B5424080FAFC00FAFC98D04400"
    . "FAFD28D04888D045039C50F930683C30483C60139DF75C98BB"
    . "C24B0000000017C24148B4424188344240C01034424108B742"
    . "40C39B424B40000000F8566FFFFFFE985FDFFFF85ED7E358B7"
    . "424088BBC24BC00000031C08B54242C8D1C378BB424C400000"
    . "08B0C8601D9803901740983EA010F8890FEFFFF83C00139C57"
    . "5E683BC24D0000000070F8EAA0100008B442474030424C7442"
    . "44007000000896C2444894424288B8424CC00000083C020894"
    . "4243C8B44243C8B9424B00000008B7C24288B0029C28944245"
    . "08B84249800000001F839C20F4EC289C68944244C39FE0F8C0"
    . "90100008B44243C8B700C8B78148B6808897424148B7010897"
    . "C245489C7897424248BB424B40000002B700489F08B7424703"
    . "9C60F4EC68BB424C4000000894424188B47FC89442404C1E00"
    . "201C6038424C8000000894424588B4424648B7C2428037C245"
    . "C3B442418894424040F8F8700000085ED7E268B8C24BC00000"
    . "08B54242431C08D1C398B0C8601D9803901740583EA01784A8"
    . "3C00139C575EA8B4424148B4C245439C8747E85C07E7A8B9C2"
    . "4BC000000896C244831C08B6C245801FBEB0983C0013944241"
    . "4745C8B54850001DA803A0074EC83E90179E78B6C244890834"
    . "424040103BC24B00000008B442404394424180F8D79FFFFFF8"
    . "3442428018B4424283944244C0F8D4CFFFFFF830424018B6C2"
    . "4448B04243944240C0F8D7EFCFFFFE911FDFFFF8B4424288B7"
    . "4245083442440078344243C1C8D4430FF894424288B4424403"
    . "98424D00000000F8F7FFEFFFF8B6C24448B7C24348B0424038"
    . "424A80000008BB424D40000000B4424688D4F01398C24D8000"
    . "0008904BE0F8ED8FCFFFF85ED7E278B7424088BBC24BC00000"
    . "08B8424C40000008D1C378B74246C8B1083C00401DA39F0C60"
    . "20075F283042401894C24348B04243944240C0F8DDEFBFFFFE"
    . "971FCFFFF89F68DBC27000000008B8424B0000000038424A80"
    . "000002B44243C894424248B8424AC000000038424B40000002"
    . "B442434398424AC000000894424380F8F520400008B8424A40"
    . "000008BB424A80000000FAF8424AC000000C74424180000000"
    . "08D04B0038424900000008BB424A0000000894424348B44242"
    . "4398424A80000000F8F2B0100008B8424AC000000C1E010894"
    . "4243C8B442434894424148B8424A8000000894424088B44241"
    . "40FB67C060289C52BAC2490000000893C240FB67C0601897C2"
    . "4040FB63C068B44241C85C00F8E140100008B4424308944241"
    . "08B44242C8944240C31C0EB5D394424207E4A8B9C24C800000"
    . "08B0C8301E90FB6540E020FB65C0E012B14242B5C24040FB60"
    . "C0E0FAFD20FAFDB29F98D14520FAFC98D149A8D144A3994249"
    . "4000000720C836C2410017865908D74260083C0013944241C0"
    . "F84A3000000394424287E9D8B9C24C40000008B0C8301E90FB"
    . "6540E020FB65C0E012B14242B5C24040FB60C0E0FAFD20FAFD"
    . "B29F98D14520FAFC98D149A8D144A3B9424940000000F865BF"
    . "FFFFF836C240C010F8950FFFFFF834424080183442414048B4"
    . "42408394424240F8DF6FEFFFF838424AC000000018BBC24A40"
    . "000008B442438017C24343B8424AC0000000F8DA0FEFFFF8B4"
    . "C241883C4785B5E89C85F5DC250008D7426008B7C24188B442"
    . "43C0B4424088B9C24D40000008D4F013B8C24D80000008904B"
    . "B0F8D84FAFFFF894C2418EB848B8424900000008B8C24B4000"
    . "000C7042400000000C74424040000000083C001C1E00789C78"
    . "B8424B0000000C1E00285C98944240889E889FD0F8ECFF8FFF"
    . "F8B9424B000000085D27E5F8B8C24A00000008B5C2404039C2"
    . "4BC00000001C1034424088944240C038424A000000089C70FB"
    . "651020FB641010FB6316BC04B6BD22601C289F0C1E00429F00"
    . "1D039C50F970383C10483C30139F975D58BBC24B0000000017"
    . "C24048B44240C83042401034424108B342439B424B40000007"
    . "582E94CF8FFFF8B8424B0000000C7042400000000C74424040"
    . "0000000C1E002894424088B8424B400000085C00F8E9200000"
    . "08B8424B000000085C07E6F8B8C24A00000008B5C24048BB42"
    . "4B800000001E9036C240801DE039C24BC000000896C240C03A"
    . "C24A00000000FB651020FB6410183C1040FB679FC83C60183C"
    . "3016BC04B6BD22601C289F8C1E00429F801D0C1F8078846FFC"
    . "643FF0039CD75CC8BB424B0000000017424048B6C240C83042"
    . "401036C24108B0424398424B40000000F856EFFFFFF83BC24B"
    . "4000000020F8E80F7FFFF8B8424BC000000038424B00000008"
    . "BAC24B800000003AC24B0000000C7442404010000008944240"
    . "88B8424B400000083E8018944240C8B8424B000000083C0018"
    . "944241083BC24B0000000027E798B44241089E92B8C24B0000"
    . "0008B5C240889EA8D34288D45FE8904240FB642010FB63A038"
    . "4249000000039F87C360FB67A0239F87C2E0FB6790139F87C2"
    . "60FB63E39F87C1F0FB63939F87C180FB6790239F87C100FB67"
    . "EFF39F87C080FB67E0139F87D04C643010183C20183C30183C"
    . "10183C6013B0C2475A3834424040103AC24B00000008B44240"
    . "48BB424B0000000017424083944240C0F8558FFFFFFE98FF6F"
    . "FFF83C47831C95B89C85E5F5DC2500090909090909090"
    x64:="4157415641554154555756534881EC88000000488B84245"
    . "0010000488BB42450010000448B94245801000089542428448"
    . "944240844898C24E80000008B40048B76144C8BBC244001000"
    . "04C8BB42448010000C74424180000000089442430488B84245"
    . "00100008974241C488BB424500100008B40088B76188944243"
    . "C488B842450010000897424388B400C89C789442440488B842"
    . "4500100008B401039C7894424100F4DC74585D289442454488"
    . "B84245001000048894424200F8ECB000000488B442420448B0"
    . "8448B68048B400885C0894424040F8E940000004489CE44890"
    . "C244531E431FF31ED0F1F8400000000004585ED7E614863142"
    . "4418D5C3D0089F848039424380100004589E0EB1D0F1F0083C"
    . "0014D63D94183C0044183C1014883C20139C34789149E74288"
    . "3F9034589C2440F45D0803A3175D783C0014C63DE4183C0048"
    . "3C6014883C20139C34789149F75D844012C2483C50103BC241"
    . "80100004403A42400010000396C24047582834424180748834"
    . "424201C8B442418398424580100000F8F35FFFFFF83F9030F8"
    . "43D0600008B8424000100008BBC24080100000FAF842410010"
    . "0008BB424000100008D3CB88B842418010000F7D885C9448D2"
    . "C860F841101000083F9010F842008000083F9020F84BF08000"
    . "08B742428C744240400000000C74424180000000089F0440FB"
    . "6CEC1E8104589CC0FB6D84889F08B7424080FB6D44189DB89F"
    . "0440FB6C64889F1C1E8100FB6CD89D60FB6C08D2C0A8B94242"
    . "00100004129C301C3438D040129CE4529C48904248B8424180"
    . "10000C1E00285D2894424080F8E660100004C89BC244001000"
    . "0448BBC24180100004585FF0F8E91040000488B8C24F800000"
    . "04863C74C6354241831D24C03942430010000488D440102EB3"
    . "A0F1F80000000004439C37C4039CE7F3C39CD7C384539CC410"
    . "F9EC044390C240F9DC14421C141880C124883C2014883C0044"
    . "139D70F8E2D040000440FB6000FB648FF440FB648FE4539C37"
    . "EBB31C9EBD58B5C2428448B8C242001000031ED4531E44889D"
    . "84189DB0FB6DB0FB6F48B84241801000041C1EB10450FB6DBC"
    . "1E0024585C98904240F8EA10000004C89BC24400100004C89B"
    . "42448010000448B7C2408448BB424180100004585F67E60488"
    . "B8C24F80000004D63D44C039424300100004863C74531C94C8"
    . "D440102410FB600410FB648FF410FB650FE4429D829F10FAFC"
    . "029DA0FAFC98D04400FAFD28D04888D04504139C7430F93040"
    . "A4983C1014983C0044539CE7FC4033C244501F483C5014401E"
    . "F39AC2420010000758C4C8BBC24400100004C8BB4244801000"
    . "08B8424180100002B4424308904248B8424200100002B44243"
    . "C894424680F88540800008B7C24404D89F5488BAC243001000"
    . "0448B7424104C89FEC74424040000000048C74424280000000"
    . "0C74424200000000089F883E801498D4487044189FF4889442"
    . "4088B44243083E801894424788B042485C00F88D9000000488"
    . "B5C24288B8424100100004D89EC448B6C245401D8C1E010894"
    . "4247089D82B8424F000000089C7B8000000000F49C731FF894"
    . "4246C0FAF842418010000894424648B8424F000000001D8894"
    . "42474908B442404897C24188D1C388B4424384139C60F84AB0"
    . "000004189C131C04585ED448B44241C7F36E9C30000000F1F4"
    . "0004139CE7E1B418B148401DA4863D2807C150000740B4183E"
    . "901782E0F1F4400004883C0014139C50F8E920000004139C78"
    . "9C17ECC8B148601DA4863D2807C15000174BD4183E80179B74"
    . "883C701393C240F8D7AFFFFFF4D89E54883442428018B9C241"
    . "8010000488B442428015C2404394424680F8DFCFEFFFF8B4C2"
    . "42089C84881C4880000005B5E5F5D415C415D415E415FC3458"
    . "5FF7E278B4C241C4C8B4424084889F28B0201D84898807C050"
    . "001740583E90178934883C2044939D075E583BC24580100000"
    . "70F8EE60100008B442478488B8C24500100000344241844896"
    . "C2450448BAC241801000044897C24404883C1204889742410C"
    . "744243C07000000448974244448897C24484989CF895C247C8"
    . "9C64C89642430418B074489EA29C28944245C8B8424E800000"
    . "001F039C20F4EC239F0894424580F8CD0000000418B47148BB"
    . "C2420010000412B7F0449635FFC458B4F08458B670C8944246"
    . "08B442474458B771039C70F4FF8488B44241048C1E3024C8D1"
    . "41848035C24308B442464448D04068B44246C39F84189C37F7"
    . "2904585C97E234489F131D2418B04924401C04898807C05000"
    . "1740583E90178464883C2014139D17FE28B4424604139C40F8"
    . "4AA0000004585E40F8EA100000089C131D2EB0D4883C201413"
    . "9D40F8E8E0000008B04934401C04898807C05000074E483E90"
    . "179DF4183C3014501E84439DF7D8F83C601397424580F8D6EF"
    . "FFFFF488B7C2448448B7C2440448B742444448B6C2450488B7"
    . "424104C8B6424304883C701393C240F8D97FDFFFFE918FEFFF"
    . "F6690037C240844017C241883442404014401EF8B442404398"
    . "424200100000F854DFBFFFF4C8BBC2440010000E996FCFFFF8"
    . "B44245C8344243C074983C71C8D7406FF8B44243C398424580"
    . "100000F8F87FEFFFF448B7C2440448B742444448B6C2450488"
    . "B7C24488B5C247C488B7424104C8B64243048634424208B542"
    . "418039424080100004C8B9C24600100000B5424708D4801398"
    . "C2468010000418914830F8E9AFDFFFF4585FF7E1D4C8B44240"
    . "84889F08B104883C00401DA4C39C04863D2C64415000075EB4"
    . "883C701393C24894C24200F8DBAFCFFFFE93BFDFFFF0F1F440"
    . "0008B842418010000038424080100002B442430894424308B8"
    . "42410010000038424200100002B44243C39842410010000894"
    . "424440F8F230400008B8424000100008BBC24080100000FAF8"
    . "42410010000448B642440448B6C24544C8B8C24F8000000C74"
    . "42420000000008D04B8034424288944243C8B4424303984240"
    . "80100000F8F2F0100008B8424100100008B6C243CC1E010894"
    . "424408B8424080100008904248D450289EF2B7C24284585ED4"
    . "898450FB61C018D45014898410FB61C014863C5410FB634010"
    . "F8E140100008B442438894424188B44241C8944240431C0EB6"
    . "244395424107E4E418B0C8601F98D5102448D41014863C9410"
    . "FB60C094863D24D63C0410FB61411470FB6040129F10FAFC94"
    . "429DA4129D80FAFD2450FAFC08D1452428D14828D144A39542"
    . "4087207836C241801786B4883C0014139C50F8E9E000000413"
    . "9C44189C27E96418B0C8701F98D5102448D41014863C9410FB"
    . "60C094863D24D63C0410FB61411470FB6040129F10FAFC9442"
    . "9DA4129D80FAFD2450FAFC08D1452428D14828D144A3B54240"
    . "80F864BFFFFFF836C2404010F8940FFFFFF8304240183C5048"
    . "B0424394424300F8DEDFEFFFF83842410010000018BBC24000"
    . "100008B442444017C243C3B8424100100000F8D9CFEFFFFE97"
    . "CFBFFFF0F1F0048634424208B5424400B1424488BBC2460010"
    . "0008D48013B8C24680100008914870F8D56FBFFFF830424018"
    . "3C504894C24208B0424394424300F8D82FEFFFFEB93448B5C2"
    . "428448B84242001000031DB8B84241801000031F6448B94241"
    . "80100004183C30141C1E3074585C08D2C85000000000F8E8CF"
    . "9FFFF4585D27E57488B8C24F80000004C63CE4C038C2430010"
    . "0004863C74531C0488D4C01020FB6110FB641FF440FB661FE6"
    . "BC04B6BD22601C24489E0C1E0044429E001D04139C3430F970"
    . "4014983C0014883C1044539C27FCC01EF4401D683C3014401E"
    . "F399C24200100007595E91CF9FFFF8B8C24200100008B84241"
    . "801000031DB31F6448B8C241801000085C98D2C85000000007"
    . "E7D4585C97E694C63C6488B8C24F80000004863C74D89C24C0"
    . "38424300100004C0394242801000031D2488D4C0102440FB61"
    . "90FB641FF4883C104440FB661FA6BC04B456BDB264101C3448"
    . "9E0C1E0044429E04401D8C1F8074188041241C60410004883C"
    . "2014139D17FC401EF4401CE83C3014401EF399C24200100007"
    . "58383BC2420010000020F8E6CF8FFFF4863B424180100008B9"
    . "C24180100008BBC2420010000488D5601448D67FFBF0100000"
    . "04889D0480394243001000048038424280100004889D58D53F"
    . "D4C8D6A0183BC241801000002488D1C067E7E4989C04D8D5C0"
    . "5004989D94929F04889E90FB610440FB650FF035424284439D"
    . "27C44440FB650014439D27C3A450FB6104439D27C31450FB61"
    . "14439D27C28450FB650FF4439D27C1E450FB650014439D27C1"
    . "4450FB651FF4439D27C0A450FB651014439D27D03C60101488"
    . "3C0014983C1014883C1014983C0014C39D8759383C7014801F"
    . "54889D84139FC0F8562FFFFFFE989F7FFFF31C9E9FAF8FFFF9"
    . "0909090909090909090909090"
    MCode(MyFunc, A_PtrSize=8 ? x64:x32)
    IfLess, Scan0, 10, return
  }
  return, DllCall(&MyFunc, "int",mode, "uint",color
    , "uint",n, "int",offsetX, "int",offsetY, Ptr,Scan0
    , "int",Stride, "int",sx, "int",sy, "int",sw, "int",sh
    , Ptr,&gs, Ptr,&ss, "AStr",text, Ptr,&s1, Ptr,&s0
    , Ptr,&input, "int",num, Ptr,&allpos, "int",allpos_max)
}

xywh2xywh(x1,y1,w1,h1,ByRef x,ByRef y,ByRef w,ByRef h)
{
  SysGet, zx, 76
  SysGet, zy, 77
  SysGet, zw, 78
  SysGet, zh, 79
  left:=x1, right:=x1+w1-1, up:=y1, down:=y1+h1-1
  left:=left<zx ? zx:left, right:=right>zx+zw-1 ? zx+zw-1:right
  up:=up<zy ? zy:up, down:=down>zy+zh-1 ? zy+zh-1:down
  x:=left, y:=up, w:=right-left+1, h:=down-up+1
}

GetBitsFromScreen(x, y, w, h, ByRef Scan0:="", ByRef Stride:=""
  , ScreenShot:=1, ByRef zx:="", ByRef zy:="", ByRef zw:="", ByRef zh:="")
{
  static bits, oldx, oldy, oldw, oldh, bpp:=32
  static Ptr:=A_PtrSize ? "UPtr" : "UInt"
  if (ScreenShot or x<oldx or y<oldy
    or x+w>oldx+oldw or y+h>oldy+oldh) and !(w<1 or h<1)
  {
    oldx:=x, oldy:=y, oldw:=w, oldh:=h, ScreenShot:=1
    VarSetCapacity(bits, w*h*4)
  }
  Scan0:=&bits, Stride:=((oldw*bpp+31)//32)*4
  , zx:=oldx, zy:=oldy, zw:=oldw, zh:=oldh
  if (!ScreenShot or w<1 or h<1)
    return
  win:=DllCall("GetDesktopWindow", Ptr)
  hDC:=DllCall("GetWindowDC", Ptr,win, Ptr)
  mDC:=DllCall("CreateCompatibleDC", Ptr,hDC, Ptr)
  ;-------------------------
  VarSetCapacity(bi, 40, 0), NumPut(40, bi, 0, "int")
  NumPut(w, bi, 4, "int"), NumPut(-h, bi, 8, "int")
  NumPut(1, bi, 12, "short"), NumPut(bpp, bi, 14, "short")
  ;-------------------------
  if (hBM:=DllCall("CreateDIBSection", Ptr,mDC, Ptr,&bi
    , "int",0, Ptr "*",ppvBits, Ptr,0, "int",0, Ptr))
  {
    oBM:=DllCall("SelectObject", Ptr,mDC, Ptr,hBM, Ptr)
    DllCall("BitBlt", Ptr,mDC, "int",0, "int",0, "int",w, "int",h
      , Ptr,hDC, "int",x, "int",y, "uint",0x00CC0020|0x40000000)
    DllCall("RtlMoveMemory", Ptr,Scan0, Ptr,ppvBits, Ptr,Stride*h)
    DllCall("SelectObject", Ptr,mDC, Ptr,oBM)
    DllCall("DeleteObject", Ptr,hBM)
  }
  DllCall("DeleteDC", Ptr,mDC)
  DllCall("ReleaseDC", Ptr,win, Ptr,hDC)
}

MCode(ByRef code, hex)
{
  bch:=A_BatchLines
  SetBatchLines, -1
  VarSetCapacity(code, len:=StrLen(hex)//2)
  lls:=A_ListLines=0 ? "Off" : "On"
  ListLines, Off
  Loop, % len
    NumPut("0x" SubStr(hex,2*A_Index-1,2),code,A_Index-1,"uchar")
  ListLines, %lls%
  Ptr:=A_PtrSize ? "UPtr" : "UInt", PtrP:=Ptr . "*"
  DllCall("VirtualProtect",Ptr,&code, Ptr,len,"uint",0x40,PtrP,0)
  SetBatchLines, %bch%
}

base64tobit(s)
{
  Chars:="0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    . "abcdefghijklmnopqrstuvwxyz"
  SetFormat, IntegerFast, d
  StringCaseSense, On
  lls:=A_ListLines=0 ? "Off" : "On"
  ListLines, Off
  Loop, Parse, Chars
  {
    i:=A_Index-1, v:=(i>>5&1) . (i>>4&1)
      . (i>>3&1) . (i>>2&1) . (i>>1&1) . (i&1)
    s:=StrReplace(s,A_LoopField,v)
  }
  ListLines, %lls%
  StringCaseSense, Off
  s:=SubStr(s,1,InStr(s,"1",0,0)-1)
  s:=RegExReplace(s,"[^01]+")
  return, s
}

bit2base64(s)
{
  s:=RegExReplace(s,"[^01]+")
  s.=SubStr("100000",1,6-Mod(StrLen(s),6))
  s:=RegExReplace(s,".{6}","|$0")
  Chars:="0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    . "abcdefghijklmnopqrstuvwxyz"
  SetFormat, IntegerFast, d
  lls:=A_ListLines=0 ? "Off" : "On"
  ListLines, Off
  Loop, Parse, Chars
  {
    i:=A_Index-1, v:="|" . (i>>5&1) . (i>>4&1)
      . (i>>3&1) . (i>>2&1) . (i>>1&1) . (i&1)
    s:=StrReplace(s,v,A_LoopField)
  }
  ListLines, %lls%
  return, s
}

ASCII(s)
{
  if RegExMatch(s,"\$(\d+)\.([\w+/]+)",r)
  {
    s:=RegExReplace(base64tobit(r2),".{" r1 "}","$0`n")
    s:=StrReplace(StrReplace(s,"0","_"),"1","0")
  }
  else s=
  return, s
}

; You can put the text library at the beginning of the script,
; and Use Pic(Text,1) to add the text library to Pic()'s Lib,
; Use Pic("comment1|comment2|...") to get text images from Lib

Pic(comments, add_to_Lib:=0)
{
  static Lib:=[]
  SetFormat, IntegerFast, d
  if (add_to_Lib)
  {
    re:="<([^>]*)>[^$]+\$\d+\.[\w+/]+"
    Loop, Parse, comments, |
      if RegExMatch(A_LoopField,re,r)
      {
        s1:=Trim(r1), s2:=""
        Loop, Parse, s1
          s2.="_" . Ord(A_LoopField)
        Lib[s2]:=r
      }
    Lib[""]:=""
  }
  else
  {
    Text:=""
    Loop, Parse, comments, |
    {
      s1:=Trim(A_LoopField), s2:=""
      Loop, Parse, s1
        s2.="_" . Ord(A_LoopField)
      Text.="|" . Lib[s2]
    }
    return, Text
  }
}

PicN(Number)
{
  return, Pic( RegExReplace(Number, ".", "|$0") )
}

; Use PicX(Text) to automatically cut into multiple characters
; Can't be used in ColorPos mode, because it can cause position errors

PicX(Text)
{
  if !RegExMatch(Text,"\|([^$]+)\$(\d+)\.([\w+/]+)",r)
    return, Text
  w:=r2, v:=base64tobit(r3), Text:=""
  c:=StrLen(StrReplace(v,"0"))<=StrLen(v)//2 ? "1":"0"
  wz:=RegExReplace(v,".{" w "}","$0`n")
  SetFormat, IntegerFast, d
  While InStr(wz,c)
  {
    While !(wz~="m`n)^" c)
      wz:=RegExReplace(wz,"m`n)^.")
    i:=0
    While (wz~="m`n)^.{" i "}" c)
      i++
    v:=RegExReplace(wz,"m`n)^(.{" i "}).*","$1")
    wz:=RegExReplace(wz,"m`n)^.{" i "}")
    if (v!="")
      Text.="|" r1 "$" i "." bit2base64(v)
  }
  return, Text
}

; Screenshot and retained as the last screenshot.

ScreenShot(x1:="", y1:="", x2:="", y2:="")
{
  if (x1+y1+x2+y2="")
    n:=150000, x:=y:=-n, w:=h:=2*n
  else
    x:=(x1<x2 ? x1:x2), y:=(y1<y2 ? y1:y2)
    , w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  xywh2xywh(x,y,w,h,x,y,w,h)
  GetBitsFromScreen(x,y,w,h)
}

; Get the RGB color of a point from the last screenshot.
; If the point to get the color is beyond the range of
; the last screenshot, an empty string will return.

ScreenShot_GetColor(x,y)
{
  local  ; Unaffected by Super-global variables
  GetBitsFromScreen(0,0,0,0,Scan0,Stride,0,zx,zy,zw,zh)
  return, (x<zx or x>zx+zw-1 or y<zy or y>zy+zh-1 or !Stride)
    ? "" : Format("0x{:06X}",NumGet(Scan0
    +(y-zy)*Stride+(x-zx)*4,"uint")&0xFFFFFF)
}

; Identify a line of text or verification code
; based on the result returned by FindText()
; Return Association array {ocr:Text, x:X, y:Y}

OcrOK(ok, offsetX:=20, offsetY:=20)
{
  ocr_Text:=ocr_X:=ocr_Y:=min_X:=""
  For k,v in ok
    x:=v.1
    , min_X:=(A_Index=1 or x<min_X ? x : min_X)
    , max_X:=(A_Index=1 or x>max_X ? x : max_X)
  While (min_X!="" and min_X<=max_X)
  {
    LeftX:=""
    For k,v in ok
    {
      x:=v.1, y:=v.2, w:=v.3, h:=v.4
      if (x<min_X) or Abs(y-ocr_Y)>offsetY
        Continue
      ; Get the leftmost X coordinates
      if (LeftX="" or x<LeftX)
        LeftX:=x, LeftY:=y, LeftW:=w, LeftH:=h, LeftOCR:=v.id
      else if (x=LeftX)
      {
        Loop, 100
        {
          err:=(A_Index-1)/100+0.000001
          if FindText(LeftX,LeftY,LeftX+LeftW-1,LeftY+LeftH-1,err,err,Text,0)
            Break
          if FindText(x, y, x+w-1, y+h-1, err, err, Text, 0)
          {
            LeftX:=x, LeftY:=y, LeftW:=w, LeftH:=h, LeftOCR:=v.id
            Break
          }
        }
      }
    }
    if (ocr_X="")
      ocr_X:=LeftX, ocr_Y:=LeftY
    ; If the interval exceeds the set value, add "*" to the result
    ocr_Text.=(ocr_Text!="" and LeftX-min_X>offsetX ? "*":"") . LeftOCR
    ; Update min_X for next search
    min_X:=LeftX+LeftW
  }
  return, {ocr:ocr_Text, x:ocr_X, y:ocr_Y}
}

; Sort the results returned by FindText() from left to right
; and top to bottom, ignore slight height difference

SortOK(ok, dy:=10)
{
  if !IsObject(ok)
    return, ok
  SetFormat, IntegerFast, d
  ypos:=[]
  For k,v in ok
  {
    x:=v.x, y:=v.y, add:=1
    For k2,v2 in ypos
      if Abs(y-v2)<=dy
      {
        y:=v2, add:=0
        Break
      }
    if (add)
      ypos.Push(y)
    n:=(y*150000+x) "." k, s:=A_Index=1 ? n : s "-" n
  }
  Sort, s, N D-
  ok2:=[]
  Loop, Parse, s, -
    ok2.Push( ok[(StrSplit(A_LoopField,".")[2])] )
  return, ok2
}

; Reordering according to the nearest distance

SortOK2(ok, px, py)
{
  if !IsObject(ok)
    return, ok
  SetFormat, IntegerFast, d
  For k,v in ok
  {
    x:=v.1+v.3//2, y:=v.2+v.4//2
    n:=((x-px)**2+(y-py)**2) "." k
    s:=A_Index=1 ? n : s "-" n
  }
  Sort, s, N D-
  ok2:=[]
  Loop, Parse, s, -
    ok2.Push( ok[(StrSplit(A_LoopField,".")[2])] )
  return, ok2
}

; Prompt mouse position in remote assistance

MouseTip(x:="", y:="")
{
  if (x="")
  {
    VarSetCapacity(pt,16,0), DllCall("GetCursorPos","ptr",&pt)
    x:=NumGet(pt,0,"uint"), y:=NumGet(pt,4,"uint")
  }
  x:=Round(x-10), y:=Round(y-10), w:=h:=2*10+1
  ;-------------------------
  Gui, _MouseTip_: +AlwaysOnTop -Caption +ToolWindow +Hwndmyid +E0x08000000
  Gui, _MouseTip_: Show, Hide w%w% h%h%
  ;-------------------------
  dhw:=A_DetectHiddenWindows
  DetectHiddenWindows, On
  d:=4, i:=w-d, j:=h-d
  s=0-0 %w%-0 %w%-%h% 0-%h% 0-0
  s=%s%  %d%-%d% %i%-%d% %i%-%j% %d%-%j% %d%-%d%
  WinSet, Region, %s%, ahk_id %myid%
  DetectHiddenWindows, %dhw%
  ;-------------------------
  Gui, _MouseTip_: Show, NA x%x% y%y%
  Loop, 4
  {
    Gui, _MouseTip_: Color, % A_Index & 1 ? "Red" : "Blue"
    Sleep, 500
  }
  Gui, _MouseTip_: Destroy
}


/***** C source code of machine code *****

int __attribute__((__stdcall__)) PicFind(
  int mode, unsigned int c, unsigned int n
  , int offsetX, int offsetY, unsigned char * Bmp
  , int Stride, int sx, int sy, int sw, int sh
  , unsigned char * gs, char * ss, char * text
  , int * s1, int * s0, int * input, int num
  , unsigned int * allpos, int allpos_max)
{
  int o, i, j, x, y, r, g, b, rr, gg, bb, max, e1, e0, ok;
  int o1, x1, y1, w1, h1, sx1, sy1, len1, len0, err1, err0;
  int o2, x2, y2, w2, h2, sx2, sy2, len21, len20, err21, err20;
  int r_min, r_max, g_min, g_max, b_min, b_max;
  //----------------------
  ok=0; w1=input[1]; h1=input[2];
  len1=input[3]; len0=input[4];
  err1=input[5]; err0=input[6];
  max=len1>len0 ? len1 : len0;
  //----------------------
  // Generate Lookup Table
  for (j=0; j<num; j+=7)
  {
    o=o1=o2=input[j]; w2=input[j+1]; h2=input[j+2];
    for (y=0; y<h2; y++)
    {
      for (x=0; x<w2; x++)
      {
        i=(mode==3) ? y*Stride+x*4 : y*sw+x;
        if (text[o++]=='1')
          s1[o1++]=i;
        else
          s0[o2++]=i;
      }
    }
  }
  // Color Position Mode
  // This mode is not support combination lookup
  // only used to recognize multicolored Verification Code
  if (mode==3)
  {
    sx1=sx+sw-w1; sy1=sy+sh-h1;
    for (y=sy; y<=sy1; y++)
    {
      for (x=sx; x<=sx1; x++)
      {
        o=y*Stride+x*4; e1=err1; e0=err0;
        j=o+c; rr=Bmp[2+j]; gg=Bmp[1+j]; bb=Bmp[j];
        for (i=0; i<max; i++)
        {
          if (i<len1)
          {
            j=o+s1[i]; r=Bmp[2+j]-rr; g=Bmp[1+j]-gg; b=Bmp[j]-bb;
            if (3*r*r+4*g*g+2*b*b>n && (--e1)<0)
              goto NoMatch3;
          }
          if (i<len0)
          {
            j=o+s0[i]; r=Bmp[2+j]-rr; g=Bmp[1+j]-gg; b=Bmp[j]-bb;
            if (3*r*r+4*g*g+2*b*b<=n && (--e0)<0)
              goto NoMatch3;
          }
        }
        allpos[ok++]=y<<16|x;
        if (ok>=allpos_max)
          goto Return1;
        NoMatch3:
        continue;
      }
    }
    goto Return1;
  }
  // Generate Two Value Image
  o=sy*Stride+sx*4; j=Stride-4*sw; i=0;
  if (mode==0)  // Color Mode
  {
    rr=(c>>16)&0xFF; gg=(c>>8)&0xFF; bb=c&0xFF;
    for (y=0; y<sh; y++, o+=j)
      for (x=0; x<sw; x++, o+=4, i++)
      {
        r=Bmp[2+o]-rr; g=Bmp[1+o]-gg; b=Bmp[o]-bb;
        ss[i]=(3*r*r+4*g*g+2*b*b<=n) ? 1:0;
      }
  }
  else if (mode==1)  // Gray Threshold Mode
  {
    c=(c+1)*128;
    for (y=0; y<sh; y++, o+=j)
      for (x=0; x<sw; x++, o+=4, i++)
        ss[i]=(Bmp[2+o]*38+Bmp[1+o]*75+Bmp[o]*15<c) ? 1:0;
  }
  else if (mode==2)  // Gray Difference Mode
  {
    for (y=0; y<sh; y++, o+=j)
    {
      for (x=0; x<sw; x++, o+=4, i++)
      {
        gs[i]=(Bmp[2+o]*38+Bmp[1+o]*75+Bmp[o]*15)>>7;
        ss[i]=0;
      }
    }
    sx1=sw-2; sy1=sh-2;
    for (y=1; y<=sy1; y++)
      for (x=1; x<=sx1; x++)
      {
        i=y*sw+x; j=gs[i]+c;
        if ( gs[i-1]>j || gs[i+1]>j
          || gs[i-sw]>j || gs[i+sw]>j
          || gs[i-sw-1]>j || gs[i-sw+1]>j
          || gs[i+sw-1]>j || gs[i+sw+1]>j )
            ss[i]=1;
      }
  }
  else // (mode==4) Color Difference Mode
  {
    r=(c>>16)&0xFF; g=(c>>8)&0xFF; b=c&0xFF;
    rr=(n>>16)&0xFF; gg=(n>>8)&0xFF; bb=n&0xFF;
    r_min=r-rr; g_min=g-gg; b_min=b-bb;
    r_max=r+rr; g_max=g+gg; b_max=b+bb;
    for (y=0; y<sh; y++, o+=j)
      for (x=0; x<sw; x++, o+=4, i++)
      {
        r=Bmp[2+o]; g=Bmp[1+o]; b=Bmp[o];
        ss[i]=(r>=r_min && r<=r_max
            && g>=g_min && g<=g_max
            && b>=b_min && b<=b_max) ? 1:0;
      }
  }
  // Start Lookup
  sx1=sw-w1; sy1=sh-h1;
  for (y=0; y<=sy1; y++)
  {
    for (x=0; x<=sx1; x++)
    {
      o=y*sw+x; e1=err1; e0=err0;
      if (e0==len0)
      {
        for (i=0; i<len1; i++)
          if (ss[o+s1[i]]!=1 && (--e1)<0)
            goto NoMatch1;
      }
      else
      {
        for (i=0; i<max; i++)
        {
          if (i<len1 && ss[o+s1[i]]!=1 && (--e1)<0)
            goto NoMatch1;
          if (i<len0 && ss[o+s0[i]]!=0 && (--e0)<0)
            goto NoMatch1;
        }
      }
      //------------------
      // Combination lookup
      if (num>7)
      {
        x1=x+w1-1; y1=y-offsetY; if (y1<0) y1=0;
        for (j=7; j<num; j+=7)
        {
          o2=input[j]; w2=input[j+1]; h2=input[j+2];
          len21=input[j+3]; len20=input[j+4];
          err21=input[j+5]; err20=input[j+6];
          sx2=sw-w2; i=x1+offsetX; if (i<sx2) sx2=i;
          sy2=sh-h2; i=y+offsetY; if (i<sy2) sy2=i;
          for (x2=x1; x2<=sx2; x2++)
          {
            for (y2=y1; y2<=sy2; y2++)
            {
              o1=y2*sw+x2; e1=err21; e0=err20;
              for (i=0; i<len21; i++)
              {
                if (ss[o1+s1[o2+i]]!=1 && (--e1)<0)
                  goto NoMatch2;
              }
              if (e0!=len20)
              {
                for (i=0; i<len20; i++)
                  if (ss[o1+s0[o2+i]]!=0 && (--e0)<0)
                    goto NoMatch2;
              }
              goto MatchOK;
              NoMatch2:
              continue;
            }
          }
          goto NoMatch1;
          MatchOK:
          x1=x2+w2-1;
        }
      }
      //------------------
      allpos[ok++]=(sy+y)<<16|(sx+x);
      if (ok>=allpos_max)
        goto Return1;
      // Clear the image that has been found
      for (i=0; i<len1; i++)
        ss[o+s1[i]]=0;
      NoMatch1:
      continue;
    }
  }
  Return1:
  return ok;
}

*/

;================= The End =================

;
Last edited by feiyue on 08 Dec 2019, 11:09, edited 219 times in total.
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

14 May 2016, 12:25

This function is similar to the AHK built-in command "ImageSearch",
but it can convert images into strings, written into the script without
saving as an external image, so it is easier to use than "ImageSearch". :beer:


<< Main changes of the function after multiple updates >>

1. Now, the way to capture images has become right-click twice.
Previously, the way to capture images was to left click and remove 100 pixels.

2. Now, the first four range parameters of FindText() function are similar to the
built-in command ImageSearch, using the search scope's upper left corner coordinate
and lower right corner coordinate (x1, y1, x2, y2), it easier for novices to use.
Previously, the first four parameters of FindText() function using the central coordinates
and left-right, up-down offsets, which were more complex.

3. Now, the array returned by the FindText() function becomes a two-level array.
Each item in the first level array represents a search result,
and each search result is an array containing 7 items, so it is a two-level array.
Previously, the function returned an array with only one level, so when extracting the result,
every 5 items should be decomposed into one result, which is more complex.

4. Now, it is recommended to save the script as "FindText.ahk" and copy it to the
Lib directory of the main program. When clicking the copy button,
do not select the check box of "Additional FindText()". And insert
"#Include <FindText>" at the beginning of the user script to import the library.
Previously, clicking the copy button would copy the FindText() function
and all the following functions, resulting in a huge user script.

<<< Update history >>>

Updated to 7.0 version - 2019/11/24
.... 1. Modify: the first four parameters of FinText() used to specify the search range
....... are changed from the previous (X, Y, W, H) to (X1, Y1, X2, Y2), Similar to the
....... built-in command imagesearch, because many novices want to use the simple way.
....... Note: this will cause incompatibility with previous scripts.
.... 2. Modify: the return array of findtext() is slightly modified,
....... The object of each result is changed from a simple array [X,Y,W,H,Comment]
....... to an associative array {1:X, 2:Y, 3:W, 4:H, x:X+W//2, y:Y+H//2, id:Comment}
....... Note: this causes slight incompatibility with previous scripts, because object.5 is renamed object.ID
.... 3. Modify: FindTextOCR() is renamed as OcrOK()
....... because it directly uses the return value of FinText() as a parameter.
.... 4. Add: now you can use a hotkey to take a screenshot,
....... So you can quickly capture some fleeting screen images,
....... And then capture some images in the last screenshot.
.... 5. Add: Undo is added to crop image edges.

Updated to 6.9 version - 2019/10/30
.... 1. The ScreenShot_GetColor() function is added.
.... Before using it, you usually need to update the last screenshot with the ScreenShot() function.
.... FindText() function can also update the last screenshot, So you can use FindText() instead of ScreenShot().

Updated to 6.8 version - 2019/10/25
.... 1. Fixed case insensitive key names in internal array of Pic() function.
.... 2. In order to facilitate this script to be included in other scripts as a library (#Include <FindText>),
.... no custom global variables are used, and the label of subprogram is greatly reduced.

Updated to 6.7 version - 2019/04/13
.... 1. Merge FindText() and FindText2() functions.
.... 2. The color difference mode is added to adjust the matching range accurately.
.... 3. Add a parameter "FindAll" that allows you to find only one result and return it immediately.

Updated to 6.6 version - 2019/04/11
.... 1. FindText(), FindText2() share the same machine code, reduces the code size.

Updated to 6.5 version - 2019/04/07
.... 1. Add Gray Difference Mode, so there are four modes.
.... 2. Fixed SplitAdd/AllAdd buttons doesn't work(broken by v6.4).

Updated to 6.4 version - 2019/02/04
.... 1. Change the label name and function name to make it easier
.... for “#Include” to be integrated into other scripts as libraries.
.... 2. After including library, you can open the tool directly by calling: ft_Gui("Show").

Updated to 6.3 version - 2018/12/18
.... 1. Changed the way to capture, Now first click the right mouse button,
.... you can move the mouse, and then click the right mouse button again.
.... 2. Add MouseTip(). It is used to prompt mouse position in remote assistance.

Updated to 6.2 version - 2018/11/11
.... 1. A parameter is added at the end of the function to allow the last screenshot to be used.
.... Of course, saving the last screenshot will take up a certain amount of memory.
.... 2. Add ScreenShot(). It can be used before the loop.

Updated to 6.1 version - 2018/10/07
.... 1. The specific color mode has been restored, so there are three modes.
.... 2. Add SortOK(), SortOK2(). It is used to sort the array by coordinates.

Updated to 6.0 version - 2018/09/21
.... 1. Slightly modified the machine code.
.... The image that has been found will be cleaned up in the same color.
.... Avoid matching the next row (column) when using tolerance lookup.
.... 2. After upgrading to v6.0, the search area uses WinAPI's
.... upper left corner X, Y coordinates, and width, height.
.... This will be better understood and used.

Updated to 5.9 version - 2018/07/21
.... 1. Add PicX(). It is used to divide the Text
.... and then use the FindText2() function to find it.

Updated to 5.8 version - 2018/04/27
.... 1. Using "gcc.exe -O2" to generate machine code (MCode).

Updated to 5.7 version - 2018/01/17
.... 1. I changed the way to capture, Before, click the left button of the mouse,
.... and then move the mouse out of 100 pixels.
.... Now, press down the right button of the mouse,
.... and then move a certain distance and release the right button.
.... 2. I updated the "Capture Image To Text" window,
.... allowing the use of tags to split the text at one time
.... and generate multiple text libraries.

Updated to 5.6 version - 2017/12/06
.... 1. Add FindText2(). It is used for combination lookup.
.... for example, a 0-9 text library has been set up,
.... then any ID number can be found.
.... Use Pic(Text,1) and PicN(number) when using.
.... 2. Add PicN(). It is used in combination with Pic().

Updated to 5.5 version - 2017/11/23
.... 1. Change the Color mode to Color position mode.
.... in order to identify a variety of color verification code,
.... now it can adapt to various colors.

Updated to 5.4 version - 2017/08/03
.... 1. Now can find out all the positions of a picture in the screen.

Updated to 5.3 version - 2017/07/30
.... 1. Changing the returned value of the object. Now, a two level array is used.
.... 2. Add FindTextOCR(). It can identify simple verification code.

Updated to 5.2 version - 2017/06/10
.... 1. Using CreateDIBSection instead of GetDIBits to speed up screen capture.
.... 2. Add Pic(). You can put the text library at the beginning of the script,
.... and Use Pic(Text,1) to add the text library to Pic()'s Lib,
.... Use Pic("comment1|comment2|...") to get text images from Lib.

Updated to 5.1 version - 2017/05/06
.... 1. Thank c4p very much. He helped me improve the interface and control hints.

Updated to 5.0 version - 2017/03/20
.... 1. Add sensitivity slider for color mode.
.... 2. Text parameters are converted to one line using Base64.
.... 3. Text parameters can be a lot of text to find, separated by "|".

Updated to 4.5 version - 2016/05/24
.... 1. Support Fault-Tolerant.

Updated to 4.0 version - 2016/05/23
.... 1. Using machine code (MCode) instead of RegExMatch() to speed up search.
Last edited by feiyue on 29 Nov 2019, 01:00, edited 63 times in total.
User avatar
kwstas13
Posts: 43
Joined: 19 Mar 2016, 06:26

Re: FindText - Catch screen image into text and then find it

14 May 2016, 12:28

wow nice this is cool script thank you!! :bravo: :D
Guest888

Re: FindText - Catch screen image into text and then find it

14 May 2016, 17:43

:thumbup: :thumbup: :thumbup:
This could be very useful for automatic operating. Thank you very much for sharing.
:bravo: :bravo: :bravo: :bravo:
guest3456
Posts: 2775
Joined: 09 Oct 2013, 10:31

Re: FindText - Catch screen image into text and then find it

14 May 2016, 19:16

very interesting image search / OCR example. thanks a lot. good job

User avatar
BGM
Posts: 466
Joined: 20 Nov 2013, 20:56
GitHub: bgmCoder
Contact:

Re: FindText - Catch screen image into text and then find it

14 May 2016, 21:36

__________________________________________00000_______________00_________
_________________________________________00___________________00_________
_________________________________________00___________________00_________
_________________________________________00___________________00_________
________00____00_____0000______00000____000000__00____00____00_________
________00____00____00___0____00___00____00_____00____00____00_________
________00____00____00________00___00____00_____00____00____00_________
________00____00____00_______00____000___00_____00____00____00_________
________00____00____0000_____00_____00___00_____00____00____00_________
________00____00_____0000____000000000___00_____00____00____00_________
________00____00_______000___00__________00_____00____00____00_________
________00____00________000__00__________00_____00____00____00_________
________00___000________000___00_________00_____00___000____00_________
________00__0000___00___00____000___00___00_____00__0000____00_________
_________0000_00____00000_______00000____00______0000_00____00_________
Guest

Re: FindText - Catch screen image into text and then find it

18 May 2016, 01:11

This is such an useful script. Your idea and approach are brilliant! I register to say thank you FeiYue.

For those who haven't tried: this is not for OCR. But image search, rather.
It stands out from the internal ImageSearch command in that no ImageFile is required, and runs much faster.
The FindText function takes less than half a second to seach a full 1680x1050 screen.
The fact that it can search for both text/character and graph/picture won my vote for killer script of the year!
I use this to automate all sort of web pages and traditional applications. Great work! :superhappy:
User avatar
jmeneses
Posts: 487
Joined: 28 Oct 2014, 11:09
Location: Catalan Republic

Re: FindText - Catch screen image into text and then find it

18 May 2016, 03:05

Hi feiyue, excellent job and thanks for sharing
:thumbup: :thumbup:
Donec Perficiam
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

19 May 2016, 09:43

In addition to searching for screen text and images,
This function can also be used for simple text recognition.
Here is an example of a simple identification Numbers.
FindTextOCR() function can also be used to identify simple verification code.

Code: Select all

F2::    ; To identify the number near the mouse

; For identification, we need to create a text library,
; Of course, the following text library is not strong enough,
; Perhaps in other computers, other screen resolutions,
; Other browser magnification, different fonts, need to regenerate.
; You can add the newly generated to the following existing text library,
; To enhance the generality of this text library.

Text:="|<0>*147$6.SnVVVVVnSU"
Text.="|<1>*147$5.9kV248Hw"
Text.="|<2>*148$6.yX11248EzU"
Text.="|<3>*149$6.yX13C11XyU"
Text.="|<4>*149$6.66+GmWz22U"
Text.="|<5>*149$6.TEES311XyU"
Text.="|<6>*148$6.CEUyXVVnSU"
Text.="|<7>*149$6.z1224A8MEU"
Text.="|<8>*148$6.SnVnSVVnSU"
Text.="|<9>*148$6.SnVVlT12QU"

CoordMode, Mouse
MouseGetPos, x, y
t1:=A_TickCount
;------------------------------
OCR:=FindTextOCR(x, y, 150, 20, 0.2, 0.1, Text)
;------------------------------
t1:=A_TickCount-t1
MsgBox, 4096, OCR, OCR Result: [%OCR%] in %t1% ms.
Return


FindTextOCR(nX, nY, nW, nH, err1, err0, Text, Interval=20)
{
  OCR:="", Right_X:=nX+nW-1
  While (ok:=FindText(nX, nY, nW, nH, err1, err0, Text))
  {
    ; For multi text search, This is the number of text images found
    Loop, % ok.MaxIndex()
    {
      ; X is the X coordinates of the upper left corner
      ; and W is the width of the image have been found
      i:=A_Index, x:=ok[i].1, y:=ok[i].2
        , w:=ok[i].3, h:=ok[i].4, comment:=ok[i].5
      ; We need the leftmost X coordinates
      if (A_Index=1 or x<Left_X)
        Left_X:=x, Left_W:=w, Left_OCR:=comment
    }
    ; If the interval exceeds the set value, add "*" to the result
    OCR.=(A_Index>1 and Left_X-nX-1>Interval ? "*":"") . Left_OCR
    ; Update nX and nW for next search
    nX:=Left_X+Left_W-1, nW:=Right_X-nX+1
  }
  Return, OCR
}


Last edited by feiyue on 22 Sep 2018, 13:39, edited 9 times in total.
ed1chandler
Posts: 15
Joined: 15 May 2015, 11:18

Re: FindText - Catch screen image into text and then find it

20 May 2016, 09:34

Want to make sure I'm understanding use case and usage:

1) Download script and run it. This pops up the "Catch Image To Text And Find Text Tool" box with "Catch", "Test", and "Copy" buttons.

2) Click the "Catch" button and the cursor is accompanied by a transparent red box.

3) Move the red box over the text you want to search for and click, then move the box more than 100 px in any direction to trigger capture.

4) This brings up the "Catch Image To Text" box with a magnified view of the captured text.

5) The lower left portion of that GUI has a series of "trimming" buttons, used to trim the edges of the captured region. I'm not sure what the abbreviations are supposed to stand for, but it appears that "LD" trims one pixel from the left edge, "LD3" trims three pixels, and the other buttons do the same for "up", "down", and "right." Use those buttons to trim the region down to what you really want to search for.

6) Click somewhere on the magnified capture to select a pixel with the color you want to use for matching. When you do, the text box next to the "Color2Two" button will populate with the hex value of the color corresponding with the pixel you chose.

It appears there are two modes: color and greyscale. If you want to use color, it appears to go something like this:
7a) After selecting a pixel (step 6), if you click the "Color2Two" button, it appears to convert the magnified image into a two-tone version where all pixels of the selected color are converted to black and all other pixels are converted to white.

And if you want to use greyscale, it goes something like this:
7b) After selecting a pixel (step 6), if you click the "Gray2Two" button, it appears to make a "best guess" at a threshold, then convert the magnified image into a two-tone black/white version where all pixels on one side of the threshold are black and all others are white. It also appears that you can change the threshold and click the button again if you want to adjust the program's guess.

8) Once you've got the two-tone image you want, click the "Ok" button. This returns a GUI with a "Text=..." block of text and a related function call. You can click the "Test" button to test that the search will actually find what you're looking for, then click the "Copy" button to copy the text block and the related function call to the clipboard. The "Exchange" button will negate the image and the "Load" button will reload the original capture.

9) Copy that block of text and the function call into the script from which the search will be run ... and be sure the script can "see" the FindText() function.

10) Modify and enjoy.

What I'm *not* quite following is how to modify the function parameters. The "w" and "h" parameters say that they're "offsets" but what does that *mean*? (i.e. What's the effect of changing them?) The default is 150, but the capture tool seeds these to 150000. What is this changing?
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

20 May 2016, 13:36

@ed1chandler, your description for use is very detailed, very good! :bravo:
Because my English is not very good, so the message is less in front,
thank you for your description. I add a few points:

1, LD meaning is the abbreviation of Left-Del, appear to be quite strange. :D

2, You can cut the edge first, then Make the image into black and white,
Can also be, first to make the image into black and white, and then cut the edge.
I recommend the way back, Because click "Auto" button can automatically cut edge.

(Slight correction)the Gray2Two button don't use the Selected color, will automatically
calculate a threshold, if you want to customize can manually enter the threshold.

3, the parameters of X, Y, W, H to determine a range:
the upper left corner (X-W, Y-H), the lower right corner of (X+W, Y+H),
So X, Y is the center point of the range, W, H is the distance between the center point
of the offset distance. The advantage is that the location of the search results is returned
Text / image center, and the parameters can be compared to know the relative text / image
as the origin of the offset.

4, In the default generated code, I use the offset 150000, So it's all around the screen,
it is full screen search. The actual use of the time required to select the appropriate scope,
such as use 150, in accordance with (3) to determine the scope of the search.

(Machine automatic translation)
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 14:01

I am not a programmer and I struggle to make these things work. Could you post an example of how you would run this code once it is generated?
I'm getting a "call to non-existing function" error
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

20 May 2016, 14:25

Useage:
1. Catch the image to text string.
2. Test find the text string on full Screen.
3. When test is successful, copy the code
and paste it into your own script.
4. Copy the "FindText()" function and the function
of the back, paste it into your own script.

You didn't do the fourth step, to copy the functions to your application.

Actual use of the time, you may also need to modify the scope of the search ( W, H offset),
the default (offset 150000) full screen to find a longer time and find the location may not be what you need.
Last edited by feiyue on 20 May 2016, 15:13, edited 1 time in total.
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 14:43

So I'm trying to write a standalone script that finds your name when I hit Win F3
How would I modify this?

#f3::

Text=
(
__000__________________________________
_00__________00________________________
_00____________________________________
0000___0000__00_00___00_00__00___0000__
_00___00__00_00__00__0__00__00__00__00_
_00___000000_00__00_00__00__00__000000_
_00___00_____00___0_00__00__00__00_____
_00___00___0_00___000___00__00__00___0_
_00____00000_00___000____00_00___00000_
__________________00___________________
__________________00___________________
)

if FindText(44,243,Text,"*177",150000,150000,X,Y)
{
CoordMode, Mouse
MouseMove, X, Y
}
return
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

20 May 2016, 15:04

Find this line at the end of my code,
Copy and paste the following into your script.

“ ;======== Copy The Next Functions To Your Code ======== ”
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 15:16

1-I clicked on catch and caught the text
2-I did the "test" and it worked fine
3=I selected "Copy" I did a ctrl-a to copy everything

I could not find
“ ;======== Copy The Next Functions To Your Code ======== ”
feiyue
Posts: 175
Joined: 08 Aug 2014, 04:08

Re: FindText - Catch screen image into text and then find it

20 May 2016, 15:21

Find this line at the end of My source code,
Copy and paste the following into your script.

My source code on Post 1,that is:
Code: [Select all] [Expand] [Download]
------------------------------------------
……
……
;======== Copy The Next Functions To Your Code ========
------------------------------------------
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 15:59

I tried pasting this first at the beginning of the code then after the code. No luck
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 16:07

Here is my code:

; Note: parameters of the X,Y is the center of the coordinates,
; and the W,H is the offset distance to the center

FindText(x,y,Text,c,w=150,h=150,ByRef rx="",ByRef ry="")
{
xywh2xywh(x-w,y-h,2*w+1,2*h+1,x,y,w,h,c)
if (w<1 or h<1)
Return, 0
v:=Trim(RegExReplace(Text,"[^0_\n]+"),"`n") . "`n"
w2:=InStr(v,"`n")-1, h2:=StrLen(v)//(w2+1)
fmt:=A_FormatInteger
SetFormat, IntegerFast, d
re1:="[0_]{" (w2+0) "}\n", re2:=".{" (w-w2) "}"
SetFormat, IntegerFast, %fmt%
if (w2<1 or RegExReplace(v,re1)!="")
Return, 0
bch:=A_BatchLines
SetBatchLines, -1
if InStr(c,"-")
re:=StrReplace(v,"_","1"), c:=StrReplace(c,"-")
else
re:=StrReplace(StrReplace(v,"0","1"),"_","0")
; Starting from the 1 and 0 mutants can speed up the match
pos:=re~="10|01", re:=SubStr(re,pos+=!pos,-1)
GetBitsFromScreen(x,y,w,h,Scan0,Stride,bits,c)
if !InStr(c,"*") ; Color Mode, it is very fast
ss:=StrGet(&bits,w*h,"utf-16")
else ; Gray Threshold Mode
{
; In order to speed up the speed,
; only to take the green as the gray value
ListLines, Off
Threshold:=StrReplace(c,"*"), i:=-3
VarSetCapacity(tmp,w*h+1,Asc("0")), k:=Asc("1")
Loop, % w*h
if NumGet(bits,i+=4,"uchar")<=Threshold
NumPut(k,tmp,A_Index,"char")
ListLines, On
ss:=StrGet(&tmp+1,w*h,0)
}
SetBatchLines, %bch%
if i:=(ss~=StrReplace(re,"`n",re2))
{
rx:=x+Mod(i-1,w)-Mod(pos-1,w2+1)+(w2-1)//2
ry:=y+(i-1)//w-(pos-1)//(w2+1)+(h2-1)//2
Return, 1
}
Return, 0
}

; Fixed the range to make it no more than screen borders

xywh2xywh(x1,y1,w1,h1,ByRef x,ByRef y,ByRef w,ByRef h,c="*")
{
; Use this rather than A_ScreenWidth etc can support multiple displays
SysGet, zx, 76
SysGet, zy, 77
SysGet, zw, 78
SysGet, zh, 79
left:=x1, right:=x1+w1-1, up:=y1, down:=y1+h1-1
left:=left<zx ? zx:left, right:=right>zx+zw-1 ? zx+zw-1:right
up:=up<zy ? zy:up, down:=down>zy+zh-1 ? zy+zh-1:down
x:=left, y:=up, w:=right-left+1, h:=down-up+1
; Color Mode --> if Mod(w,2)!=0 --> (Stride:=((w*16+31)//32)*4)!=w*2
if !InStr(c,"*")
w:=(w//2)*2
}

GetBitsFromScreen(x,y,w,h,ByRef Scan0,ByRef Stride,ByRef bits,c="*")
{
Ptr:=A_PtrSize ? "UPtr" : "UInt"
; Use this rather than GetDC(0) can support multiple displays
win:=DllCall("GetDesktopWindow", Ptr)
hDC:=DllCall("GetDC", Ptr,win, Ptr)
mDC1:=DllCall("CreateCompatibleDC", Ptr,hDC, Ptr)
hBM1:=DllCall("CreateCompatibleBitmap", Ptr,hDC, "int",w, "int",h, Ptr)
oBM1:=DllCall("SelectObject", Ptr,mDC1, Ptr,hBM1, Ptr)
DllCall("BitBlt", Ptr,mDC1, "int",0, "int",0, "int",w, "int",h
, Ptr,hDC, "int",x, "int",y, "uint",0xCC0020) ; SRCCOPY
if !InStr(c,"*") ; Color Mode
{
; Color c(RGB)-->COLORREF for WinApi=RGB(R,G,B)=BGR
BGR:=((c&0xFF)<<16)|(c&0xFF00)|((c&0xFF0000)>>16)
; All colors are converted to 1 or 0 utf-16 characters
c1:=16bppTo24bpp(Asc("1")), c0:=16bppTo24bpp(Asc("0"))
VarSetCapacity(Rect, 16)
NumPut(0, Rect, 0, "uint"), NumPut(0, Rect, 4, "uint")
NumPut(w, Rect, 8, "uint"), NumPut(h, Rect, 12, "uint")
mDC2:=DllCall("CreateCompatibleDC", Ptr,hDC, Ptr)
hBM2:=DllCall("CreateCompatibleBitmap", Ptr,hDC, "int",w, "int",h, Ptr)
oBM2:=DllCall("SelectObject", Ptr,mDC2, Ptr,hBM2, Ptr)
hBrush1:=DllCall("CreateSolidBrush", "uint",BGR^c1^c0, Ptr)
DllCall("FillRect", Ptr,mDC2, Ptr,&Rect, Ptr,hBrush1)
DllCall("DeleteObject", Ptr,hBrush1)
DllCall("msimg32.dll\TransparentBlt", Ptr,mDC2
, "int",0, "int",0, "int",w, "int",h, Ptr,mDC1
, "int",0, "int",0, "int",w, "int",h, "uint",BGR)
DllCall("BitBlt", Ptr,mDC2, "int",0, "int",0, "int",w, "int",h
, Ptr,mDC1, "int",0, "int",0, "uint",0x660046) ; SRCINVERT
; Color c-->BGR^(BGR^c1^c0)=c1^c0, the others-->X^X=0
hBrush2:=DllCall("CreateSolidBrush", "uint",c0, Ptr)
DllCall("FillRect", Ptr,mDC1, Ptr,&Rect, Ptr,hBrush2)
DllCall("DeleteObject", Ptr,hBrush2)
DllCall("BitBlt", Ptr,mDC1, "int",0, "int",0, "int",w, "int",h
, Ptr,mDC2, "int",0, "int",0, "uint",0x660046) ; SRCINVERT
; Color c-->(c1^c0)^c0=c1, the others-->0^c0=c0
DllCall("SelectObject", Ptr,mDC2, Ptr,oBM2)
DllCall("DeleteObject", Ptr,hBM2)
DllCall("DeleteDC", Ptr,mDC2)
}
DllCall("ReleaseDC", Ptr,win, Ptr,hDC)
VarSetCapacity(bits, w*h*4, 0), bpp:=!InStr(c,"*") ? 16:32
VarSetCapacity(bi, 40, 0)
NumPut(40, bi, 0, "int"), NumPut(w, bi, 4, "int")
NumPut(-h, bi, 8, "int"), NumPut(1, bi, 12, "short")
NumPut(bpp, bi, 14, "short"), NumPut(0, bi, 16, "int")
DllCall("GetDIBits", Ptr,mDC1, Ptr,hBM1
, "int",0, "int",h, Ptr,&bits, Ptr,&bi, "int",0)
DllCall("SelectObject", Ptr,mDC1, Ptr,oBM1)
DllCall("DeleteObject", Ptr,hBM1)
DllCall("DeleteDC", Ptr,mDC1)
Scan0:=&bits, Stride:=((w*bpp+31)//32)*4
}

16bppTo24bpp(c) {
B:=(c&0x1F)<<3, G:=((c>>5)&0x1F)<<3, R:=((c>>10)&0x1F)<<3
Return, (B<<16)|(G<<8)|R
}

;================= The End =================

Text=
(
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_00000__________0________0_____________________________________________
00___00__________________0_____________________________________________
0_____0__________________0_____________________________________________
0_____00_0___0__0___000__0__00____0000___000___000___000__000__000_____
0_____00_0___0__0__00____0_0_________0__00____00____0___0_0____0_______
0_____00_0___0__0__0_____000_______000__0_____0_____00000_00___00______
0_____0__0___0__0__0_____000______0__0__0_____0_____0______00___00_____
00___00__0___0__0__00____0_00_____0__0__00____00____00______00___00____
_00000____0000__0___000__0__00____0000___000___000___0000_000__000_____
______00_______________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
_______________________________________________________________________
)

if FindText(81,134,Text,"*147",1500,1500,X,Y)
{
CoordMode, Mouse
MouseMove, X, Y
}
Mkonopko
Posts: 21
Joined: 25 Jan 2015, 07:30

Re: FindText - Catch screen image into text and then find it

20 May 2016, 19:37

Got it!!! This will be very useful to me. I'd like to recommend that you automatically post the additional code to the file.
Anyway many thanks for your help.

Marty Konopko

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 45 guests