FindText - Capture screen image into text and then find it

Post your working scripts, libraries and tools for AHK v1.1 and older
feiyue
Posts: 348
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.
3、The introduction by Descolada (latest).

Code: Select all

;/*
;===========================================
;  FindText - Capture screen image into text and then find it
;  https://www.autohotkey.com/boards/viewtopic.php?f=6&t=17834
;
;  Author  : FeiYue
;  Version : 9.4
;  Date    : 2023-12-18
;
;  Usage:  (required AHK v1.1.34+)
;  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>
;  5. If you want to call a method in the "FindTextClass" class,
;     use the parameterless FindText() to get the default object
;
;===========================================
;*/


if (!A_IsCompiled && A_LineFile=A_ScriptFullPath)
  FindText().Gui("Show")


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


FindText(ByRef x:="FindTextClass", ByRef y:="", args*)
{
  static init, obj
  if (!VarSetCapacity(init) && init:="1")
    obj:=new FindTextClass()
  return (x=="FindTextClass" && !args.Length()) ? obj : obj.FindText(x, y, args*)
}

Class FindTextClass
{  ;// Class Begin

__New()
{
  this.bits:={ Scan0: 0, hBM: 0, oldzw: 0, oldzh: 0 }
  this.bind:={ id: 0, mode: 0, oldStyle: 0 }
  this.Lib:=[]
  this.Cursor:=0
}

__Delete()
{
  if (this.bits.hBM)
    DllCall("DeleteObject", "Ptr",this.bits.hBM)
}

New()
{
  return new FindTextClass()
}

help()
{
return "
(
;--------------------------------
;  FindText - Capture screen image into text and then find it
;  Version : 9.4  (2023-12-18)
;--------------------------------
;  returnArray:=FindText(
;      OutputX --> The name of the variable used to store the returned X coordinate
;    , OutputY --> The name of the variable used to store the returned Y coordinate
;    , 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 --> Fault tolerance percentage of text       (0.1=10%)
;    , err0 --> Fault tolerance percentage of background (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 you want to combine find, it can be 1, or an array of words to find
;    , offsetX --> Set the max text offset (X) for combination lookup
;    , offsetY --> Set the max text offset (Y) for combination lookup
;    , dir --> Nine directions for searching: up, down, left, right and center
;    , zoomW --> Zoom percentage of image width  (1.0=100%)
;    , zoomH --> Zoom percentage of image height (1.0=100%)
;  )
;
;  The function returns an Array containing all lookup results,
;  any result is a object with the following values:
;  {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.
;  All coordinates are relative to Screen, colors are in RGB format
;
;  If the return variable is set to 'ok', ok[1] is the first result found.
;  ok[1].1, ok[1].2 is the X, 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.
;
;  If OutputX is equal to 'wait' or 'wait1'(appear), or 'wait0'(disappear)
;  it means using a loop to wait for the image to appear or disappear.
;  the OutputY is the wait time in seconds, time less than 0 means infinite waiting
;  Timeout means failure, return 0, and return other values means success
;  If you want to appear and the image is found, return the found array object
;  If you want to disappear and the image cannot be found, return 1
;  Example 1: FindText(X:='wait', Y:=3, 0,0,0,0,0,0,Text)   ; Wait 3 seconds for appear
;  Example 2: FindText(X:='wait0', Y:=-1, 0,0,0,0,0,0,Text) ; Wait indefinitely for disappear
;
;  <FindMultiColor> <FindColor> : FindColor is FindMultiColor with only one point
;  Text:='|<>##DRDGDB $ 0/0/RRGGBB1-DRDGDB1/RRGGBB2/-RRGGBB3, xn/yn/...'
;  Color behind '##' (0xDRDGDB) is the default allowed variation for all colors
;  Initial point (0,0) match 0xRRGGBB1(+/-0xDRDGDB1) or 0xRRGGBB2(+/-0xDRDGDB)
;  or not 0xRRGGBB3(+/-0xDRDGDB)
;  Each point can take up to 10 sets of colors (xn/yn/RRGGBB1/.../RRGGBB10)
;  The color starting with '-' means excluding this color
;  If multiple colors need to be excluded, you can do this: ',xn/yn/-RRGGBB1,xn/yn/-RRGGBB2'
;
;  <FindPic> : Text parameter require manual input
;  Text:='|<>##DRDGDB-RRGGBB1-RRGGBB2... $ d:\a.bmp'
;  the 0xRRGGBB1(+/-0xDRDGDB)... all as transparent color
;
;--------------------------------
)"
}

FindText(ByRef OutputX:="", ByRef OutputY:=""
  , x1:=0, y1:=0, x2:=0, y2:=0, err1:=0, err0:=0, text:=""
  , ScreenShot:=1, FindAll:=1, JoinText:=0, offsetX:=20, offsetY:=10
  , dir:=1, zoomW:=1, zoomH:=1)
{
  local
  if (OutputX ~= "i)^\s*wait[10]?\s*$")
  {
    found:=!InStr(OutputX,"0"), time:=Round(OutputY,15)
    , timeout:=A_TickCount+Round(time*1000)
    Loop
    {
      ok:=this.FindText(,, x1, y1, x2, y2, err1, err0, text, ScreenShot
        , FindAll, JoinText, offsetX, offsetY, dir, zoomW, zoomH)
      if (found && ok)
      {
        OutputX:=ok[1].x, OutputY:=ok[1].y
        return ok
      }
      if (!found && !ok)
        return 1
      if (time>=0 && A_TickCount>=timeout)
        Break
      Sleep 50
    }
    return 0
  }
  SetBatchLines, % (bch:=A_BatchLines)?"-1":"-1"
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
    n:=150000, x:=y:=-n, w:=h:=2*n
  else
    x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  bits:=this.GetBitsFromScreen(x,y,w,h,ScreenShot,zx,zy), x-=zx, y-=zy
  , this.ok:=0, info:=[]
  Loop Parse, text, |
    if IsObject(j:=this.PicInfo(A_LoopField))
      info.Push(j)
  if (w<1 || h<1 || !(num:=info.Length()) || !bits.Scan0)
  {
    SetBatchLines, %bch%
    return 0
  }
  arr:=[], info2:=[], k:=0, s:=""
  , mode:=(IsObject(JoinText) ? 2 : JoinText ? 1 : 0)
  For i,j in info
  {
    k:=Max(k, (j[7]=5 && j[8]=0 ? j[9] : j[2]*j[3]))
    if (mode)
      v:=(mode=2 ? j[10] : i) . "", s.="|" v
      , (!info2.HasKey(v) && info2[v]:=[]), (v!="" && info2[v].Push(j))
  }
  sx:=x, sy:=y, sw:=w, sh:=h
  , JoinText:=(mode=1 ? [s] : JoinText)
  , VarSetCapacity(s1, k*4), VarSetCapacity(s0, k*4)
  , VarSetCapacity(ss, sw*(sh+2))
  , FindAll:=(dir=9 ? 1 : FindAll)
  , allpos_max:=(FindAll || JoinText ? 10240 : 1)
  , ini:={ sx:sx, sy:sy, sw:sw, sh:sh, zx:zx, zy:zy
  , mode:mode, bits:bits, ss:&ss, s1:&s1, s0:&s0
  , err1:err1, err0:err0, allpos_max:allpos_max
  , zoomW:zoomW, zoomH:zoomH }
  Loop 2
  {
    if (err1=0 && err0=0) && (num>1 || A_Index>1)
      ini.err1:=err1:=0.05, ini.err0:=err0:=0.05
    if (!JoinText)
    {
      VarSetCapacity(allpos, allpos_max*4), allpos_ptr:=&allpos
      For i,j in info
      Loop % this.PicFind(ini, j, dir, sx, sy, sw, sh, allpos_ptr)
      {
        pos:=NumGet(allpos, 4*(A_Index-1), "uint")
        , x:=(pos&0xFFFF)+zx, y:=(pos>>16)+zy
        , w:=Floor(j[2]*zoomW), h:=Floor(j[3]*zoomH), comment:=j[10]
        , arr.Push({1:x, 2:y, 3:w, 4:h, x:x+w//2, y:y+h//2, id:comment})
        if (!FindAll)
          Break 3
      }
    }
    else
    For k,v in JoinText
    {
      v:=StrSplit(Trim(RegExReplace(v, "\s*\|[|\s]*", "|"), "|")
      , (InStr(v,"|")?"|":""), " `t")
      , this.JoinText(arr, ini, info2, v, 1, offsetX, offsetY
      , FindAll, dir, 0, 0, 0, sx, sy, sw, sh)
      if (!FindAll && arr.Length())
        Break 2
    }
    if (err1!=0 || err0!=0 || arr.Length() || info[1][4] || info[1][7]=5)
      Break
  }
  if (dir=9 && arr.Length())
    arr:=this.Sort2(arr, (x1+x2)//2, (y1+y2)//2)
  SetBatchLines, %bch%
  if (arr.Length())
  {
    OutputX:=arr[1].x, OutputY:=arr[1].y, this.ok:=arr
    return arr
  }
  return 0
}

; the join text object <==> [ "abc", "xyz", "a1|a2|a3" ]

JoinText(arr, ini, info2, text, index, offsetX, offsetY
  , FindAll, dir, minX, minY, maxY, sx, sy, sw, sh)
{
  local
  if !(Len:=text.Length())
    return 0
  VarSetCapacity(allpos, ini.allpos_max*4), allpos_ptr:=&allpos
  , zoomW:=ini.zoomW, zoomH:=ini.zoomH, mode:=ini.mode
  For i,j in info2[text[index]]
  if (mode!=2 || text[index]==j[10])
  Loop % this.PicFind(ini, j, dir, sx, sy, (index=1 ? sw
  : Min(sx+offsetX+Floor(j[2]*zoomW),ini.sx+ini.sw)-sx), sh, allpos_ptr)
  {
    pos:=NumGet(allpos, 4*(A_Index-1), "uint")
    , x:=pos&0xFFFF, y:=pos>>16
    , w:=Floor(j[2]*zoomW), h:=Floor(j[3]*zoomH)
    , (index=1 && (minX:=x, minY:=y, maxY:=y+h))
    , minY1:=Min(y, minY), maxY1:=Max(y+h, maxY), sx1:=x+w
    if (index<Len)
    {
      sy1:=Max(minY1-offsetY, ini.sy)
      , sh1:=Min(maxY1+offsetY, ini.sy+ini.sh)-sy1
      if this.JoinText(arr, ini, info2, text, index+1, offsetX, offsetY
      , FindAll, 5, minX, minY1, maxY1, sx1, sy1, 0, sh1)
      && (index>1 || !FindAll)
        return 1
    }
    else
    {
      comment:=""
      For k,v in text
        comment.=(mode=2 ? v : info2[v][1][10])
      x:=minX+ini.zx, y:=minY1+ini.zy, w:=sx1-minX, h:=maxY1-minY1
      , arr.Push({1:x, 2:y, 3:w, 4:h, x:x+w//2, y:y+h//2, id:comment})
      if (index>1 || !FindAll)
        return 1
    }
  }
  return 0
}

PicFind(ini, j, dir, sx, sy, sw, sh, allpos_ptr)
{
  local
  static MyFunc:=""
  if (!MyFunc)
  {
    x32:="VVdWU4PEgIO8JJQAAAAFi6wkzAAAAA+EoAoAAIu8JNAAAACF@w+O7RAAAMcEJAAA"
    . "AADHRCQQAAAAADH@x0QkHAAAAADHRCQUAAAAAIuEJMgAAACLTCQUMfYx2wHIhe2J"
    . "RCQMf0HplAAAAI22AAAAAA+vhCS0AAAAicGJ8Jn3@QHBi0QkDIA8GDF0TIuEJMQA"
    . "AACDwwEDtCTkAAAAiQy4g8cBOd10VIsEJJn3vCTQAAAAg7wklAAAAAR1tQ+vhCSo"
    . "AAAAicGJ8Jn3@Y0MgYtEJAyAPBgxdbSLRCQci5QkwAAAAIPDAQO0JOQAAACJDIKD"
    . "wAE53YlEJBx1rAFsJBSDRCQQAYuMJOgAAACLRCQQAQwkOYQk0AAAAA+FLv@@@4tM"
    . "JBy7rYvbaIl8JCQPr4wk1AAAAInIwfkf9+vB+gwpyouMJNgAAACJVCQYD6@PicjB"
    . "+R@368H6DCnKiVQkKIO8JJQAAAAED4RNCgAAi4QkqAAAAIu0JKwAAAAPr4QksAAA"
    . "AIuMJKgAAACNPLCLhCS0AAAA99iNBIGJRCQwi4QklAAAAIXAD4VWAwAAi4QkmAAA"
    . "AIusJLgAAADHRCQsAAAAAMdEJDQAAAAAwegQD7bAiUQkDIuEJJgAAAAPtsSJRCQQ"
    . "D7aEJJgAAACJRCQUi4QktAAAAMHgAoXtiUQkPA+OvQAAAIu0JLQAAACF9g+OlAAA"
    . "AIu0JKQAAACLbCQ0A6wkvAAAAAH+A3wkPIl8JDgDvCSkAAAAiTwkifaNvCcAAAAA"
    . "D7ZOAotcJAwPtkYBD7YWK0QkECtUJBSJzwHZKd+NmQAEAAAPr8APr9@B4AsPr98B"
    . "w7j+BQAAKcgPr8IPr9AB0zmcJJwAAAAPk0UAg8YEg8UBOzQkdaqLjCS0AAAAAUwk"
    . "NIt8JDiDRCQsAQN8JDCLRCQsOYQkuAAAAA+FQ@@@@4tEJBiJRCREi0QkKIlEJFyL"
    . "dCQci0wkRDHAOc6LTCRcD07wiXQkHIt0JCQ5zg9PxolEJCSLdCQki0QkHDnGD03G"
    . "iUQkFIuEJJQAAACD6ASD+AEPhnQHAADHRCRkAAAAAMdEJHAAAAAAi0QkcAOEJLQA"
    . "AAArhCTkAAAAiUQkdItEJGQDhCS4AAAAK4Qk6AAAAIlEJEyLhCSgAAAAg+gBg@gH"
    . "D4fpBgAAg@gDiUQkVA+O5AYAAIt0JGSLRCRwiXQkcIlEJGSLdCR0OXQkZA+P5AYA"
    . "AItEJHSLTCQci3QkVMdEJDwAAAAAiUQkeIuEJMAAAACNBIiJRCRgifCD4AGJRCRY"
    . "ifCD4AOJRCR8i0QkcIt0JEw58A+PAQEAAIN8JHwBi0wkZA9PTCR4iXQkQIlEJCyJ"
    . "TCRQi0QkWIXAi0QkQA9ERCQsg3wkVAOJRCQ0D49PAgAAg7wklAAAAAWLRCRQiUQk"
    . "OItEJDgPhFcCAACDvCSUAAAABA+EKwQAAA+vhCS0AAAAi0wkFIt0JDSFyY0sMA+E"
    . "4AMAAIt0JFyLXCREMcCLlCS8AAAAiWwkEItMJByLfCQkiRwkiXQkDAHqi2wkFOsL"
    . "g8ABOcUPhKUDAAA5yH0Xi5wkwAAAAIs0gwHWgD4AdQaDLCQBeBw5x37Wi5wkxAAA"
    . "AIs0gwHWgD4BdcWDbCQMAXm+g0QkLAGDbCRAAYtEJCw5RCRMD40Z@@@@g0QkZAGD"
    . "bCR4AYtEJGQ5RCR0D43X@v@@i0QkPIPsgFteX13CWACDvCSUAAAAAQ+EQgsAAIO8"
    . "JJQAAAACD4QtCQAAi4QkmAAAAA+2jCScAAAAx0QkFAAAAADHRCQsAAAAAMHoEA+2"
    . "wIkEJIuEJJgAAAAPtsSJRCQMD7aEJJgAAACJRCQQi4QknAAAAMHoEA+26IuEJJwA"
    . "AACJ7g+v9Q+2xIl0JCCJxg+v8InID6@BiXQkCIlEJASLhCS0AAAAweACiUQkNIuE"
    . "JLgAAACFwA+OEv3@@4uEJLQAAACFwA+OfgAAAIuUJKQAAACLXCQsA5wkvAAAAAH6"
    . "A3wkNIl8JDgDvCSkAAAAif0PtkICMf8PtkoBKwQkD7YyD6@AOUQkIHwiK0wkDA+v"
    . "yTlMJAh8FYnwD7bwK3QkEA+v9jl0JAQPncCJx4n4g8IEg8MBiEP@Oep1touMJLQA"
    . "AAABTCQsi3wkOINEJBQBA3wkMItEJBQ5hCS4AAAAD4VZ@@@@6Wb8@@+NtCYAAAAA"
    . "i0QkNIO8JJQAAAAFiUQkOItEJFCJRCQ0i0QkOA+Fqf3@@w+vhCSoAAAAi0wkNIts"
    . "JGiF7Y0EiIlEJDAPhQUDAACLRCRsi3wkFMdEJBgAAAAAiUQkKItEJESF@4lEJEgP"
    . "hBsBAACLfCQgjbQmAAAAAItMJBiLtCTAAAAAi0QkMAMEjou0JMQAAACNFImNHJGL"
    . "VCQoizSOi4wkpAAAAIkUJIm0JJwAAACLtCSkAAAAD7Z0BgKJdCQMi7QkpAAAAA+2"
    . "dAYBD7YEAYl0JBCJRCQgid7reYsEJIPGAosIi1gEiciJ38HoEMHvEA+2wCtEJAyJ"
    . "+g+2+g+21w+224n9iVwkBDHbD6@viVQkCA+vwDnofysPtsUrRCQQidUPr+oPr8A5"
    . "6H8Yi1QkBA+2wStEJCCJ0w+v2g+vwDnYD57DgwQkCIH5@@@@AA+XwDjYdRg5tCSc"
    . "AAAAD4d6@@@@g2wkSAEPiIICAACDRCQYAYNEJChUi0QkGDlEJBQPhfT+@@+JfCQg"
    . "i4Qk3AAAAINEJDwBi0wkPIXAD4TK@P@@i1QkOAOUJLAAAACLRCQ0A4QkrAAAAIu0"
    . "JNwAAADB4hAJ0DuMJOAAAACJRI78D4yX@P@@6cL8@@+LbCQQi1QkHIXSdKSLtCS8"
    . "AAAAi4QkwAAAAItcJGCNDC6LEIPABAHKOdjGAgB18ul8@@@@D6+EJKgAAACLdCQ0"
    . "i1wkFI0EsIu0JKQAAACJBCQDhCSYAAAAhduJ8Q+2bAYCD7Z0BgEPtgQBiXQkDIlE"
    . "JBAPhDj@@@+LRCRcMduJbCQYiUQkMItEJESJRCQoZpA7XCQcfWSLhCTAAAAAixQk"
    . "i3wkGAMUmA+2dBECD7ZEEQErRCQMD7YUEStUJBCJ9QH+Kf2NvgAEAAAPr8APr@3B"
    . "4AsPr@0Bx7j+BQAAKfAPr8IPr9AB1zu8JJwAAAB2C4NsJCgBD4iY+@@@OVwkJH5k"
    . "i4QkxAAAAIsUJIt8JBgDFJgPtnQRAg+2RBEBK0QkDA+2FBErVCQQifUB@in9jb4A"
    . "BAAAD6@AD6@9weALD6@9Ace4@gUAACnwD6@CD6@QAdc7vCScAAAAdwuDbCQwAQ+I"
    . "Lvv@@4PDATlcJBQPhR@@@@@pOv7@@4t0JBSF9g+ELv7@@4t0JESLnCSkAAAAMdKQ"
    . "i4QkwAAAAIt8JDADPJCLhCTEAAAAiyyQD7ZEOwKJ6cHpEA+2ySnID7ZMOwEPtjw7"
    . "D6@AO0QkIH8niegPtsQpwQ+vyTtMJAh@F4n4D7b4iegPtsApxw+v@zt8JAR+B2aQ"
    . "g+4BeBWDwgE5VCQUdZKJrCSYAAAA6ab9@@+JrCSYAAAA6Xz6@@+JfCQg6XP6@@@H"
    . "RCRUAAAAAIt0JHSLRCRMiXQkTIlEJHSLdCR0OXQkZA+OHPn@@8dEJDwAAAAAi0Qk"
    . "PIPsgFteX13CWACLhCSwAAAAx4QksAAAAAAAAACJRCRki4QkrAAAAMeEJKwAAAAA"
    . "AAAAiUQkcOlr+P@@i7QkmAAAADHAhfYPlcCJRCRoD4U6AQAAi5wknAAAAIXbD4R@"
    . "BgAAi7QknAAAAIu8JMAAAACLnCTEAAAAjQS3MfaJBCSLhCTIAAAAMdKDxwSDwwSL"
    . "BLCJhCSYAAAAwegE9@UPr4Qk6AAAAIlUJAyZ97wk0AAAAA+vhCSoAAAAicGLRCQM"
    . "D6+EJOQAAACZ9@2NBIGJR@yLhCSYAAAAg+APjQRGg8YViUP8OzwkdZeLhCScAAAA"
    . "i4wk1AAAALqti9toD6@IiUQkHInIwfkf9+qJ0MH4DCnIi7QkyAAAAIlEJETHRCRc"
    . "AAAAAMdEJCQAAAAAg8YEiXQkbOkX9@@@i4QkmAAAAMHoEA+vhCToAAAAmfe8JNAA"
    . "AAAPr4QkqAAAAInBD7eEJJgAAAAPr4Qk5AAAAJn3@Y0EgYmEJJgAAACLRCQYiUQk"
    . "RItEJCiJRCRc6cH2@@+LhCTQAAAAi7QkyAAAAA+2lCSYAAAAD6@FjQSGiUQkbIuE"
    . "JJgAAADB6BAPtsiLhCSYAAAAic4Pr@GLjCTQAAAAD7bEiXQkIInGD6@widAPr8KF"
    . "yYl0JAiJRCQED47OBAAAi3QkbI0ErQAAAACJrCTMAAAAi5wkmAAAAIu8JJwAAACL"
    . "bCQgiUQkNDHAx0QkLAAAAADHRCQwAAAAAMdEJBwAAAAAiTQki5QkzAAAAIXSD44a"
    . "AQAAi4wkyAAAAIs0JMdEJBQAAAAAiVwkKAHBA0QkNIlMJBCJRCQ4A4QkyAAAAIlE"
    . "JCSLRCQQhf8PtlABD7ZIAg+2AIkUJIlEJAx0RjHSZpCLHJaJ2MHoEA+2wCnID6@A"
    . "OcV8Iw+2xysEJA+vwDlEJAh8FA+2wytEJAwPr8A5RCQED435AAAAg8IBOfp1wolc"
    . "JCiLRCQcweEQweACiUQkGItEJCyZ97wk0AAAAA+vhCSoAAAAicOLRCQUmfe8JMwA"
    . "AACLVCQcjQSDi5wkwAAAAIkEk4sEJIPCAYucJMQAAACJVCQcweAICcELTCQMi0Qk"
    . "GIkMA4NEJBAEi5Qk5AAAAItEJBABVCQUO0QkJA+FIP@@@4tcJCiLRCQ4iTQkg0Qk"
    . "MAGLtCToAAAAi0wkMAF0JCw5jCTQAAAAD4W2@v@@i0wkHLqti9toiZwkmAAAAA+v"
    . "jCTUAAAAx0QkXAAAAADHRCQkAAAAAInIwfkf9+rB+gwpyolUJETplPT@@5CNdCYA"
    . "iVwkKOlr@@@@i4wktAAAAIuEJLwAAAAx7YuUJLgAAADHBCQAAAAAjQRIiUQkNInI"
    . "weAChdKJRCQMD45A9P@@i4QktAAAAIXAfleLjCSkAAAAi0QkNAH5A3wkDI0cKIl8"
    . "JBADvCSkAAAAD7ZRAg+2QQGDwQQPtnH8g8MBa8BLa9ImAcKJ8MHgBCnwAdDB+AeI"
    . "Q@85+XXTA6wktAAAAIt8JBCDBCQBA3wkMIsEJDmEJLgAAAB1iouEJLQAAACLvCSc"
    . "AAAAMfbHRCQMAAAAAIPoAYlEJCyLhCS4AAAAg+gBiUQkMIuEJLQAAACFwA+O7QAA"
    . "AItEJAyLjCS0AAAAi6wkvAAAAIXAi0QkNA+URCQUAfGJTCQ4icONFDABy4nxK4wk"
    . "tAAAAAHuiXQkEAHBMcCJDCTpkgAAAIB8JBQAD4WPAAAAOUQkLA+EhQAAAItMJAw5"
    . "TCQwdHsPtjoPtmr@vgEAAAADvCSYAAAAOe9yPA+2agE573I0iwwkD7YpOe9yKg+2"
    . "KznvciMPtmn@Oe9yGw+2aQE573ITD7Zr@znvcgsPtnMBOfcPksGJzotsJBCJ8YhM"
    . "BQCDwAGDwgGDwwGDBCQBOYQktAAAAHQShcAPhWb@@@+LdCQQxgQGAuvYi3QkOINE"
    . "JAwBi0QkDDmEJLgAAAAPhe7+@@+LRCQYibwknAAAAIlEJESLRCQoiUQkXOl@8v@@"
    . "i4QkmAAAAIucJLgAAAAx7ccEJAAAAACDwAHB4AeJhCSYAAAAi4QktAAAAMHgAoXb"
    . "iUQkDA+ONfL@@4uMJLQAAACFyX5mi4wkpAAAAIucJLwAAACJbCQUAfkDfCQMAeuL"
    . "rCSYAAAAiXwkEAO8JKQAAAAPtlECD7ZBAQ+2MWvAS2vSJgHCifDB4AQp8AHCOdUP"
    . "lwODwQSDwwE5+XXVi2wkFAOsJLQAAACLfCQQgwQkAQN8JDCLBCQ5hCS4AAAAD4V3"
    . "@@@@6afx@@@HRCQoAAAAAMdEJBgAAAAAx0QkJAAAAADHRCQcAAAAAOkg8P@@x0Qk"
    . "RAAAAADHRCQcAAAAAMdEJFwAAAAAx0QkJAAAAADpkfH@@zHAx0QkHAAAAADpIPr@"
    . "@5CQkJCQkJCQkJCQkJCQkA=="
    x64:="QVdBVkFVQVRVV1ZTSIHsuAAAAEiLtCQgAQAAi5wkcAEAAIP5BYmMJAABAACJ1UWJ"
    . "xUSJjCQYAQAARIu8JHgBAAAPhDIMAABFhf8PjlASAABEiXQkEIl8JBgxwIu8JAAB"
    . "AABEi6wkQAEAAEUx0kyLtCRgAQAAi6wkoAEAAEUx20SJZCQgx0QkCAAAAABBicTH"
    . "RCQoAAAAAImUJAgBAABEiYQkEAEAAEiJtCQgAQAASGN0JChFMclFMcBIA7QkaAEA"
    . "AIXbfzfrfWYPH4QAAAAAAEEPr8WJwUSJyJn3+wHBQoA8BjF0PUmDwAFJY8NBAelB"
    . "g8MBRDnDQYkMhn5ERInQmUH3@4P@BHXID6+EJCgBAACJwUSJyJn3+0KAPAYxjQyB"
    . "dcNIi5QkWAEAAEmDwAFJY8RBAelBg8QBRDnDiQyCf7wBXCQog0QkCAFEA5QkqAEA"
    . "AItEJAhBOccPhVD@@@9EieFBuK2L22hEi3QkEA+vjCSAAQAARIlkJFSLrCQIAQAA"
    . "RItkJCBEi6wkEAEAAEiLtCQgAQAAi3wkGESJXCQIicjB+R9B9+jB+gwpyouMJIgB"
    . "AACJVCQQQQ+vy4nIwfkfQffowfoMKcqJVCQYg7wkAAEAAAQPhIMLAACLhCQoAQAA"
    . "i5wkMAEAAA+vhCQ4AQAAjQSYi5wkKAEAAIlEJCiLhCRAAQAA99iNBIOLnCQAAQAA"
    . "iUQkLIXbD4X6AwAAiehEi5wkSAEAAMHoEA+22EiJ6A+2xEWF24nBQA+2xYnCD44X"
    . "BQAAi4QkQAEAAImsJAgBAABEi3wkKIusJEABAABEiXQkQESJZCRIQYnWweACx0Qk"
    . "IAAAAADHRCQwAAAAAIlEJDiJfCRQQYnMhe0PjooAAABIY3wkMEljx0Ux20gDvCRQ"
    . "AQAATI1UBgIPH4QAAAAAAEEPtlL+RQ+2CkEPtkL@RCnyRInJQYnQQo0UCynZRCng"
    . "RI2KAAQAAA+vwEQPr8nB4AtED6@Juf4FAAAp0YnKQQ+v0EGNBAFBD6@QAdBBOcVC"
    . "D5MEH0mDwwFJg8IERDndf59EA3wkOAFsJDCDRCQgAUQDfCQsi0QkIDmEJEgBAAAP"
    . "hVP@@@+LRCQQRIt0JECLfCRQRItkJEiLrCQIAQAAiUQkWItEJBiJhCSAAAAAi1wk"
    . "VItMJFgxwDnLi4wkgAAAAA9O2IlcJFSLXCQIOcsPT8OJRCQIi1wkCItEJFQ5ww9N"
    . "w0GJw4uEJAABAACD6ASD+AEPhpsIAADHhCSMAAAAAAAAAMeEJKAAAAAAAAAAi4Qk"
    . "oAAAAAOEJEABAAArhCSgAQAAiYQkpAAAAIuEJIwAAAADhCRIAQAAK4QkqAEAAIlE"
    . "JGCLhCQYAQAAg+gBg@gHD4f+BwAAg@gDiUQkaA+O+QcAAIucJIwAAACLhCSgAAAA"
    . "iZwkoAAAAImEJIwAAACLnCSkAAAAOZwkjAAAAA+P8wcAAIuEJKQAAABIi5wkWAEA"
    . "AImsJAgBAABEieVFidxEibQkhAAAAMdEJEAAAAAARYnuiYQkqAAAAItEJFSJvCSI"
    . "AAAAg+gBSI1EgwRIiUQkeEGNQ@9JifNIi7QkUAEAAEiNRIMEi1wkaEiJRCRIidiD"
    . "4AGJRCRsidiD4AOJhCSsAAAAi4QkoAAAAIt8JGA5+A+PBQEAAIO8JKwAAAABi5wk"
    . "jAAAAA9PnCSoAAAAiXwkUIlEJCCJXCRkDx+EAAAAAACLTCRsi0QkUIXJD0REJCCD"
    . "fCRoA4lEJCwPj1QCAACDvCQAAQAABYtEJGSJRCQwD4RcAgAAg7wkAAEAAAQPhFQE"
    . "AACLTCQwD6+MJEABAAADTCQsRYXkD4QJBAAARIuUJIAAAABEi0wkWDHARItEJFSL"
    . "fCQITIusJFgBAABMi7wkYAEAAOsNSIPAAUE5xA+O0gMAAEE5wInCfhOJy0EDXIUA"
    . "gDweAHUGQYPpAXgWOdd+1YnKQQMUh4A8FgF1yUGD6gF5w4NEJCABg2wkUAGLRCQg"
    . "OUQkYA+NJv@@@4OEJIwAAAABg6wkqAAAAAGLhCSMAAAAOYQkpAAAAA+NxP7@@4tE"
    . "JEBIgcS4AAAAW15fXUFcQV1BXkFfw4O8JAABAAABD4S3CwAAg7wkAAEAAAIPhIcJ"
    . "AABIiehEi4QkSAEAAEEPts0PtsSJ60GJzEGJw0APtsXB6xCJRCQgRInoD7bbwegQ"
    . "x0QkMAAAAADHRCQ4AAAAAA+20EyJ6A+2xEGJ1onHD6@4i4QkQAEAAEQPr@JEjTyF"
    . "AAAAAEQPr+FFhcAPjrkAAACJrCQIAQAAi6wkQAEAAESJrCQQAQAARYndhe1+b0hj"
    . "RCQoTGNcJDhFMcBMA5wkUAEAAEiNVAYCDx+EAAAAAAAPtgJFMdIPtkr@RA+2Sv4p"
    . "2A+vwEE5xnwaRCnpD6@JOc98EEQrTCQgRQ+vyUU5zEEPncJHiBQDSYPAAUiDwgRE"
    . "OcV@vEQBfCQoAWwkOItUJCyDRCQwAQFUJCiLRCQwOYQkSAEAAA+Fb@@@@4usJAgB"
    . "AABEi6wkEAEAAItEJBCJRCRYi0QkGImEJIAAAADp6@v@@4tEJCyDvCQAAQAABYlE"
    . "JDCLRCRkiUQkLA+FpP3@@4tEJDCLfCQsD6+EJCgBAACLlCSQAAAAhdKNBLiJRCQ4"
    . "D4VVAwAARYXkD4RRAQAASIu8JGABAABIi4QkWAEAAESLrCSEAAAASIm0JJgAAADH"
    . "RCQoAAAAAESJpCSUAAAASIl8JBBIi3wkcEiJxkiJfCQYi3wkWIl8JFyLvCSIAAAA"
    . "i0QkOAMGSItcJBBEi0QkKEiLVCQYjUgBRIszRI1IAkiYSGPJTWPJRQ+2JANBD7Yc"
    . "C0cPthQLQYnZ63QPH0QAAItKBIsaQYPAAkGJzYnYD7b9wegQQcHtEA+26UUPtu0P"
    . "tsAxyUQp0EWJ7w+vwEUPr@1EOfh@KA+2x0GJ@0QpyA+vwEQPr@9EOfh@Ew+2w4np"
    . "RCngD6@ND6@AOcgPnsFIg8IIgfv@@@8AD5fAOMh1EEU5xneMg2wkXAEPiBADAABI"
    . "g8YESINEJBAEg0QkKBVIg0QkGFRIO3QkSA+FIf@@@0SLpCSUAAAASIu0JJgAAABE"
    . "iawkhAAAAIm8JIgAAABmkINEJEABSIO8JJABAAAAi3wkQA+Emvz@@4tEJDADhCQ4"
    . "AQAASGPXi0wkLAOMJDABAABIi5wkkAEAAMHgEAnIO7wkmAEAAIlEk@wPjGP8@@@p"
    . "mvz@@4tEJFSFwHSkSIuEJFgBAABMi0QkeA8fgAAAAACJygMQSIPABEw5wMYEFgB1"
    . "7+l6@@@@i0QkMIt8JCwPr4QkKAEAAI0EuInBA4QkCAEAAEWF5I1QAkhj0kEPthwT"
    . "jVABSJhBD7YEA0hj0kEPtjwTQYn9D4Q1@@@@i7wkgAAAAIlsJBhFMcmJzUGJx0iJ"
    . "dCQ4iXwkEIt8JFiJfCQoi3wkVEQ5z0WJyn5uSIuEJFgBAABCixSIAeqNQgJImEEP"
    . "tgwDjUIBSGPSQQ+2FBNImEEPtgQDic4B2USNgQAEAAAp3kQp+kQPr8ZEKegPr8BE"
    . "D6@GweALQQHAuP4FAAApyA+vwg+v0EEB0EU58HYLg2wkKAEPiEcBAABEOVQkCH5w"
    . "SIuEJGABAABCixSIAeqNQgJImEEPtgwDjUIBSGPSQQ+2FBNImEEPtgQDQYnKAdlE"
    . "jYEABAAAQSnaRCn6RQ+vwkQp6A+vwEUPr8LB4AtBAcC4@gUAACnID6@CD6@QQQHQ"
    . "RTnwdwuDbCQQAQ+I0AAAAEmDwQFFOcwPjwb@@@+LbCQYSIt0JDjpBf7@@0WF5A+E"
    . "@P3@@0SJdCQoRItMJFgxyUSLrCSEAAAAi7wkiAAAAESLVCQ4TIu0JFgBAABMi7wk"
    . "YAEAAEGLFI5BixyPRAHSQYnYjUICQcHoEEUPtsBImEEPtgQDRCnARI1CAUhj0g+v"
    . "wEEPthQTTWPARw+2BANEOeh@HQ+2x0EpwEUPr8BBOfh@Dg+2wynCD6@SOep+CGaQ"
    . "QYPpAXgoSIPBAUE5zH+ViZwkCAEAAESLdCQo6VD9@@+LbCQYSIt0JDjp9Pn@@4mc"
    . "JAgBAABEi3QkKOnj+f@@RImsJIQAAACJvCSIAAAARIukJJQAAABIi7QkmAAAAOm@"
    . "+f@@x0QkaAAAAACLnCSkAAAAi0QkYIlcJGCJhCSkAAAAi5wkpAAAADmcJIwAAAAP"
    . "jg34@@@HRCRAAAAAAOm8+f@@i4QkOAEAAMeEJDgBAAAAAAAAiYQkjAAAAIuEJDAB"
    . "AADHhCQwAQAAAAAAAImEJKAAAADpRPf@@zHAhdIPlcCJhCSQAAAAD4UTAQAARTHJ"
    . "RTHbRYXATIuUJGgBAAAPhEkGAABBiyox0kmDwlSJ6MHoBPfzD6+EJKgBAACJ0ZlB"
    . "9@8Pr4QkKAEAAEGJwIuEJKABAAAPr8FIi4wkWAEAAJn3+0GNBIBCiQSJiehIi4wk"
    . "YAEAAIPgD0GNBENBg8MVQokEiUmDwQFFOc13mIuMJIABAAC6rYvbaESJbCRUQQ+v"
    . "zYnIwfkf9+qJ0MH4DCnISIucJGgBAACJRCRYx4QkgAAAAAAAAADHRCQIAAAAAEiD"
    . "wwRIiVwkcOkB9v@@iejB6BAPr4QkqAEAAJlB9@8Pr4QkKAEAAInBD7fFD6+EJKAB"
    . "AACZ9@uNLIGLRCQQiUQkWItEJBiJhCSAAAAA6b31@@+J2EEPr8fB4AJImEgDhCRo"
    . "AQAASIlEJHCJ0A+20sHoEEGJ1A+2yEiJ6A+2xEGJzonHRA+v8Q+v+EQPr+JFhf8P"
    . "juAEAACNQ@9Ii0wkcEGNUP@HRCQoAAAAAMdEJBAAAAAASI0EhQYAAADHRCQYAAAA"
    . "AESJhCQQAQAATI1UkQQx0onpSIlEJCCNBJ0AAAAAQYnVSIm0JCABAACJnCRwAQAA"
    . "iUQkLESJvCR4AQAAi7QkcAEAAIX2D47tAAAASGNEJBhIi7QkaAEAAEiNXAYCSANE"
    . "JCBIAfAx9kiJRCQIDx9AAIusJBABAABED7YDRA+2S@9ED7Zb@oXtdEBIi1QkcGaQ"
    . "iwqJyMHoEA+2wEQpwA+vwEE5xnwbD7bFRCnID6@AOcd8Dg+2wUQp2A+vwEE5xH1a"
    . "SIPCBEw50nXHi0QkKE1j@UHB4BBBweEIQYPFAUUJyJlFCdj3vCR4AQAAD6+EJCgB"
    . "AACJxYnwmfe8JHABAABIi5QkWAEAAI1EhQBCiQS6SIuEJGABAABGiQS4SIPDBAO0"
    . "JKABAABIOVwkCA+FQP@@@4t0JCwBdCQYg0QkEAGLnCSoAQAAi0QkEAFcJCg5hCR4"
    . "AQAAD4Xj@v@@RIlsJFSJzYtMJFQPr4wkgAEAALqti9toRIusJBABAABIi7QkIAEA"
    . "AMeEJIAAAAAAAAAAx0QkCAAAAACJyMH5H@fqwfoMKcqJVCRY6aTz@@+LhCRAAQAA"
    . "RIuMJEgBAABFMdsx2wHASJhIA4QkUAEAAEWFyUiJRCQwi4QkQAEAAESNPIUAAAAA"
    . "D45k9@@@iXwkIIu8JEABAACF@35PSGNEJChMY9NMA1QkMEUxwEiNTAYCD7YRD7ZB"
    . "@0iDwQRED7ZJ+mvAS2vSJgHCRInIweAERCnIAdDB+AdDiAQCSYPAAUQ5x3@NRAF8"
    . "JCgB+0GDwwGLTCQsAUwkKEQ5nCRIAQAAdZdIY4QkQAEAALoBAAAASIm0JCABAACL"
    . "tCRAAQAAi3wkIEUx28dEJCwAAAAASI1YAUgpwouEJEABAABIiVQkQEiJXCQ4g+gB"
    . "iUQkKIuEJEgBAACD6AGJRCQghfYPjt8AAABMY0wkLEiLTCQ4RYXbSItEJDBMi5Qk"
    . "UAEAAA+Uw06NBAlIi0wkQEqNVAgBTQHKSQHATAHJSAHBMcDpjgAAAITbD4WOAAAA"
    . "OUQkKA+EhAAAAEQ5XCQgdH1ED7Zq@0QPtnr+QbkBAAAAQQHtRTn9ckVED7Y6RTn9"
    . "cjxED7Z5@0U5@XIyRQ+2eP9FOf1yKEQPtnn+RTn9ch5ED7Y5RTn9chVFD7Z4@kU5"
    . "@XILRQ+2CEU5zUEPksFFiAwCSIPAAUiDwgFJg8ABSIPBATnGfg+FwA+Fav@@@0HG"
    . "BAIC690BdCQsQYPDAUQ5nCRIAQAAD4UH@@@@i0QkEEiLtCQgAQAAiUQkWItEJBiJ"
    . "hCSAAAAA6YLx@@+NRQFEi5QkSAEAAEUx2zHbweAHicWLhCRAAQAARYXSRI08hQAA"
    . "AAAPjlL1@@9EiXQkIESLtCRAAQAARYX2flNIY0QkKExj00wDlCRQAQAARTHASI1M"
    . "BgIPthEPtkH@RA+2Sf5rwEtr0iYBwkSJyMHgBEQpyAHQOcVDD5cEAkmDwAFIg8EE"
    . "RTnGf81EAXwkKEQB80GDwwGLTCQsAUwkKEQ5nCRIAQAAdZKLRCQQRIt0JCCJRCRY"
    . "i0QkGImEJIAAAADpvfD@@8dEJBgAAAAAx0QkEAAAAADHRCQIAAAAAMdEJFQAAAAA"
    . "6Qbv@@@HRCRYAAAAAMeEJIAAAAAAAAAAx0QkVAAAAADHRCQIAAAAAOmX8P@@McDH"
    . "RCRUAAAAAOkz+v@@kJCQkA=="
    this.MCode(MyFunc, StrReplace((A_PtrSize=8?x64:x32),"@","/"))
  }
  text:=j[1], w:=j[2], h:=j[3]
  , err1:=(j[4] ? j[5] : ini.err1)
  , err0:=(j[4] ? j[6] : ini.err0)
  , mode:=j[7], color:=j[8], n:=j[9]
  return (!ini.bits.Scan0) ? 0 : DllCall(&MyFunc
    , "int",mode, "uint",color, "uint",n, "int",dir
    , "Ptr",ini.bits.Scan0, "int",ini.bits.Stride
    , "int",sx, "int",sy, "int",sw, "int",sh
    , "Ptr",ini.ss, "Ptr",ini.s1, "Ptr",ini.s0
    , (mode=5 ? "Ptr":"AStr"),text, "int",w, "int",h
    , "int",Floor(err1*10000), "int",Floor(err0*10000)
    , "Ptr",allpos_ptr, "int",ini.allpos_max
    , "int",Floor(w*ini.zoomW), "int",Floor(h*ini.zoomH))
}

code()
{
return "
(

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

int __attribute__((__stdcall__)) PicFind(
  int mode, unsigned int c, unsigned int n, int dir
  , unsigned char * Bmp, int Stride
  , int sx, int sy, int sw, int sh
  , unsigned char * ss, unsigned int * s1, unsigned int * s0
  , unsigned char * text, int w, int h, int err1, int err0
  , unsigned int * allpos, int allpos_max
  , int new_w, int new_h )
{
  int ok, o, i, j, k, v, e1, e0, len1, len0, max;
  int x, y, x1, y1, x2, y2, x3, y3;
  int r, g, b, rr, gg, bb, dR, dG, dB;
  unsigned int c1, c2;
  unsigned char * gs;
  unsigned int * cors;
  ok=0; o=0; len1=0; len0=0;
  //----------------------
  if (mode==5)
  {
    if (k=(c!=0))  // FindPic
    {
      cors=(unsigned int *)(text+w*h*4);
      r=(c>>16)&0xFF; g=(c>>8)&0xFF; b=c&0xFF;
      dR=r*r; dG=g*g; dB=b*b;
      for (y=0; y<h; y++)
      {
        for (x=0; x<w; x++, o+=4)
        {
          rr=text[2+o]; gg=text[1+o]; bb=text[o];
          for (i=0; i<n; i++)
          {
            c=cors[i]; r=((c>>16)&0xFF)-rr;
            g=((c>>8)&0xFF)-gg; b=(c&0xFF)-bb;
            if (r*r<=dR && g*g<=dG && b*b<=dB) goto NoMatch1;
          }
          s1[len1]=(y*new_h/h)*Stride+(x*new_w/w)*4;
          s0[len1++]=(rr<<16)|(gg<<8)|bb;
          NoMatch1:;
        }
      }
    }
    else  // FindMultiColor or FindColor
    {
      cors=(unsigned int *)text;
      for (; len1<n; len1++, o+=21)
      {
        c=cors[o]; y=(c>>4)/w; x=(c>>4)%w;
        s1[len1]=(y*new_h/h)*Stride+(x*new_w/w)*4;
        s0[len1]=o+(c&0xF)*2;
      }
      cors++;
    }
    goto StartLookUp;
  }
  //----------------------
  // Generate Lookup Table
  for (y=0; y<h; y++)
  {
    for (x=0; x<w; x++)
    {
      if (mode==4)
        i=(y*new_h/h)*Stride+(x*new_w/w)*4;
      else
        i=(y*new_h/h)*sw+(x*new_w/w);
      if (text[o++]=='1')
        s1[len1++]=i;
      else
        s0[len0++]=i;
    }
  }
  //----------------------
  // Color Position Mode
  // only used to recognize multicolored Verification Code
  if (mode==4)
  {
    y=c>>16; x=c&0xFFFF;
    c=(y*new_h/h)*Stride+(x*new_w/w)*4;
    goto StartLookUp;
  }
  //----------------------
  // Generate Two Value Image
  o=sy*Stride+sx*4; j=Stride-sw*4; 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; v=r+rr+rr;
        ss[i]=((1024+v)*r*r+2048*g*g+(1534-v)*b*b<=n) ? 1:0;
      }
  }
  else if (mode==1)  // Gray Threshold Mode
  {
    c=(c+1)<<7;
    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
  {
    gs=ss+sw*2;
    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;
    }
    for (i=0, y=0; y<sh; y++)
      for (x=0; x<sw; x++, i++)
      {
        if (x==0 || y==0 || x==sw-1 || y==sh-1)
          ss[i]=2;
        else
        {
          n=gs[i]+c;
          ss[i]=(gs[i-1]>n || gs[i+1]>n
          || gs[i-sw]>n   || gs[i+sw]>n
          || gs[i-sw-1]>n || gs[i-sw+1]>n
          || gs[i+sw-1]>n || gs[i+sw+1]>n) ? 1:0;
        }
      }
  }
  else  // (mode==3) Color Difference Mode
  {
    rr=(c>>16)&0xFF; gg=(c>>8)&0xFF; bb=c&0xFF;
    r=(n>>16)&0xFF; g=(n>>8)&0xFF; b=n&0xFF;
    dR=r*r; dG=g*g; dB=b*b;
    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]=(r*r<=dR && g*g<=dG && b*b<=dB) ? 1:0;
      }
  }
  //----------------------
  StartLookUp:
  err1=len1*err1/10000;
  err0=len0*err0/10000;
  if (err1>=len1) len1=0;
  if (err0>=len0) len0=0;
  max=(len1>len0) ? len1 : len0;
  if (mode==5 || mode==4)
  {
    x1=sx; y1=sy; sx=0; sy=0;
  }
  else
  {
    x1=0; y1=0;
  }
  x2=x1+sw-new_w; y2=y1+sh-new_h;
  // 1 ==> ( Left to Right ) Top to Bottom
  // 2 ==> ( Right to Left ) Top to Bottom
  // 3 ==> ( Left to Right ) Bottom to Top
  // 4 ==> ( Right to Left ) Bottom to Top
  // 5 ==> ( Top to Bottom ) Left to Right
  // 6 ==> ( Bottom to Top ) Left to Right
  // 7 ==> ( Top to Bottom ) Right to Left
  // 8 ==> ( Bottom to Top ) Right to Left
  if (dir<1 || dir>8) dir=1;
  if (--dir>3) { r=y1; y1=x1; x1=r; r=y2; y2=x2; x2=r; }
  for (y3=y1; y3<=y2; y3++)
  {
    for (x3=x1; x3<=x2; x3++)
    {
      y=((dir&3)>1) ? y1+y2-y3 : y3;
      x=(dir&1) ? x1+x2-x3 : x3;
      if (dir>3) { r=y; y=x; x=r; }
      //----------------------
      e1=err1; e0=err0;
      if (mode==5)
      {
        o=y*Stride+x*4;
        if (k)
        {
          for (i=0; i<max; i++)
          {
            j=o+s1[i]; c=s0[i]; r=Bmp[2+j]-((c>>16)&0xFF);
            g=Bmp[1+j]-((c>>8)&0xFF); b=Bmp[j]-(c&0xFF);
            if ((r*r>dR || g*g>dG || b*b>dB) && (--e1)<0) goto NoMatch;
          }
        }
        else
        {
          for (i=0; i<max; i++)
          {
            j=o+s1[i]; rr=Bmp[2+j]; gg=Bmp[1+j]; bb=Bmp[j];
            for (j=i*21, n=s0[i]; j<n;)
            {
              c1=cors[j++]; c2=cors[j++];
              r=((c1>>16)&0xFF)-rr; g=((c1>>8)&0xFF)-gg; b=(c1&0xFF)-bb;
              dR=(c2>>16)&0xFF; dG=(c2>>8)&0xFF; dB=c2&0xFF;
              if ((r*r<=dR*dR && g*g<=dG*dG && b*b<=dB*dB)^(c1>0xFFFFFF))
                goto MatchOK;
            }
            if ((--e1)<0) goto NoMatch;
            MatchOK:;
          }
        }
      }
      else if (mode==4)
      {
        o=y*Stride+x*4;
        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; v=r+rr+rr;
            if ((1024+v)*r*r+2048*g*g+(1534-v)*b*b>n && (--e1)<0) goto NoMatch;
          }
          if (i<len0)
          {
            j=o+s0[i]; r=Bmp[2+j]-rr; g=Bmp[1+j]-gg; b=Bmp[j]-bb; v=r+rr+rr;
            if ((1024+v)*r*r+2048*g*g+(1534-v)*b*b<=n && (--e0)<0) goto NoMatch;
          }
        }
      }
      else
      {
        o=y*sw+x;
        for (i=0; i<max; i++)
        {
          if (i<len1 && ss[o+s1[i]]==0 && (--e1)<0) goto NoMatch;
          if (i<len0 && ss[o+s0[i]]==1 && (--e0)<0) goto NoMatch;
        }
        // Clear the image that has been found
        for (i=0; i<len1; i++)
          ss[o+s1[i]]=0;
      }
      ok++;
      if (allpos!=0)
      {
        allpos[ok-1]=(sy+y)<<16|(sx+x);
        if (ok>=allpos_max) goto Return1;
      }
      NoMatch:;
    }
  }
  //----------------------
  Return1:
  return ok;
}

)"
}

PicInfo(text)
{
  local
  if !InStr(text, "$")
    return
  static init, info, bmp
  if (!VarSetCapacity(init) && init:="1")
    info:=[], bmp:=[]
  key:=(r:=StrLen(text))<10000 ? text
    : DllCall("ntdll\RtlComputeCrc32", "uint",0
    , "Ptr",&text, "uint",r*(1+!!A_IsUnicode), "uint")
  if info.HasKey(key)
    return info[key]
  v:=text, comment:="", seterr:=err1:=err0:=0
  ; You Can Add Comment Text within The <>
  if RegExMatch(v, "O)<([^>\n]*)>", r)
    v:=StrReplace(v,r[0]), comment:=Trim(r[1])
  ; You can Add two fault-tolerant in the [], separated by commas
  if RegExMatch(v, "O)\[([^\]\n]*)]", r)
  {
    v:=StrReplace(v,r[0]), r:=StrSplit(r[1] ",", ",")
    , seterr:=1, err1:=r[1], err0:=r[2]
  }
  color:=SubStr(v,1,InStr(v,"$")-1), v:=Trim(SubStr(v,InStr(v,"$")+1))
  mode:=InStr(color,"##") ? 5
    : InStr(color,"#") ? 4 : InStr(color,"-") ? 3
    : InStr(color,"**") ? 2 : InStr(color,"*") ? 1 : 0
  color:=RegExReplace(color, "[*#\s]")
  (mode=0 || mode=3 || mode=5) && color:=StrReplace(color,"0x")
  if (mode=5)
  {
    if !(v~="/[\s\-\w]+/[\s\-\w,/]+$")  ; FindPic
    {
      ; Text:="|<>##DRDGDB-RRGGBB1-RRGGBB2... $ d:\a.bmp"
      ; the 0xRRGGBB1(+/-0xDRDGDB)... all as transparent color
      if !(hBM:=LoadPicture(v))
        return
      this.GetBitmapWH(hBM, w, h)
      if (w<1 || h<1)
        return
      hBM2:=this.CreateDIBSection(w, h, 32, Scan0)
      this.CopyHBM(hBM2, 0, 0, hBM, 0, 0, w, h)
      DllCall("DeleteObject", "Ptr",hBM)
      if (!Scan0)
        return
      ; All images used for Search are cached
      StrReplace(color, "-",, n)
      bmp.Push(buf:=this.Buffer(w*h*4+n*4)), v:=buf.Ptr
      DllCall("RtlMoveMemory", "Ptr",v, "Ptr",Scan0, "Ptr",w*h*4)
      DllCall("DeleteObject", "Ptr",hBM2)
      p:=v+w*h*4-4
      , tab:=Object("Black", "000000", "White", "FFFFFF"
      , "Red", "FF0000", "Green", "008000", "Blue", "0000FF"
      , "Yellow", "FFFF00", "Silver", "C0C0C0", "Gray", "808080"
      , "Teal", "008080", "Navy", "000080", "Aqua", "00FFFF"
      , "Olive", "808000", "Lime", "00FF00", "Fuchsia", "FF00FF"
      , "Purple", "800080", "Maroon", "800000")
      For k1,v1 in StrSplit(color, "-")
      if (k1>1)
        NumPut(Floor("0x" (tab.HasKey(v1)?tab[v1]:v1)), 0|p+=4, "uint")
      color:=Floor("0x" StrSplit(color "-", "-")[1])|0x1000000
    }
    else  ; FindMultiColor or FindColor
    {
      ; Text:='|<>##DRDGDB $ 0/0/RRGGBB1-DRDGDB1/RRGGBB2/-RRGGBB3, xn/yn/...'
      ; Color behind '##' (0xDRDGDB) is the default allowed variation for all colors
      ; Initial point (0,0) match 0xRRGGBB1(+/-0xDRDGDB1) or 0xRRGGBB2(+/-0xDRDGDB)
      ; or not 0xRRGGBB3(+/-0xDRDGDB)
      ; Each point can take up to 10 sets of colors (xn/yn/RRGGBB1/.../RRGGBB10)
      ; The color starting with '-' means excluding this color
      ; If multiple colors need to be excluded, you can do this: ',xn/yn/-RRGGBB1,xn/yn/-RRGGBB2'
      arr:=StrSplit(Trim(RegExReplace(v, "i)\s|0x"), ","), ",")
      if !(n:=arr.Length())
        return
      bmp.Push(buf:=this.Buffer(n*(1+10+10)*4)), v:=buf.Ptr
      , color:=StrSplit(color "-", "-")[1]
      For k1,v1 in arr
      {
        r:=StrSplit(v1 "/", "/")
        , x:=Floor(r[1]), y:=Floor(r[2])
        , (A_Index=1) ? (x1:=x2:=x, y1:=y2:=y)
        : (x1:=Min(x1,x), x2:=Max(x2,x)
        , y1:=Min(y1,y), y2:=Max(y2,y))
      }
      w:=x2-x1+1, h:=y2-y1+1
      For k1,v1 in arr
      {
        r:=StrSplit(v1 "/", "/")
        , x:=Floor(r[1])-x1, y:=Floor(r[2])-y1
        , n1:=Min(Max(r.Length()-3, 0), 10)
        , NumPut((y*w+x)<<4|n1, 0|p:=v+(A_Index-1)*84, "uint")
        Loop % n1
          k1:=(InStr(v1:=r[2+A_Index], "-")=1 ? 0x1000000:0)
          , c:=StrSplit(Trim(v1,"-") "-" color, "-")
          , NumPut(Floor("0x" c[1])&0xFFFFFF|k1, 0|p+=4, "uint")
          , NumPut(Floor("0x" c[2]), 0|p+=4, "uint")
      }
      color:=0
    }
  }
  else
  {
    r:=StrSplit(v ".", "."), w:=Floor(r[1])
    , v:=this.base64tobit(r[2]), h:=StrLen(v)//w
    if (w<1 || h<1 || StrLen(v)!=w*h)
      return
    if (mode=3)
    {
      r:=StrSplit(color, "-")
      , color:=Floor("0x" r[1]), n:=Floor("0x" r[2])
    }
    else
    {
      r:=StrSplit(color "@1", "@")
      , color:=Floor((mode=0?"0x":"") r[1]), n:=r[2]
      , n:=(n<=0||n>1?1:n), n:=Floor(4606*255*255*(1-n)*(1-n))
      , (mode=4) && color:=((color-1)//w)<<16|Mod(color-1,w)
    }
  }
  return info[key]:=[v, w, h, seterr, err1, err0, mode, color, n, comment]
}

Buffer(size, FillByte:="")
{
  local
  buf:={}, buf.SetCapacity("a", size), p:=buf.GetAddress("a")
  , (FillByte!="" && DllCall("RtlFillMemory","Ptr",p,"Ptr",size,"uchar",FillByte))
  , buf.Ptr:=p, buf.Size:=size
  return buf
}

GetBitsFromScreen(ByRef x:=0, ByRef y:=0, ByRef w:=0, ByRef h:=0
  , ScreenShot:=1, ByRef zx:=0, ByRef zy:=0, ByRef zw:=0, ByRef zh:=0)
{
  local
  static CAPTUREBLT:=""
  (!IsObject(this.bits) && this.bits:={Scan0:0, hBM:0, oldzw:0, oldzh:0})
  , bits:=this.bits
  if (!ScreenShot && bits.Scan0)
  {
    zx:=bits.zx, zy:=bits.zy, zw:=bits.zw, zh:=bits.zh
    , w:=Min(x+w,zx+zw), x:=Max(x,zx), w-=x
    , h:=Min(y+h,zy+zh), y:=Max(y,zy), h-=y
    return bits
  }
  bch:=A_BatchLines, cri:=A_IsCritical
  Critical
  if (id:=this.BindWindow(0,0,1))
  {
    WinGet, id, ID, ahk_id %id%
    WinGetPos, zx, zy, zw, zh, ahk_id %id%
  }
  if (!id)
  {
    SysGet, zx, 76
    SysGet, zy, 77
    SysGet, zw, 78
    SysGet, zh, 79
  }
  this.UpdateBits(bits, zx, zy, zw, zh)
  , w:=Min(x+w,zx+zw), x:=Max(x,zx), w-=x
  , h:=Min(y+h,zy+zh), y:=Max(y,zy), h-=y
  if (!ScreenShot || w<1 || h<1 || !bits.hBM)
  {
    Critical, %cri%
    SetBatchLines, %bch%
    return bits
  }
  if IsFunc(k:="GetBitsFromScreen2")
    && %k%(bits, x-zx, y-zy, w, h)
  {
    ; Each small range of data obtained from DXGI must be
    ; copied to the screenshot cache using this.CopyBits()
    zx:=bits.zx, zy:=bits.zy, zw:=bits.zw, zh:=bits.zh
    Critical, %cri%
    SetBatchLines, %bch%
    return bits
  }
  if (CAPTUREBLT="")  ; thanks Descolada
  {
    DllCall("Dwmapi\DwmIsCompositionEnabled", "Int*",i:=0)
    CAPTUREBLT:=i ? 0 : 0x40000000
  }
  mDC:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
  oBM:=DllCall("SelectObject", "Ptr",mDC, "Ptr",bits.hBM, "Ptr")
  if (id)
  {
    if (mode:=this.BindWindow(0,0,0,1))<2
    {
      hDC:=DllCall("GetDCEx", "Ptr",id, "Ptr",0, "int",3, "Ptr")
      DllCall("BitBlt","Ptr",mDC,"int",x-zx,"int",y-zy,"int",w,"int",h
        , "Ptr",hDC, "int",x-zx, "int",y-zy, "uint",0xCC0020|CAPTUREBLT)
      DllCall("ReleaseDC", "Ptr",id, "Ptr",hDC)
    }
    else
    {
      hBM2:=this.CreateDIBSection(zw, zh)
      mDC2:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
      oBM2:=DllCall("SelectObject", "Ptr",mDC2, "Ptr",hBM2, "Ptr")
      DllCall("PrintWindow", "Ptr",id, "Ptr",mDC2, "uint",(mode>3)*3)
      DllCall("BitBlt","Ptr",mDC,"int",x-zx,"int",y-zy,"int",w,"int",h
        , "Ptr",mDC2, "int",x-zx, "int",y-zy, "uint",0xCC0020)
      DllCall("SelectObject", "Ptr",mDC2, "Ptr",oBM2)
      DllCall("DeleteDC", "Ptr",mDC2)
      DllCall("DeleteObject", "Ptr",hBM2)
    }
  }
  else
  {
    hDC:=DllCall("GetWindowDC","Ptr",id:=DllCall("GetDesktopWindow","Ptr"),"Ptr")
    DllCall("BitBlt","Ptr",mDC,"int",x-zx,"int",y-zy,"int",w,"int",h
      , "Ptr",hDC, "int",x, "int",y, "uint",0xCC0020|CAPTUREBLT)
    DllCall("ReleaseDC", "Ptr",id, "Ptr",hDC)
  }
  if this.CaptureCursor(0,0,0,0,0,1)
    this.CaptureCursor(mDC, zx, zy, zw, zh)
  DllCall("SelectObject", "Ptr",mDC, "Ptr",oBM)
  DllCall("DeleteDC", "Ptr",mDC)
  Critical, %cri%
  SetBatchLines, %bch%
  return bits
}

UpdateBits(bits, zx, zy, zw, zh)
{
  local
  if (zw>bits.oldzw || zh>bits.oldzh || !bits.hBM)
  {
    Try DllCall("DeleteObject", "Ptr",bits.hBM)
    bits.hBM:=this.CreateDIBSection(zw, zh, bpp:=32, ppvBits)
    , bits.Scan0:=(!bits.hBM ? 0:ppvBits)
    , bits.Stride:=((zw*bpp+31)//32)*4
    , bits.oldzw:=zw, bits.oldzh:=zh
  }
  bits.zx:=zx, bits.zy:=zy, bits.zw:=zw, bits.zh:=zh
}

CreateDIBSection(w, h, bpp:=32, ByRef ppvBits:=0)
{
  local
  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")
  return DllCall("CreateDIBSection", "Ptr",0, "Ptr",&bi
    , "int",0, "Ptr*",ppvBits:=0, "Ptr",0, "int",0, "Ptr")
}

GetBitmapWH(hBM, ByRef w, ByRef h)
{
  local
  VarSetCapacity(bm, size:=(A_PtrSize=8 ? 32:24))
  , DllCall("GetObject", "Ptr",hBM, "int",size, "Ptr",&bm)
  , w:=NumGet(bm,4,"int"), h:=Abs(NumGet(bm,8,"int"))
}

CopyHBM(hBM1, x1, y1, hBM2, x2, y2, w, h, Clear:=0, trans:=0, alpha:=255)
{
  local
  if (w<1 || h<1 || !hBM1 || !hBM2)
    return
  mDC1:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
  oBM1:=DllCall("SelectObject", "Ptr",mDC1, "Ptr",hBM1, "Ptr")
  mDC2:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
  oBM2:=DllCall("SelectObject", "Ptr",mDC2, "Ptr",hBM2, "Ptr")
  if (trans)
    DllCall("GdiAlphaBlend", "Ptr",mDC1, "int",x1, "int",y1, "int",w, "int",h
    , "Ptr",mDC2, "int",x2, "int",y2, "int",w, "int",h, "uint",alpha<<16)
  else
    DllCall("BitBlt", "Ptr",mDC1, "int",x1, "int",y1, "int",w, "int",h
    , "Ptr",mDC2, "int",x2, "int",y2, "uint",0xCC0020)
  if (Clear)
    DllCall("BitBlt", "Ptr",mDC1, "int",x1, "int",y1, "int",w, "int",h
    , "Ptr",mDC1, "int",x1, "int",y1, "uint",MERGECOPY:=0xC000CA)
  DllCall("SelectObject", "Ptr",mDC1, "Ptr",oBM1)
  DllCall("DeleteDC", "Ptr",mDC1)
  DllCall("SelectObject", "Ptr",mDC2, "Ptr",oBM2)
  DllCall("DeleteDC", "Ptr",mDC2)
}

CopyBits(Scan01,Stride1,x1,y1,Scan02,Stride2,x2,y2,w,h,Reverse:=0)
{
  local
  if (w<1 || h<1 || !Scan01 || !Scan02)
    return
  static init, MFCopyImage
  if (!VarSetCapacity(init) && init:="1")
  {
    MFCopyImage:=DllCall("GetProcAddress", "Ptr"
    , DllCall("LoadLibrary", "Str","Mfplat.dll", "Ptr")
    , "AStr","MFCopyImage", "Ptr")
  }
  if (MFCopyImage && !Reverse)  ; thanks QQ:RenXing
  {
    return DllCall(MFCopyImage
      , "Ptr",Scan01+y1*Stride1+x1*4, "int",Stride1
      , "Ptr",Scan02+y2*Stride2+x2*4, "int",Stride2
      , "uint",w*4, "uint",h)
  }
  ListLines % (lls:=A_ListLines)?0:0
  SetBatchLines, % (bch:=A_BatchLines)?"-1":"-1"
  p1:=Scan01+(y1-1)*Stride1+x1*4
  , p2:=Scan02+(y2-1)*Stride2+x2*4, w*=4
  if (Reverse)
    p2+=(h+1)*Stride2, Stride2:=-Stride2
  Loop % h
    DllCall("RtlMoveMemory","Ptr",p1+=Stride1,"Ptr",p2+=Stride2,"Ptr",w)
  SetBatchLines, %bch%
  ListLines %lls%
}

DrawHBM(hBM, lines)
{
  local
  mDC:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
  oBM:=DllCall("SelectObject", "Ptr",mDC, "Ptr",hBM, "Ptr")
  oldc:="", brush:=0, VarSetCapacity(rect, 16)
  For k,v in lines  ; [ [x, y, w, h, color] ]
  if IsObject(v)
  {
    if (oldc!=v[5])
    {
      oldc:=v[5], BGR:=(oldc&0xFF)<<16|oldc&0xFF00|(oldc>>16)&0xFF
      DllCall("DeleteObject", "Ptr",brush)
      brush:=DllCall("CreateSolidBrush", "UInt",BGR, "Ptr")
    }
    DllCall("SetRect", "Ptr",&rect, "int",v[1], "int",v[2]
      , "int",v[1]+v[3], "int",v[2]+v[4])
    DllCall("FillRect", "Ptr",mDC, "Ptr",&rect, "Ptr",brush)
  }
  DllCall("DeleteObject", "Ptr",brush)
  DllCall("SelectObject", "Ptr",mDC, "Ptr",oBM)
  DllCall("DeleteObject", "Ptr",mDC)
}

; Bind the window so that it can find images when obscured
; by other windows, it's equivalent to always being
; at the front desk. Unbind Window using FindText().BindWindow(0)

BindWindow(bind_id:=0, bind_mode:=0, get_id:=0, get_mode:=0)
{
  local
  (!IsObject(this.bind) && this.bind:={id:0, mode:0, oldStyle:0})
  , bind:=this.bind
  if (get_id)
    return bind.id
  if (get_mode)
    return bind.mode
  if (bind_id)
  {
    bind.id:=bind_id, bind.mode:=bind_mode, bind.oldStyle:=0
    if (bind_mode & 1)
    {
      WinGet, i, ExStyle, ahk_id %bind_id%
      bind.oldStyle:=i
      WinSet, Transparent, 255, ahk_id %bind_id%
      Loop 30
      {
        Sleep 100
        WinGet, i, Transparent, ahk_id %bind_id%
      }
      Until (i=255)
    }
  }
  else
  {
    bind_id:=bind.id
    if (bind.mode & 1)
      WinSet, ExStyle, % bind.oldStyle, ahk_id %bind_id%
    bind.id:=0, bind.mode:=0, bind.oldStyle:=0
  }
}

; Use FindText().CaptureCursor(1) to Capture Cursor
; Use FindText().CaptureCursor(0) to Cancel Capture Cursor

CaptureCursor(hDC:=0, zx:=0, zy:=0, zw:=0, zh:=0, get_cursor:=0)
{
  local
  if (get_cursor)
    return this.Cursor
  if (hDC=1 || hDC=0) && (zw=0)
  {
    this.Cursor:=hDC
    return
  }
  VarSetCapacity(mi, 40, 0), NumPut(16+A_PtrSize, mi, "int")
  DllCall("GetCursorInfo", "Ptr",&mi)
  bShow:=NumGet(mi, 4, "int")
  hCursor:=NumGet(mi, 8, "Ptr")
  x:=NumGet(mi, 8+A_PtrSize, "int")
  y:=NumGet(mi, 12+A_PtrSize, "int")
  if (!bShow) || (x<zx || y<zy || x>=zx+zw || y>=zy+zh)
    return
  VarSetCapacity(ni, 40, 0)
  DllCall("GetIconInfo", "Ptr",hCursor, "Ptr",&ni)
  xCenter:=NumGet(ni, 4, "int")
  yCenter:=NumGet(ni, 8, "int")
  hBMMask:=NumGet(ni, (A_PtrSize=8?16:12), "Ptr")
  hBMColor:=NumGet(ni, (A_PtrSize=8?24:16), "Ptr")
  DllCall("DrawIconEx", "Ptr",hDC
    , "int",x-xCenter-zx, "int",y-yCenter-zy, "Ptr",hCursor
    , "int",0, "int",0, "int",0, "int",0, "int",3)
  DllCall("DeleteObject", "Ptr",hBMMask)
  DllCall("DeleteObject", "Ptr",hBMColor)
}

MCode(ByRef code, hex)
{
  local
  flag:=((hex~="[^\s\da-fA-F]")?1:4), hex:=RegExReplace(hex, "[\s=]")
  VarSetCapacity(code, len:=(flag=1 ? StrLen(hex)//4*3+3 : StrLen(hex)//2))
  DllCall("crypt32\CryptStringToBinary", "Str",hex, "uint",0
    , "uint",flag, "Ptr",&code, "uint*",len, "Ptr",0, "Ptr",0)
  DllCall("VirtualProtect", "Ptr",&code, "Ptr",len, "uint",0x40, "Ptr*",0)
}

bin2hex(addr, size, base64:=1)
{
  local
  flag:=(base64 ? 1|0x40000000 : 4|0x0000000C)
  Loop 2
    p:=(A_Index=1 ? 0 : VarSetCapacity(hex,len*2)*0 + &hex)
    , DllCall("Crypt32\CryptBinaryToString", "Ptr",addr, "uint",size
    , "uint",flag, "Ptr",p, "uint*",len:=0)
  return RegExReplace(StrGet(p, len), "\s+")
}

base64tobit(s)
{
  local
  ListLines % (lls:=A_ListLines)?0:0
  Chars:="0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  SetFormat, IntegerFast, d
  Loop Parse, Chars
    if InStr(s, A_LoopField, 1)
      s:=RegExReplace(s, "[" A_LoopField "]", ((i:=A_Index-1)>>5&1)
      . (i>>4&1) . (i>>3&1) . (i>>2&1) . (i>>1&1) . (i&1))
  s:=RegExReplace(RegExReplace(s,"[^01]+"),"10*$")
  ListLines %lls%
  return s
}

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

ASCII(s)
{
  local
  if RegExMatch(s, "O)\$(\d+)\.([\w+/]+)", r)
  {
    s:=RegExReplace(this.base64tobit(r[2]),".{" r[1] "}","$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 FindText().PicLib(Text,1) to add the text library to PicLib()'s Lib,
; Use FindText().PicLib("comment1|comment2|...") to get text images from Lib

PicLib(comments, add_to_Lib:=0, index:=1)
{
  local
  (!IsObject(this.Lib) && this.Lib:=[]), Lib:=this.Lib
  , (!Lib.HasKey(index) && Lib[index]:=[]), Lib:=Lib[index]
  if (add_to_Lib)
  {
    re:="O)<([^>\n]*)>[^$\n]+\$[^""\r\n]+"
    Loop Parse, comments, |
      if RegExMatch(A_LoopField, re, r)
      {
        s1:=Trim(r[1]), s2:=""
        Loop Parse, s1
          s2.="_" . Format("{:d}",Ord(A_LoopField))
        Lib[s2]:=r[0]
      }
    Lib[""]:=""
  }
  else
  {
    Text:=""
    Loop Parse, comments, |
    {
      s1:=Trim(A_LoopField), s2:=""
      Loop Parse, s1
        s2.="_" . Format("{:d}",Ord(A_LoopField))
      if Lib.HasKey(s2)
        Text.="|" . Lib[s2]
    }
    return Text
  }
}

; Decompose a string into individual characters and get their data

PicN(Number, index:=1)
{
  return this.PicLib(RegExReplace(Number,".","|$0"), 0, index)
}

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

PicX(Text)
{
  local
  if !RegExMatch(Text, "O)(<[^$\n]+)\$(\d+)\.([\w+/]+)", r)
    return Text
  v:=this.base64tobit(r[3]), Text:=""
  c:=StrLen(StrReplace(v,"0"))<=StrLen(v)//2 ? "1":"0"
  txt:=RegExReplace(v,".{" r[2] "}","$0`n")
  While InStr(txt,c)
  {
    While !(txt~="m`n)^" c)
      txt:=RegExReplace(txt,"m`n)^.")
    i:=0
    While (txt~="m`n)^.{" i "}" c)
      i:=Format("{:d}",i+1)
    v:=RegExReplace(txt,"m`n)^(.{" i "}).*","$1")
    txt:=RegExReplace(txt,"m`n)^.{" i "}")
    if (v!="")
      Text.="|" r[1] "$" i "." this.bit2base64(v)
  }
  return Text
}

; Screenshot and retained as the last screenshot.

ScreenShot(x1:=0, y1:=0, x2:=0, y2:=0)
{
  this.FindText(,, x1, y1, x2, y2)
}

; Get the RGB color of a point from the last screenshot.
; If the point to get the color is beyond the range of
; Screen, it will return White color (0xFFFFFF).

GetColor(x, y, fmt:=1)
{
  local
  bits:=this.GetBitsFromScreen(,,,,0,zx,zy,zw,zh)
  , c:=(x<zx || x>=zx+zw || y<zy || y>=zy+zh || !bits.Scan0)
  ? 0xFFFFFF : NumGet(bits.Scan0+(y-zy)*bits.Stride+(x-zx)*4,"uint")
  return (fmt ? Format("0x{:06X}",c&0xFFFFFF) : c)
}

; Set the RGB color of a point in the last screenshot

SetColor(x, y, color:=0x000000)
{
  local
  bits:=this.GetBitsFromScreen(,,,,0,zx,zy,zw,zh)
  if !(x<zx || x>=zx+zw || y<zy || y>=zy+zh || !bits.Scan0)
    NumPut(color, bits.Scan0+(y-zy)*bits.Stride+(x-zx)*4, "uint")
}

; Identify a line of text or verification code
; based on the result returned by FindText().
; offsetX is the maximum interval between two texts,
; if it exceeds, a "*" sign will be inserted.
; offsetY is the maximum height difference between two texts.
; overlapW is used to set the width of the overlap.
; Return Association array {text:Text, x:X, y:Y, w:W, h:H}

Ocr(ok, offsetX:=20, offsetY:=20, overlapW:=0)
{
  local
  ocr_Text:=ocr_X:=ocr_Y:=min_X:=dx:=""
  For k,v in ok
    x:=v.1
    , min_X:=(A_Index=1 || x<min_X ? x : min_X)
    , max_X:=(A_Index=1 || x>max_X ? x : max_X)
  While (min_X!="" && min_X<=max_X)
  {
    LeftX:=""
    For k,v in ok
    {
      x:=v.1, y:=v.2
      if (x<min_X) || (ocr_Y!="" && Abs(y-ocr_Y)>offsetY)
        Continue
      ; Get the leftmost X coordinates
      if (LeftX="" || x<LeftX)
        LeftX:=x, LeftY:=y, LeftW:=v.3, LeftH:=v.4, LeftOCR:=v.id
    }
    if (LeftX="")
      Break
    if (ocr_X="")
      ocr_X:=LeftX, min_Y:=LeftY, max_Y:=LeftY+LeftH
    ; If the interval exceeds the set value, add "*" to the result
    ocr_Text.=(ocr_Text!="" && LeftX>dx ? "*":"") . LeftOCR
    ; Update for next search
    min_X:=LeftX+LeftW-(overlapW>LeftW//2 ? LeftW//2:overlapW)
    , dx:=LeftX+LeftW+offsetX, ocr_Y:=LeftY
    , (LeftY<min_Y && min_Y:=LeftY)
    , (LeftY+LeftH>max_Y && max_Y:=LeftY+LeftH)
  }
  if (ocr_X="")
    ocr_X:=0, min_Y:=0, min_X:=0, max_Y:=0
  return {text:ocr_Text, x:ocr_X, y:min_Y
    , w: min_X-ocr_X, h: max_Y-min_Y}
}

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

Sort(ok, dy:=10)
{
  local
  if !IsObject(ok)
    return ok
  s:="", n:=150000, ypos:=[]
  For k,v in ok
  {
    x:=v.x, y:=v.y, add:=1
    For k1,v1 in ypos
    if Abs(y-v1)<=dy
    {
      y:=v1, add:=0
      Break
    }
    if (add)
      ypos.Push(y)
    s.=(y*n+x) "." k "|"
  }
  s:=Trim(s,"|")
  Sort, s, N D|
  ok2:=[]
  Loop Parse, s, |
    ok2.Push( ok[StrSplit(A_LoopField,".")[2]] )
  return ok2
}

; Sort the results of FindText() according to the nearest distance

Sort2(ok, px, py)
{
  local
  if !IsObject(ok)
    return ok
  s:=""
  For k,v in ok
    s.=((v.x-px)**2+(v.y-py)**2) "." k "|"
  s:=Trim(s,"|")
  Sort, s, N D|
  ok2:=[]
  Loop Parse, s, |
    ok2.Push( ok[StrSplit(A_LoopField,".")[2]] )
  return ok2
}

; Sort the results of FindText() according to the search direction

Sort3(ok, dir:=1)
{
  local
  if !IsObject(ok)
    return ok
  s:="", n:=150000
  For k,v in ok
    x:=v.1, y:=v.2
    , s.=(dir=1 ? y*n+x
    : dir=2 ? y*n-x
    : dir=3 ? -y*n+x
    : dir=4 ? -y*n-x
    : dir=5 ? x*n+y
    : dir=6 ? x*n-y
    : dir=7 ? -x*n+y
    : dir=8 ? -x*n-y : y*n+x) "." k "|"
  s:=Trim(s,"|")
  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:="", w:=10, h:=10, d:=3)
{
  local
  if (x="")
  {
    VarSetCapacity(pt,16,0), DllCall("GetCursorPos","Ptr",&pt)
    x:=NumGet(pt,0,"uint"), y:=NumGet(pt,4,"uint")
  }
  Loop 4
  {
    this.RangeTip(x-w, y-h, 2*w+1, 2*h+1, (A_Index & 1 ? "Red":"Blue"), d)
    Sleep 500
  }
  this.RangeTip()
}

; Shows a range of the borders, similar to the ToolTip

RangeTip(x:="", y:="", w:="", h:="", color:="Red", d:=3)
{
  local
  static id:=""
  ListLines % (lls:=A_ListLines)?0:0
  if (x="")
  {
    id:=0
    Loop 4
      Gui, Range_%A_Index%: Destroy
    ListLines % lls
    return
  }
  if (!id)
  {
    Loop 4
      Gui, Range_%A_Index%: +Hwndid +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
  }
  x:=Floor(x), y:=Floor(y), w:=Floor(w), h:=Floor(h), d:=Floor(d)
  Loop 4
  {
    i:=A_Index
    , x1:=(i=2 ? x+w : x-d)
    , y1:=(i=3 ? y+h : y-d)
    , w1:=(i=1 || i=3 ? w+2*d : d)
    , h1:=(i=2 || i=4 ? h+2*d : d)
    Gui, Range_%i%: Color, %color%
    Gui, Range_%i%: Show, NA x%x1% y%y1% w%w1% h%h1%
  }
  ListLines % lls
}

; Use RButton to select the screen range

GetRange(ww:=25, hh:=8, key:="RButton")
{
  local
  static Gui_Off:="", hk
  if (!Gui_Off)
    Gui_Off:=this.GetRange.Bind(this, "Off")
  if (ww="Off")
    return hk:=Trim(A_ThisHotkey, "*")
  ;---------------------
  Gui, FindText_HotkeyIf: Destroy
  Gui, FindText_HotkeyIf: -Caption +ToolWindow +E0x80000
  Gui, FindText_HotkeyIf: Show, NA x0 y0 w0 h0, FindText_HotkeyIf
  ;---------------------
  Hotkey, IfWinExist, FindText_HotkeyIf
  keys:=key "|Up|Down|Left|Right"
  For k,v in StrSplit(keys, "|")
  {
    KeyWait, %v%
    Hotkey, *%v%, %Gui_Off%, On UseErrorLevel
  }
  KeyWait, Ctrl
  Hotkey, IfWinExist
  ;---------------------
  Critical % (cri:=A_IsCritical)?"Off":"Off"
  CoordMode, Mouse
  tip:=this.Lang("s5")
  hk:="", oldx:=oldy:="", keydown:=0
  Loop
  {
    Sleep 50
    MouseGetPos, x, y
    if (hk=key) || GetKeyState(key,"P") || GetKeyState("Ctrl","P")
    {
      keydown++
      if (keydown=1)
        MouseGetPos, x1, y1, Bind_ID
      KeyWait, % key
      KeyWait, Ctrl
      hk:=""
      if (keydown>1)
        Break
    }
    else if (hk="Up") || GetKeyState("Up","P")
      (hh>1 && hh--), hk:=""
    else if (hk="Down") || GetKeyState("Down","P")
      hh++, hk:=""
    else if (hk="Left") || GetKeyState("Left","P")
      (ww>1 && ww--), hk:=""
    else if (hk="Right") || GetKeyState("Right","P")
      ww++, hk:=""
    this.RangeTip((keydown?x1:x)-ww, (keydown?y1:y)-hh
      , 2*ww+1, 2*hh+1, (A_MSec<500?"Red":"Blue"))
    if (oldx=x && oldy=y)
      Continue
    oldx:=x, oldy:=y
    ToolTip % "x: " (keydown?x1:x) " y: " (keydown?y1:y) "`n" tip
  }
  ToolTip
  this.RangeTip()
  Hotkey, IfWinExist, FindText_HotkeyIf
  For k,v in StrSplit(keys, "|")
    Hotkey, *%v%, %Gui_Off%, Off UseErrorLevel
  Hotkey, IfWinExist
  Gui, FindText_HotkeyIf: Destroy
  Critical %cri%
  return [x1-ww, y1-hh, x1+ww, y1+hh, Bind_ID]
}

; Take a screenshot to Clipboard or File, or only get Range

SnapShot(ScreenShot:=1, key:="LButton")
{
  local
  static Gui_Off:="", hk
  if (!Gui_Off)
    Gui_Off:=this.SnapShot.Bind(this, "Off")
  if (ScreenShot="Off")
    return hk:=Trim(A_ThisHotkey, "*")
  n:=150000, x:=y:=-n, w:=h:=2*n
  hBM:=this.BitmapFromScreen(x,y,w,h,ScreenShot,zx,zy,zw,zh)
  ;---------------
  Gui, SnapShot_HotkeyIf: Destroy
  Gui, SnapShot_HotkeyIf: +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
  Gui, SnapShot_HotkeyIf: Margin, 0, 0
  Gui, SnapShot_HotkeyIf: Add, Pic, w%zw% h%zh%, % "HBITMAP:*" hBM
  Gui, SnapShot_HotkeyIf: Show, NA x%zx% y%zy% w%zw% h%zh%, SnapShot_HotkeyIf
  ;---------------
  Gui, SnapShot_Box: Destroy
  Gui, SnapShot_Box: +Hwndbox_id +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
  Gui, SnapShot_Box: Margin, 0, 0
  Gui, SnapShot_Box: Font, s12
  For k,v in StrSplit(this.Lang("s15"), "|")
    Gui, SnapShot_Box: Add, Button, % (k=1 ? "":"x+0") " Hwndid", %v%
  GuiControlGet, p, SnapShot_Box: Pos, %id%
  box_w:=pX+pW+10, box_h:=pH+10
  Gui, SnapShot_Box: Show, Hide, SnapShot_Box
  ;---------------
  Hotkey, IfWinExist, SnapShot_HotkeyIf
  keys:=key "|RButton|Esc|Up|Down|Left|Right"
  For k,v in StrSplit(keys, "|")
  {
    KeyWait, %v%
    Hotkey, *%v%, %Gui_Off%, On UseErrorLevel
  }
  Hotkey, IfWinExist
  ;---------------
  Critical % (cri:=A_IsCritical)?"Off":"Off"
  CoordMode, Mouse
  Loop
  {  ;// For ReTry
  tip:=this.Lang("s16")
  hk:="", oldx:=oldy:="", ok:=0, d:=10, oldt:=0, oldf:=""
  x:=y:=w:=h:=0
  Loop
  {
    Sleep 50
    if (hk="RButton") || (hk="Esc") || GetKeyState("RButton","P") || GetKeyState("Esc","P")
      Break 2
    MouseGetPos, x1, y1
    if (oldx=x1 && oldy=y1)
      Continue
    oldx:=x1, oldy:=y1
    ToolTip % "x: " x1 " y: " y1 " w: 0 h: 0`n" tip
  }
  Until (hk=key) || GetKeyState(key,"P")
  Loop
  {
    Sleep 50
    MouseGetPos, x2, y2
    x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x1-x2)+1, h:=Abs(y1-y2)+1
    this.RangeTip(x, y, w, h, (A_MSec<500 ? "Red":"Blue"))
    if (oldx=x2 && oldy=y2)
      Continue
    oldx:=x2, oldy:=y2
    ToolTip % "x: " x " y: " y " w: " w " h: " h "`n" tip
  }
  Until !GetKeyState(key,"P")
  hk:=""
  Loop
  {
    Sleep 50
    MouseGetPos, x3, y3
    x1:=x, y1:=y, x2:=x+w-1, y2:=y+h-1
    , d1:=Abs(x3-x1)<=d, d2:=Abs(x3-x2)<=d
    , d3:=Abs(y3-y1)<=d, d4:=Abs(y3-y2)<=d
    , d5:=x3>x1+d && x3<x2-d, d6:=y3>y1+d && y3<y2-d
    , f:=(d1 && d3 ? 1 : d2 && d3 ? 2 : d1 && d4 ? 3
    : d2 && d4 ? 4 : d5 && d3 ? 5 : d5 && d4 ? 6
    : d6 && d1 ? 7 : d6 && d2 ? 8 : d5 && d6 ? 9 : 0)
    if (oldf!=f)
      oldf:=f, this.SetCursor(f=1 || f=4 ? "SIZENWSE"
      : f=2 || f=3 ? "SIZENESW" : f=5 || f=6 ? "SIZENS"
      : f=7 || f=8 ? "SIZEWE" : f=9 ? "SIZEALL" : "ARROW")
    ;--------------
    if (hk="Up") || GetKeyState("Up","P")
      hk:="", y--
    else if (hk="Down") || GetKeyState("Down","P")
      hk:="", y++
    else if (hk="Left") || GetKeyState("Left","P")
      hk:="", x--
    else if (hk="Right") || GetKeyState("Right","P")
      hk:="", x++
    else if (hk="RButton") || (hk="Esc") || GetKeyState("RButton","P") || GetKeyState("Esc","P")
      Break
    else if (hk=key) || GetKeyState(key,"P")
    {
      MouseGetPos,,, id, mc
      if (id=box_id) && (mc="Button1")
      {
        KeyWait, % key
        this.RangeTip(), this.SetCursor()
        Gui, SnapShot_Box: Hide
        Continue 2
      }
      if (id=box_id) && (ok:=mc="Button2" ? 2 : mc="Button4" ? 1:100)
        Break
      Gui, SnapShot_Box: Hide
      ToolTip
      Loop
      {
        Sleep 50
        MouseGetPos, x4, y4
        x1:=x, y1:=y, x2:=x+w-1, y2:=y+h-1, dx:=x4-x3, dy:=y4-y3
        , (f=1 ? (x1+=dx, y1+=dy) : f=2 ? (x2+=dx, y1+=dy)
        : f=3 ? (x1+=dx, y2+=dy) : f=4 ? (x2+=dx, y2+=dy)
        : f=5 ? y1+=dy : f=6 ? y2+=dy : f=7 ? x1+=dx : f=8 ? x2+=dx
        : f=9 ? (x1+=dx, y1+=dy, x2+=dx, y2+=dy) : 0)
        , (f ? this.RangeTip(Min(x1,x2), Min(y1,y2), Abs(x1-x2)+1, Abs(y1-y2)+1
        , (A_MSec<500 ? "Red":"Blue")) : 0)
      }
      Until !GetKeyState(key,"P")
      hk:="", x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x1-x2)+1, h:=Abs(y1-y2)+1
      if (f=9) && Abs(dx)<2 && Abs(dy)<2 && (ok:=(-oldt)+(oldt:=A_TickCount)<400)
        Break
    }
    this.RangeTip(x, y, w, h, (A_MSec<500 ? "Red":"Blue"))
    x1:=x+w-box_w, (x1<10 && x1:=10), (x1>zx+zw-box_w && x1:=zx+zw-box_w)
    , y1:=y+h+10, (y1>zy+zh-box_h && y1:=y-box_h), (y1<10 && y1:=10)
    Gui, SnapShot_Box: Show, NA x%x1% y%y1%
    ;-------------
    if (oldx=x3 && oldy=y3)
      Continue
    oldx:=x3, oldy:=y3
    ToolTip % "x: " x " y: " y " w: " w " h: " h "`n" tip
  }
  Break
  }  ;// For ReTry
  Hotkey, IfWinExist, SnapShot_HotkeyIf
  For k,v in StrSplit(keys, "|")
  {
    KeyWait, %v%
    Hotkey, *%v%, %Gui_Off%, Off UseErrorLevel
  }
  Hotkey, IfWinExist
  ToolTip
  this.RangeTip()
  this.SetCursor()
  Gui, SnapShot_Box: Destroy
  Gui, SnapShot_HotkeyIf: Destroy
  Critical %cri%
  ;---------------
  w:=Min(x+w,zx+zw), x:=Max(x,zx), w-=x
  h:=Min(y+h,zy+zh), y:=Max(y,zy), h-=y
  if (ok=1)
    this.SaveBitmapToFile(0, hBM, x-zx, y-zy, w, h)
  else if (ok=2)
  {
    FileSelectFile, f, S18, %A_Desktop%\1.bmp, SaveAs, Image (*.bmp)
    this.SaveBitmapToFile(f, hBM, x-zx, y-zy, w, h)
  }
  DllCall("DeleteObject", "Ptr",hBM)
  return [x, y, x+w-1, y+h-1]
}

SetCursor(cursor:="", args*)
{
  local
  static init, tab
  if (!VarSetCapacity(init) && init:="1")
  {
    tab:=[], OnExit(this.SetCursor.Bind(this,"")), this.SetCursor()
    s:="ARROW,32512, SIZENWSE,32642, SIZENESW,32643"
      . ", SIZEWE,32644, SIZENS,32645, SIZEALL,32646"
      . ", IBEAM,32513, WAIT,32514, CROSS,32515, UPARROW,32516"
      . ", NO,32648, HAND,32649, APPSTARTING,32650, HELP,32651"
    For i,v in StrSplit(s, ",", " ")
      (i&1) ? (k:=v) : (tab[k]:=DllCall("CopyImage", "Ptr"
      , DllCall("LoadCursor", "Ptr",0, "Ptr",v, "Ptr")
      , "int",2, "int",0, "int",0, "int",0, "Ptr"))
  }
  if (cursor!="") && tab.HasKey(cursor)
    DllCall("SetSystemCursor", "Ptr", DllCall("CopyImage", "Ptr",tab[cursor]
    , "int",2, "int",0, "int",0, "int",0, "Ptr"), "int",32512)
  else
    DllCall("SystemParametersInfo", "int",0x57, "int",0, "Ptr",0, "int",0)
}

BitmapFromScreen(ByRef x:=0, ByRef y:=0, ByRef w:=0, ByRef h:=0
  , ScreenShot:=1, ByRef zx:=0, ByRef zy:=0, ByRef zw:=0, ByRef zh:=0)
{
  local
  bits:=this.GetBitsFromScreen(x,y,w,h,ScreenShot,zx,zy,zw,zh)
  if (w<1 || h<1 || !bits.hBM)
    return
  hBM:=this.CreateDIBSection(w, h)
  this.CopyHBM(hBM, 0, 0, bits.hBM, x-zx, y-zy, w, h, 1)
  return hBM
}

; Quickly save screen image to BMP file for debugging
; if file = 0 or "", save to Clipboard

SavePic(file:=0, x1:=0, y1:=0, x2:=0, y2:=0, ScreenShot:=1)
{
  local
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
    n:=150000, x:=y:=-n, w:=h:=2*n
  else
    x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  hBM:=this.BitmapFromScreen(x, y, w, h, ScreenShot)
  this.SaveBitmapToFile(file, hBM)
  DllCall("DeleteObject", "Ptr",hBM)
}

; Save Bitmap To File, if file = 0 or "", save to Clipboard
; hBM_or_file can be a bitmap handle or file path, eg: "c:\1.bmp"

SaveBitmapToFile(file, hBM_or_file, x:=0, y:=0, w:=0, h:=0)
{
  local
  if hBM_or_file is Number
    hBM_or_file:="HBITMAP:*" hBM_or_file
  if !hBM:=DllCall("CopyImage", "Ptr",LoadPicture(hBM_or_file)
  , "int",0, "int",0, "int",0, "uint",0x2008)
    return
  if (file) || (w!=0 && h!=0)
  {
    (w=0 || h=0) && this.GetBitmapWH(hBM, w, h)
    hBM2:=this.CreateDIBSection(w, -h, bpp:=(file ? 24 : 32))
    this.CopyHBM(hBM2, 0, 0, hBM, x, y, w, h)
    DllCall("DeleteObject", "Ptr",hBM), hBM:=hBM2
  }
  VarSetCapacity(dib, dib_size:=(A_PtrSize=8 ? 104:84))
  , DllCall("GetObject", "Ptr",hBM, "int",dib_size, "Ptr",&dib)
  , pbi:=&dib+(bitmap_size:=A_PtrSize=8 ? 32:24)
  , size:=NumGet(pbi+20, "uint"), pBits:=NumGet(pbi-A_PtrSize, "Ptr")
  if (!file)
  {
    hdib:=DllCall("GlobalAlloc", "uint",2, "Ptr",40+size, "Ptr")
    pdib:=DllCall("GlobalLock", "Ptr",hdib, "Ptr")
    DllCall("RtlMoveMemory", "Ptr",pdib, "Ptr",pbi, "Ptr",40)
    DllCall("RtlMoveMemory", "Ptr",pdib+40, "Ptr",pBits, "Ptr",size)
    DllCall("GlobalUnlock", "Ptr",hdib)
    DllCall("OpenClipboard", "Ptr",0)
    DllCall("EmptyClipboard")
    if !DllCall("SetClipboardData", "uint",8, "Ptr",hdib)
      DllCall("GlobalFree", "Ptr",hdib)
    DllCall("CloseClipboard")
  }
  else
  {
    if InStr(file,"\") && !FileExist(dir:=RegExReplace(file,"[^\\]*$"))
      Try FileCreateDir, % dir
    VarSetCapacity(bf, 14, 0), NumPut(0x4D42, bf, "short")
    NumPut(54+size, bf, 2, "uint"), NumPut(54, bf, 10, "uint")
    f:=FileOpen(file, "w"), f.RawWrite(bf, 14)
    , f.RawWrite(pbi+0, 40), f.RawWrite(pBits+0, size), f.Close()
  }
  DllCall("DeleteObject", "Ptr",hBM)
}

; Show the saved Picture file

ShowPic(file:="", show:=1, ByRef x:="", ByRef y:="", ByRef w:="", ByRef h:="")
{
  local
  if (file="")
  {
    this.ShowScreenShot()
    return
  }
  if !(hBM:=LoadPicture(file))
    return
  this.GetBitmapWH(hBM, w, h)
  this.GetBitsFromScreen(,,,,0,x,y)
  bits:=this.GetBitsFromScreen(x,y,w,h,0)
  this.CopyHBM(bits.hBM, 0, 0, hBM, 0, 0, w, h)
  DllCall("DeleteObject", "Ptr",hBM)
  if (show)
    this.ShowScreenShot(x, y, x+w-1, y+h-1, 0)
}

; Show the memory Screenshot for debugging

ShowScreenShot(x1:=0, y1:=0, x2:=0, y2:=0, ScreenShot:=1)
{
  local
  static hPic, oldx, oldy, oldw, oldh
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
  {
    Gui, FindText_Screen: Destroy
    return
  }
  x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  if !hBM:=this.BitmapFromScreen(x,y,w,h,ScreenShot)
    return
  ;---------------
  Gui, FindText_Screen: +LastFoundExist
  IfWinNotExist
  {
    ; WS_EX_NOACTIVATE:=0x08000000
    Gui, FindText_Screen: +AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000
    Gui, FindText_Screen: Margin, 0, 0
    Gui, FindText_Screen: Add, Pic, HwndhPic w%w% h%h%
    Gui, FindText_Screen: Show, NA x%x% y%y% w%w% h%h%, Show Pic
    oldx:=x, oldy:=y, oldw:=w, oldh:=h
  }
  else if (oldx!=x || oldy!=y || oldw!=w || oldh!=h)
  {
    if (oldw!=w || oldh!=h)
      GuiControl, FindText_Screen: Move, %hPic%, w%w% h%h%
    Gui, FindText_Screen: Show, NA x%x% y%y% w%w% h%h%
    oldx:=x, oldy:=y, oldw:=w, oldh:=h
  }
  this.BitmapToWindow(hPic, 0, 0, hBM, 0, 0, w, h)
  DllCall("DeleteObject", "Ptr",hBM)
}

BitmapToWindow(hwnd, x1, y1, hBM, x2, y2, w, h)
{
  local
  mDC:=DllCall("CreateCompatibleDC", "Ptr",0, "Ptr")
  oBM:=DllCall("SelectObject", "Ptr",mDC, "Ptr",hBM, "Ptr")
  hDC:=DllCall("GetDC", "Ptr",hwnd, "Ptr")
  DllCall("BitBlt", "Ptr",hDC, "int",x1, "int",y1, "int",w, "int",h
    , "Ptr",mDC, "int",x2, "int",y2, "uint",0xCC0020)
  DllCall("ReleaseDC", "Ptr",hwnd, "Ptr",hDC)
  DllCall("SelectObject", "Ptr",mDC, "Ptr",oBM)
  DllCall("DeleteDC", "Ptr",mDC)
}

; Quickly get the search data of screen image

GetTextFromScreen(x1, y1, x2, y2, Threshold:=""
  , ScreenShot:=1, ByRef rx:="", ByRef ry:="", cut:=1)
{
  local
  SetBatchLines, % (bch:=A_BatchLines)?"-1":"-1"
  x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  bits:=this.GetBitsFromScreen(x,y,w,h,ScreenShot,zx,zy)
  Scan0:=bits.Scan0, Stride:=bits.Stride
  if (w<1 || h<1 || !Scan0)
  {
    SetBatchLines, %bch%
    return
  }
  ListLines % (lls:=A_ListLines)?0:0
  gray:=[]
  Loop % h + 0*(j:=y-zy-1)*(k:=0)
  Loop % w + 0*(i:=x-zx-1)*(j++)
    c:=NumGet(Scan0+j*Stride+(++i)*4,"uint")
    , gray[++k]:=(((c>>16)&0xFF)*38+((c>>8)&0xFF)*75+(c&0xFF)*15)>>7
  if InStr(Threshold,"**")
  {
    Threshold:=StrReplace(Threshold,"*")
    if (Threshold="")
      Threshold:=50
    s:="", sw:=w, w-=2, h-=2, x++, y++
    Loop % h + 0*(y1:=0)
    Loop % w + 0*(y1++)
      i:=y1*sw+A_Index+1, j:=gray[i]+Threshold
      , s.=( gray[i-1]>j || gray[i+1]>j
      || gray[i-sw]>j || gray[i+sw]>j
      || gray[i-sw-1]>j || gray[i-sw+1]>j
      || gray[i+sw-1]>j || gray[i+sw+1]>j ) ? "1":"0"
    Threshold:="**" Threshold
  }
  else
  {
    Threshold:=StrReplace(Threshold,"*")
    if (Threshold="")
    {
      pp:=[]
      Loop 256
        pp[A_Index-1]:=0
      Loop % w*h
        pp[gray[A_Index]]++
      IP0:=IS0:=0
      Loop 256
        k:=A_Index-1, IP0+=k*pp[k], IS0+=pp[k]
      Threshold:=Floor(IP0/IS0)
      Loop 20
      {
        LastThreshold:=Threshold
        IP1:=IS1:=0
        Loop % LastThreshold+1
          k:=A_Index-1, IP1+=k*pp[k], IS1+=pp[k]
        IP2:=IP0-IP1, IS2:=IS0-IS1
        if (IS1!=0 && IS2!=0)
          Threshold:=Floor((IP1/IS1+IP2/IS2)/2)
        if (Threshold=LastThreshold)
          Break
      }
    }
    s:=""
    Loop % w*h
      s.=gray[A_Index]<=Threshold ? "1":"0"
    Threshold:="*" Threshold
  }
  ListLines % lls
  ;--------------------
  w:=Format("{:d}",w), CutUp:=CutDown:=0
  if (cut=1)
  {
    re1:="(^0{" w "}|^1{" w "})"
    re2:="(0{" w "}$|1{" w "}$)"
    While (s~=re1)
      s:=RegExReplace(s,re1), CutUp++
    While (s~=re2)
      s:=RegExReplace(s,re2), CutDown++
  }
  rx:=x+w//2, ry:=y+CutUp+(h-CutUp-CutDown)//2
  s:="|<>" Threshold "$" w "." this.bit2base64(s)
  ;--------------------
  SetBatchLines, %bch%
  return s
}

; Wait for the screen image to change within a few seconds
; Take a Screenshot before using it: FindText().ScreenShot()

WaitChange(time:=-1, x1:=0, y1:=0, x2:=0, y2:=0)
{
  local
  hash:=this.GetPicHash(x1, y1, x2, y2, 0)
  timeout:=A_TickCount+Round(time*1000)
  Loop
  {
    if (hash!=this.GetPicHash(x1, y1, x2, y2, 1))
      return 1
    if (time>=0 && A_TickCount>=timeout)
      Break
    Sleep 10
  }
  return 0
}

; Wait for the screen image to stabilize

WaitNotChange(time:=1, timeout:=30, x1:=0, y1:=0, x2:=0, y2:=0)
{
  local
  oldhash:="", timeout:=A_TickCount+Round(timeout*1000)
  Loop
  {
    hash:=this.GetPicHash(x1, y1, x2, y2, 1), t:=A_TickCount
    if (hash!=oldhash)
      oldhash:=hash, timeout2:=t+Round(time*1000)
    if (t>=timeout2)
      return 1
    if (t>=timeout)
      return 0
    Sleep 10
  }
}

GetPicHash(x1:=0, y1:=0, x2:=0, y2:=0, ScreenShot:=1)
{
  local
  static init:=DllCall("LoadLibrary", "Str","ntdll", "Ptr")
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
    n:=150000, x:=y:=-n, w:=h:=2*n
  else
    x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  bits:=this.GetBitsFromScreen(x,y,w,h,ScreenShot,zx,zy), x-=zx, y-=zy
  if (w<1 || h<1 || !bits.Scan0)
    return 0
  hash:=0, Stride:=bits.Stride, p:=bits.Scan0+(y-1)*Stride+x*4, w*=4
  ListLines % (lls:=A_ListLines)?0:0
  Loop % h
    hash:=(hash*31+DllCall("ntdll\RtlComputeCrc32", "uint",0
      , "Ptr",p+=Stride, "uint",w, "uint"))&0xFFFFFFFF
  ListLines % lls
  return hash
}

WindowToScreen(ByRef x, ByRef y, x1, y1, id:="")
{
  local
  if (!id)
    WinGet, id, ID, A
  VarSetCapacity(rect, 16, 0)
  , DllCall("GetWindowRect", "Ptr",id, "Ptr",&rect)
  , x:=x1+NumGet(rect,"int"), y:=y1+NumGet(rect,4,"int")
}

ScreenToWindow(ByRef x, ByRef y, x1, y1, id:="")
{
  local
  this.WindowToScreen(dx, dy, 0, 0, id), x:=x1-dx, y:=y1-dy
}

ClientToScreen(ByRef x, ByRef y, x1, y1, id:="")
{
  local
  if (!id)
    WinGet, id, ID, A
  VarSetCapacity(pt, 8, 0), NumPut(0, pt, "int64")
  , DllCall("ClientToScreen", "Ptr",id, "Ptr",&pt)
  , x:=x1+NumGet(pt,"int"), y:=y1+NumGet(pt,4,"int")
}

ScreenToClient(ByRef x, ByRef y, x1, y1, id:="")
{
  local
  this.ClientToScreen(dx, dy, 0, 0, id), x:=x1-dx, y:=y1-dy
}

; It is not like FindText always use Screen Coordinates,
; But like built-in command ImageSearch using CoordMode Settings
; ImageFile can use "*n *TransBlack-White-RRGGBB... d:\a.bmp"

ImageSearch(ByRef rx:="", ByRef ry:="", x1:=0, y1:=0, x2:=0, y2:=0
  , ImageFile:="", ScreenShot:=1, FindAll:=0)
{
  local
  dx:=dy:=0
  if (A_CoordModePixel="Window")
    this.WindowToScreen(dx, dy, 0, 0)
  else if (A_CoordModePixel="Client")
    this.ClientToScreen(dx, dy, 0, 0)
  text:=""
  Loop Parse, ImageFile, |
  if (v:=Trim(A_LoopField))!=""
  {
    text.=InStr(v,"$") ? "|" v : "|##"
    . (RegExMatch(v, "O)(^|\s)\*(\d+)\s", r)
    ? Format("{:06X}", r[2]<<16|r[2]<<8|r[2]) : "000000")
    . (RegExMatch(v, "Oi)(^|\s)\*Trans([\-\w]+)\s", r)
    ? "-" . Trim(r[2],"-") : "") . "$"
    . Trim(RegExReplace(v, "(?<=^|\s)\*\S+"))
  }
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
    n:=150000, x1:=y1:=-n, x2:=y2:=n
  if (ok:=this.FindText(,, x1+dx, y1+dy, x2+dx, y2+dy
    , 0, 0, text, ScreenShot, FindAll))
  {
    For k,v in ok  ; you can use ok:=FindText().ok
      v.1-=dx, v.2-=dy, v.x-=dx, v.y-=dy
    rx:=ok[1].1, ry:=ok[1].2, ErrorLevel:=0
    return ok
  }
  else
  {
    rx:=ry:="", ErrorLevel:=1
    return 0
  }
}

; It is not like FindText always use Screen Coordinates,
; But like built-in command PixelSearch using CoordMode Settings
; ColorID can use "RRGGBB-DRDGDB|RRGGBB-DRDGDB", Variation in 0-255

PixelSearch(ByRef rx:="", ByRef ry:="", x1:=0, y1:=0, x2:=0, y2:=0
  , ColorID:="", Variation:=0, ScreenShot:=1, FindAll:=0)
{
  local
  n:=Floor(Variation), text:=Format("##{:06X}$0/0", n<<16|n<<8|n)
  Loop Parse, ColorID, |
  if (v:=Trim(A_LoopField))!=""
    text.="/" v
  return this.ImageSearch(rx, ry, x1, y1, x2, y2, text, ScreenShot, FindAll)
}

; Pixel count of certain colors within the range indicated by Screen Coordinates
; ColorID can use "RRGGBB-DRDGDB|RRGGBB-DRDGDB", Variation in 0-255

PixelCount(x1:=0, y1:=0, x2:=0, y2:=0, ColorID:="", Variation:=0, ScreenShot:=1)
{
  local
  SetBatchLines, % (bch:=A_BatchLines)?"-1":"-1"
  x1:=Floor(x1), y1:=Floor(y1), x2:=Floor(x2), y2:=Floor(y2)
  if (x1=0 && y1=0 && x2=0 && y2=0)
    n:=150000, x:=y:=-n, w:=h:=2*n
  else
    x:=Min(x1,x2), y:=Min(y1,y2), w:=Abs(x2-x1)+1, h:=Abs(y2-y1)+1
  bits:=this.GetBitsFromScreen(x,y,w,h,ScreenShot,zx,zy), x-=zx, y-=zy
  sum:=0, VarSetCapacity(s1,4), VarSetCapacity(s0,4)
  , ini:={ bits:bits, ss:0, s1:&s1, s0:&s0
  , err1:0, err0:0, allpos_max:0, zoomW:1, zoomH:1 }
  , n:=Floor(Variation), text:=Format("##{:06X}$0/0", n<<16|n<<8|n)
  Loop Parse, ColorID, |
  if (v:=Trim(A_LoopField))!=""
    text.="/" v
  if (w>0 && h>0 && bits.Scan0) && IsObject(j:=this.PicInfo(text))
    sum:=this.PicFind(ini, j, 1, x, y, w, h, 0)
  SetBatchLines, %bch%
  return sum
}

Click(x:="", y:="", other1:="", other2:="", GoBack:=0)
{
  local
  CoordMode, Mouse, % (bak:=A_CoordModeMouse)?"Screen":"Screen"
  if GoBack
    MouseGetPos, oldx, oldy
  MouseMove, x, y, 0
  Click % x "," y "," other1 "," other2
  if GoBack
    MouseMove, oldx, oldy, 0
  CoordMode, Mouse, %bak%
}

; Using ControlClick instead of Click, Use Screen Coordinates,
; If you want to click on the background window, please provide hwnd

ControlClick(x, y, WhichButton:="", ClickCount:=1, Opt:="", hwnd:="")
{
  local
  if !hwnd
    hwnd:=DllCall("WindowFromPoint", "int64",y<<32|x&0xFFFFFFFF, "Ptr")
  VarSetCapacity(pt,8,0), ScreenX:=x, ScreenY:=y
  Loop
  {
    NumPut(0,pt,"int64"), DllCall("ClientToScreen", "Ptr",hwnd, "Ptr",&pt)
    , x:=ScreenX-NumGet(pt,"int"), y:=ScreenY-NumGet(pt,4,"int")
    , id:=DllCall("ChildWindowFromPoint", "Ptr",hwnd, "int64",y<<32|x, "Ptr")
    if (!id || id=hwnd)
      Break
    else hwnd:=id
  }
  DetectHiddenWindows, % (bak:=A_DetectHiddenWindows)?1:1
  PostMessage, 0x200, 0, y<<16|x,, ahk_id %hwnd%  ; WM_MOUSEMOVE
  SetControlDelay -1
  ControlClick, x%x% y%y%, ahk_id %hwnd%,, %WhichButton%, %ClickCount%, NA Pos %Opt%
  DetectHiddenWindows, % bak
}

; Running AHK code dynamically with new threads

Class Thread
{
  __New(args*)
  {
    this.pid:=this.Exec(args*)
  }
  __Delete()
  {
    Process, Close, % this.pid
  }
  Exec(s, Ahk:="", args:="")    ; required AHK v1.1.34+
  {
    local
    Ahk:=Ahk ? Ahk : A_IsCompiled ? A_ScriptFullPath : A_AhkPath
    s:="`nDllCall(""SetWindowText"",""Ptr"",A_ScriptHwnd,""Str"",""<AHK>"")`n"
      . "`nSetBatchLines,-1`n" . s, s:=RegExReplace(s, "\R", "`r`n")
    Try
    {
      shell:=ComObjCreate("WScript.Shell")
      oExec:=shell.Exec("""" Ahk """ /script /force /CP0 * " args)
      oExec.StdIn.Write(s)
      oExec.StdIn.Close(), pid:=oExec.ProcessID
    }
    Catch
    {
      f:=A_Temp "\~ahk.tmp"
      s:="`r`nTry FileDelete " f "`r`n" s
      Try FileDelete %f%
      FileAppend %s%, %f%
      r:=this.Clear.Bind(this)
      SetTimer %r%, -3000
      Run "%Ahk%" /script /force /CP0 "%f%" %args%,, UseErrorLevel, pid
    }
    return pid
  }
  Clear()
  {
    Try FileDelete % A_Temp "\~ahk.tmp"
    SetTimer,, Off
  }
}

; FindText().QPC() Use the same as A_TickCount

QPC()
{
  static f:="", c
  (!f) && DllCall("QueryPerformanceFrequency","Int*",f)+(f/=1000)
  return (!DllCall("QueryPerformanceCounter","Int64*",c))*0+(c/f)
}

; FindText().ToolTip() Use the same as ToolTip

ToolTip(s:="", x:="", y:="", num:=1, arg:="")
{
  local
  static init, ini, timer
  if (!VarSetCapacity(init) && init:="1")
    ini:=[], timer:=[]
  f:="ToolTip_" . Floor(num)
  if (s="")
  {
    ini[f]:=""
    Gui, %f%: Destroy
    return
  }
  ;-----------------
  r1:=A_CoordModeToolTip
  r2:=A_CoordModeMouse
  CoordMode Mouse, Screen
  MouseGetPos x1, y1
  CoordMode Mouse, %r1%
  MouseGetPos x2, y2
  CoordMode Mouse, %r2%
  (x!="" && x:="x" (Floor(x)+x1-x2))
  , (y!="" && y:="y" (Floor(y)+y1-y2))
  , (x="" && y="" && x:="x" (x1+16) " y" (y1+16))
  ;-----------------
  bgcolor:=arg.bgcolor!="" ? arg.bgcolor : "FAFBFC"
  color:=arg.color!="" ? arg.color : "Black"
  font:=arg.font ? arg.font : "Consolas"
  size:=arg.size ? arg.size : "10"
  bold:=arg.bold ? arg.bold : ""
  trans:=arg.trans!="" ? arg.trans & 255 : 255
  timeout:=arg.timeout!="" ? arg.timeout : ""
  ;-----------------
  r:=bgcolor "|" color "|" font "|" size "|" bold "|" trans "|" s
  if (!ini.HasKey(f) || ini[f]!=r)
  {
    ini[f]:=r
    Gui, %f%: Destroy  ; WS_EX_LAYERED:=0x80000, WS_EX_TRANSPARENT:=0x20
    Gui, %f%: +AlwaysOnTop -Caption +ToolWindow -DPIScale +Hwndid +E0x80020
    Gui, %f%: Margin, 2, 2
    Gui, %f%: Color, %bgcolor%
    Gui, %f%: Font, c%color% s%size% %bold%, %font%
    Gui, %f%: Add, Text,, %s%
    Gui, %f%: Show, Hide, %f%
    ;------------------
    DetectHiddenWindows, % (bak:=A_DetectHiddenWindows)?1:1
    WinSet, Transparent, %trans%, ahk_id %id%
    DetectHiddenWindows, % bak
  }
  Gui, %f%: +AlwaysOnTop
  Gui, %f%: Show, % "NA " x " " y
  if (timeout)
  {
    (!timer.HasKey(f) && timer[f]:=this.ToolTip.Bind(this,"","","",num))
    , r:=timer[f]
    SetTimer, %r%, % -Round(Abs(timeout*1000))-1
  }
}

; FindText().ObjView()  view object values for Debug

ObjView(obj, keyname:="")
{
  local
  if IsObject(obj)  ; thanks lexikos's type(v)
  {
    s:=""
    For k,v in obj
      s.=this.ObjView(v, keyname "[" (StrLen(k)>1000
      || [k].GetCapacity(1) ? """" k """":k) "]")
  }
  else
    s:=keyname ": " (StrLen(obj)>1000
    || [obj].GetCapacity(1) ? """" obj """":obj) "`n"
  if (keyname!="")
    return s
  ;------------------
  Gui, Gui_DeBug: Destroy
  Gui, Gui_DeBug: +LastFound +AlwaysOnTop
  Gui, Gui_DeBug: Add, Button, y270 w350 gCancel Default, OK
  Gui, Gui_DeBug: Add, Edit, xp y10 w350 h250 -Wrap -WantReturn
  GuiControl, Gui_DeBug:, Edit1, %s%
  Gui, Gui_DeBug: Show,, Debug view object values
  DetectHiddenWindows, 0
  WinWaitClose, % "ahk_id " WinExist()
  Gui, Gui_DeBug: Destroy
}

EditScroll(hEdit, regex:="", line:=0, pos:=0)
{
  local
  ControlGetText, s,, ahk_id %hEdit%
  pos:=(regex!="") ? InStr(SubStr(s,1,s~=regex),"`n",0,0)
    : (line>1) ? InStr(s,"`n",0,1,line-1) : pos
  SendMessage, 0xB1, pos, pos,, ahk_id %hEdit%
  SendMessage, 0xB7,,,, ahk_id %hEdit%
}

; Get Script from Compiled programs

GetScript()  ; thanks TAC109
{
  local
  if (!A_IsCompiled)
    return
  For i,ahk in ["#1", ">AUTOHOTKEY SCRIPT<"]
  if (rc:=DllCall("FindResource", "Ptr",0, "Str",ahk, "Ptr",10, "Ptr"))
  && (sz:=DllCall("SizeofResource", "Ptr",0, "Ptr",rc, "Uint"))
  && (pt:=DllCall("LoadResource", "Ptr",0, "Ptr",rc, "Ptr"))
  && (pt:=DllCall("LockResource", "Ptr",pt, "Ptr"))
  && (DllCall("VirtualProtect", "Ptr",pt, "Ptr",sz, "UInt",0x4, "UInt*",0))
  && (InStr(StrGet(pt, 20, "utf-8"), "<COMPILER"))
    return this.FormatScript(StrGet(pt, sz, "utf-8"))
}

FormatScript(s, space:="", tab:="    ")
{
  local
  ListLines % (lls:=A_ListLines)?0:0
  VarSetCapacity(ss, StrLen(s)*2), n:=0, w:=StrLen(tab)
  , space2:=StrReplace(Format("{:020d}",0), "0", tab)
  Loop Parse, s, `n, `r
  {
    v:=Trim(A_LoopField), n2:=n
    if RegExMatch(v, "O)^\s*[{}][\s{}]*|\{\s*$|\{\s+;", r)
      n+=w*(StrLen(RegExReplace(r[0], "[^{]"))
      -StrLen(RegExReplace(r[0], "[^}]"))), n2:=Min(n,n2)
    ss.=Space . SubStr(space2,1,n2) . v . "`r`n"
  }
  ListLines %lls%
  return SubStr(ss,1,-2)
}

; Get Last GuiControl Hwnd from Gui +LastFound

LastCtrl()
{
  local
  WinGet, s, ControlListHwnd
  return SubStr(s, InStr(s,"`n",0,0)+1)
}

; Hide Gui from Gui +LastFound

Hide(id:="")
{
  if (id ? WinExist("ahk_id " id) : WinExist())
  {
    WinMinimize
    WinHide
    ToolTip
    DetectHiddenWindows, 0
    WinWaitClose, % "ahk_id " WinExist()
  }
}


;==== Optional GUI interface ====


Gui(cmd, arg1:="", args*)
{
  local
  static
  local bch, cri, lls
  ListLines, % InStr("MouseMove|ToolTipOff",cmd)?0:A_ListLines
  static init
  if (!VarSetCapacity(init) && init:="1")
  {
    SavePicDir:=A_Temp "\Ahk_ScreenShot\"
    Gui_ := this.Gui.Bind(this)
    Gui_G := this.Gui.Bind(this, "G")
    Gui_Run := this.Gui.Bind(this, "Run")
    Gui_Off := this.Gui.Bind(this, "Off")
    Gui_Show := this.Gui.Bind(this, "Show")
    Gui_KeyDown := this.Gui.Bind(this, "KeyDown")
    Gui_LButtonDown := this.Gui.Bind(this, "LButtonDown")
    Gui_RButtonDown := this.Gui.Bind(this, "RButtonDown")
    Gui_MouseMove := this.Gui.Bind(this, "MouseMove")
    Gui_ScreenShot := this.Gui.Bind(this, "ScreenShot")
    Gui_ShowPic := this.Gui.Bind(this, "ShowPic")
    Gui_Slider := this.Gui.Bind(this, "Slider")
    Gui_ToolTip := this.Gui.Bind(this, "ToolTip")
    Gui_ToolTipOff := this.Gui.Bind(this, "ToolTipOff")
    Gui_SaveScr := this.Gui.Bind(this, "SaveScr")
    bch:=A_BatchLines, cri:=A_IsCritical
    Critical
    #NoEnv
    Lang:=this.Lang(,1), Tip_Text:=this.Lang(,2)
    %Gui_%("MakeCaptureWindow")
    %Gui_%("MakeMainWindow")
    OnMessage(0x100, Gui_KeyDown)
    OnMessage(0x201, Gui_LButtonDown)
    OnMessage(0x204, Gui_RButtonDown)
    OnMessage(0x200, Gui_MouseMove)
    Menu, Tray, Add
    Menu, Tray, Add, % Lang["s1"], %Gui_Show%
    if (!A_IsCompiled && A_LineFile=A_ScriptFullPath)
    {
      Menu, Tray, Default, % Lang["s1"]
      Menu, Tray, Click, 1
      Menu, Tray, Icon, Shell32.dll, 23
    }
    Critical, %cri%
    SetBatchLines, %bch%
    Gui, New, +LastFound
    Gui, Destroy
    ;-------------------
    Pics:=PrevControl:=x:=y:=oldx:=oldy:="", oldt:=0
  }
  Switch cmd
  {
  Case "Off":
    return hk:=Trim(A_ThisHotkey, "*")
  Case "G":
    id:=this.LastCtrl()
    GuiControl, +g, %id%, %Gui_Run%
    return
  Case "Run":
    Critical
    %Gui_%(A_GuiControl)
    return
  Case "Show":
    Gui, FindText_Main: Default
    Gui, Show, % arg1 ? "Center" : ""
    GuiControl, Focus, %hscr%
    return
  Case "Cancel", "Cancel2":
    WinHide
    return
  Case "MakeCaptureWindow":
    WindowColor:="0xDDEEFF"
    Gui, FindText_Capture: New
    Gui, +LastFound +AlwaysOnTop -DPIScale
    Gui, Margin, 15, 15
    Gui, Color, %WindowColor%
    Gui, Font, s12, Verdana
    Gui, Add, Tab3, vMyTab1 -Wrap, % Lang["s18"]
    Gui, Tab, 1
    C_:=[], nW:=71, nH:=25, w:=h:=12, pW:=nW*(w+1)-1, pH:=(nH+1)*(h+1)-1
    Gui, -Theme
    ListLines % (lls:=A_ListLines)?0:0
    Loop % nW*(nH+1)
    {
      i:=A_Index, j:=i=1 ? "Section" : Mod(i,nW)=1 ? "xs y+1":"x+1"
      Gui, Add, Progress, %j% w%w% h%h% Hwndid -E0x20000 Smooth
      C_[i]:=id
    }
    ListLines % lls
    Gui, +Theme
    Gui, Add, Slider, xs w%pW% vMySlider1 +Center Page20 Line10 NoTicks AltSubmit
    %Gui_G%()
    Gui, Add, Slider, ys h%pH% vMySlider2 +Center Page20 Line10 NoTicks AltSubmit +Vertical
    %Gui_G%()
    Gui, Tab, 2
    pW-=120+15
    Gui, Add, Text, w%pW% h%pH% Hwndparent_id +Border Section
    Gui, Add, Slider, xs w%pW% vMySlider3 +Center Page20 Line10 NoTicks AltSubmit
    %Gui_G%()
    Gui, Add, Slider, ys h%pH% vMySlider4 +Center Page20 Line10 NoTicks AltSubmit +Vertical
    %Gui_G%()
    Gui, Add, ListBox, % "ys w120 h200 vSelectBox AltSubmit 0x100"
    %Gui_G%()
    Gui, Add, Button, y+0 wp vClearAll, % Lang["ClearAll"]
    %Gui_G%()
    Gui, Add, Button, y+0 wp vOpenDir, % Lang["OpenDir"]
    %Gui_G%()
    Gui, Add, Button, y+0 wp vLoadPic, % Lang["LoadPic"]
    %Gui_G%()
    Gui, Add, Button, y+0 wp vSavePic, % Lang["SavePic"]
    %Gui_G%()
    Gui, Tab
    MySlider1:=MySlider2:=MySlider3:=MySlider4:=dx:=dy:=0
    ;--------------
    Gui, Add, Button, xm Hidden Section, % Lang["Auto"]
    GuiControlGet, p, Pos, % this.LastCtrl()
    w:=Round(pW*0.75), i:=Round(w*3+15+pW*0.5-w*1.5)
    Gui, Add, Button, xm+%i% yp w%w% hp -Wrap vRepU, % Lang["RepU"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutU, % Lang["CutU"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutU3, % Lang["CutU3"]
    %Gui_G%()
    Gui, Add, Button, xm wp hp -Wrap vRepL, % Lang["RepL"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutL, % Lang["CutL"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutL3, % Lang["CutL3"]
    %Gui_G%()
    Gui, Add, Button, x+15 w%pW% hp -Wrap vAuto, % Lang["Auto"]
    %Gui_G%()
    Gui, Add, Button, x+15 w%w% hp -Wrap vRepR, % Lang["RepR"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutR, % Lang["CutR"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutR3, % Lang["CutR3"]
    %Gui_G%()
    Gui, Add, Button, xm+%i% wp hp -Wrap vRepD, % Lang["RepD"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutD, % Lang["CutD"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp hp -Wrap vCutD3, % Lang["CutD3"]
    %Gui_G%()
    ;--------------
    Gui, Add, Text, x+60 ys+3 Section, % Lang["SelGray"]
    Gui, Add, Edit, x+3 yp-3 w60 vSelGray ReadOnly
    Gui, Add, Text, x+15 ys, % Lang["SelColor"]
    Gui, Add, Edit, x+3 yp-3 w150 vSelColor ReadOnly
    Gui, Add, Text, x+15 ys, % Lang["SelR"]
    Gui, Add, Edit, x+3 yp-3 w60 vSelR ReadOnly
    Gui, Add, Text, x+5 ys, % Lang["SelG"]
    Gui, Add, Edit, x+3 yp-3 w60 vSelG ReadOnly
    Gui, Add, Text, x+5 ys, % Lang["SelB"]
    Gui, Add, Edit, x+3 yp-3 w60 vSelB ReadOnly
    ;--------------
    x:=w*6+pW+15*4
    Gui, Add, Tab3, x%x% y+15 -Wrap, % Lang["s2"]
    Gui, Tab, 1
    Gui, Add, Text, x+15 y+15, % Lang["Threshold"]
    Gui, Add, Edit, x+15 w100 vThreshold
    Gui, Add, Button, x+15 yp-3 vGray2Two, % Lang["Gray2Two"]
    %Gui_G%()
    Gui, Tab, 2
    Gui, Add, Text, x+15 y+15, % Lang["GrayDiff"]
    Gui, Add, Edit, x+15 w100 vGrayDiff, 50
    Gui, Add, Button, x+15 yp-3 vGrayDiff2Two, % Lang["GrayDiff2Two"]
    %Gui_G%()
    Gui, Tab, 3
    Gui, Add, Text, x+15 y+15, % Lang["Similar1"] " 0"
    Gui, Add, Slider, x+0 w120 vSimilar1 +Center Page1 NoTicks ToolTip, 100
    %Gui_G%()
    Gui, Add, Text, x+0, 100
    Gui, Add, Button, x+15 yp-3 vColor2Two, % Lang["Color2Two"]
    %Gui_G%()
    Gui, Tab, 4
    Gui, Add, Text, x+15 y+15, % Lang["Similar2"] " 0"
    Gui, Add, Slider, x+0 w120 vSimilar2 +Center Page1 NoTicks ToolTip, 100
    %Gui_G%()
    Gui, Add, Text, x+0, 100
    Gui, Add, Button, x+15 yp-3 vColorPos2Two, % Lang["ColorPos2Two"]
    %Gui_G%()
    Gui, Tab, 5
    Gui, Add, Text, x+10 y+15, % Lang["DiffR"]
    Gui, Add, Edit, x+5 w80 vDiffR Limit3
    Gui, Add, UpDown, vdR Range0-255 Wrap
    Gui, Add, Text, x+5, % Lang["DiffG"]
    Gui, Add, Edit, x+5 w80 vDiffG Limit3
    Gui, Add, UpDown, vdG Range0-255 Wrap
    Gui, Add, Text, x+5, % Lang["DiffB"]
    Gui, Add, Edit, x+5 w80 vDiffB Limit3
    Gui, Add, UpDown, vdB Range0-255 Wrap
    Gui, Add, Button, x+15 yp-3 vColorDiff2Two, % Lang["ColorDiff2Two"]
    %Gui_G%()
    Gui, Tab, 6
    Gui, Add, Text, x+10 y+15, % Lang["DiffRGB"]
    Gui, Add, Edit, x+5 w80 vDiffRGB Limit3
    Gui, Add, UpDown, vdRGB Range0-255 Wrap
    Gui, Add, Checkbox, x+15 yp+5 vMultiColor, % Lang["MultiColor"]
    %Gui_G%()
    Gui, Add, Button, x+15 yp-5 vUndo, % Lang["Undo"]
    %Gui_G%()
    Gui, Tab
    ;--------------
    Gui, Add, Button, xm vReset, % Lang["Reset"]
    %Gui_G%()
    Gui, Add, Checkbox, x+15 yp+5 vModify, % Lang["Modify"]
    %Gui_G%()
    Gui, Add, Text, x+30, % Lang["Comment"]
    Gui, Add, Edit, x+5 yp-2 w150 vComment
    Gui, Add, Button, x+10 yp-3 vSplitAdd, % Lang["SplitAdd"]
    %Gui_G%()
    Gui, Add, Button, x+10 vAllAdd, % Lang["AllAdd"]
    %Gui_G%()
    Gui, Add, Button, x+30 wp vOK, % Lang["OK"]
    %Gui_G%()
    Gui, Add, Button, x+10 wp vCancel, % Lang["Cancel"]
    %Gui_G%()
    Gui, Add, Button, xm vBind0, % Lang["Bind0"]
    %Gui_G%()
    Gui, Add, Button, x+10 vBind1, % Lang["Bind1"]
    %Gui_G%()
    Gui, Add, Button, x+10 vBind2, % Lang["Bind2"]
    %Gui_G%()
    Gui, Add, Button, x+10 vBind3, % Lang["Bind3"]
    %Gui_G%()
    Gui, Add, Button, x+10 vBind4, % Lang["Bind4"]
    %Gui_G%()
    Gui, Add, Button, x+60 vSavePic2, % Lang["SavePic2"]
    %Gui_G%()
    Gui, Show, Hide, % Lang["s3"]
    ;--------------------
    Gui, FindText_SubPic: New
    Gui, +Parent%parent_id% +AlwaysOnTop -Caption +ToolWindow -DPIScale
    Gui, Margin, 0, 0
    Gui, Color, White
    Gui, Add, Pic, x0 y0 w500 h500 +Hwndsub_hpic
    Gui, Show, NA x0 y0, SubPic
    return
  Case "MakeMainWindow":
    Gui, FindText_Main: New
    Gui, +LastFound +AlwaysOnTop -DPIScale
    Gui, Margin, 15, 10
    Gui, Color, %WindowColor%
    Gui, Font, s12, Verdana
    Gui, Add, Text, xm, % Lang["NowHotkey"]
    Gui, Add, Edit, x+5 w160 vNowHotkey ReadOnly
    Gui, Add, Hotkey, x+5 w160 vSetHotkey1
    s:="F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|LWin|MButton"
      . "|ScrollLock|CapsLock|Ins|Esc|BS|Del|Tab|Home|End|PgUp|PgDn"
      . "|NumpadDot|NumpadSub|NumpadAdd|NumpadDiv|NumpadMult"
    Gui, Add, DDL, x+5 w160 vSetHotkey2, % s
    Gui, Add, Button, x+15 vApply, % Lang["Apply"]
    %Gui_G%()
    Gui, Add, GroupBox, xm y+0 w280 h55 vMyGroup cBlack
    Gui, Add, Text, xp+15 yp+20 Section, % Lang["Myww"] ": "
    Gui, Add, Text, x+0 w80, % nW//2
    Gui, Add, UpDown, vMyww Range1-100, % nW//2
    Gui, Add, Text, x+15 ys, % Lang["Myhh"] ": "
    Gui, Add, Text, x+0 w80, % nH//2
    Gui, Add, UpDown, vMyhh Range1-100, % nH//2
    GuiControlGet, p, Pos, % this.LastCtrl()
    GuiControl, Move, MyGroup, % "w" (pX+pW) " h" (pH+30)
    Gui, Add, Checkbox, x+100 ys vAddFunc, % Lang["AddFunc"] " FindText()"
    GuiControlGet, p, Pos, % this.LastCtrl()
    pW:=pX+pW-15, pW:=(pW<720?720:pW), w:=pW//5
    Gui, Add, Button, xm y+18 w%w% vCutL2, % Lang["CutL2"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vCutR2, % Lang["CutR2"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vCutU2, % Lang["CutU2"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vCutD2, % Lang["CutD2"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vUpdate, % Lang["Update"]
    %Gui_G%()
    Gui, Font, s6 bold, Verdana
    Gui, Add, Edit, xm y+10 w%pW% h260 vMyPic -Wrap HScroll
    Gui, Font, s12 norm, Verdana
    w:=pW//3
    Gui, Add, Button, xm w%w% vCapture, % Lang["Capture"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vTest, % Lang["Test"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vCopy, % Lang["Copy"]
    %Gui_G%()
    Gui, Add, Button, xm y+0 wp vCaptureS, % Lang["CaptureS"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vGetRange, % Lang["GetRange"]
    %Gui_G%()
    Gui, Add, Button, x+0 wp vGetOffset, % Lang["GetOffset"]
    %Gui_G%()
    Gui, Add, Edit, xm y+10 w130 hp vClipText
    Gui, Add, Button, x+0 vPaste, % Lang["Paste"]
    %Gui_G%()
    Gui, Add, Button, x+0 vTestClip, % Lang["TestClip"]
    %Gui_G%()
    Gui, Add, Button, x+0 vGetClipOffset, % Lang["GetClipOffset"]
    %Gui_G%()
    r:=pW
    GuiControlGet, p, Pos, % this.LastCtrl()
    w:=((r+15)-(pX+pW))//2, pW:=r
    Gui, Add, Edit, x+0 w%w% hp vOffset
    Gui, Add, Button, x+0 wp vCopyOffset, % Lang["CopyOffset"]
    %Gui_G%()
    Gui, Font, cBlue
    Gui, Add, Edit, xm w%pW% h250 vscr Hwndhscr -Wrap HScroll
    Gui, Show, Hide, % Lang["s4"]
    %Gui_%("LoadScr")
    OnExit(Gui_SaveScr)
    return
  Case "LoadScr":
    f:=A_Temp "\~scr1.tmp"
    FileRead, s, %f%
    GuiControl, FindText_Main:, scr, %s%
    return
  Case "SaveScr":
    f:=A_Temp "\~scr1.tmp"
    GuiControlGet, s, FindText_Main:, scr
    FileDelete, %f%
    FileAppend, %s%, %f%
    return
  Case "Capture", "CaptureS":
    Gui, FindText_Main: +Hwndid
    if WinExist()!=id
      return this.GetRange()
    this.Hide()
    if !InStr(cmd, "CaptureS")
    {
      Gui, FindText_Main: Default
      GuiControlGet, w,, Myww
      GuiControlGet, h,, Myhh
      p:=this.GetRange(w, h)
      sx:=p[1], sy:=p[2], sw:=p[3]-p[1]+1, sh:=p[4]-p[2]+1
      , Bind_ID:=p[5], bind_mode:=""
      Gui, FindText_Capture: Default
      GuiControl, Choose, MyTab1, 1
    }
    else
    {
      sx:=0, sy:=0, sw:=1, sh:=1, Bind_ID:=WinExist("A"), bind_mode:=""
      Gui, FindText_Capture: Default
      GuiControl, Choose, MyTab1, 2
    }
    this.ScreenShot()
    n:=150000, x:=y:=-n, w:=h:=2*n
    hBM:=this.BitmapFromScreen(x,y,w,h,0)
    %Gui_%("CaptureUpdate")
    %Gui_%("PicUpdate")
    Names:=[], s:=""
    Loop Files, % SavePicDir "*.bmp"
      Names.Push(v:=A_LoopFileFullPath), s.="|" RegExReplace(v,"i)^.*\\|\.bmp$")
    GuiControl,, SelectBox, % "|" Trim(s,"|")
    ;----------------------
    Loop Parse, % "SelGray|SelColor|SelR|SelG|SelB|Threshold|Comment", |
      GuiControl,, % A_LoopField
    GuiControl,, Modify, % Modify:=0
    GuiControl,, MultiColor, % MultiColor:=0
    GuiControl,, GrayDiff, 50
    GuiControl, Focus, Gray2Two
    GuiControl, +Default, Gray2Two
    Gui, +LastFound
    Gui, Show, Center
    Event:=Result:=""
    DetectHiddenWindows, 0
    Critical, Off
    WinWaitClose, % "ahk_id " WinExist()
    Critical
    ToolTip
    Gui, FindText_Main: Default
    ;--------------------------------
    if (bind_mode!="")
    {
      WinGetTitle, tt, ahk_id %Bind_ID%
      WinGetClass, tc, ahk_id %Bind_ID%
      tt:=Trim(SubStr(tt,1,30) (tc ? " ahk_class " tc:""))
      tt:=StrReplace(RegExReplace(tt,"[;``]","``$0"),"""","""""")
      Result:="`nSetTitleMatchMode 2`nid:=WinExist(""" tt """)"
        . "`nFindText().BindWindow(id" (bind_mode=0 ? "":"," bind_mode)
        . ")  `; " Lang["s6"] " FindText().BindWindow(0)`n`n" Result
    }
    if (Event="OK")
    {
      if (!A_IsCompiled)
        FileRead, s, %A_LineFile%
      else
        s:=this.GetScript()
      re:="Oi)\n\s*FindText[^\n]+args\*[\s\S]*?Script_End[(){\s]+}"
      if RegExMatch(s, re, r)
        s:="`n;==========`n" r[0] "`n"
      GuiControl,, scr, % Result "`n" s
      GuiControl,, MyPic, % Trim(this.ASCII(Result),"`n")
    }
    else if (Event="SplitAdd") || (Event="AllAdd")
    {
      GuiControlGet, s,, scr
      r:=SubStr(s, 1, InStr(s,"=FindText("))
      i:=j:=0, re:="<[^>\n]*>[^$\n]+\$[^""\r\n]+"
      While j:=RegExMatch(r, re,, j+1)
        i:=InStr(r, "`n", 0, j)
      GuiControl,, scr, % SubStr(s,1,i) . Result . SubStr(s,i+1)
      GuiControl,, MyPic, % Trim(this.ASCII(Result),"`n")
    }
    if (Event) && RegExMatch(Result, "O)\$\d+\.[\w+/]{1,100}", r)
      this.EditScroll(hscr, "\Q" r[0] "\E")
    Event:=Result:=s:=""
    ;----------------------
    %Gui_Show%()
    return
  Case "CaptureUpdate":
    nX:=sx, nY:=sy, nW:=sw, nH:=sh
    bits:=this.GetBitsFromScreen(nX,nY,nW,nH,0,zx,zy)
    cors:=[], cut:=[], ascii:=[]
    bg:=color:="", dx:=dy:=CutLeft:=CutRight:=CutUp:=CutDown:=0
    ListLines % (lls:=A_ListLines)?0:0
    if (nW>0 && nH>0 && bits.Scan0)
    {
      j:=bits.Stride-nW*4, p:=bits.Scan0+(nY-zy)*bits.Stride+(nX-zx)*4-4-j
      Loop % nH + 0*(k:=0)
      Loop % nW + 0*(p+=j)
        cors[++k]:=NumGet(0|p+=4,"uint")
    }
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    {
      c:=(++tx)<nW && ty<nH ? cors[ty*nW+tx+1] : WindowColor
      SendMessage,0x2001,0,(c&0xFF)<<16|c&0xFF00|(c>>16)&0xFF,,% "ahk_id " C_[++k]
    }
    Loop % 71 + 0*(k:=71*25)
      SendMessage,0x2001,0,0xAAFFFF,,% "ahk_id " C_[++k]
    ListLines % lls
    Gui, FindText_Capture: Default
    GuiControl, % "Enable" (nW>71), MySlider1
    GuiControl, % "Enable" (nH>25), MySlider2
    GuiControl,, MySlider1, % MySlider1:=0
    GuiControl,, MySlider2, % MySlider2:=0
    return
  Case "PicUpdate":
    GuiControl, FindText_SubPic:, %sub_hpic%, % "*w0 *h0 HBITMAP:" hBM
    Gui, FindText_Capture: Default
    GuiControl,, MySlider3, % MySlider3:=0
    GuiControl,, MySlider4, % MySlider4:=0
    %Gui_%("MySlider3")
    return
  Case "Reset":
    %Gui_%("CaptureUpdate")
    return
  Case "LoadPic":
    Gui, FindText_Capture: +OwnDialogs
    f:=arg1
    if (f="")
    {
      if !FileExist(SavePicDir)
        FileCreateDir, % SavePicDir
      f:=SavePicDir "*.bmp"
      Loop Files, % f
        f:=A_LoopFileFullPath
      FileSelectFile, f,, %f%, Select Picture
    }
    if !FileExist(f)
    {
      MsgBox, 4096, Tip, % Lang["s17"] " !", 1
      return
    }
    this.ShowPic(f, 0, sx, sy, sw, sh)
    hBM:=this.BitmapFromScreen(sx, sy, sw, sh, 0)
    sw:=Min(sw,200), sh:=Min(sh,200)
    %Gui_%("CaptureUpdate")
    %Gui_%("PicUpdate")
    return
  Case "SavePic":
    Gui, FindText_Capture: Default
    GuiControlGet, SelectBox
    f:=Names[SelectBox]
    Gui, Hide
    this.ShowPic(f)
    pos:=this.SnapShot(0)
    %Gui_%("ScreenShot", pos[1] "|" pos[2] "|" pos[3] "|" pos[4] "|0")
    this.ShowPic()
    return
  Case "SelectBox":
    Gui, FindText_Capture: Default
    GuiControlGet, SelectBox
    f:=Names[SelectBox]
    if (f!="")
      %Gui_%("LoadPic", f)
    return
  Case "ClearAll":
    Gui, FindText_Capture: Hide
    FileDelete, % SavePicDir "*.bmp"
    return
  Case "OpenDir":
    Gui, FindText_Capture: Minimize
    if !FileExist(SavePicDir)
      FileCreateDir, % SavePicDir
    Run, % SavePicDir
    return
  Case "GetRange":
    Gui, FindText_Main: +LastFound
    this.Hide()
    p:=this.SnapShot(), v:=p[1] ", " p[2] ", " p[3] ", " p[4]
    Gui, FindText_Main: Default
    GuiControlGet, s,, scr
    re:="i)(=FindText\([^\n]*?)([^(,\n]*,){4}([^,\n]*,[^,\n]*,[^,\n]*Text)"
    if SubStr(s,1,s~="i)\n\s*FindText[^\n]+args\*")~=re
    {
      s:=RegExReplace(s, re, "$1 " v ",$3",, 1)
      GuiControl,, scr, %s%
    }
    GuiControl,, Offset, %v%
    %Gui_Show%()
    return
  Case "Test", "TestClip":
    Gui, FindText_Main: Default
    Gui, +LastFound
    this.Hide()
    ;----------------------
    if (cmd="Test")
      GuiControlGet, s,, scr
    else
      GuiControlGet, s,, ClipText
    if (cmd="Test") && InStr(s, "MCode(")
    {
      s:="`n#NoEnv`nMenu, Tray, Click, 1`n" s "`nExitApp`n"
      Thread1:=new this.Thread(s)
      DetectHiddenWindows, 1
      WinWait, % "ahk_class AutoHotkey ahk_pid " Thread1.pid,, 3
      if (!ErrorLevel)
        WinWaitClose,,, 30
      ; Thread1:=""  ; kill the Thread
    }
    else
    {
      t:=A_TickCount, v:=X:=Y:=""
      if RegExMatch(s, "O)<[^>\n]*>[^$\n]+\$[^""\r\n]+", r)
        v:=this.FindText(X, Y, 0,0,0,0, 0,0, r[0])
      r:=StrSplit(Lang["s8"] "||||", "|")
      MsgBox, 4096, Tip, % r[1] ":`t" (IsObject(v)?v.Length():v) "`n`n"
        . r[2] ":`t" (A_TickCount-t) " " r[3] "`n`n"
        . r[4] ":`t" X ", " Y "`n`n"
        . r[5] ":`t<" (IsObject(v)?v[1].id:"") ">", 3
      Try For i,j in v
        if (i<=2)
          this.MouseTip(j.x, j.y)
      v:="", Clipboard:=X "," Y
    }
    ;----------------------
    %Gui_Show%()
    return
  Case "GetOffset", "GetClipOffset":
    Gui, FindText_Main: Hide
    p:=this.GetRange()
    Gui, FindText_Main: Default
    if (cmd="GetOffset")
      GuiControlGet, s,, scr
    else
      GuiControlGet, s,, ClipText
    if RegExMatch(s, "O)<[^>\n]*>[^$\n]+\$[^""\r\n]+", r)
    && this.FindText(X, Y, 0,0,0,0, 0,0, r[0])
    {
      r:=StrReplace("X+" ((p[1]+p[3])//2-X)
        . ", Y+" ((p[2]+p[4])//2-Y), "+-", "-")
      if (cmd="GetOffset")
      {
        re:="i)(\(\)\.\w*Click\w*\()[^,\n]*,[^,)\n]*"
        if SubStr(s,1,s~="i)\n\s*FindText[^\n]+args\*")~=re
          s:=RegExReplace(s, re, "$1" r,, 1)
        GuiControl,, scr, %s%
      }
      GuiControl,, Offset, %r%
    }
    s:="", %Gui_Show%()
    return
  Case "Paste":
    s:=Clipboard
    if RegExMatch(s, "O)\|?<[^>\n]*>[^$\n]+\$[^""\r\n]+", r)
    {
      Gui, FindText_Main: Default
      GuiControl,, ClipText, % r[0]
      GuiControl,, MyPic, % Trim(this.ASCII(r[0]),"`n")
    }
    return
  Case "CopyOffset":
    GuiControlGet, s, FindText_Main:, Offset
    Clipboard:=s
    return
  Case "Copy":
    Gui, FindText_Main: Default
    ControlGet, s, Selected,,, ahk_id %hscr%
    if (s="")
    {
      GuiControlGet, s,, scr
      GuiControlGet, r,, AddFunc
      if (r != 1)
        s:=RegExReplace(s, "i)\n\s*FindText[^\n]+args\*[\s\S]*")
        , s:=RegExReplace(s, "i)\n; ok:=FindText[\s\S]*")
        , s:=SubStr(s, (s~="i)\n[ \t]*Text"))
    }
    Clipboard:=RegExReplace(s,"\R","`r`n")
    GuiControl, Focus, scr
    return
  Case "Apply":
    Gui, FindText_Main: Default
    GuiControlGet, NowHotkey
    GuiControlGet, SetHotkey1
    GuiControlGet, SetHotkey2
    if (NowHotkey!="")
      Hotkey, *%NowHotkey%,, Off UseErrorLevel
    k:=SetHotkey1!="" ? SetHotkey1 : SetHotkey2
    if (k!="")
      Hotkey, *%k%, %Gui_ScreenShot%, On UseErrorLevel
    GuiControl,, NowHotkey, %k%
    GuiControl,, SetHotkey1
    GuiControl, Choose, SetHotkey2, 0
    return
  Case "ScreenShot":
    Critical
    if !FileExist(SavePicDir)
      FileCreateDir, % SavePicDir
    Loop
      f:=SavePicDir . Format("{:03d}.bmp",A_Index)
    Until !FileExist(f)
    this.SavePic(f, StrSplit(arg1,"|")*)
    CoordMode, ToolTip
    this.ToolTip(Lang["s9"],, 0,, { bgcolor:"Yellow", color:"Red"
      , size:48, bold:"bold", trans:200, timeout:0.2 })
    return
  Case "Bind0", "Bind1", "Bind2", "Bind3", "Bind4":
    this.BindWindow(Bind_ID, bind_mode:=SubStr(cmd,5))
    n:=150000, x:=y:=-n, w:=h:=2*n
    hBM:=this.BitmapFromScreen(x,y,w,h,1)
    %Gui_%("PicUpdate")
    GuiControl, FindText_Capture: Choose, MyTab1, 2
    this.BindWindow(0)
    return
  Case "MySlider1", "MySlider2":
    SetTimer, %Gui_Slider%, -10
    return
  Case "Slider":
    Critical
    dx:=nW>71 ? Round((nW-71)*MySlider1/100) : 0
    dy:=nH>25 ? Round((nH-25)*MySlider2/100) : 0
    if (oldx=dx && oldy=dy)
      return
    ListLines % (lls:=A_ListLines)?0:0
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    {
      c:=((++tx)>=nW || ty>=nH || cut[i:=ty*nW+tx+1]
      ? WindowColor : bg="" ? cors[i] : ascii[i])
      SendMessage,0x2001,0,(c&0xFF)<<16|c&0xFF00|(c>>16)&0xFF,,% "ahk_id " C_[++k]
    }
    Loop % 71*(oldx!=dx) + 0*(i:=nW*nH+dx)*(k:=71*25)
      SendMessage,0x2001,0,(cut[++i]?0x0000FF:0xAAFFFF),,% "ahk_id " C_[++k]
    ListLines % lls
    oldx:=dx, oldy:=dy
    return
  Case "MySlider3", "MySlider4":
    GuiControlGet, p, FindText_Capture: Pos, % parent_id
    w:=pW, h:=pH
    GuiControlGet, p, FindText_SubPic: Pos, % sub_hpic
    x:=pW>w ? -Round((pW-w)*MySlider3/100) : 0
    y:=pH>h ? -Round((pH-h)*MySlider4/100) : 0
    Gui, FindText_SubPic: Show, NA x%x% y%y% w%pW% h%pH%
    return
  Case "RepColor":
    cut[k]:=0, c:=(bg="" ? cors[k] : ascii[k])
    if (tx:=Mod(k-1,nW)-dx)>=0 && tx<71 && (ty:=(k-1)//nW-dy)>=0 && ty<25
      SendMessage,0x2001,0,(c&0xFF)<<16|c&0xFF00|(c>>16)&0xFF,,% "ahk_id " C_[ty*71+tx+1]
    return
  Case "CutColor":
    cut[k]:=1, c:=WindowColor
    if (tx:=Mod(k-1,nW)-dx)>=0 && tx<71 && (ty:=(k-1)//nW-dy)>=0 && ty<25
      SendMessage,0x2001,0,(c&0xFF)<<16|c&0xFF00|(c>>16)&0xFF,,% "ahk_id " C_[ty*71+tx+1]
    return
  Case "RepL":
    if (CutLeft<=0) || (bg!="" && InStr(color,"**") && CutLeft=1)
      return
    k:=CutLeft-nW, CutLeft--
    Loop %nH%
      k+=nW, (A_Index>CutUp && A_Index<nH+1-CutDown && %Gui_%("RepColor"))
    return
  Case "CutL":
    if (CutLeft+CutRight>=nW)
      return
    CutLeft++, k:=CutLeft-nW
    Loop %nH%
      k+=nW, (A_Index>CutUp && A_Index<nH+1-CutDown && %Gui_%("CutColor"))
    return
  Case "CutL3":
    Loop 3
      %Gui_%("CutL")
    return
  Case "RepR":
    if (CutRight<=0) || (bg!="" && InStr(color,"**") && CutRight=1)
      return
    k:=1-CutRight, CutRight--
    Loop %nH%
      k+=nW, (A_Index>CutUp && A_Index<nH+1-CutDown && %Gui_%("RepColor"))
    return
  Case "CutR":
    if (CutLeft+CutRight>=nW)
      return
    CutRight++, k:=1-CutRight
    Loop %nH%
      k+=nW, (A_Index>CutUp && A_Index<nH+1-CutDown && %Gui_%("CutColor"))
    return
  Case "CutR3":
    Loop 3
      %Gui_%("CutR")
    return
  Case "RepU":
    if (CutUp<=0) || (bg!="" && InStr(color,"**") && CutUp=1)
      return
    k:=(CutUp-1)*nW, CutUp--
    Loop %nW%
      k++, (A_Index>CutLeft && A_Index<nW+1-CutRight && %Gui_%("RepColor"))
    return
  Case "CutU":
    if (CutUp+CutDown>=nH)
      return
    CutUp++, k:=(CutUp-1)*nW
    Loop %nW%
      k++, (A_Index>CutLeft && A_Index<nW+1-CutRight && %Gui_%("CutColor"))
    return
  Case "CutU3":
    Loop 3
      %Gui_%("CutU")
    return
  Case "RepD":
    if (CutDown<=0) || (bg!="" && InStr(color,"**") && CutDown=1)
      return
    k:=(nH-CutDown)*nW, CutDown--
    Loop %nW%
      k++, (A_Index>CutLeft && A_Index<nW+1-CutRight && %Gui_%("RepColor"))
    return
  Case "CutD":
    if (CutUp+CutDown>=nH)
      return
    CutDown++, k:=(nH-CutDown)*nW
    Loop %nW%
      k++, (A_Index>CutLeft && A_Index<nW+1-CutRight && %Gui_%("CutColor"))
    return
  Case "CutD3":
    Loop 3
      %Gui_%("CutD")
    return
  Case "Gray2Two":
    ListLines % (lls:=A_ListLines)?0:0
    gray:=[], k:=0
    Loop % nW*nH
      gray[++k]:=((((c:=cors[k])>>16)&0xFF)*38+((c>>8)&0xFF)*75+(c&0xFF)*15)>>7
    Gui, FindText_Capture: Default
    GuiControl, Focus, Threshold
    GuiControlGet, Threshold
    if (Threshold="")
    {
      pp:=[]
      Loop 256
        pp[A_Index-1]:=0
      Loop % nW*nH
        if (!cut[A_Index])
          pp[gray[A_Index]]++
      IP0:=IS0:=0
      Loop 256
        k:=A_Index-1, IP0+=k*pp[k], IS0+=pp[k]
      Threshold:=Floor(IP0/IS0)
      Loop 20
      {
        LastThreshold:=Threshold
        IP1:=IS1:=0
        Loop % LastThreshold+1
          k:=A_Index-1, IP1+=k*pp[k], IS1+=pp[k]
        IP2:=IP0-IP1, IS2:=IS0-IS1
        if (IS1!=0 && 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
      ? 0x000000:0xFFFFFF), (!cut[k] && i:=(v?i-1:i+1))
    bg:=(i>0 ? "1":"0")
    ;--------------
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    if (k++)*0 + (++tx)<nW && ty<nH && !cut[i:=ty*nW+tx+1]
      SendMessage,0x2001,0,ascii[i],,% "ahk_id " C_[k]
    ListLines % lls
    return
  Case "GrayDiff2Two":
    Gui, FindText_Capture: Default
    GuiControlGet, GrayDiff
    if (GrayDiff="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, % Lang["s11"] " !", 1
      return
    }
    ListLines % (lls:=A_ListLines)?0:0
    gray:=[], k:=0
    Loop % nW*nH
      gray[++k]:=((((c:=cors[k])>>16)&0xFF)*38+((c>>8)&0xFF)*75+(c&0xFF)*15)>>7
    if (CutLeft=0)
      %Gui_%("CutL")
    if (CutRight=0)
      %Gui_%("CutR")
    if (CutUp=0)
      %Gui_%("CutU")
    if (CutDown=0)
      %Gui_%("CutD")
    GrayDiff:=Round(GrayDiff)
    color:="**" GrayDiff, k:=i:=0
    Loop % nW*nH
      j:=gray[++k]+GrayDiff
      , ascii[k]:=v:=(gray[k-1]>j || gray[k+1]>j
      || gray[k-nW]>j || gray[k+nW]>j
      || gray[k-nW-1]>j || gray[k-nW+1]>j
      || gray[k+nW-1]>j || gray[k+nW+1]>j
      ? 0x000000:0xFFFFFF), (!cut[k] && i:=(v?i-1:i+1))
    bg:=(i>0 ? "1":"0")
    ;--------------
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    if (k++)*0 + (++tx)<nW && ty<nH && !cut[i:=ty*nW+tx+1]
      SendMessage,0x2001,0,ascii[i],,% "ahk_id " C_[k]
    ListLines % lls
    return
  Case "Color2Two", "ColorPos2Two":
    Gui, FindText_Capture: Default
    GuiControlGet, c,, SelColor
    if (c="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, % Lang["s12"] " !", 1
      return
    }
    UsePos:=(cmd="ColorPos2Two")
    GuiControlGet, n,, % UsePos ? "Similar2" : "Similar1"
    n:=Round(n/100,2), color:=StrReplace(c,"0x") "@" n
    , n:=Floor(4606*255*255*(1-n)*(1-n)), k:=i:=0
    , rr:=(c>>16)&0xFF, gg:=(c>>8)&0xFF, bb:=c&0xFF
    ListLines % (lls:=A_ListLines)?0:0
    Loop % nW*nH
      c:=cors[++k], r:=((c>>16)&0xFF)-rr
      , g:=((c>>8)&0xFF)-gg, b:=(c&0xFF)-bb, j:=r+rr+rr
      , ascii[k]:=v:=((1024+j)*r*r+2048*g*g+(1534-j)*b*b<=n
      ? 0x000000:0xFFFFFF), (!cut[k] && i:=(v?i-1:i+1))
    bg:=(i>0 ? "1":"0")
    ;--------------
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    if (k++)*0 + (++tx)<nW && ty<nH && !cut[i:=ty*nW+tx+1]
      SendMessage,0x2001,0,ascii[i],,% "ahk_id " C_[k]
    ListLines % lls
    return
  Case "ColorDiff2Two":
    Gui, FindText_Capture: Default
    GuiControlGet, c,, SelColor
    if (c="")
    {
      Gui, +OwnDialogs
      MsgBox, 4096, Tip, % Lang["s12"] " !", 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
    ListLines % (lls:=A_ListLines)?0: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 && Abs(g-gg)<=dG && Abs(b-bb)<=dB
      ? 0x000000:0xFFFFFF), (!cut[k] && i:=(v?i-1:i+1))
    bg:=(i>0 ? "1":"0")
    ;--------------
    Loop % 25 + 0*(ty:=dy-1)*(k:=0)
    Loop % 71 + 0*(tx:=dx-1)*(ty++)
    if (k++)*0 + (++tx)<nW && ty<nH && !cut[i:=ty*nW+tx+1]
      SendMessage,0x2001,0,ascii[i],,% "ahk_id " C_[k]
    ListLines % lls
    return
  Case "Modify":
    Gui, FindText_Capture: Default
    GuiControlGet, Modify
    return
  Case "MultiColor":
    Gui, FindText_Capture: Default
    GuiControlGet, MultiColor
    Result:=""
    ToolTip
    return
  Case "Undo":
    Result:=RegExReplace(Result, ",[^/]+/[^/]+/[^/]+$")
    ToolTip % Trim(Result,"/,")
    return
  Case "Similar1":
    Gui, FindText_Capture: Default
    GuiControl,, Similar2, %Similar1%
    return
  Case "Similar2":
    Gui, FindText_Capture: Default
    GuiControl,, Similar1, %Similar2%
    return
  Case "GetTxt":
    txt:=""
    if (bg="")
      return
    k:=0
    ListLines % (lls:=A_ListLines)?0:0
    Loop %nH%
    {
      v:=""
      Loop %nW%
        v.=cut[++k] ? "" : ascii[k] ? "0":"1"
      txt.=v="" ? "" : v "`n"
    }
    ListLines % lls
    return
  Case "Auto":
    %Gui_%("GetTxt")
    if (txt="")
    {
      Gui, FindText_Capture: +OwnDialogs
      MsgBox, 4096, Tip, % Lang["s13"] " !", 1
      return
    }
    While InStr(txt,bg)
    {
      if (txt~="^" bg "+\n")
        txt:=RegExReplace(txt, "^" bg "+\n"), %Gui_%("CutU")
      else if !(txt~="m`n)[^\n" bg "]$")
        txt:=RegExReplace(txt, "m`n)" bg "$"), %Gui_%("CutR")
      else if (txt~="\n" bg "+\n$")
        txt:=RegExReplace(txt, "\n\K" bg "+\n$"), %Gui_%("CutD")
      else if !(txt~="m`n)^[^\n" bg "]")
        txt:=RegExReplace(txt, "m`n)^" bg), %Gui_%("CutL")
      else Break
    }
    txt:=""
    return
  Case "OK", "SplitAdd", "AllAdd":
    Gui, FindText_Capture: Default
    Gui, +OwnDialogs
    %Gui_%("GetTxt")
    if (txt="") && (!MultiColor)
    {
      MsgBox, 4096, Tip, % Lang["s13"] " !", 1
      return
    }
    if InStr(color,"@") && (UsePos) && (!MultiColor)
    {
      r:=StrSplit(color,"@")
      k:=i:=j:=0
      ListLines % (lls:=A_ListLines)?0:0
      Loop % nW*nH
      {
        if (cut[++k])
          Continue
        i++
        if (k=cors.SelPos)
        {
          j:=i
          Break
        }
      }
      ListLines % lls
      if (j=0)
      {
        MsgBox, 4096, Tip, % Lang["s12"] " !", 1
        return
      }
      color:="#" j "@" r[2]
    }
    GuiControlGet, Comment
    if (cmd="SplitAdd") && (!MultiColor)
    {
      if InStr(color,"#")
      {
        MsgBox, 4096, Tip, % Lang["s14"], 3
        return
      }
      bg:=StrLen(StrReplace(txt,"0"))
        > StrLen(StrReplace(txt,"1")) ? "1":"0"
      s:="", i:=0, k:=nW*nH+1+CutLeft
      Loop % w:=nW-CutLeft-CutRight
      {
        i++
        if (!cut[k++] && A_Index<w)
          Continue
        i:=Format("{:d}",i)
        v:=RegExReplace(txt,"m`n)^(.{" i "}).*","$1")
        txt:=RegExReplace(txt,"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) "." this.bit2base64(v)
          s.="`nText.=""|<" SubStr(Comment,1,1) ">" color "$" v """`n"
          Comment:=SubStr(Comment, 2)
        }
      }
      Event:=cmd, Result:=s
      Gui, Hide
      return
    }
    if (!MultiColor)
      txt:=Format("{:d}",InStr(txt,"`n")-1) "." this.bit2base64(txt)
    else
    {
      GuiControlGet, n,, dRGB
      color:=Format("##{:06X}", n<<16|n<<8|n)
      r:=StrSplit(Trim(StrReplace(Result, ",", "/"), "/"), "/")
      , x:=r[1], y:=r[2], s:="", i:=1
      SetFormat, IntegerFast, d
      Loop % r.Length()//3
        s.="," (r[i++]-x) "/" (r[i++]-y) "/" r[i++]
      txt:=SubStr(s,2)
    }
    s:="`nText.=""|<" Comment ">" color "$" txt """`n"
    if (cmd="AllAdd")
    {
      Event:=cmd, Result:=s
      Gui, Hide
      return
    }
    x:=nX+CutLeft+(nW-CutLeft-CutRight)//2
    y:=nY+CutUp+(nH-CutUp-CutDown)//2
    s:=StrReplace(s, "Text.=", "Text:="), r:=StrSplit(Lang["s8"] "|||||||", "|")
    s:="`; #Include <FindText>`n"
    . "`nt1:=A_TickCount, Text:=X:=Y:=""""`n" s
    . "`nif (ok:=FindText(X, Y, " x "-150000, "
    . y "-150000, " x "+150000, " y "+150000, 0, 0, Text))"
    . "`n{"
    . "`n  `; FindText()." . "Click(" . "X, Y, ""L"")"
    . "`n}`n"
    . "`n`; ok:=FindText(X:=""wait"", Y:=3, 0,0,0,0,0,0,Text)    `; " r[7]
    . "`n`; ok:=FindText(X:=""wait0"", Y:=-1, 0,0,0,0,0,0,Text)  `; " r[8]
    . "`n`nMsgBox, 4096, Tip, `% """ r[1] ":``t"" (IsObject(ok)?ok.Length():ok)"
    . "`n  . ""``n``n" r[2] ":``t"" (A_TickCount-t1) "" " r[3] """"
    . "`n  . ""``n``n" r[4] ":``t"" X "", "" Y"
    . "`n  . ""``n``n" r[5] ":``t<"" (IsObject(ok)?ok[1].id:"""") "">""`n"
    . "`nTry For i,v in ok  `; ok " r[6] " ok:=FindText().ok"
    . "`n  if (i<=2)"
    . "`n    FindText().MouseTip(ok[i].x, ok[i].y)`n"
    Event:=cmd, Result:=s
    Gui, Hide
    return
  Case "SavePic2":
    x:=nX+CutLeft, w:=nW-CutLeft-CutRight
    y:=nY+CutUp, h:=nH-CutUp-CutDown
    %Gui_%("ScreenShot", x "|" y "|" (x+w-1) "|" (y+h-1) "|0")
    return
  Case "ShowPic":
    Gui, FindText_Main: Default
    ControlGet, i, CurrentLine,,, ahk_id %hscr%
    ControlGet, s, Line, %i%,, ahk_id %hscr%
    GuiControl,, MyPic, % Trim(this.ASCII(s),"`n")
    return
  Case "KeyDown":
    Critical
    if (A_Gui!="FindText_Main")
      return
    if (A_GuiControl="scr")
      SetTimer, %Gui_ShowPic%, -150
    else if (A_GuiControl="ClipText")
    {
      GuiControlGet, s, FindText_Main:, ClipText
      GuiControl, FindText_Main:, MyPic, % Trim(this.ASCII(s),"`n")
    }
    return
  Case "LButtonDown":
    Critical
    if (A_Gui="FindText_SubPic")
    {
      ; Two windows trigger two messages
      if (A_TickCount-oldt)<100 || !GetKeyState("LButton","P")
        return
      CoordMode, Mouse
      MouseGetPos, k1, k2
      ListLines % (lls:=A_ListLines)?0:0
      Loop
      {
        Sleep 50
        MouseGetPos, k3, k4
        this.RangeTip(Min(k1,k3), Min(k2,k4)
        , Abs(k1-k3), Abs(k2-k4), (A_MSec<500 ? "Red":"Blue"))
      }
      Until !GetKeyState("LButton","P")
      ListLines % lls
      this.RangeTip()
      this.GetBitsFromScreen(,,,,0,zx,zy)
      this.ClientToScreen(sx, sy, 0, 0, sub_hpic)
      if Abs(k1-k3)+Abs(k2-k4)>4
        sx:=zx+Min(k1,k3)-sx, sy:=zy+Min(k2,k4)-sy
        , sw:=Abs(k1-k3), sh:=Abs(k2-k4)
      else
        sx:=zx+k1-sx-71//2, sy:=zy+k2-sy-25//2, sw:=71, sh:=25
      %Gui_%("CaptureUpdate")
      GuiControl, FindText_Capture: Choose, MyTab1, 1
      oldt:=A_TickCount
      return
    }
    if (A_Gui!="FindText_Capture")
      return %Gui_%("KeyDown")
    MouseGetPos,,,, k2, 2
    k1:=0
    ListLines % (lls:=A_ListLines)?0:0
    For k_,v_ in C_
      if (v_=k2) && (k1:=k_)
        Break
    ListLines % lls
    if (k1<1)
      return
    else if (k1>71*25)
    {
      k3:=nW*nH+dx+(k1-71*25)
      SendMessage,0x2001,0,((cut[k3]:=!cut[k3])?0x0000FF:0xAAFFFF),,% "ahk_id " k2
      return
    }
    k2:=Mod(k1-1,71)+dx, k3:=(k1-1)//71+dy
    if (k2<0 || k2>=nW || k3<0 || k3>=nH)
      return
    k1:=k, k4:=c, k:=k3*nW+k2+1
    if (MultiColor && !cut[k])
    {
      c:="," (nX+k2) "/" (nY+k3) "/"
      . Format("{:06X}",cors[k]&0xFFFFFF)
      , Result.=InStr(Result,c) ? "":c
      ToolTip % Trim(Result,"/,")
    }
    if (Modify && bg!="" && !cut[k])
    {
      ascii[k]:=c:=(ascii[k] ? 0x000000:0xFFFFFF)
      if (tx:=Mod(k-1,nW)-dx)>=0 && tx<71 && (ty:=(k-1)//nW-dy)>=0 && ty<25
        SendMessage,0x2001,0,c,,% "ahk_id " C_[ty*71+tx+1]
    }
    else
    {
      c:=cors[k], cors.SelPos:=k
      Gui, FindText_Capture: Default
      GuiControl,, SelGray, % (((c>>16)&0xFF)*38+((c>>8)&0xFF)*75+(c&0xFF)*15)>>7
      GuiControl,, SelColor, % Format("0x{:06X}",c&0xFFFFFF)
      GuiControl,, SelR, % (c>>16)&0xFF
      GuiControl,, SelG, % (c>>8)&0xFF
      GuiControl,, SelB, % c&0xFF
    }
    k:=k1, c:=k4
    return
  Case "RButtonDown":
    Critical
    if (A_Gui!="FindText_SubPic")
      return
    ; Two windows trigger two messages
    if (A_TickCount-oldt)<100 || !GetKeyState("RButton","P")
      return
    r:=[x, y, w, h, pX, pY, pW, pH]
    CoordMode, Mouse
    MouseGetPos, k1, k2
    WinGetPos, x, y, w, h, ahk_id %parent_id%
    WinGetPos, pX, pY, pW, pH, ahk_id %sub_hpic%
    pX-=x, pY-=y, pW-=w, pH-=h
    ListLines % (lls:=A_ListLines)?0:0
    Loop
    {
      Sleep 10
      MouseGetPos, k3, k4
      x:=Min(Max(pX+k3-k1,-pW),0), y:=Min(Max(pY+k4-k2,-pH),0)
      Gui, FindText_SubPic: Show, NA x%x% y%y%
      GuiControl, FindText_Capture:, MySlider3, % Round(-x/pW*100)
      GuiControl, FindText_Capture:, MySlider4, % Round(-y/pH*100)
    }
    Until !GetKeyState("RButton","P")
    ListLines % lls
    x:=r[1], y:=r[2], w:=r[3], h:=r[4], pX:=r[5], pY:=r[6], pW:=r[7], pH:=r[8]
    oldt:=A_TickCount
    return
  Case "MouseMove":
    if (PrevControl!=A_GuiControl)
    {
      ToolTip
      PrevControl:=A_GuiControl
      if (Gui_ToolTip)
      {
        SetTimer, %Gui_ToolTip%, % PrevControl ? -500 : "Off"
        SetTimer, %Gui_ToolTipOff%, % PrevControl ? -5500 : "Off"
      }
    }
    return
  Case "ToolTip":
    MouseGetPos,,, _TT
    IfWinExist, ahk_id %_TT% ahk_class AutoHotkeyGUI
      ToolTip % Tip_Text[PrevControl]
    return
  Case "ToolTipOff":
    ToolTip
    return
  Case "CutL2", "CutR2", "CutU2", "CutD2":
    Gui, FindText_Main: Default
    GuiControlGet, s,, MyPic
    s:=Trim(s,"`n") . "`n", v:=SubStr(cmd,4,1)
    if (v="U")
      s:=RegExReplace(s,"^[^\n]+\n")
    else if (v="D")
      s:=RegExReplace(s,"[^\n]+\n$")
    else if (v="L")
      s:=RegExReplace(s,"m`n)^[^\n]")
    else if (v="R")
      s:=RegExReplace(s,"m`n)[^\n]$")
    GuiControl,, MyPic, % Trim(s,"`n")
    return
  Case "Update":
    Gui, FindText_Main: Default
    GuiControl, Focus, scr
    ControlGet, i, CurrentLine,,, ahk_id %hscr%
    ControlGet, s, Line, %i%,, ahk_id %hscr%
    if !RegExMatch(s, "O)(<[^>\n]*>[^$\n]+\$)\d+\.[\w+/]+", r)
      return
    GuiControlGet, v,, MyPic
    v:=Trim(v,"`n") . "`n", w:=Format("{:d}",InStr(v,"`n")-1)
    v:=StrReplace(StrReplace(v,"0","1"),"_","0")
    s:=StrReplace(s, r[0], r[1] . w "." this.bit2base64(v))
    v:="{End}{Shift Down}{Home}{Shift Up}{Del}"
    ControlSend,, %v%, ahk_id %hscr%
    Control, EditPaste, %s%,, ahk_id %hscr%
    ControlSend,, {Home}, ahk_id %hscr%
    return
  }
}

Lang(text:="", getLang:=0)
{
  local
  static init, Lang1, Lang2
  if (!VarSetCapacity(init) && init:="1")
  {
    s:="
    (
Myww       = Width = Adjust the width of the capture range
Myhh       = Height = Adjust the height of the capture range
AddFunc    = Add = Additional FindText() in Copy
NowHotkey  = Hotkey = Current screenshot hotkey
SetHotkey1 = = First sequence Screenshot hotkey
SetHotkey2 = = Second sequence Screenshot hotkey
Apply      = Apply = Apply new screenshot hotkey
CutU2      = CutU = Cut the Upper Edge of the text in the edit box below
CutL2      = CutL = Cut the Left Edge of the text in the edit box below
CutR2      = CutR = Cut the Right Edge of the text in the edit box below
CutD2      = CutD = Cut the Lower Edge of the text in the edit box below
Update     = Update = Update the text in the edit box below to the line of Code
GetRange   = GetRange = Get screen range and update the search range of the Code
GetOffset  = GetOffset = Get position offset relative to the Text from the Code and update FindText().Click()
GetClipOffset  = GetOffset2 = Get position offset relative to the Text from the Left Box
Capture    = Capture = Initiate Image Capture Sequence
CaptureS   = CaptureS = Restore the Saved ScreenShot by Hotkey and then start capturing
Test       = Test = Test the Text from the Code to see if it can be found on the screen
TestClip   = Test2 = Test the Text from the Left Box and copy the result to Clipboard
Paste      = Paste = Paste the Text from Clipboard to the Left Box
CopyOffset = Copy2 = Copy the Offset to Clipboard
Copy       = Copy = Copy the selected or all of the code to the clipboard
Reset      = Reset = Reset to Original Captured Image
SplitAdd   = SplitAdd = Using Markup Segmentation to Generate Text Library
AllAdd     = AllAdd = Append Another FindText Search Text into Previously Generated Code
Gray2Two      = Gray2Two = Converts Image Pixels from Gray Threshold to Black or White
GrayDiff2Two  = GrayDiff2Two = Converts Image Pixels from Gray Difference to Black or White
Color2Two     = Color2Two = Converts Image Pixels from Color Similar to Black or White
ColorPos2Two  = ColorPos2Two = Converts Image Pixels from Color Position to Black or White
ColorDiff2Two = ColorDiff2Two = Converts Image Pixels from Color Difference to Black or White
SelGray    = Gray = Gray value of the selected color
SelColor   = Color = The selected color
SelR       = R = Red component of the selected color
SelG       = G = Green component of the selected color
SelB       = B = Blue component of the selected color
RepU       = -U = Undo Cut the Upper Edge by 1
CutU       = U = Cut the Upper Edge by 1
CutU3      = U3 = Cut the Upper Edge by 3
RepL       = -L = Undo Cut the Left Edge by 1
CutL       = L = Cut the Left Edge by 1
CutL3      = L3 = Cut the Left Edge by 3
Auto       = Auto = Automatic Cut Edge after image has been converted to black and white
RepR       = -R = Undo Cut the Right Edge by 1
CutR       = R = Cut the Right Edge by 1
CutR3      = R3 = Cut the Right Edge by 3
RepD       = -D = Undo Cut the Lower Edge by 1
CutD       = D = Cut the Lower Edge by 1
CutD3      = D3 = Cut the Lower Edge by 3
Modify     = Modify = Allows Modify the Black and White Image
MultiColor = FindMultiColor = Click multiple colors with the mouse, then Click OK button
Undo       = Undo = Undo the last selected color
Comment    = Comment = Optional Comment used to Label Code ( Within <> )
Threshold  = Gray Threshold = Gray Threshold which Determines Black or White Pixel Conversion (0-255)
GrayDiff   = Gray Difference = Gray Difference which Determines Black or White Pixel Conversion (0-255)
Similar1   = Similarity = Adjust color similarity as Equivalent to The Selected Color
Similar2   = Similarity = Adjust color similarity as Equivalent to The Selected Color
DiffR      = R = Red Difference which Determines Black or White Pixel Conversion (0-255)
DiffG      = G = Green Difference which Determines Black or White Pixel Conversion (0-255)
DiffB      = B = Blue Difference which Determines Black or White Pixel Conversion (0-255)
DiffRGB    = R/G/B = Determine the allowed R/G/B Error (0-255) when Find MultiColor
Bind0      = BindWin1 = Bind the window and Use GetDCEx() to get the image of background window
Bind1      = BindWin1+ = Bind the window Use GetDCEx() and Modify the window to support transparency
Bind2      = BindWin2 = Bind the window and Use PrintWindow() to get the image of background window
Bind3      = BindWin2+ = Bind the window Use PrintWindow() and Modify the window to support transparency
Bind4      = BindWin3 = Bind the window and Use PrintWindow(,,3) to get the image of background window
OK         = OK = Create New FindText Code for Testing
OK2        = OK = Restore this ScreenShot then Capturing
Cancel     = Cancel = Close the Window Don't Do Anything
Cancel2    = Cancel = Close the Window Don't Do Anything
ClearAll   = ClearAll = Clean up all saved ScreenShots
OpenDir    = OpenDir = Open the saved screenshots directory
SavePic    = SavePic = Select a range and save as a picture
SavePic2   = SavePic = Save the trimmed original image as a picture
LoadPic    = LoadPic = Load a picture as Capture image
ClipText   = = Displays the Text data from clipboard
Offset     = = Displays the results of GetOffset2 or GetRange
SelectBox  = = Select a screenshot to display in the upper left corner of the screen
s1  = FindText
s2  = Gray|GrayDiff|Color|ColorPos|ColorDiff|MultiColor
s3  = Capture Image To Text
s4  = Capture Image To Text and Find Text Tool
s5  = Direction keys to fine tune\nFirst click RButton\nMove the mouse away\nSecond click RButton
s6  = Unbind Window using
s7  = Please drag a range with the LButton\nCoordinates are copied to clipboard
s8  = Found|Time|ms|Pos|Result|value can be get from|Wait 3 seconds for appear|Wait indefinitely for disappear
s9  = Success
s10 = The Capture Position|Perspective binding window\nRight click to finish capture
s11 = Please Set Gray Difference First
s12 = Please select the core color first
s13 = Please convert the image to black or white first
s14 = Can't be used in ColorPos mode, because it can cause position errors
s15 = ReTry|ToFile|GetRange|ToClipboard
s16 = LButton Drag to select range\nDirection keys to fine tune\nRButton or ESC to get range\nDouble-Click copy to Clipboard
s17 = Please Save Picture First
s18 = Capture|ScreenShot
    )"
    Lang1:=[], Lang2:=[]
    Loop Parse, s, `n, `r
      if InStr(v:=A_LoopField, "=")
        r:=StrSplit(StrReplace(v "==","\n","`n"), "=", "`t ")
        , Lang1[r[1]]:=r[2], Lang2[r[1]]:=r[3]
  }
  return getLang=1 ? Lang1 : getLang=2 ? Lang2 : Lang1[text]
}

}  ;// Class End

Script_End() {
}

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

;
Last edited by feiyue on 03 Jan 2024, 01:58, edited 363 times in total.
feiyue
Posts: 348
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:


<<< Update history >>>

Code: Select all


Updated to 9.4 version - 2023/12/18
1. Add: The image processing window has added a screenshot page, 
   which makes it more convenient to obtain colors when using multi-color search mode
2. Add: EditScroll() function is added to Locate the specified row in the edit box.

Updated to 9.3 version - 2023/11/05
1. Modify: FindMultiColor search mode, which can manually enhance the generated Text parameters,
   such as adding multiple colors for each point, adding their own color bias, and using exclusion colors.
2. Add: bin2hex(), DrawHBM(), you can draw lines and rectangles in memory images.
3. Add: New() function, you can use ft:=FindText().New() instead of ft:=new FindTextClass()
4. Modify: Machine code using base64 strings reduces script size.

Updated to 9.2 version - 2023/10/01 
1. Modify: After successfully searching for images in a loop (X:='wait' or 'wait1'),
   it is no longer necessary to wait for the images to stabilize before returning,
   as many people need to return immediately for gaming purposes
2. Modify: When searching for image files, multiple transparent colors can be set.
   You can use Text:="##10-RRGGBB-RRGGBB... $ d:\a.bmp", then the 0xRRGGBB(+/-10)... as transparent color.
   You can use FindText().ImageSearch(,,,,,, "*10 *TransBlack-White-RRGGBB... d:\a.bmp")
3. Add: Added Snapshot() function, which can be used to save screenshot to clipboard or file.
   eg: PrintScreen::FindText().Snapshot()

Updated to 9.1 version - 2023/07/30 
1. Modify: Omitting the first two parameters is no longer allowed (improvements made in v8.9 have been removed).
   Because considering compatibility with code before v8.5 would add a lot of complexity.
   The first and second parameters can be default values, as long as the subsequent parameters are not all empty.
   eg: ok:=FindText(,,,,,,,, Text)
2. Modify: Improved the speed of ImageSearch() in finding large image files.
   ImageSearch()、ShowPic() can now use image handles. eg: HBITMAP:*%handle% 
3. Add: Added PixelSearch(), PixelCount(), ControlClick(), GetRange() function.

Updated to 9.0 version - 2023/04/15 
1. Modify: Fixed the bug that the returned width and height were incorrect if the image scaling parameter (zoomW, zoomH) was used. In version 8.9.
2. Modify: Fixed a bug in the compiled program where the generated code does not contain library function content.
3. Modify: When searching for image files, a transparent color can be specified without the need to create transparent images yourself.
   You can use Text:="##10-RRGGBB $ d:\a.bmp", then the 0xRRGGBB(+/-10) as transparent color.
   You can use FindText().ImageSearch(,,,,,, "*10 *Trans112233  d:\a.bmp")
4. Add: Added WaitNotChange() function.

Updated to 8.9 version - 2022/05/28
1. Add: FindText() adds two parameters (zoomW, zoomH) at the end to scale the original image.
2. Modify: Fixed the problem that adding two parameters in v8.6 resulted in incompatible v8.5 code.
3. Modify: Optimize whether BitBlt requires captureblt flag.   ( thanks Descolada )
    
Updated to 8.8 version - 2022/03/22
1. Add: Added RangeTip(), Sort3() function.
2. Modify: Combined text search can collect multiple fonts for the same text,
   Now, instead of identifying in order, it is found by the string declared by the JoinText parameter,
   The JoinText parameter needs to add the string to the array, so that multiple combined text can be found at the same time.
    
Updated to 8.7 version - 2022/01/24
1. Add: Several functions are added: ShowScreenShot(), WaitChange(), GetPicHash(), objview().
2. Modify: When capturing the screen image, you can use the arrow keys
   (Up/Down/Left/Right) to adjust the size of the selection box.
3. Add: The return value of the FindText() function is cached in the OK key
   and can be easily obtained when necessary. (ok:=FindText().ok)

Updated to 8.6 version - 2021/09/12
1. Modify: FindText() adds two parameters that output X,Y coordinates as the first two parameters of the function,
   This is more friendly to novices without adding additional calculation coordinate statements.
   However, this will cause some compatibility problems. The previous code needs to add two parameters: FindText(0,0, ......).
2. Add: The first two newly added parameters for returning coordinates are also parameters for starting circular search. 
   If the first parameter is equal to the string "wait", you can start circular search. See the function description for details.
3. Add: FindText.ToolTip() has been added, which can be displayed in an equal width font, or the font size and color can be modified. 
   FindText.Click() has been added, which can click absolute coordinates without changing "CoordMode, Mouse" settings.
4. Add the "GetOffset" function in the main window to obtain the offset coordinates in FindText.Click(X+0,Y+0),
   It automatically calculates the offset value based on the found image as the origin.
5. Modify: Now the contents of the edit box of the main window will be restored when restarting,
   In addition, clicking the "CaptureS" button no longer requires at least one picture in the screenshot directory.
6. Modify: There are some small changes to move closer to v2. Of course, it is still difficult to change to v2.
   The class name of V2 cannot have the same name as the function, so the class name of FindText is changed to FindTextClass,
   This causes some compatibility problems. The previous "FindText.Method()" needs to be replaced with "FindText().Method()"

Updated to 8.5 version - 2021/04/22
1. Add: Since you can search for pictures now, the function of saving pictures has been added in CaptureS.
   You can select a small range of pictures from the ScreenShots to save as pictures.
2. Modify: Now when capturing the image, it no longer judges the physical state of the right mouse button, 
   but uses the logical state, which can be used in remote control.

Updated to 8.4 version - 2021/04/02
1. Improvement: in multicolor mode,You can find multiple colors and adapt to different resolutions.
   Now it also supports searching images, for example: Text:="|<>##10$ c:\a.bmp"
   It's better to limit the search range to a small one.
2. the return value of OCR identification function is changed.
   eg: MsgBox, % FindText.Ocr(ok).ocr --> Change to: MsgBox, % FindText.Ocr(ok).text
3. Add: FindText.QPC() and FindText.SetColor() is added.
4. Modify: The search direction (dir = 1-9),  dir = 9: From the Center to all around.

Updated to 8.3 version - 2021/03/19
1. Add: Now You can set the capture mouse when capturing an image Use FindText.CaptureCursor(1) .
2. Add: The FindText.ImageSearch() function is added, which is completely similar to
   the built-in command ImageSearch, The coordinates are set by "CoordMode, Pixel" .
3. Add: A new ScreenShot selection window has been added.
   Now you can get multiple screenshots using hotkeys,
   and then click the "CaptureS" button to select which screenshot to restore.

Updated to 8.2 version - 2021/03/03
1. Add: Added a search direction (dir = 5): From the Center to all around.
2. Modify: The size of machine code is reduced.

Updated to 8.1 version - 2021/02/19
1. Add: since all the input and output coordinates of the FindText function are relative to the screen, 
   You can use the following functions to convert coordinates to relative window:
   WindowToScreen(), ScreenToWindow(), ClientToScreen(), ScreenToClient().
2. Modify: previously, the binding window could not be moved beyond the screen range, 
   otherwise it would not be found. Now this problem has been fixed.

Updated to 8.0 version - 2021/01/31
1. Add a parameter ( dir ), search direction, up, down, left, right four kinds.
2. Modify: Now, the combination search can mix different mode.
3. Add: the FindMultiColor mode is added, it's fun, but not visual enough.

Updated to 7.9 version - 2021/01/15
1. Modify: At present, it can support 101 × 101 pixel capture.
   The previous sub window will generate 10201 controls, the number of controls exceeds the limit,
   now do not use the sub window to generate enlarged image.
2. Modify: At present, the combination search can return the exact width.
   Previously, only an approximate width can be estimated.

Updated to 7.8 version - 2020/12/20
1. Modify: FindText_Gui() function is moved to the FindText class, reducing the extra functions.
2. The text of the interface is read from one language library, which is easy to translate into other languages.

Updated to 7.7 version - 2020/12/10
1. Modify: Images can already be found correctly on multiple displays.
2. Add: the SavePic() function is added to save the screenshot for debugging.
3. Some buttons are added in the main interface to adjust the result Text in the code.

Updated to 7.6 version - 2020/11/09
1. Modify: The functions are packaged into the FindText class,
   which can generate multiple objects, bind different windows,
   and store different screenshots. However, this creates some incompatibility.
   For example: MouseTip() must be changed to FindText.MouseTip()
2. Modify: Renamed SortOK, SortOK2, OcrOK,  ScreenShot_GetColor to Sort, Sort2, Ocr, GetColor.

Updated to 7.5 version - 2020/04/13
1. Add: the button to get the range has been added,
   You can select a range on the screen with the left mouse button,
   The range coordinates are copied to the clipboard,
   And the search scope of FindText() function in the code will be updated.
2. Modify: in order to reduce the conflict of function names, 
   Use "FindText_" as the function prefix. However, this creates some incompatibility.
   For example: MouseTip() must be changed to FindText_MouseTip()

Updated to 7.4 version - 2020/01/20
1. Add: the GetTextFromScreen() function and FindText_Gui("Capture_NoGui") is added.
   So you can quickly generate the final code without GUI interface.

Updated to 7.3 version - 2019/12/21
1. Add: the BindWindow() function is added so that the window image can be found in the background.
   It is a state that affects subsequent FindText() and ScreenShot() functions.

Updated to 7.2 version - 2019/12/16
1. Improvement: the GetBitsFromScreen() function was redesigned.
   Because some static variables are used in this function to retain
   the last screenshot data, if multiple threads interrupt each other,
   the later thread may modify the static variables of this function,
   which may cause fatal errors after the previous thread resumes execution.
   Now this problem is solved.

Updated to 7.1 version - 2019/12/10
1. Add: A new function PicInfo() is added to ensure that each text parameter
   is parsed only once, which slightly improves the efficiency of loop execution.
2. Add: A sub window is added to the capture image window to process the captured image.
   This sub window can scroll, so it can support larger captured image.
   Therefore, the capture range can be adjusted at any time in the main window interface.
3. Modify: Pic() is renamed as PicLib(). In addition, a sequence number parameter
   has been added, so that multiple text libraries can be set up and used.
4. Some details have been improved to make it easy for users to use.
5. Fixed some minor bugs in ft_ShowScreenShot() and OcrOk().

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}.
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 18 Dec 2023, 12:22, edited 111 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: 3453
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: 507
Joined: 20 Nov 2013, 20:56
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: 524
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: 348
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.
FindText.OCR() function can also be used to identify simple verification code.

Code: Select all


#Include <FindText>

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
;------------------------------
ok:=FindText(x-150, y-20, x+150, y+20, 0.2, 0.1, Text)
arr:=FindText.OCR(ok), OCR:=arr.text
;------------------------------
t1:=A_TickCount-t1
MsgBox, 4096, OCR, OCR Result: [%OCR%] in %t1% ms.
Return

Last edited by feiyue on 21 Apr 2021, 23:10, edited 11 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: 348
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: 348
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: 348
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: 348
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:

Code: Select all

; 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 (v1)”

Who is online

Users browsing this forum: furqan, Google [Bot], Spawnova, TheNaviator and 77 guests