Page 17 of 17

Re: Vis2 - Image to Text OCR()

Posted: 02 Mar 2023, 00:41
by Ntey
Im kind of figured how to do what I want, but it takes alot of actions between, so I thought maybe there is easier way.

Whay Ive done is by ahk hotkey i call windows snippet tool, then loop files in screenshot directory to find latest one, move it to desired location and rename, then using OCR check for this file using text := OCR (filename)

Re: Vis2 - Image to Text OCR()

Posted: 24 Mar 2023, 07:30
by Flowgun
I changed the way that Vis2.Ahk deals with directories to make it easier to include into other scripts:

Code: Select all

; Script:    Vis2.ahk
; License:   Custom
; Author:    Edison Hua (iseahound)
; Date:      2022-03-03
; Version:   2.1.0

#include %A_LineFile%\..\Gdip_All.ahk
#include %A_LineFile%\..\ImagePut.ahk
#include %A_LineFile%\..\JSON.ahk

; ImageIdentify() - Label and identify objects in images.
ImageIdentify(image:="", search:="", options:=""){
   return Vis2.ImageIdentify(image, search, options)
}

; OCR() - Convert pictures of text into text.
OCR(image:="", language:="", options:=""){
   return Vis2.OCR(image, language, options)
}

class Vis2 {

   class ImageIdentify extends Vis2.functor {
      call(self, image:="", search:="", options:=""){
         return (image != "") ? (new Vis2.provider.GoogleCloudVision()).ImageIdentify(image, search, options)
            : Vis2.core.returnText({"provider":(new Vis2.provider.GoogleCloudVision(search)), "tooltip":"Image Identification Tool", "splashImage":true})
      }
   }

   class OCR extends Vis2.functor {
      call(self, image:="", language:="", options:=""){
         return (image != "") ? (new Vis2.provider.Tesseract()).OCR(image, language, options)
            : Vis2.core.returnText({"provider":(new Vis2.provider.Tesseract(language)), "tooltip":"Optical Character Recognition Tool", "textPreview":true})
      }

      google(){
         return (image != "") ? (new Vis2.provider.Tesseract()).OCR(image, language, options).google()
            : Vis2.core.returnText({"provider":(new Vis2.provider.Tesseract(language)), "tooltip":"Any selected text will be Googled.", "textPreview":true, "noCopy":true}).google()
      }
   }

   class core {

      ; returnText() is a wrapper function of Vis2.core.ux.start()
      ; Unlike Vis2.core.ux.start(), this function will return a string of text.
      returnText(obj := ""){
         obj := IsObject(obj) ? obj : {}
         obj.callback := "returnText"
         if (Vis2.core.ux.start(obj) == "") {
            while !(EXITCODE := Vis2.obj.EXITCODE)
               Sleep 1
            text := Vis2.obj.database
            Vis2.obj.callbackConfirmed := true
            text.base.google := ObjBindMethod(Vis2.Text, "google")
            text.base.clipboard := ObjBindMethod(Vis2.Text, "clipboard")
            return (EXITCODE > 0) ? text : ""
         }
      }

      class ux {

         ; start() is the function that launches the user interface.
         ; This can be called directly without calling Vis2.core.returnText().
         start(obj := ""){
         static void := ObjBindMethod({}, {})

            if (Vis2.obj != "")
               return "Already in use."

            Vis2.Graphics.Startup()
            Vis2.stdlib.setSystemCursor(32515) ; IDC_Cross := 32515
            Hotkey, LButton, % void, On
            Hotkey, ^LButton, % void, On
            Hotkey, !LButton, % void, On
            Hotkey, +LButton, % void, On
            Hotkey, RButton, % void, On
            Hotkey, Escape, % void, On

            Vis2.obj := IsObject(obj) ? obj : {}
            Vis2.obj.EXITCODE := 0 ; 0 = in progress, -1 = escape, 1 = success
            Vis2.obj.selectMode := "Quick"
            Vis2.obj.area := new Vis2.Graphics.Area("Vis2_Aries", "0x7FDDDDDD")
            Vis2.obj.image := new Vis2.Graphics.Image("Vis2_Kitsune").Hide()
            Vis2.obj.subtitle := new Vis2.Graphics.Subtitle("Vis2_Hermes")

            Vis2.obj.style1_back := {"x":"center", "y":"83%", "padding":"1.35%", "color":"DD000000", "radius":8}
            Vis2.obj.style1_text := {"q":4, "size":"2.23%", "font":"Arial", "z":"Arial Narrow", "justify":"left", "color":"White"}
            Vis2.obj.style2_back := {"x":"center", "y":"83%", "padding":"1.35%", "color":"FF88EAB6", "radius":8}
            Vis2.obj.style2_text := {"q":4, "size":"2.23%", "font":"Arial", "z":"Arial Narrow", "justify":"left", "color":"Black"}
            Vis2.obj.subtitle.render(Vis2.obj.tooltip, Vis2.obj.style1_back, Vis2.obj.style1_text)

            return Vis2.core.ux.waitForUserInput()
         }

         waitForUserInput(){
         static escape := ObjBindMethod(Vis2.core.ux, "escape")
         static waitForUserInput := ObjBindMethod(Vis2.core.ux, "waitForUserInput")
         static selectImage := ObjBindMethod(Vis2.core.ux.process, "selectImage")
         static textPreview := ObjBindMethod(Vis2.core.ux.process, "textPreview")

            if (GetKeyState("Escape", "P")) {
               Vis2.obj.EXITCODE := -1
               SetTimer, % escape, -9
               return
            }
            else if (GetKeyState("LButton", "P")) {
               SetTimer, % selectImage, -10
               if (Vis2.obj.textPreview)
                  SetTimer, % textPreview, -25
               else
                  Vis2.obj.subtitle.render("Waiting for user selection...", Vis2.obj.style2_back, Vis2.obj.style2_text)
            }
            else {
               Vis2.obj.area.origin()
               SetTimer, % waitForUserInput, -10
            }
            return
         }

         class process {

            selectImage(){
            static selectImage := ObjBindMethod(Vis2.core.ux.process, "selectImage")

               Critical
               if (GetKeyState("Escape", "P")) {
                  Vis2.obj.EXITCODE := -1
                  return Vis2.core.ux.process.finale(A_ThisFunc)
               }

               if (Vis2.obj.selectMode == "Quick")
                  Vis2.core.ux.process.selectImageQuick()
               if (Vis2.obj.selectMode == "Advanced")
                  Vis2.core.ux.process.selectImageAdvanced()

               if (Vis2.core.ux.overlap()) {
                  if (Vis2.obj.textPreview && Vis2.obj.dialogue != Vis2.obj.dialogue_past) {
                     Vis2.obj.dialogue_past := Vis2.obj.dialogue
                     Vis2.obj.style1_back.y := (Vis2.obj.style1_back.y == "83%") ? "2.07%" : "83%"
                     Vis2.obj.subtitle.render(Vis2.obj.dialogue, Vis2.obj.style1_back, Vis2.obj.style1_text)
                  }
                  else if !(Vis2.obj.textPreview) {
                     Vis2.obj.style2_back.y := (Vis2.obj.style2_back.y == "83%") ? "2.07%" : "83%"
                     Vis2.obj.subtitle.render("Still patiently waiting for user selection...", Vis2.obj.style2_back, Vis2.obj.style2_text)
                  }
               }

               if !(Vis2.obj.unlock.1 ~= "^Vis2.core.ux.process.selectImage" || Vis2.obj.unlock.2 ~= "^Vis2.core.ux.process.selectImage")
                  SetTimer, % selectImage, -10
               return
            }

            selectImageQuick(){
               if (GetKeyState("LButton", "P")) {
                  if (GetKeyState("Control", "P") || GetKeyState("Alt", "P") || GetKeyState("Shift", "P"))
                     Vis2.core.ux.process.selectImageTransition()
                  else if (GetKeyState("RButton", "P")) {
                     Vis2.obj.area.move()
                     if (!Vis2.obj.area.isMouseOnCorner() && Vis2.obj.area.isMouseStopped())
                        Vis2.obj.area.draw() ; Error Correction of Offset
                  }
                  else
                     Vis2.obj.area.draw()
               }
               else
                  Vis2.core.ux.process.finale(A_ThisFunc)
               ; Do not return.
            }

            selectImageTransition(){
            static void := ObjBindMethod({}, {})

               DllCall("SystemParametersInfo", "uInt",0x57, "uInt",0, "uInt",0, "uInt",0) ; RestoreCursor()
               Hotkey, Space, % void, On
               Hotkey, ^Space, % void, On
               Hotkey, !Space, % void, On
               Hotkey, +Space, % void, On
               Vis2.obj.note_01 := Vis2.Graphics.Subtitle.Render("Advanced Mode", "time: 2500, xCenter y75% p1.35% cFFB1AC r8", "c000000 s2.23%")
               Vis2.obj.tokenMousePressed := 1
               Vis2.obj.selectMode := "Advanced" ; Exit selectImageQuick.
               Vis2.obj.key := {}
               Vis2.obj.action := {}
            }

            selectImageAdvanced(){
            static void := ObjBindMethod({}, {})

               if ((Vis2.obj.area.width() < -25 || Vis2.obj.area.height() < -25) && !Vis2.obj.note_02)
                  Vis2.obj.note_02 := Vis2.Graphics.Subtitle.Render("Press Alt + LButton to create a new selection anywhere on screen", "time: 6250, x: center, y: 67%, p1.35%, c: FCF9AF, r8", "c000000 s2.23%")

               Vis2.obj.key.LButton := GetKeyState("LButton", "P") ? 1 : 0
               Vis2.obj.key.RButton := GetKeyState("RButton", "P") ? 1 : 0
               Vis2.obj.key.Space   := GetKeyState("Space", "P")   ? 1 : 0
               Vis2.obj.key.Control := GetKeyState("Control", "P") ? 1 : 0
               Vis2.obj.key.Alt     := GetKeyState("Alt", "P")     ? 1 : 0
               Vis2.obj.key.Shift   := GetKeyState("Shift", "P")   ? 1 : 0

               ; Check if mouse is inside on activation.
               Vis2.obj.action.Control_LButton := (Vis2.obj.area.isMouseInside() && Vis2.obj.key.Control && Vis2.obj.key.LButton)
                  ? 1 : (Vis2.obj.key.Control && Vis2.obj.key.LButton) ? Vis2.obj.action.Control_LButton : 0
               Vis2.obj.action.Shift_LButton   := (Vis2.obj.area.isMouseInside() && Vis2.obj.key.Shift && Vis2.obj.key.LButton)
                  ? 1 : (Vis2.obj.key.Shift && Vis2.obj.key.LButton) ? Vis2.obj.action.Shift_LButton : 0
               Vis2.obj.action.LButton         := (Vis2.obj.area.isMouseInside() && Vis2.obj.key.LButton)
                  ? 1 : (Vis2.obj.key.LButton) ? Vis2.obj.action.LButton : 0
               Vis2.obj.action.RButton         := (Vis2.obj.area.isMouseInside() && Vis2.obj.key.RButton)
                  ? 1 : (Vis2.obj.key.RButton) ? Vis2.obj.action.RButton : 0

               ;___|¯¯¯|___ 00011111000 Keypress
               ;___|_______ 0001----000 Activation Function (pseudo heaviside)
               Vis2.obj.action.Control_Space   := (Vis2.obj.key.Control && Vis2.obj.key.Space)
                  ? ((!Vis2.obj.action.Control_Space) ? 1 : -1) : 0
               Vis2.obj.action.Alt_Space       := (Vis2.obj.key.Alt && Vis2.obj.key.Space)
                  ? ((!Vis2.obj.action.Alt_Space) ? 1 : -1) : 0
               Vis2.obj.action.Shift_Space     := (Vis2.obj.key.Shift && Vis2.obj.key.Space)
                  ? ((!Vis2.obj.action.Shift_Space) ? 1 : -1) : 0
               Vis2.obj.action.Alt_LButton     := (Vis2.obj.key.Alt && Vis2.obj.key.LButton)
                  ? ((!Vis2.obj.action.Alt_LButton) ? 1 : -1) : 0

               ; Ensure only Space is pressed.
               Vis2.obj.action.Space := (Vis2.obj.key.Space && !Vis2.obj.key.Control && !Vis2.obj.key.Alt && !Vis2.obj.key.Shift)
                  ? ((!Vis2.obj.action.Space) ? 1 : -1) : 0

               ; Space Hotkeys
               if (Vis2.obj.action.Control_Space = 1){
                  Vis2.obj.image.render(Vis2.obj.provider.getPreprocessImage(), 0.5)
                  Vis2.obj.image.toggleVisible()
               } else if (Vis2.obj.action.Alt_Space = 1){
                  Vis2.obj.area.toggleCoordinates()
               } else if (Vis2.obj.action.Shift_Space = 1){

               } else if (Vis2.obj.action.Space = 1)
                  Vis2.core.ux.process.finale(A_ThisFunc) ; Exit function.

               ; Mouse Hotkeys
               if (Vis2.obj.action.Control_LButton)
                  Vis2.obj.area.resizeCorners()
               else if (Vis2.obj.action.Alt_LButton = 1)
                  Vis2.obj.area.origin()
               else if (Vis2.obj.action.Alt_LButton = -1)
                  Vis2.obj.area.draw()
               else if (Vis2.obj.action.Shift_LButton)
                  Vis2.obj.area.resizeEdges()
               else if (Vis2.obj.action.LButton || Vis2.obj.action.RButton)
                  Vis2.obj.area.move()
               else {
                  Vis2.obj.area.hover() ; Collapse Stack
                  if Vis2.obj.area.isMouseInside() {
                     Hotkey, LButton, % void, On
                     Hotkey, RButton, % void, On
                  } else {
                     Hotkey, LButton, % void, Off
                     Hotkey, RButton, % void, Off
                  }
               }
               ; Do not return.
            }

            textPreview(bypass:=""){
            static textPreview := ObjBindMethod(Vis2.core.ux.process, "textPreview")

               if (!Vis2.obj.unlock.1 || bypass) {
                  if (coordinates := Vis2.obj.Area.ScreenshotRectangle()) {
                     (overlap := Vis2.core.ux.overlap()) ? Vis2.obj.subtitle.hide() : ""
                     pBitmap := ImagePutBitmap({screenshot: coordinates}) ; To avoid the grey tint, call Area.Hide() but this will cause flickering.
                     (overlap) ? Vis2.obj.subtitle.show() : ""
                     if !(coordinates == Vis2.obj.coordinates && Vis2.stdlib.Gdip_isBitmapEqual(pBitmap, Vis2.obj.pBitmap)) {
                        Gdip_DisposeImage(Vis2.obj.pBitmap)
                        Vis2.obj.coordinates := coordinates
                        Vis2.obj.pBitmap := pBitmap

                        ; Process screenshot.
                        try {
                           if (Vis2.obj.provider.file != "")
                              ImagePutFile({bitmap: pBitmap}, Vis2.obj.provider.file, Vis2.obj.provider.jpegQuality)
                           if (Vis2.obj.provider.base64 != "")
                              Vis2.obj.provider.base64 := ImagePutBase64({bitmap: pBitmap}, Vis2.obj.provider.ext, Vis2.obj.provider.jpegQuality)
                           Vis2.obj.provider.preprocess()
                           if (Vis2.obj.image.isVisible() == true)
                              Vis2.obj.image.render(Vis2.obj.provider.getPreprocessImage(), 0.5)
                           Vis2.obj.provider.convert()
                           Vis2.obj.database := Vis2.obj.provider.getText()
                        }
                        catch e {
                           MsgBox, 16,, % "Exception thrown!`n`nwhat: " e.what "`nfile: " e.file
                              . "`nline: " e.line "`nmessage: " e.message "`nextra: " e.extra "`ncoordinates: " coordinates
                        }

                        ; Retrieve three lines of text.
                        dialogue := ""
                        i := 1
                        Loop, Parse, % Vis2.obj.database, `r`n
                        {
                           data := RegExReplace(A_LoopField, "^\s*(.*?)\s*$", "$1")
                           if (data != "") {
                              dialogue .= (dialogue) ? ("`n" . data) : data
                              i++
                           }
                        } until (i > 3)

                        if (dialogue != "") {
                           Vis2.obj.firstDialogue := true
                           Vis2.obj.dialogue := dialogue
                        }
                        else
                           Vis2.obj.dialogue := (Vis2.obj.firstDialogue == true) ? "ERROR: No Text Data Found" : "Searching for text..."

                        if !(bypass)
                           Vis2.obj.Subtitle.Render(Vis2.obj.dialogue, Vis2.obj.style1_back, Vis2.obj.style1_text)
                     }
                     else {
                        Gdip_DisposeImage(pBitmap)
                     }
                  }
               }

               if (Vis2.obj.unlock.1)
                  return Vis2.core.ux.process.finale(A_ThisFunc)
               else
                  SetTimer, % textPreview, -100
               return
            }

            finale(key){
            static escape := ObjBindMethod(Vis2.core.ux, "escape")

               (IsObject(Vis2.obj.unlock) && key != Vis2.obj.unlock.1) ? Vis2.obj.unlock.push(key) : (Vis2.obj.unlock := [key])

               if (key ~= "^Vis2.core.ux.process.selectImage") {
                  Vis2.obj.Area.ChangeColor(0x01FFFFFF) ; Lighten Area object, but do not hide or delete it until key up.
                  if (!Vis2.obj.textPreview)
                     Vis2.core.ux.process.textPreview("bypass")
               }

               if (Vis2.obj.unlock.MaxIndex() == 2) {
                  if (Vis2.obj.area.screenshotRectangle() != Vis2.obj.coordinates)
                     Vis2.core.ux.process.textPreview("bypass")
                  if (Vis2.obj.database != "" && Vis2.obj.EXITCODE == 0) {
                     if (Vis2.obj.noCopy != true) {
                        clipboard := Vis2.obj.database
                        t := 2500
                        if (Vis2.obj.splashImage == true) {
                           t := 5000
                           w := Gdip_GetImageWidth(Vis2.obj.pBitmap)
                           h := Gdip_GetImageHeight(Vis2.obj.pBitmap)
                           Vis2.Graphics.Subtitle.Render("", {"time":t, "x":((A_ScreenWidth-w)/2)-10, "y":((A_ScreenHeight-h)/2)-10, "w":w+20, "h":h+20, "color":"Black"})
                           Vis2.Graphics.Image.Render(Vis2.obj.pBitmap, 1, t).Border()
                        }
                        Vis2.obj.Subtitle.Hide()
                        Vis2.Graphics.Subtitle.Render(Vis2.obj.dialogue
                           , {"time":t, "x":"center", "y":"83%", "padding":"1.35%", "color":"Black", "radius":8}, {"q":4, "size":"2.23%", "font":"Arial", "z":"Arial Narrow", "justify":"left", "color":"White"})
                        Vis2.Graphics.Subtitle.Render("Saved to Clipboard.", "time: " t ", x: center, y: 75%, p: 1.35%, c: F9E486, r: 8", "c: 0x000000, s:2.23%, f:Arial")
                     }
                     Vis2.obj.EXITCODE := 1
                  }
                  Vis2.obj.EXITCODE := (Vis2.obj.EXITCODE == 0) ? -1 : Vis2.obj.EXITCODE
                  SetTimer, % escape, -9
               }
               return
            }
         }

         escape(){
         static escape := ObjBindMethod(Vis2.core.ux, "escape")
         static void := ObjBindMethod({}, {})

            if (Vis2.obj.callback) {
               if !(Vis2.obj.callbackConfirmed) {
                  SetTimer, % escape, -9
                  return
               }
            }

            ; Delete temporary image and text files.
            Gdip_DisposeImage(Vis2.obj.pBitmap)
            Vis2.obj.provider.cleanup()
            Vis2.obj.area.destroy()
            Vis2.obj.image.destroy()
            Vis2.obj.subtitle.destroy()
            Vis2.obj.note_01.hide() ; Let them time out instead of Destroy()
            Vis2.obj.note_02.destroy()
            Vis2.obj := "" ; Goodbye all, you were loved :c
            Vis2.Graphics.Shutdown()

            ; Fixes a bug where AHK does not detect key releases if there is an admin-level window beneath.
            if WinActive("ahk_id" Vis2.obj.area.hwnd) {
               KeyWait Control
               KeyWait Alt
               KeyWait Shift
               KeyWait RButton
               KeyWait LButton
               KeyWait Space
               KeyWait Escape
            }

            Hotkey, LButton, % void, Off
            Hotkey, ^LButton, % void, Off
            Hotkey, !LButton, % void, Off
            Hotkey, +LButton, % void, Off
            Hotkey, RButton, % void, Off
            Hotkey, Escape, % void, Off
            Hotkey, Space, % void, Off
            Hotkey, ^Space, % void, Off
            Hotkey, !Space, % void, Off
            Hotkey, +Space, % void, Off

            return DllCall("SystemParametersInfo", "uInt",0x57, "uInt",0, "uInt",0, "uInt",0) ; RestoreCursor()
         }

         overlap() {
            p1 := Vis2.obj.area.x1()
            p2 := Vis2.obj.area.x2()
            r1 := Vis2.obj.area.y1()
            r2 := Vis2.obj.area.y2()

            q1 := Vis2.obj.subtitle.x1()
            q2 := Vis2.obj.subtitle.x2()
            s1 := Vis2.obj.subtitle.y1()
            s2 := Vis2.obj.subtitle.y2()

            a := (p1 < q1 && q1 < p2) || (p1 < q2 && q2 < p2) || (q1 < p1 && p1 < q2) || (q1 < p2 && p2 < q2)
            b := (r1 < s1 && s1 < r2) || (r1 < s2 && s2 < r2) || (s1 < r1 && r1 < s2) || (s1 < r2 && r2 < s2)

            ;Tooltip % a "`t" b "`n`n" p1 "`t" r1 "`n" p2 "`t" r2 "`n`n" q1 "`t" s1 "`n" q2 "`t" s2
            return (a && b)
         }

         suspend(){
            static void := ObjBindMethod({}, {})
            Hotkey, LButton, % void, Off
            Hotkey, ^LButton, % void, Off
            Hotkey, !LButton, % void, Off
            Hotkey, +LButton, % void, Off
            Hotkey, RButton, % void, Off
            Hotkey, Escape, % void, Off
            Hotkey, Space, % void, Off
            Hotkey, ^Space, % void, Off
            Hotkey, !Space, % void, Off
            Hotkey, +Space, % void, Off
            DllCall("SystemParametersInfo", "uInt",0x57, "uInt",0, "uInt",0, "uInt",0) ; RestoreCursor
            Vis2.obj.area.hide()
            return
         }

         resume(){
            Hotkey, LButton, % void, On
            Hotkey, ^LButton, % void, On
            Hotkey, !LButton, % void, On
            Hotkey, +LButton, % void, On
            Hotkey, RButton, % void, On
            Hotkey, Escape, % void, On

            if (Vis2.obj.selectMode == "Quick")
               Vis2.stdlib.setSystemCursor(32515) ; IDC_Cross := 32515

            if (Vis2.obj.selectMode == "Advanced") {
               Hotkey, Space, % void, On
               Hotkey, ^Space, % void, On
               Hotkey, !Space, % void, On
               Hotkey, +Space, % void, On
            }
            Vis2.obj.area.show()
            return
         }
      }
   }

   class functor {

      __Call(method, ByRef arg := "", args*) {
      ; When casting to Call(), use a new instance of the "function object"
      ; so as to avoid directly storing the properties(used across sub-methods)
      ; into the "function object" itself.
      ; Thanks to coco for this code. Modified by iseahound.
         if IsObject(method)
            return (new this).Call(method, arg, args*)
         else if (method == "")
            return (new this).Call(arg, args*)
      }
   }

   class Graphics {

      static pToken, Gdip := 0

      Startup(){
         global pToken
         return Vis2.Graphics.pToken := (Vis2.Graphics.Gdip++ > 0) ? Vis2.Graphics.pToken : (pToken) ? pToken : Gdip_Startup()
      }

      Shutdown(){
         global pToken
         return Vis2.Graphics.pToken := (--Vis2.Graphics.Gdip <= 0) ? ((pToken) ? pToken : Gdip_Shutdown(Vis2.Graphics.pToken)) : Vis2.Graphics.pToken
      }

      Name(){
         VarSetCapacity(UUID, 16, 0)
         if (DllCall("rpcrt4.dll\UuidCreate", "ptr", &UUID) != 0)
             return (ErrorLevel := 1) & 0
         if (DllCall("rpcrt4.dll\UuidToString", "ptr", &UUID, "uint*", suuid) != 0)
             return (ErrorLevel := 2) & 0
         return A_TickCount "n" SubStr(StrGet(suuid), 1, 8), DllCall("rpcrt4.dll\RpcStringFree", "uint*", suuid)
      }

      class Area{

         ScreenWidth := A_ScreenWidth, ScreenHeight := A_ScreenHeight,
         action := ["base"], x := [0], y := [0], w := [1], h := [1], a := ["top left"], q := ["bottom right"]

         __New(name := "", color := "0x7FDDDDDD") {
            this.name := name := (name == "") ? Vis2.Graphics.Name() "_Graphics_Area" : name "_Graphics_Area"
            this.color := color

            Vis2.Graphics.Startup()
            Gui, %name%:New, +LastFound +AlwaysOnTop -Caption -DPIScale +E0x80000 +ToolWindow +hwndSecretName, % this.name
            Gui, %name%:Show, % (this.isDrawable()) ? "NoActivate" : ""
            this.hwnd := SecretName
            this.hbm := CreateDIBSection(this.ScreenWidth, this.ScreenHeight)
            this.hdc := CreateCompatibleDC()
            this.obm := SelectObject(this.hdc, this.hbm)
            this.G := Gdip_GraphicsFromHDC(this.hdc)
            Gdip_SetSmoothingMode(this.G, 4) ;Adds one clickable pixel to the edge.
            this.pBrush := Gdip_BrushCreateSolid(this.color)
         }

         __Delete(){
            Vis2.Graphics.Shutdown()
         }

         Destroy(){
            Gdip_DeleteBrush(this.pBrush)
            SelectObject(this.hdc, this.obm)
            DeleteObject(this.hbm)
            DeleteDC(this.hdc)
            Gdip_DeleteGraphics(this.G)
            Gui, % this.name ":Destroy"
         }

         Hide(){
            DllCall("ShowWindow", "ptr",this.hWnd, "int",0)
         }

         Show(){ ; NoActivate
            DllCall("ShowWindow", "ptr",this.hWnd, "int",8)
         }

         ToggleVisible(){
            this.isVisible() ? this.Hide() : this.Show()
         }

         isVisible(){
            return DllCall("IsWindowVisible", "ptr",this.hWnd)
         }

         isDrawable(win := "A"){
             static WM_KEYDOWN := 0x100,
             static WM_KEYUP := 0x101,
             static vk_to_use := 7
             ; Test whether we can send keystrokes to this window.
             ; Use a virtual keycode which is unlikely to do anything:
             PostMessage, WM_KEYDOWN, vk_to_use, 0,, % win
             if !ErrorLevel
             {   ; Seems best to post key-up, in case the window is keeping track.
                 PostMessage, WM_KEYUP, vk_to_use, 0xC0000000,, % win
                 return true
             }
             return false
         }

         DetectScreenResolutionChange(){
            if (this.ScreenWidth != A_ScreenWidth || this.ScreenHeight != A_ScreenHeight) {
               this.ScreenWidth := A_ScreenWidth, this.ScreenHeight := A_ScreenHeight
               SelectObject(this.hdc, this.obm)
               DeleteObject(this.hbm)
               DeleteDC(this.hdc)
               Gdip_DeleteGraphics(this.G)
               this.hbm := CreateDIBSection(this.ScreenWidth, this.ScreenHeight)
               this.hdc := CreateCompatibleDC()
               this.obm := SelectObject(this.hdc, this.hbm)
               this.G := Gdip_GraphicsFromHDC(this.hdc)
               Gdip_SetSmoothingMode(this.G, 4)
            }
         }

         Redraw(x, y, w, h){
            Critical On
            this.DetectScreenResolutionChange()
            Gdip_GraphicsClear(this.G)
            Gdip_FillRectangle(this.G, this.pBrush, x, y, w, h)
            if (this.coordinates)
               Vis2.Graphics.Subtitle.Draw("x: " x " │ y: " y " │ w: " w " │ h: " h
                  , {"a":"top_right", "x":"right", "y":"top", "color":"Black"}, {"font":"Lucida Sans Typewriter", "size":"1.67%"}, this.G)
            UpdateLayeredWindow(this.hwnd, this.hdc, 0, 0, this.ScreenWidth, this.ScreenHeight)
            Critical Off
         }

         ChangeColor(color){
            this.color := color
            Gdip_DeleteBrush(this.pBrush)
            this.pBrush := Gdip_BrushCreateSolid(this.color)
            this.Redraw(this.x[this.x.MaxIndex()], this.y[this.y.MaxIndex()], this.w[this.w.MaxIndex()], this.h[this.h.MaxIndex()])
         }

         ShowCoordinates(){
            this.coordinates := true
            this.Redraw(this.x[this.x.MaxIndex()], this.y[this.y.MaxIndex()], this.w[this.w.MaxIndex()], this.h[this.h.MaxIndex()])
         }

         HideCoordinates(){
            this.coodinates := false
            this.Redraw(this.x[this.x.MaxIndex()], this.y[this.y.MaxIndex()], this.w[this.w.MaxIndex()], this.h[this.h.MaxIndex()])
         }

         ToggleCoordinates(){
            this.coordinates := !this.coordinates
            this.Redraw(this.x[this.x.MaxIndex()], this.y[this.y.MaxIndex()], this.w[this.w.MaxIndex()], this.h[this.h.MaxIndex()])
         }

         Propagate(v){
            this.a[v] := (this.a[v] == "") ? this.a[v-1] : this.a[v]
            this.q[v] := (this.q[v] == "") ? this.q[v-1] : this.q[v]
            this.x[v] := (this.x[v] == "") ? this.x[v-1] : this.x[v]
            this.y[v] := (this.y[v] == "") ? this.y[v-1] : this.y[v]
            this.w[v] := (this.w[v] == "") ? this.w[v-1] : this.w[v]
            this.h[v] := (this.h[v] == "") ? this.h[v-1] : this.h[v]
         }

         BackPropagate(pasts){
            action := this.action.pop()
            a := this.a.pop()
            q := this.q.pop()
            x := this.x.pop()
            y := this.y.pop()
            w := this.w.pop()
            h := this.h.pop()

            dx := x - this.x[pasts-1]
            dy := y - this.y[pasts-1]
            dw := w - this.w[pasts-1]
            dh := h - this.h[pasts-1]

            i := pasts-1
            while (i >= 1) {
               this.x[i] += dx
               this.y[i] += dy
               this.w[i] += dw
               this.h[i] += dh
               i--
            }
         }

         Converge(v := ""){
            v := (v) ? v : this.action.MaxIndex()

            if (v > 2) {
               this.action := [this.action[v-1], this.action[v]]
               this.a := [this.a[v-1], this.a[v]]
               this.q := [this.q[v-1], this.q[v]]
               this.x := [this.x[v-1], this.x[v]]
               this.y := [this.y[v-1], this.y[v]]
               this.w := [this.w[v-1], this.w[v]]
               this.h := [this.h[v-1], this.h[v]]
            }
         }

         Debug(function){
            v := (v) ? v : this.action.MaxIndex()
            Tooltip % function "`t" v . "`n" v-1 ": " this.action[v-1]
               . "`n" this.x[v-2] ", " this.y[v-2] ", " this.w[v-2] ", " this.h[v-2]
               . "`n" this.x[v-1] ", " this.y[v-1] ", " this.w[v-1] ", " this.h[v-1]
               . "`n" this.x[v] ", " this.y[v] ", " this.w[v] ", " this.h[v]
               . "`nAnchor:`t" this.a[v] "`nMouse:`t" this.q[v] "`t" this.isMouseInside()
         }

         Hover(){
            CoordMode, Mouse, Screen
            MouseGetPos, x_hover, y_hover
            this.x_hover := x_hover
            this.y_hover := y_hover

            ; Resets the stack to 1.
            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.action := [A_ThisFunc]
               this.a := [this.a.pop()]
               this.q := [this.q.pop()]
               this.x := [this.x.pop()]
               this.y := [this.y.pop()]
               this.w := [this.w.pop()]
               this.h := [this.h.pop()]
            }
         }

         Origin(v := ""){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse

            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.action.push(A_ThisFunc)
               this.x_hover := x_mouse
               this.y_hover := y_mouse
            }

            v := (v) ? v : this.action.MaxIndex()

            if (x_mouse != this.x_last || y_mouse != this.y_last) {
               this.x_last := x_mouse, this.y_last := y_mouse

               this.x[v] := x_mouse
               this.y[v] := y_mouse

               this.Propagate(v)
               this.Redraw(x_mouse, y_mouse, 1, 1) ;stabilize x/y corrdinates in window spy.
            }
         }

         Draw(v := ""){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse

            if (A_ThisFunc == this.action[this.action.MaxIndex()-1]){
               this.BackPropagate(this.action.MaxIndex())
               this.x_hover := x_mouse
               this.y_hover := y_mouse
               pass := 1
            }
            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.Converge()
               this.action.push(A_ThisFunc)
               this.x_hover := x_mouse
               this.y_hover := y_mouse
               pass := 1
            }

            v := (v) ? v : this.action.MaxIndex()
            dx := x_mouse - this.x_hover
            dy := y_mouse - this.y_hover
            xr := (x_mouse > this.x[v-1]) ? 1 : 0
            yr := (y_mouse > this.y[v-1]) ? 1 : 0

            if (pass == 1 || x_mouse != this.x_last || y_mouse != this.y_last) {
               this.x_last := x_mouse, this.y_last := y_mouse

               this.x[v] := (xr) ? this.x[v-1] : x_mouse
               this.y[v] := (yr) ? this.y[v-1] : y_mouse
               this.w[v] := (xr) ? x_mouse - this.x[v-1] : this.x[v-1] - x_mouse
               this.h[v] := (yr) ? y_mouse - this.y[v-1] : this.y[v-1] - y_mouse

               this.a[v] := (xr && yr) ? "top left" : (xr && !yr) ? "bottom left" : (!xr && yr) ? "top right" : "bottom right"
               this.q[v] := (xr && yr) ? "bottom right" : (xr && !yr) ? "top right" : (!xr && yr) ? "bottom left" : "top left"

               this.Propagate(v)
               this.Redraw(this.x[v], this.y[v], this.w[v], this.h[v])
            }
         }

         Move(v := ""){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse

            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.Converge()
               this.action.push(A_ThisFunc)
               this.x_hover := x_mouse
               this.y_hover := y_mouse
               pass := 1
            }

            v := (v) ? v : this.action.MaxIndex()
            dx := x_mouse - this.x_hover
            dy := y_mouse - this.y_hover

            if (pass == 1 || x_mouse != this.x_last || y_mouse != this.y_last) {
               this.x_last := x_mouse, this.y_last := y_mouse

               this.x[v] := this.x[v-1] + dx
               this.y[v] := this.y[v-1] + dy

               this.Propagate(v)
               this.Redraw(this.x[v], this.y[v], this.w[v], this.h[v])
            }
         }

         ResizeCorners(v := ""){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse

            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.Converge()
               this.action.push(A_ThisFunc)
               this.x_hover := x_mouse
               this.y_hover := y_mouse
               pass := 1
            }

            v := (v) ? v : this.action.MaxIndex()
            xr := this.x_hover - this.x[v-1] - (this.w[v-1] / 2)
            yr := this.y[v-1] - this.y_hover + (this.h[v-1] / 2) ; Keep Change Change
            dx := x_mouse - this.x_hover
            dy := y_mouse - this.y_hover

            if (pass == 1 || x_mouse != this.x_last || y_mouse != this.y_last) {
               this.x_last := x_mouse, this.y_last := y_mouse

               if (xr < -1 && yr > 1) {
                  r := "top left"
                  this.x[v] := this.x[v-1] + dx
                  this.y[v] := this.y[v-1] + dy
                  this.w[v] := this.w[v-1] - dx
                  this.h[v] := this.h[v-1] - dy
               }
               if (xr >= -1 && yr > 1) {
                  r := "top right"
                  this.x[v] := this.x[v-1]
                  this.y[v] := this.y[v-1] + dy
                  this.w[v] := this.w[v-1] + dx
                  this.h[v] := this.h[v-1] - dy
               }
               if (xr < -1 && yr <= 1) {
                  r := "bottom left"
                  this.x[v] := this.x[v-1] + dx
                  this.y[v] := this.y[v-1]
                  this.w[v] := this.w[v-1] - dx
                  this.h[v] := this.h[v-1] + dy
               }
               if (xr >= -1 && yr <= 1) {
                  r := "bottom right"
                  this.x[v] := this.x[v-1]
                  this.y[v] := this.y[v-1]
                  this.w[v] := this.w[v-1] + dx
                  this.h[v] := this.h[v-1] + dy
               }

               this.Propagate(v)
               this.Redraw(this.x[v], this.y[v], this.w[v], this.h[v])
            }
         }

         ; This works by finding the line equations of the diagonals of the rectangle.
         ; To identify the quadrant the cursor is located in, the while loop compares it's y value
         ; with the function line values f(x) = m * xr and y = -m * xr.
         ; So if yr is below both theoretical y values, then we know it's in the bottom quadrant.
         ; Be careful with this code, it converts the y plane inversely to match the Decartes tradition.

         ; Safety features include checking for past values to prevent flickering
         ; Sleep statements are required in every while loop.

         ResizeEdges(v := ""){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse

            if (A_ThisFunc != this.action[this.action.MaxIndex()]){
               this.Converge()
               this.action.push(A_ThisFunc)
               this.x_hover := x_mouse
               this.y_hover := y_mouse
               pass := 1
            }

            v := (v) ? v : this.action.MaxIndex()
            m := -(this.h[v-1] / this.w[v-1])                              ; slope (dy/dx)
            xr := this.x_hover - this.x[v-1] - (this.w[v-1] / 2)           ; draw a line across the center
            yr := this.y[v-1] - this.y_hover + (this.h[v-1] / 2)           ; draw a vertical line halfing it
            dx := x_mouse - this.x_hover
            dy := y_mouse - this.y_hover

            if (pass == 1 || x_mouse != this.x_last || y_mouse != this.y_last) {
               this.x_last := x_mouse, this.y_last := y_mouse

               if (m * xr >= yr && yr > -m * xr)
                  r := "left",                this.x[v] := this.x[v-1] + dx,               this.w[v] := this.w[v-1] - dx
               if (m * xr < yr && yr > -m * xr)
                  r := "top",                 this.y[v] := this.y[v-1] + dy,               this.h[v] := this.h[v-1] - dy
               if (m * xr < yr && yr <= -m * xr)
                  r := "right",   this.w[v] := this.w[v-1] + dx
               if (m * xr >= yr && yr <= -m * xr)
                  r := "bottom",  this.h[v] := this.h[v-1] + dy

               this.Propagate(v)
               this.Redraw(this.x[v], this.y[v], this.w[v], this.h[v])
            }
         }

         isMouseInside(){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse
            return (x_mouse >= this.x[this.x.MaxIndex()]
               && x_mouse <= this.x[this.x.MaxIndex()] + this.w[this.w.MaxIndex()]
               && y_mouse >= this.y[this.y.MaxIndex()]
               && y_mouse <= this.y[this.y.MaxIndex()] + this.h[this.h.MaxIndex()])
         }

         isMouseOutside(){
            return !this.isMouseInside()
         }

         isMouseOnCorner(){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse
            return (x_mouse == this.x[this.x.MaxIndex()] || x_mouse == this.x[this.x.MaxIndex()] + this.w[this.w.MaxIndex()])
               && (y_mouse == this.y[this.y.MaxIndex()] || y_mouse == this.y[this.y.MaxIndex()] + this.h[this.h.MaxIndex()])
         }

         isMouseOnEdge(){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse
            return ((x_mouse >= this.x[this.x.MaxIndex()] && x_mouse <= this.x[this.x.MaxIndex()] + this.w[this.w.MaxIndex()])
               && (y_mouse == this.y[this.y.MaxIndex()] || y_mouse == this.y[this.y.MaxIndex()] + this.h[this.h.MaxIndex()]))
               OR ((y_mouse >= this.y[this.y.MaxIndex()] && y_mouse <= this.y[this.y.MaxIndex()] + this.h[this.h.MaxIndex()])
               && (x_mouse == this.x[this.x.MaxIndex()] || x_mouse == this.x[this.x.MaxIndex()] + this.w[this.w.MaxIndex()]))
         }

         isMouseStopped(){
            CoordMode, Mouse, Screen
            MouseGetPos, x_mouse, y_mouse
            return x_mouse == this.x_last && y_mouse == this.y_last
         }

         ScreenshotRectangle(){
            x := this.x1(), y := this.y1(), w := this.width(), h := this.height()
            return (w > 0 && h > 0) ? [x, y, w, h] : ""
         }

         x1(){
            return this.x[this.x.MaxIndex()]
         }

         x2(){
            return this.x[this.x.MaxIndex()] + this.w[this.w.MaxIndex()]
         }

         y1(){
            return this.y[this.y.MaxIndex()]
         }

         y2(){
            return this.y[this.y.MaxIndex()] + this.h[this.h.MaxIndex()]
         }

         width(){
            return this.w[this.w.MaxIndex()]
         }

         height(){
            return this.h[this.h.MaxIndex()]
         }
      }

      Class CustomFont{
         /*
         CustomFont v2.00 (2016-2-24) by tmplinshi
         ---------------------------------------------------------
         Description: Load font from file or resource, without needed install to system.
         ---------------------------------------------------------
         Useage Examples:

            * Load From File
               font1 := New CustomFont("ewatch.ttf")
               Gui, Font, s100, ewatch

            * Load From Resource
               Gui, Add, Text, HWNDhCtrl w400 h200, 12345
               font2 := New CustomFont("res:ewatch.ttf", "ewatch", 80) ; <- Add a res: prefix to the resource name.
               font2.ApplyTo(hCtrl)

            * The fonts will removed automatically when script exits.
              To remove a font manually, just clear the variable (e.g. font1 := "").
         */

         static FR_PRIVATE  := 0x10

         __New(FontFile, FontName="", FontSize=30) {
            if RegExMatch(FontFile, "i)res:\K.*", _FontFile) {
               this.AddFromResource(_FontFile, FontName, FontSize)
            } else {
               this.AddFromFile(FontFile)
            }
         }

         AddFromFile(FontFile) {
            DllCall( "AddFontResourceEx", "Str", FontFile, "UInt", this.FR_PRIVATE, "UInt", 0 )
            this.data := FontFile
         }

         AddFromResource(ResourceName, FontName, FontSize = 30) {
            static FW_NORMAL := 400, DEFAULT_CHARSET := 0x1

            nSize    := this.ResRead(fData, ResourceName)
            fh       := DllCall( "AddFontMemResourceEx", "Ptr", &fData, "UInt", nSize, "UInt", 0, "UIntP", nFonts )
            hFont    := DllCall( "CreateFont", Int,FontSize, Int,0, Int,0, Int,0, UInt,FW_NORMAL, UInt,0
                        , Int,0, Int,0, UInt,DEFAULT_CHARSET, Int,0, Int,0, Int,0, Int,0, Str,FontName )

            this.data := {fh: fh, hFont: hFont}
         }

         ApplyTo(hCtrl) {
            SendMessage, 0x30, this.data.hFont, 1,, ahk_id %hCtrl%
         }

         __Delete() {
            if IsObject(this.data) {
               DllCall( "RemoveFontMemResourceEx", "UInt", this.data.fh    )
               DllCall( "DeleteObject"           , "UInt", this.data.hFont )
            } else {
               DllCall( "RemoveFontResourceEx"   , "Str", this.data, "UInt", this.FR_PRIVATE, "UInt", 0 )
            }
         }

         ; ResRead() By SKAN, from http://www.autohotkey.com/board/topic/57631-crazy-scripting-resource-only-dll-for-dummies-36l-v07/?p=609282
         ResRead( ByRef Var, Key ) {
            VarSetCapacity( Var, 128 ), VarSetCapacity( Var, 0 )
            If ! ( A_IsCompiled ) {
               FileGetSize, nSize, %Key%
               FileRead, Var, *c %Key%
               Return nSize
            }

            If hMod := DllCall( "GetModuleHandle", UInt,0 )
               If hRes := DllCall( "FindResource", UInt,hMod, Str,Key, UInt,10 )
                  If hData := DllCall( "LoadResource", UInt,hMod, UInt,hRes )
                     If pData := DllCall( "LockResource", UInt,hData )
                        Return VarSetCapacity( Var, nSize := DllCall( "SizeofResource", UInt,hMod, UInt,hRes ) )
                           ,  DllCall( "RtlMoveMemory", Str,Var, UInt,pData, UInt,nSize )
            Return 0
         }
      }

      class Image{

         ScreenWidth := A_ScreenWidth, ScreenHeight := A_ScreenHeight

         __New(name := "") {
            this.name := name := (name == "") ? Vis2.Graphics.Name() "_Graphics_Image" : name "_Graphics_Image"

            Vis2.Graphics.Startup()
            Gui, %name%: New, +LastFound +AlwaysOnTop -Caption -DPIScale +E0x80000 +ToolWindow +hwndSecretName, % this.name
            this.hwnd := SecretName
            DllCall("ShowWindow", "ptr",this.hwnd, "int",8)
            this.hbm := CreateDIBSection(A_ScreenWidth, A_ScreenHeight)
            this.hdc := CreateCompatibleDC()
            this.obm := SelectObject(this.hdc, this.hbm)
            this.G := Gdip_GraphicsFromHDC(this.hdc)
            Gdip_SetInterpolationMode(this.G, 7)
         }

         __Delete(){
            Vis2.Graphics.Shutdown()
         }

         Border() {
            Gui, % this.name ": +Border"
            UpdateLayeredWindow(this.hwnd, this.hdc, (A_ScreenWidth-this.w)/2, (A_ScreenHeight-this.h)/2, this.w, this.h)
            return this
         }

         Destroy() {
            SelectObject(this.hdc, this.obm)
            DeleteObject(this.hbm)
            DeleteDC(this.hdc)
            Gdip_DeleteGraphics(this.G)
            Gui, % this.name ":Destroy"
            return this
         }

         Hide() {
            Gui, % this.name ":Show", Hide
            return this
         }

         Show() {
            Gui, % this.name ":Show", NoActivate
            return this
         }

         ToggleVisible() {
            if DllCall("IsWindowVisible", "UInt", this.hwnd)
               Gui, % this.name ":Show", Hide
            else
               Gui, % this.name ":Show", NoActivate
            return this
         }

         isVisible() {
            return DllCall("IsWindowVisible", "UInt", this.hwnd)
         }

         Render(file, scale := 1, time := 0) {
            if (this.hwnd){
               this.scale := scale
               if (time) {
                  self_destruct := ObjBindMethod(this, "Destroy")
                  SetTimer, % self_destruct, % -1 * time
               }
               Critical On
               if FileExist(file)
                  f := pBitmap := Gdip_CreateBitmapFromFile(file)
               else pBitmap := file
               Width := Gdip_GetImageWidth(pBitmap), Height := Gdip_GetImageHeight(pBitmap)
               this.DetectScreenResolutionChange(Width, Height)
               Gdip_DrawImage(this.G, pBitmap, 0, 0, Floor(Width*scale), Floor(Height*scale), 0, 0, Width, Height)
               UpdateLayeredWindow(this.hwnd, this.hdc, 0, 0, Floor(Width*scale), Floor(Height*scale))
               this.w := Floor(Width*scale)
               this.h := Floor(Height*scale)
               if f
                  Gdip_DisposeImage(pBitmap)
               Critical Off
               return this
            }
            else {
               parent := ((___ := RegExReplace(A_ThisFunc, "^(.*)\..*\..*$", "$1")) != A_ThisFunc) ? ___ : ""
               Loop, Parse, parent, .
                  parent := (A_Index=1) ? %A_LoopField% : parent[A_LoopField]
               _image := (parent) ? new parent.image() : new image()
               return _image.Render(file, scale, time)
            }
         }

         DetectScreenResolutionChange(w:="", h:=""){
            w := (w) ? w : A_ScreenWidth
            h := (h) ? h : A_ScreenHeight
            if (this.ScreenWidth != w || this.ScreenHeight != h) {
               this.ScreenWidth := w, this.ScreenHeight := h
               SelectObject(this.hdc, this.obm)
               DeleteObject(this.hbm)
               DeleteDC(this.hdc)
               Gdip_DeleteGraphics(this.G)
               this.hbm := CreateDIBSection(this.ScreenWidth, this.ScreenHeight)
               this.hdc := CreateCompatibleDC()
               this.obm := SelectObject(this.hdc, this.hbm)
               this.G := Gdip_GraphicsFromHDC(this.hdc)
               Gdip_SetInterpolationMode(this.G, 7)
            }
         }
      }

      class Subtitle{

         layers := {}, ScreenWidth := A_ScreenWidth, ScreenHeight := A_ScreenHeight

         __New(name := ""){
            parent := ((___ := RegExReplace(A_ThisFunc, "^(.*)\..*\..*$", "$1")) != A_ThisFunc) ? ___ : ""
            Loop, Parse, parent, .
               this.parent := (A_Index=1) ? %A_LoopField% : this.parent[A_LoopField]

            this.parent.Startup()
            Gui, New, +LastFound +AlwaysOnTop -Caption -DPIScale +E0x80000 +ToolWindow +hwndSecretName
            this.hwnd := SecretName
            this.name := (name != "") ? name "_Subtitle" : "Subtitle_" this.hwnd
            DllCall("ShowWindow", "ptr",this.hwnd, "int",8)
            DllCall("SetWindowText", "ptr",this.hwnd, "str",this.name)
            this.hbm := CreateDIBSection(this.ScreenWidth, this.ScreenHeight)
            this.hdc := CreateCompatibleDC()
            this.obm := SelectObject(this.hdc, this.hbm)
            this.G := Gdip_GraphicsFromHDC(this.hdc)
            this.colorMap := this.colorMap()
         }

         __Delete(){
            this.parent.Shutdown()
         }

         FreeMemory(){
            SelectObject(this.hdc, this.obm)
            DeleteObject(this.hbm)
            DeleteDC(this.hdc)
            Gdip_DeleteGraphics(this.G)
            return this
         }

         Destroy(){
            this.FreeMemory()
            DllCall("DestroyWindow", "ptr",this.hwnd)
            return this
         }

         Hide(){
            DllCall("ShowWindow", "ptr",this.hwnd, "int",0)
            return this
         }

         Show(){
            DllCall("ShowWindow", "ptr",this.hwnd, "int",8)
            return this
         }

         ToggleVisible(){
            this.isVisible() ? this.Hide() : this.Show()
            return this
         }

         isVisible(){
            return DllCall("IsWindowVisible", "ptr",this.hwnd)
         }

         ClickThrough(){
            DetectHiddenWindows On
            WinSet, ExStyle, +0x20, % "ahk_id" this.hwnd
            DetectHiddenWindows Off
            return this
         }

         DetectScreenResolutionChange(w:="", h:=""){
            w := (w) ? w : A_ScreenWidth
            h := (h) ? h : A_ScreenHeight
            if (this.ScreenWidth != w || this.ScreenHeight != h) {
               this.ScreenWidth := w, this.ScreenHeight := h
               SelectObject(this.hdc, this.obm)
               DeleteObject(this.hbm)
               DeleteDC(this.hdc)
               Gdip_DeleteGraphics(this.G)
               this.hbm := CreateDIBSection(this.ScreenWidth, this.ScreenHeight)
               this.hdc := CreateCompatibleDC()
               this.obm := SelectObject(this.hdc, this.hbm)
               this.G := Gdip_GraphicsFromHDC(this.hdc)
            }
         }

         Draw(text := "", style1 := "", style2 := "", pGraphics := "") {
            if (pGraphics == "") {
               pGraphics := this.G
               if (this.rendered == true) {
                  this.rendered := false
                  this.layers := {}
                  this.x := this.y := this.xx := this.yy := ""
                  Gdip_GraphicsClear(this.G)
               }
               this.layers.push([text, style1, style2])
            }

            ; Remember styles so that they can be loaded next time.
            style1 := (style1) ? this.style1 := style1 : this.style1
            style2 := (style2) ? this.style2 := style2 : this.style2

            static q1 := "i)^.*?(?<!-|:|:\s)\b(?![^\(]*\))"
            static q2 := "(:\s?)?\(?(?<value>(?<=\()[\s\-\da-z\.#%]+(?=\))|[\-\da-z\.#%]+).*$"

            time := (style1.t) ? style1.t : (style1.time) ? style1.time
                  : (!IsObject(style1) && (___ := RegExReplace(style1, q1 "(t(ime)?)" q2, "${value}")) != style1) ? ___
                  : (style2.t) ? style2.t : (style2.time) ? style2.time
                  : (!IsObject(style2) && (___ := RegExReplace(style2, q1 "(t(ime)?)" q2, "${value}")) != style2) ? ___
                  : 0

            if (time) {
               self_destruct := ObjBindMethod(this, "Destroy")
               SetTimer, % self_destruct, % -1 * time
            }

            static alpha := "^[A-Za-z]+$"
            static decimal := "^(\-?\d+(\.\d*)?)$"
            static integer := "^\d+$"
            static percentage := "^(\-?\d+(?:\.\d*)?)%$"
            static positive := "^\d+(\.\d*)?$"

            if IsObject(style1){
               _a  := (style1.a != "")  ? style1.a  : style1.anchor
               _x  := (style1.x != "")  ? style1.x  : style1.left
               _y  := (style1.y != "")  ? style1.y  : style1.top
               _w  := (style1.w != "")  ? style1.w  : style1.width
               _h  := (style1.h != "")  ? style1.h  : style1.height
               _r  := (style1.r != "")  ? style1.r  : style1.radius
               _c  := (style1.c != "")  ? style1.c  : style1.color
               _m  := (style1.m != "")  ? style1.m  : style1.margin
               _p  := (style1.p != "")  ? style1.p  : style1.padding
               _q  := (style1.q != "")  ? style1.q  : (style1.quality) ? style1.quality : style1.SmoothingMode
            } else {
               _a  := ((___ := RegExReplace(style1, q1    "(a(nchor)?)"            q2, "${value}")) != style1) ? ___ : ""
               _x  := ((___ := RegExReplace(style1, q1    "(x|left)"               q2, "${value}")) != style1) ? ___ : ""
               _y  := ((___ := RegExReplace(style1, q1    "(y|top)"                q2, "${value}")) != style1) ? ___ : ""
               _w  := ((___ := RegExReplace(style1, q1    "(w(idth)?)"             q2, "${value}")) != style1) ? ___ : ""
               _h  := ((___ := RegExReplace(style1, q1    "(h(eight)?)"            q2, "${value}")) != style1) ? ___ : ""
               _r  := ((___ := RegExReplace(style1, q1    "(r(adius)?)"            q2, "${value}")) != style1) ? ___ : ""
               _c  := ((___ := RegExReplace(style1, q1    "(c(olor)?)"             q2, "${value}")) != style1) ? ___ : ""
               _m  := ((___ := RegExReplace(style1, q1    "(m(argin)?)"            q2, "${value}")) != style1) ? ___ : ""
               _p  := ((___ := RegExReplace(style1, q1    "(p(adding)?)"           q2, "${value}")) != style1) ? ___ : ""
               _q  := ((___ := RegExReplace(style1, q1    "(q(uality)?)"           q2, "${value}")) != style1) ? ___ : ""
            }

            if IsObject(style2){
               a  := (style2.a != "")  ? style2.a  : style2.anchor
               x  := (style2.x != "")  ? style2.x  : style2.left
               y  := (style2.y != "")  ? style2.y  : style2.top
               w  := (style2.w != "")  ? style2.w  : style2.width
               h  := (style2.h != "")  ? style2.h  : style2.height
               m  := (style2.m != "")  ? style2.m  : style2.margin
               f  := (style2.f != "")  ? style2.f  : style2.font
               s  := (style2.s != "")  ? style2.s  : style2.size
               c  := (style2.c != "")  ? style2.c  : style2.color
               b  := (style2.b != "")  ? style2.b  : style2.bold
               i  := (style2.i != "")  ? style2.i  : style2.italic
               u  := (style2.u != "")  ? style2.u  : style2.underline
               j  := (style2.j != "")  ? style2.j  : style2.justify
               n  := (style2.n != "")  ? style2.n  : style2.noWrap
               z  := (style2.z != "")  ? style2.z  : style2.condensed
               d  := (style2.d != "")  ? style2.d  : style2.dropShadow
               o  := (style2.o != "")  ? style2.o  : style2.outline
               q  := (style2.q != "")  ? style2.q  : (style2.quality) ? style2.quality : style2.TextRenderingHint
            } else {
               a  := ((___ := RegExReplace(style2, q1    "(a(nchor)?)"            q2, "${value}")) != style2) ? ___ : ""
               x  := ((___ := RegExReplace(style2, q1    "(x|left)"               q2, "${value}")) != style2) ? ___ : ""
               y  := ((___ := RegExReplace(style2, q1    "(y|top)"                q2, "${value}")) != style2) ? ___ : ""
               w  := ((___ := RegExReplace(style2, q1    "(w(idth)?)"             q2, "${value}")) != style2) ? ___ : ""
               h  := ((___ := RegExReplace(style2, q1    "(h(eight)?)"            q2, "${value}")) != style2) ? ___ : ""
               m  := ((___ := RegExReplace(style2, q1    "(m(argin)?)"            q2, "${value}")) != style2) ? ___ : ""
               f  := ((___ := RegExReplace(style2, q1    "(f(ont)?)"              q2, "${value}")) != style2) ? ___ : ""
               s  := ((___ := RegExReplace(style2, q1    "(s(ize)?)"              q2, "${value}")) != style2) ? ___ : ""
               c  := ((___ := RegExReplace(style2, q1    "(c(olor)?)"             q2, "${value}")) != style2) ? ___ : ""
               b  := ((___ := RegExReplace(style2, q1    "(b(old)?)"              q2, "${value}")) != style2) ? ___ : ""
               i  := ((___ := RegExReplace(style2, q1    "(i(talic)?)"            q2, "${value}")) != style2) ? ___ : ""
               u  := ((___ := RegExReplace(style2, q1    "(u(nderline)?)"         q2, "${value}")) != style2) ? ___ : ""
               j  := ((___ := RegExReplace(style2, q1    "(j(ustify)?)"           q2, "${value}")) != style2) ? ___ : ""
               n  := ((___ := RegExReplace(style2, q1    "(n(oWrap)?)"            q2, "${value}")) != style2) ? ___ : ""
               z  := ((___ := RegExReplace(style2, q1    "(z|condensed?)"         q2, "${value}")) != style2) ? ___ : ""
               d  := ((___ := RegExReplace(style2, q1    "(d(ropShadow)?)"        q2, "${value}")) != style2) ? ___ : ""
               o  := ((___ := RegExReplace(style2, q1    "(o(utline)?)"           q2, "${value}")) != style2) ? ___ : ""
               q  := ((___ := RegExReplace(style2, q1    "(q(uality)?)"           q2, "${value}")) != style2) ? ___ : ""
            }

            ; Step 1 - Simulate string width and height, setting only the variables we need to determine it.
            style += (b) ? 1 : 0      ; bold
            style += (i) ? 2 : 0      ; italic
            style += (u) ? 4 : 0      ; underline
            style += (strike) ? 8 : 0 ; strikeout, not implemented.
            s  := (s ~= percentage) ? A_ScreenHeight * SubStr(s, 1, -1)  / 100 :  s
            s  := (s ~= positive) ? s : 36
            q  := (q >= 0 && q <= 5) ? q : 4
            n  := (n) ? 0x4000 | 0x1000 : 0x4000
            j  := (j ~= "i)cent(er|re)") ? 1 : (j ~= "i)(far|right)") ? 2 : 0
            _q := (_q >= 0 && _q <= 4) ? _q : 4

            Gdip_SetSmoothingMode(pGraphics, _q)
            Gdip_SetTextRenderingHint(pGraphics, q) ; 4 = Anti-Alias, 5 = Cleartype
            hFamily := (___ := Gdip_FontFamilyCreate(f)) ? ___ : Gdip_FontFamilyCreate("Arial")
            hFont := Gdip_FontCreate(hFamily, s, style)
            hFormat := Gdip_StringFormatCreate(n)
            Gdip_SetStringFormatAlign(hFormat, j)

            CreateRectF(RC, 0, 0, 0, 0)
            ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, RC)
            ReturnRC := StrSplit(ReturnRC, "|")

            ; Step 2 - Define margins and padding.
            _m := this.margin(_m)
            _p := this.margin(_p)
             m := this.margin( m)
             p := this.margin( p)

            ; Bonus - Condense Text using a Condensed Font if simulated text width exceeds screen width.
            if (___ := Gdip_FontFamilyCreate(z)) {
               ExtraMargin := (_m.2 + _m.4 + _p.2 + _p.4)
               if (ReturnRC[3] + ExtraMargin > A_ScreenWidth){
                  hFamily := Gdip_FontFamilyCreate(z)
                  hFont := Gdip_FontCreate(hFamily, s, style)
                  ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, RC)
                  ReturnRC := StrSplit(ReturnRC, "|")
                  _w  := ReturnRC[3]
               }
            }

            ; Step 3 - Define _width and _height. Do not modify with margin and padding.
            _w  := (_w  ~= percentage) ? A_ScreenWidth  * SubStr(_w, 1, -1)  / 100 : _w
            _h  := (_h  ~= percentage) ? A_ScreenHeight * SubStr(_h, 1, -1)  / 100 : _h
            _w  := (_w  ~= positive) ? _w  : ReturnRC[3]
            _h  := (_h  ~= positive) ? _h  : ReturnRC[4]

            ; Step 4 - Define _anchor with a default value of 1.
            _a  := (_a = "top") ? 2 : (_a = "left") ? 4 : (_a = "right") ? 6 : (_a = "bottom") ? 8
                  : (_a ~= "i)top" && _a ~= "i)left") ? 1 : (_a ~= "i)top" && _a ~= "i)cent(er|re)") ? 2
                  : (_a ~= "i)top" && _a ~= "i)bottom") ? 3 : (_a ~= "i)cent(er|re)" && _a ~= "i)left") ? 4
                  : (_a ~= "i)cent(er|re)") ? 5 : (_a ~= "i)cent(er|re)" && _a ~= "i)bottom") ? 6
                  : (_a ~= "i)bottom" && _a ~= "i)left") ? 7 : (_a ~= "i)bottom" && _a ~= "i)cent(er|re)") ? 8
                  : (_a ~= "i)bottom" && _a ~= "i)right") ? 9 : (_a ~= "^[1-9]$") ? _a : 1 ; default

            ; Step 5 - Modify _anchor with _x and _y.
            _a  := (_x  = "left") ? 1+(((_a-1)//3)*3) : (_x ~= "i)cent(er|re)") ? 2+(((_a-1)//3)*3) : (_x = "right") ? 3+(((_a-1)//3)*3) : _a
            _a  := (_y  = "top") ? 1+(mod(_a-1,3)) : (_y ~= "i)cent(er|re)") ? 4+(mod(_a-1,3)) : (_y = "bottom") ? 7+(mod(_a-1,3)) : _a

            ; Step 6 - Define _x and _y with respect to _anchor.
            _x  := (_x  = "left") ? 0 : (_x ~= "i)cent(er|re)") ? 0.5*A_ScreenWidth : (_x = "right") ? A_ScreenWidth : _x
            _y  := (_y  = "top") ? 0 : (_y ~= "i)cent(er|re)") ? 0.5*A_ScreenHeight : (_y = "bottom") ? A_ScreenHeight : _y
            _x  := (_x  ~= percentage) ? A_ScreenWidth  * SubStr(_x, 1, -1)  / 100 : _x
            _y  := (_y  ~= percentage) ? A_ScreenHeight * SubStr(_y, 1, -1)  / 100 : _y
            _x  := (_x  ~= decimal) ? _x  : 0
            _y  := (_y  ~= decimal) ? _y  : 0
            _x  -= (mod(_a-1,3) == 0) ? 0 : (mod(_a-1,3) == 1) ? _w/2 : (mod(_a-1,3) == 2) ? _w : 0
            _y  -= (((_a-1)//3) == 0) ? 0 : (((_a-1)//3) == 1) ? _h/2 : (((_a-1)//3) == 2) ? _h : 0
            ; Fractional y values might cause gdi+ slowdown.


            ; Round 1 - Define width and height.
            w  := ( w  ~= percentage) ? _w * RegExReplace( w, percentage, "$1")  / 100 : w
            h  := ( h  ~= percentage) ? _h * RegExReplace( h, percentage, "$1")  / 100 : h
            w  := ( w  ~= positive) ?  w  : (_w) ? _w : ReturnRC[3] ;if _w = 0
            h  := ( h  ~= positive) ?  h  : (_h) ? _h : ReturnRC[4]

            ; Round 2 - Define anchor.
            a  := (a = "top") ? 2 : (a = "left") ? 4 : (a = "right") ? 6 : (a = "bottom") ? 8
                  : (a ~= "i)top" && a ~= "i)left") ? 1 : (a ~= "i)top" && a ~= "i)cent(er|re)") ? 2
                  : (a ~= "i)top" && a ~= "i)bottom") ? 3 : (a ~= "i)cent(er|re)" && a ~= "i)left") ? 4
                  : (a ~= "i)cent(er|re)") ? 5 : (_a ~= "i)cent(er|re)" && a ~= "i)bottom") ? 6
                  : (a ~= "i)bottom" && a ~= "i)left") ? 7 : (a ~= "i)bottom" && a ~= "i)cent(er|re)") ? 8
                  : (a ~= "i)bottom" && a ~= "i)right") ? 9 : (a ~= "^[1-9]$") ? a : 1 ; default

            a  := ( x  = "left") ? 1+((( a-1)//3)*3) : ( x ~= "i)cent(er|re)") ? 2+((( a-1)//3)*3) : ( x = "right") ? 3+((( a-1)//3)*3) :  a
            a  := ( y  = "top") ? 1+(mod( a-1,3)) : ( y ~= "i)cent(er|re)") ? 4+(mod( a-1,3)) : ( y = "bottom") ? 7+(mod( a-1,3)) :  a

            ; Round 3 - Define x and y with respect to anchor.
            x  := ( x  = "left") ? _x : (x ~= "i)cent(er|re)") ? _x + 0.5*_w : (x = "right") ? _x + _w : x
            y  := ( y  = "top") ? _y : (y ~= "i)cent(er|re)") ? _y + 0.5*_h : (y = "bottom") ? _y + _h : y
            x  := ( x  ~= percentage) ? _x + (_w * RegExReplace( x, percentage, "$1")  / 100) : x
            y  := ( y  ~= percentage) ? _y + (_h * RegExReplace( y, percentage, "$1")  / 100) : y
            x  := ( x  ~= decimal) ? x  : _x
            y  := ( y  ~= decimal) ? y  : _y
            x  -= (mod(a-1,3) == 0) ? 0 : (mod(a-1,3) == 1) ? ReturnRC[3]/2 : (mod(a-1,3) == 2) ? ReturnRC[3] : 0
            y  -= (((a-1)//3) == 0) ? 0 : (((a-1)//3) == 1) ? ReturnRC[4]/2 : (((a-1)//3) == 2) ? ReturnRC[4] : 0

            ; Round 4 - Modify _x, _y, _w, _h with margin and padding.
            if (_w && _h) {
               _w  += (_m.2 + _m.4 + _p.2 + _p.4) + (m.2 + m.4 + p.2 + p.4)
               _h  += (_m.1 + _m.3 + _p.1 + _p.3) + (m.1 + m.3 + p.1 + p.3)
               _x  -= (_m.1 + _p.1)
               _y  -= (_m.4 + _p.4)
            }

            ; Round 5 - Modify x, y with margin and padding.
            x  += (m.1 + p.1)
            y  += (m.4 + p.4)

            ; Round 6 - Define radius of rounded corners.
            _smaller := (_w > _h) ? _h : _w
            _r  := (_r  ~= percentage) ? _smaller * RegExReplace(_r, percentage, "$1")  / 100 : _r
            _r  := (_r  <= _smaller / 2 && _r ~= positive) ? _r : 0

            ; Round 7 - Define color.
            _c := this.color(_c, 0xDD424242)
             c := this.color( c, 0xFFFFFFFF)

            ; Round 8 - Define outline and dropShadow.
            o := this.outline(o)
            d := this.dropShadow(d)

            ; Round 9 - Define Text
            if (!A_IsUnicode){
               nSize := DllCall("MultiByteToWideChar", "uint",0, "uint",0, "ptr",&text, "int",-1, "ptr",0, "int",0)
               VarSetCapacity(wtext, nSize*2)
               DllCall("MultiByteToWideChar", "uint",0, "uint",0, "ptr",&text, "int",-1, "ptr",&wtext, "int",nSize)
            }


            ; Draw 1 - Background
            if (_w && _h && _c && (_c & 0xFF000000)) {
               pBrushBackground := Gdip_BrushCreateSolid(_c)
               Gdip_FillRoundedRectangle(pGraphics, pBrushBackground, _x, _y, _w, _h, _r)
               Gdip_DeleteBrush(pBrushBackground)
            }

            ; Draw 2 - DropShadow
            if (!d.void) {
               delta := 2*d.3 + 2*o.1
               offset := d.3 + o.1

               if (d.3) {
                  pBitmap := Gdip_CreateBitmap(w + delta, h + delta)
                  pGraphicsDropShadow := Gdip_GraphicsFromImage(pBitmap)
                  Gdip_SetSmoothingMode(pGraphicsDropShadow, _q)
                  Gdip_SetTextRenderingHint(pGraphicsDropShadow, q)
                  CreateRectF(RC, offset, offset, w + delta, h + delta)
               } else {
                  CreateRectF(RC, x + d.1, y + d.2, w, h)
                  pGraphicsDropShadow := pGraphics
               }

               if (!o.void)
               {
                  DllCall("gdiplus\GdipCreatePath", "int",1, "uptr*",pPath)
                  DllCall("gdiplus\GdipAddPathString", "ptr",pPath, "ptr", A_IsUnicode ? &text : &wtext, "int",-1
                                                     , "ptr",hFamily, "int",style, "float",s, "ptr",&RC, "ptr",hFormat)
                  pPen := Gdip_CreatePen(d.4, o.1)
                  DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPen, "uInt",2)
                  DllCall("gdiplus\GdipDrawPath", "ptr",pGraphicsDropShadow, "ptr",pPen, "ptr",pPath)
                  Gdip_DeletePen(pPen)
                  pBrush := Gdip_BrushCreateSolid(d.4)
                  Gdip_SetCompositingMode(pGraphicsDropShadow, 1) ; Turn off alpha blending
                  Gdip_SetSmoothingMode(pGraphicsDropShadow, 3)   ; Turn off anti-aliasing
                  Gdip_FillPath(pGraphicsDropShadow, pBrush, pPath)
                  Gdip_DeleteBrush(pBrush)
                  Gdip_DeletePath(pPath)
                  Gdip_SetCompositingMode(pGraphicsDropShadow, 0)
                  Gdip_SetSmoothingMode(pGraphicsDropShadow, _q)
               }
               else
               {
                  pBrush := Gdip_BrushCreateSolid(d.4)
                  Gdip_DrawString(pGraphicsDropShadow, Text, hFont, hFormat, pBrush, RC)
                  Gdip_DeleteBrush(pBrush)
               }

               if (d.3) {
                  Gdip_DeleteGraphics(pGraphicsDropShadow)
                  pBlur := Gdip_BlurBitmap(pBitmap, d.3)
                  Gdip_DisposeImage(pBitmap)
                  Gdip_DrawImage(pGraphics, pBlur, x + d.1 - offset, y + d.2 - offset, w + delta, h + delta)
                  Gdip_DisposeImage(pBlur)
               }
            }

            ; Draw 3 - Text Outline
            if (!o.void) {
               ; Convert our text to a path.
               CreateRectF(RC, x, y, w, h)
               DllCall("gdiplus\GdipCreatePath", "int",1, "uptr*",pPath)
               DllCall("gdiplus\GdipAddPathString", "ptr",pPath, "ptr", A_IsUnicode ? &text : &wtext, "int",-1
                                                  , "ptr",hFamily, "int",style, "float",s, "ptr",&RC, "ptr",hFormat)

               ; Create a pen.
               pPen := Gdip_CreatePen(o.2, o.1)
               DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPen, "uint",2)

               ; Create a glow effect around the edges.
               if (o.3) {
                  DllCall("gdiplus\GdipClonePath", "ptr",pPath, "uptr*",pPathGlow)
                  DllCall("gdiplus\GdipWidenPath", "ptr",pPathGlow, "ptr",pPen, "ptr",0, "float",1)

                  ; Set color to glowColor or use the previous color.
                  color := (o.4) ? o.4 : o.2

                  loop % o.3
                  {
                     ARGB := Format("0x{:02X}",((color & 0xFF000000) >> 24)/o.3) . Format("{:06X}",(color & 0x00FFFFFF))
                     pPenGlow := Gdip_CreatePen(ARGB, A_Index)
                     DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPenGlow, "uInt",2)
                     DllCall("gdiplus\GdipDrawPath", "ptr",pGraphics, "ptr",pPenGlow, "ptr",pPathGlow)
                     Gdip_DeletePen(pPenGlow)
                  }
                  Gdip_DeletePath(pPathGlow)
               }

               ; Draw outline text.
               if (o.1)
                  DllCall("gdiplus\GdipDrawPath", "ptr",pGraphics, "ptr",pPen, "ptr",pPath)

               ; Fill outline text.
               if (c && (c & 0xFF000000)) {
                  pBrush := Gdip_BrushCreateSolid(c)
                  Gdip_FillPath(pGraphics, pBrush, pPath)
                  Gdip_DeleteBrush(pBrush)
               }
               Gdip_DeletePen(pPen)
               Gdip_DeletePath(pPath)
            }

            ; Draw Text
            if (text != "" && d.void && o.void) {
               CreateRectF(RC, x, y, w, h)
               pBrushText := Gdip_BrushCreateSolid(c)
               Gdip_DrawString(pGraphics, text, hFont, hFormat, pBrushText, RC)
               Gdip_DeleteBrush(pBrushText)
            }

            ; Complete
            Gdip_DeleteStringFormat(hFormat)
            Gdip_DeleteFont(hFont)
            Gdip_DeleteFontFamily(hFamily)

            ; Correct Offsets
            _w := (_w == 0) ? (ReturnRC[3] + d.1 + 2*d.3 + 2*o.1 + 2*o.3) : _w
            _h := (_h == 0) ? (ReturnRC[4] + d.2 + 2*d.3 + 2*o.1 + 2*o.3) : _h

            this.x  := (this.x  = "" || _x < this.x) ? _x : this.x
            this.y  := (this.y  = "" || _y < this.y) ? _y : this.y
            this.xx := (this.xx = "" || _x + _w > this.xx) ? _x + _w : this.xx
            this.yy := (this.yy = "" || _y + _h > this.yy) ? _y + _h : this.yy
            return
         }

         Render(text := "", style1 := "", style2 := "", update := 1){
            if (this.hWnd){
               Critical On
               this.DetectScreenResolutionChange()
               this.Draw(text, style1, style2)
               if (update)
                  UpdateLayeredWindow(this.hwnd, this.hdc, 0, 0, A_ScreenWidth, A_ScreenHeight)
               this.rendered := true
               Critical Off
               return this
            }
            else {
               parent := ((___ := RegExReplace(A_ThisFunc, "^(.*)\..*\..*$", "$1")) != A_ThisFunc) ? ___ : ""
               Loop, Parse, parent, .
                  parent := (A_Index=1) ? %A_LoopField% : parent[A_LoopField]
               _subtitle := (parent) ? new parent.Subtitle() : new Subtitle()
               return _subtitle.Render(text, style1, style2, update)
            }
         }

         Bitmap(x:=0, y:=0, w:=0, h:=0){
            pBitmap := Gdip_CreateBitmap(A_ScreenWidth, A_ScreenHeight)
            pGraphics := Gdip_GraphicsFromImage(pBitmap)
            loop % this.layers.MaxIndex()
               this.Draw(this.layers[A_Index].1, this.layers[A_Index].2, this.layers[A_Index].3, pGraphics)
            Gdip_DeleteGraphics(pGraphics)

            if (x || y || w || h) {
               w := (w = 0) ? A_ScreenWidth, h := (h = 0) ? A_ScreenHeight
               pBitmap2 := Gdip_CloneBitmapArea(pBitmap, x, y, w, h)
               Gdip_DisposeImage(pBitmap)
               pBitmap := pBitmap2
            }
            return pBitmap ; Please dispose of this image responsibly.
         }

         Save(filename := "", quality := 92, fullscreen := 0){
            filename := (filename ~= "i)\.(bmp|dib|rle|jpg|jpeg|jpe|jfif|gif|tif|tiff|png)$") ? filename
                      : (filename != "") ? filename ".png" : this.name ".png"
            pBitmap := (fullscreen) ? this.Bitmap() : this.Bitmap(this.x, this.y, this.xx - this.x, this.yy - this.y)
            Gdip_SaveBitmapToFile(pBitmap, filename, quality)
            Gdip_DisposeImage(pBitmap)
         }

         SaveFullScreen(filename := "", quality := ""){
            return this.Save(filename, quality, 1)
         }

         hBitmap(alpha := 0xFFFFFFFF){
            ; hBitmap converts alpha channel to specified alpha color.
            ; Add 1 pixel because Anti-Alias (SmoothingMode = 4)
            ; Should it be crop 1 pixel instead?
            pBitmap := this.Bitmap(this.x, this.y, this.xx - this.x, this.yy - this.y)
            hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap, alpha)
            Gdip_DisposeImage(pBitmap)
            return hBitmap
         }

         RenderToHBitmap(text := "", style1 := "", style2 := ""){
            if (this.hWnd){
               this.Render(text, style1, style2, 0)
               return this.hBitmap()
            }
            else {
               parent := ((___ := RegExReplace(A_ThisFunc, "^(.*)\..*\..*$", "$1")) != A_ThisFunc) ? ___ : ""
               Loop, Parse, parent, .
                  parent := (A_Index=1) ? %A_LoopField% : parent[A_LoopField]
               _subtitle := (parent) ? new parent.Subtitle() : new Subtitle()
               _subtitle.Render(text, style1, style2, 0)
               return _subtitle.hBitmap() ; Does not return a subtitle object.
            }
         }

         hIcon(){
            pBitmap := this.Bitmap(this.x, this.y, this.xx - this.x, this.yy - this.y)
            hIcon := Gdip_CreateHICONFromBitmap(pBitmap)
            Gdip_DisposeImage(pBitmap)
            return hIcon
         }

         color(c, default := 0xDD424242){
            static colorRGB  := "^0x([0-9A-Fa-f]{6})$"
            static colorARGB := "^0x([0-9A-Fa-f]{8})$"
            static hex6      :=   "^([0-9A-Fa-f]{6})$"
            static hex8      :=   "^([0-9A-Fa-f]{8})$"

            if ObjGetCapacity([c], 1){
               c  := (c ~= "^#") ? SubStr(c, 2) : c
               c  := ((___ := this.colorMap[c]) != "") ? ___ : c
               c  := (c ~= colorRGB) ? "0xFF" RegExReplace(c, colorRGB, "$1") : (c ~= hex8) ? "0x" c : (c ~= hex6) ? "0xFF" c : c
               c  := (c ~= colorARGB) ? c : default
            }
            return (c != "") ? c : default
         }

         margin(m, default := 0){
            static percentage := "^(\-?\d+(?:\.\d*)?)%$"
            static positive := "^\d+(\.\d*)?$"

            if IsObject(m){
               m.1 := (m.y  != "") ? m.y  : m.top
               m.2 := (m.x2 != "") ? m.x2 : m.right
               m.3 := (m.y2 != "") ? m.y2 : m.bottom
               m.4 := (m.x  != "") ? m.x  : m.left
            }
            else if (m) {
               m   := StrSplit(m, " ")
               if (m.length() == 3)
                  m.4 := m.2
               else if (m.length() == 2)
                  m.4 := m.2, m.3 := m.1
               else if (m.length() == 1)
                  m.4 := m.3 := m.2 := m.1, exception := true
               else
                  m.Delete(5, m.MaxIndex())
            }
            else
               return {1:default, 2:default, 3:default, 4:default}

            m.1 := (m.1 ~= percentage) ? A_ScreenHeight * SubStr(m.1, 1, -1)  / 100 : m.1
            m.2 := (m.2 ~= percentage) ? (exception ? A_ScreenHeight : A_ScreenWidth) * SubStr(m.2, 1, -1)  / 100 : m.2
            m.3 := (m.3 ~= percentage) ? A_ScreenHeight * SubStr(m.3, 1, -1)  / 100 : m.3
            m.4 := (m.4 ~= percentage) ? (exception ? A_ScreenHeight : A_ScreenWidth) * SubStr(m.4, 1, -1)  / 100 : m.4

            m.1 := (m.1 ~= positive) ? m.1 : default
            m.2 := (m.2 ~= positive) ? m.2 : default
            m.3 := (m.3 ~= positive) ? m.3 : default
            m.4 := (m.4 ~= positive) ? m.4 : default

            return m
         }

         outline(o){
            static percentage := "^(\-?\d+(?:\.\d*)?)%$"
            static positive := "^\d+(\.\d*)?$"

            if IsObject(o){
               o.1 := (o.w  != "") ? o.w  : o.width
               o.2 := (o.c  != "") ? o.c  : o.color
               o.3 := (o.g  != "") ? o.g  : o.glow
               o.4 := (o.c2 != "") ? o.c2 : o.glowColor
            } else if (o)
               o   := StrSplit(o, " ")
            else
               return {"void":true, 1:0, 2:0, 3:0, 4:0}

            o.1 := (o.1 ~= "px$") ? SubStr(o.1, 1, -2) : o.1
            o.1 := (o.1 ~= percentage) ?  s * RegExReplace(o.1, percentage, "$1")  // 100 : o.1
            o.1 := (o.1 ~= positive) ? o.1 : 1

            o.2 := this.color(o.2, 0xFF000000)

            o.3 := (o.3 ~= "px$") ? SubStr(o.3, 1, -2) : o.3
            o.3 := (o.3 ~= percentage) ?  s * RegExReplace(o.3, percentage, "$1")  // 100 : o.3
            o.3 := (o.3 ~= positive) ? o.3 : 0

            o.4 := this.color(o.4, 0x00000000)
            return o
         }

         dropShadow(d){
            static decimal := "^(\-?\d+(\.\d*)?)$"
            static percentage := "^(\-?\d+(?:\.\d*)?)%$"
            static positive := "^\d+(\.\d*)?$"

            if IsObject(d){
               d.1 := (d.h != "") ? d.h : d.horizontal
               d.2 := (d.v != "") ? d.v : d.vertical
               d.3 := (d.b != "") ? d.b : d.blur
               d.4 := (d.c != "") ? d.c : d.color
               d.5 := (d.s != "") ? d.s : d.strength
            } else if (d)
               d   := StrSplit(d, " ")
            else
               return {"void":true, 1:0, 2:0, 3:0, 4:0, 5:0}

            d.1 := (d.1 ~= "px$") ? SubStr(d.1, 1, -2) : d.1
            d.1 := (d.1 ~= percentage) ? ReturnRC[3] * RegExReplace(d.1, percentage, "$1")  / 100 : d.1
            d.1 := (d.1 ~= decimal) ? d.1 : 0

            d.2 := (d.2 ~= "px$") ? SubStr(d.2, 1, -2) : d.2
            d.2 := (d.2 ~= percentage) ? ReturnRC[4] * RegExReplace(d.2, percentage, "$1")  / 100 : d.2
            d.2 := (d.2 ~= decimal) ? d.2 : 0

            d.3 := (d.3 ~= "px$") ? SubStr(d.3, 1, -2) : d.3
            d.3 := (d.3 ~= percentage) ? s * RegExReplace(d.3, percentage, "$1")  / 100 : d.3
            d.3 := (d.3 ~= positive) ? d.3 : 1

            d.4 := this.color(d.4, 0xFF000000)

            d.5 := (d.5 ~= percentage) ? s * RegExReplace(d.5, percentage, "$1")  / 100 : d.5
            d.5 := (d.5 ~= positive) ? d.5 : 1
            return d
         }

         colorMap(){
            color := [] ; 73 LINES MAX
            color["Clear"] := color["Off"] := color["None"] := color["Transparent"] := "0x00000000"

               color["AliceBlue"]             := "0xFFF0F8FF"
             , color["AntiqueWhite"]          := "0xFFFAEBD7"
             , color["Aqua"]                  := "0xFF00FFFF"
             , color["Aquamarine"]            := "0xFF7FFFD4"
             , color["Azure"]                 := "0xFFF0FFFF"
             , color["Beige"]                 := "0xFFF5F5DC"
             , color["Bisque"]                := "0xFFFFE4C4"
             , color["Black"]                 := "0xFF000000"
             , color["BlanchedAlmond"]        := "0xFFFFEBCD"
             , color["Blue"]                  := "0xFF0000FF"
             , color["BlueViolet"]            := "0xFF8A2BE2"
             , color["Brown"]                 := "0xFFA52A2A"
             , color["BurlyWood"]             := "0xFFDEB887"
             , color["CadetBlue"]             := "0xFF5F9EA0"
             , color["Chartreuse"]            := "0xFF7FFF00"
             , color["Chocolate"]             := "0xFFD2691E"
             , color["Coral"]                 := "0xFFFF7F50"
             , color["CornflowerBlue"]        := "0xFF6495ED"
             , color["Cornsilk"]              := "0xFFFFF8DC"
             , color["Crimson"]               := "0xFFDC143C"
             , color["Cyan"]                  := "0xFF00FFFF"
             , color["DarkBlue"]              := "0xFF00008B"
             , color["DarkCyan"]              := "0xFF008B8B"
             , color["DarkGoldenRod"]         := "0xFFB8860B"
             , color["DarkGray"]              := "0xFFA9A9A9"
             , color["DarkGrey"]              := "0xFFA9A9A9"
             , color["DarkGreen"]             := "0xFF006400"
             , color["DarkKhaki"]             := "0xFFBDB76B"
             , color["DarkMagenta"]           := "0xFF8B008B"
             , color["DarkOliveGreen"]        := "0xFF556B2F"
             , color["DarkOrange"]            := "0xFFFF8C00"
             , color["DarkOrchid"]            := "0xFF9932CC"
             , color["DarkRed"]               := "0xFF8B0000"
             , color["DarkSalmon"]            := "0xFFE9967A"
             , color["DarkSeaGreen"]          := "0xFF8FBC8F"
             , color["DarkSlateBlue"]         := "0xFF483D8B"
             , color["DarkSlateGray"]         := "0xFF2F4F4F"
             , color["DarkSlateGrey"]         := "0xFF2F4F4F"
             , color["DarkTurquoise"]         := "0xFF00CED1"
             , color["DarkViolet"]            := "0xFF9400D3"
             , color["DeepPink"]              := "0xFFFF1493"
             , color["DeepSkyBlue"]           := "0xFF00BFFF"
             , color["DimGray"]               := "0xFF696969"
             , color["DimGrey"]               := "0xFF696969"
             , color["DodgerBlue"]            := "0xFF1E90FF"
             , color["FireBrick"]             := "0xFFB22222"
             , color["FloralWhite"]           := "0xFFFFFAF0"
             , color["ForestGreen"]           := "0xFF228B22"
             , color["Fuchsia"]               := "0xFFFF00FF"
             , color["Gainsboro"]             := "0xFFDCDCDC"
             , color["GhostWhite"]            := "0xFFF8F8FF"
             , color["Gold"]                  := "0xFFFFD700"
             , color["GoldenRod"]             := "0xFFDAA520"
             , color["Gray"]                  := "0xFF808080"
             , color["Grey"]                  := "0xFF808080"
             , color["Green"]                 := "0xFF008000"
             , color["GreenYellow"]           := "0xFFADFF2F"
             , color["HoneyDew"]              := "0xFFF0FFF0"
             , color["HotPink"]               := "0xFFFF69B4"
             , color["IndianRed"]             := "0xFFCD5C5C"
             , color["Indigo"]                := "0xFF4B0082"
             , color["Ivory"]                 := "0xFFFFFFF0"
             , color["Khaki"]                 := "0xFFF0E68C"
             , color["Lavender"]              := "0xFFE6E6FA"
             , color["LavenderBlush"]         := "0xFFFFF0F5"
             , color["LawnGreen"]             := "0xFF7CFC00"
             , color["LemonChiffon"]          := "0xFFFFFACD"
             , color["LightBlue"]             := "0xFFADD8E6"
             , color["LightCoral"]            := "0xFFF08080"
             , color["LightCyan"]             := "0xFFE0FFFF"
             , color["LightGoldenRodYellow"]  := "0xFFFAFAD2"
             , color["LightGray"]             := "0xFFD3D3D3"
             , color["LightGrey"]             := "0xFFD3D3D3"
               color["LightGreen"]            := "0xFF90EE90"
             , color["LightPink"]             := "0xFFFFB6C1"
             , color["LightSalmon"]           := "0xFFFFA07A"
             , color["LightSeaGreen"]         := "0xFF20B2AA"
             , color["LightSkyBlue"]          := "0xFF87CEFA"
             , color["LightSlateGray"]        := "0xFF778899"
             , color["LightSlateGrey"]        := "0xFF778899"
             , color["LightSteelBlue"]        := "0xFFB0C4DE"
             , color["LightYellow"]           := "0xFFFFFFE0"
             , color["Lime"]                  := "0xFF00FF00"
             , color["LimeGreen"]             := "0xFF32CD32"
             , color["Linen"]                 := "0xFFFAF0E6"
             , color["Magenta"]               := "0xFFFF00FF"
             , color["Maroon"]                := "0xFF800000"
             , color["MediumAquaMarine"]      := "0xFF66CDAA"
             , color["MediumBlue"]            := "0xFF0000CD"
             , color["MediumOrchid"]          := "0xFFBA55D3"
             , color["MediumPurple"]          := "0xFF9370DB"
             , color["MediumSeaGreen"]        := "0xFF3CB371"
             , color["MediumSlateBlue"]       := "0xFF7B68EE"
             , color["MediumSpringGreen"]     := "0xFF00FA9A"
             , color["MediumTurquoise"]       := "0xFF48D1CC"
             , color["MediumVioletRed"]       := "0xFFC71585"
             , color["MidnightBlue"]          := "0xFF191970"
             , color["MintCream"]             := "0xFFF5FFFA"
             , color["MistyRose"]             := "0xFFFFE4E1"
             , color["Moccasin"]              := "0xFFFFE4B5"
             , color["NavajoWhite"]           := "0xFFFFDEAD"
             , color["Navy"]                  := "0xFF000080"
             , color["OldLace"]               := "0xFFFDF5E6"
             , color["Olive"]                 := "0xFF808000"
             , color["OliveDrab"]             := "0xFF6B8E23"
             , color["Orange"]                := "0xFFFFA500"
             , color["OrangeRed"]             := "0xFFFF4500"
             , color["Orchid"]                := "0xFFDA70D6"
             , color["PaleGoldenRod"]         := "0xFFEEE8AA"
             , color["PaleGreen"]             := "0xFF98FB98"
             , color["PaleTurquoise"]         := "0xFFAFEEEE"
             , color["PaleVioletRed"]         := "0xFFDB7093"
             , color["PapayaWhip"]            := "0xFFFFEFD5"
             , color["PeachPuff"]             := "0xFFFFDAB9"
             , color["Peru"]                  := "0xFFCD853F"
             , color["Pink"]                  := "0xFFFFC0CB"
             , color["Plum"]                  := "0xFFDDA0DD"
             , color["PowderBlue"]            := "0xFFB0E0E6"
             , color["Purple"]                := "0xFF800080"
             , color["RebeccaPurple"]         := "0xFF663399"
             , color["Red"]                   := "0xFFFF0000"
             , color["RosyBrown"]             := "0xFFBC8F8F"
             , color["RoyalBlue"]             := "0xFF4169E1"
             , color["SaddleBrown"]           := "0xFF8B4513"
             , color["Salmon"]                := "0xFFFA8072"
             , color["SandyBrown"]            := "0xFFF4A460"
             , color["SeaGreen"]              := "0xFF2E8B57"
             , color["SeaShell"]              := "0xFFFFF5EE"
             , color["Sienna"]                := "0xFFA0522D"
             , color["Silver"]                := "0xFFC0C0C0"
             , color["SkyBlue"]               := "0xFF87CEEB"
             , color["SlateBlue"]             := "0xFF6A5ACD"
             , color["SlateGray"]             := "0xFF708090"
             , color["SlateGrey"]             := "0xFF708090"
             , color["Snow"]                  := "0xFFFFFAFA"
             , color["SpringGreen"]           := "0xFF00FF7F"
             , color["SteelBlue"]             := "0xFF4682B4"
             , color["Tan"]                   := "0xFFD2B48C"
             , color["Teal"]                  := "0xFF008080"
             , color["Thistle"]               := "0xFFD8BFD8"
             , color["Tomato"]                := "0xFFFF6347"
             , color["Turquoise"]             := "0xFF40E0D0"
             , color["Violet"]                := "0xFFEE82EE"
             , color["Wheat"]                 := "0xFFF5DEB3"
             , color["White"]                 := "0xFFFFFFFF"
             , color["WhiteSmoke"]            := "0xFFF5F5F5"
               color["Yellow"]                := "0xFFFFFF00"
             , color["YellowGreen"]           := "0xFF9ACD32"
            return color
         }

         x1(){
            return this.x
         }

         y1(){
            return this.y
         }

         x2(){
            return this.xx
         }

         y2(){
            return this.yy
         }

         width(){
            return this.xx - this.x
         }

         height(){
            return this.yy - this.y
         }
      }
   }

   class provider {

      class GoogleCloudVision {

         static ext := "jpg"
         static jpegQuality := "75"

         base64 := true

         ; Cloud Platform Console Help - Setting up API keys
         ; Step 1: https://support.google.com/cloud/answer/6158862?hl=en
         ; Step 2: https://cloud.google.com/vision/docs/before-you-begin

         ; You must enter billing information to use the Cloud Vision API.
         ; https://cloud.google.com/vision/pricing
         ; First 1000 LABEL_DETECTION per month is free.

         ; Please enter your api_key for Google Cloud Vision API.
         static api_key := "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
         ; FOR SAFETY REASONS, DO NOT PASTE YOUR API KEY HERE.
         ; Instead, keep your api_key in a separate file, "Vis2_API.txt"

         getCredentials(error:=""){
            if (error != "") {
               (Vis2.obj) ? Vis2.core.ux.suspend() : ""
               InputBox, api_key, Vis2.GoogleCloudVision.ImageIdentify, Enter your api_key for GoogleCloudVision.
               (Vis2.obj) ? Vis2.core.ux.resume() : ""
               FileAppend, GoogleCloudVision=%api_key%, Vis2_API.txt
               return api_key
            }

            if (this.api_key ~= "^X{39}$") {
               if FileExist("Vis2_API.txt") {
                  file := FileOpen("Vis2_API.txt", "r")
                  keys := file.Read()
                  api_key := ((___ := RegExReplace(keys, "s)^.*?GoogleCloudVision(?:\s*)=(?:\s*)([A-Za-z0-9_\-]+).*$", "$1")) != keys) ? ___ : ""
                  file.close()

                  if (api_key != "")
                     return api_key
               }
            }
            else
               return this.api_key
         }

         ; https://cloud.google.com/vision/docs/supported-files
         ; Supported Image Formats
         ; JPEG, PNG8, PNG24, GIF, Animated GIF (first frame only)
         ; BMP, WEBP, RAW, ICO
         ; Maximum Image Size - 4 MB
         ; Maximum Size per Request - 8 MB
         ; Compression to 640 x 480 - LABEL_DETECTION

         ImageIdentify(image, search:="", options:=""){
            base64 := ImagePutBase64({image: image, crop: options}, "png", this.jpegQuality)
            reply := this.convert(base64)
            return this.getText()
         }

         convert(in:=""){
            in := (in) ? in : this.base64
            req := {}
            req.requests := {}
            req.requests[1] := {"image":{}, "features":{}}
            req.requests[1].image.content := in
            req.requests[1].features[1] := {"type":"LABEL_DETECTION"}
            body := JSON.Dump(req)

            whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
            if (api_key := this.getCredentials())
               whr.Open("POST", "https://vision.googleapis.com/v1/images:annotate?key=" api_key, true)
            else
               whr.Open("POST", "https://cxl-services.appspot.com/proxy?url=https%3A%2F%2Fvision.googleapis.com%2Fv1%2Fimages%3Aannotate", true)
            whr.SetRequestHeader("Accept", "*/*")
            whr.SetRequestHeader("Origin", "https://cloud.google.com")
            whr.SetRequestHeader("Content-Type", "text/plain;charset=UTF-8")
            whr.SetRequestHeader("Referer", "https://cloud.google.com/vision/")
            whr.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36")

            whr.Send(body)
            whr.WaitForResponse()

            try reply := JSON.Load(whr.ResponseText)
            catch
               this.getCredentials(whr.ResponseText)
            i := 1
            while (i <= reply.responses[1].labelAnnotations.length()) {
               sentence  .= (i == 1) ? "" : ", "
               sentence2 .= (i == 1) ? "" : ", "
               sentence  .= reply.responses[1].labelAnnotations[i].description
               sentence2 .= reply.responses[1].labelAnnotations[i].description " (" Format("{:i}",  100*reply.responses[1].labelAnnotations[i].score) "%)"
               i++
            }

            this.text := sentence2
            return reply
         }

         getText(){
            return this.text
         }
      }

      class Tesseract {
		
		
         static leptonica := StrReplace(A_LineFile,"\lib\Vis2.ahk","") "\bin\leptonica_util\leptonica_util.exe"
         static tesseract := StrReplace(A_LineFile,"\lib\Vis2.ahk","") "\bin\tesseract\tesseract.exe"
         static tessdata_best := StrReplace(A_LineFile,"\lib\Vis2.ahk","") "\bin\tesseract\tessdata_best"
         static tessdata_fast := StrReplace(A_LineFile,"\lib\Vis2.ahk","") "\bin\tesseract\tessdata_fast"

         uuid := Vis2.stdlib.CreateUUID()
         file := A_Temp "\Vis2_screenshot" this.uuid ".bmp"
         fileProcessedImage := A_Temp "\Vis2_preprocess" this.uuid ".tif"
         fileConvertedText := A_Temp "\Vis2_text" this.uuid ".txt"

         ; public OCR()
         ; public preprocess()
         ; public convert()
         ; public cleanup()
         ; public getPreprocessImage()
         ; public getText()
         ; private convert_best()
         ; private convert_fast()
         ; private getTextLines()

         __New(language:=""){
            this.language := language
         }

         OCR(image, language:="", options:=""){
            this.language := language
            try {
               screenshot := ImagePutFile({image: image, crop: options}, this.file)
               this.preprocess(screenshot, this.fileProcessedImage)
               this.convert_best(this.fileProcessedImage, this.fileConvertedText)
               text := this.getText(this.fileConvertedText)
            } catch e {
               MsgBox, 16,, % "Exception thrown!`n`nwhat: " e.what "`nfile: " e.file
                  . "`nline: " e.line "`nmessage: " e.message "`nextra: " e.extra
            }
            finally {
               this.cleanup()
               text.base.google := ObjBindMethod(Vis2.Text, "google")
               text.base.clipboard := ObjBindMethod(Vis2.Text, "clipboard")
            }
            return text
         }

         cleanup(){
            FileDelete, % this.file
            FileDelete, % this.fileProcessedImage
            FileDelete, % this.fileConvertedText
         }

         convert(in:="", out:="", fast:=1){
            in := (in) ? in : this.fileProcessedImage
            out := (out) ? out : this.fileConvertedText
            fast := (fast) ? this.tessdata_fast : this.tessdata_best

            if !(FileExist(in))
               throw Exception("Input image for conversion not found.",, in)

            if !(FileExist(this.tesseract))
               throw Exception("Tesseract not found",, this.tesseract)

            static q := Chr(0x22)
            _cmd .= q this.tesseract q " --tessdata-dir " q fast q " " q in q " " q SubStr(out, 1, -4) q
            _cmd .= (this.language) ? " -l " q this.language q : ""
            _cmd := ComSpec " /C " q _cmd q
            RunWait % _cmd,, Hide

            if !(FileExist(out))
               throw Exception("Tesseract failed.",, _cmd)

            return out
         }

         convert_best(in:="", out:=""){
            return this.convert(in, out, 0)
         }

         convert_fast(in:="", out:=""){
            return this.convert(in, out, 1)
         }

         getPreprocessImage(){
            return this.fileProcessedImage
         }

         getText(in:="", lines:=""){
            in := (in) ? in : this.fileConvertedText

            if !(database := FileOpen(in, "r`n", "UTF-8"))
               throw Exception("Text file could not be found or opened.",, in)

            if (lines == "") {
               text := RegExReplace(database.Read(), "^\s*(.*?)\s*$", "$1")
               text := RegExReplace(text, "(?<!\r)\n", "`r`n")
            } else {
               while (lines > 0) {
                  data := database.ReadLine()
                  data := RegExReplace(data, "^\s*(.*?)\s*$", "$1")
                  if (data != "") {
                     text .= (text) ? ("`n" . data) : data
                     lines--
                  }
                  if (!database || database.AtEOF)
                     break
               }
            }
            database.Close()
            return text
         }

         getTextLines(lines){
            return this.read(, lines)
         }

         preprocess(in:="", out:=""){
            static ocrPreProcessing := 1
            static negateArg := 2
            static performScaleArg := 1
            static scaleFactor := 3.5

            in := (in != "") ? in : this.file
            out := (out != "") ? out : this.fileProcessedImage

            if !(FileExist(in))
               throw Exception("Input image for preprocessing not found.",, in)

            if !(FileExist(this.leptonica))
               throw Exception("Leptonica not found",, this.leptonica)

            static q := Chr(0x22)
            _cmd .= q this.leptonica q " " q in q " " q out q
            _cmd .= " " negateArg " 0.5 " performScaleArg " " scaleFactor " " ocrPreProcessing " 5 2.5 " ocrPreProcessing  " 2000 2000 0 0 0.0"
            _cmd := ComSpec " /C " q _cmd q
            RunWait, % _cmd,, Hide

            if !(FileExist(out))
               throw Exception("Preprocessing failed.",, _cmd)

            return out
         }
      }
   }

   class stdlib {

      isBinaryImageFormat(data){
         Loop 12
            bytes .= Chr(NumGet(data, A_Index-1, "uchar"))

         ; Null bytes are not passed, so they have been omitted below

         if (bytes ~= "^BM")
            return "bmp"
         if (bytes ~= "^(GIF87a|GIF89a)")
            return "gif"
         if (bytes ~= "^ÿØÿÛ")
            return "jpg"
         if (bytes ~= "s)^ÿØÿà..\x4A\x46\x49\x46") ;\x00\x01
            return "jfif"
         if (bytes ~= "^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
            return "png"
         if (bytes ~= "^(\x49\x49\x2A|\x4D\x4D\x2A)") ; 49 49 2A 00, 4D 4D 00 2A
            return "tif"
         return
      }

      isURL(url){
         regex .= "((https?|ftp)\:\/\/)" ; SCHEME
         regex .= "([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)?" ; User and Pass
         regex .= "([a-z0-9-.]*)\.([a-z]{2,3})" ; Host or IP
         regex .= "(\:[0-9]{2,5})?" ; Port
         regex .= "(\/([a-z0-9+\$_-]\.?)+)*\/?" ; Path
         regex .= "(\?[a-z+&\$_.-][a-z0-9;:@&%=+\/\$_.-]*)?" ; GET Query
         regex .= "(#[a-z_.-][a-z0-9+\$_.-]*)?" ; Anchor

         return (url ~= "i)" regex) ? true : false
      }

      b64Encode( ByRef buf, bufLen:="" ) {
         bufLen := (bufLen) ? bufLen : StrLen(buf) << !!A_IsUnicode
         DllCall( "crypt32\CryptBinaryToStringA", "ptr", &buf, "UInt", bufLen, "Uint", 1 | 0x40000000, "Ptr", 0, "UInt*", outLen )
         VarSetCapacity( outBuf, outLen, 0 )
         DllCall( "crypt32\CryptBinaryToStringA", "ptr", &buf, "UInt", bufLen, "Uint", 1 | 0x40000000, "Ptr", &outBuf, "UInt*", outLen )
         return strget( &outBuf, outLen, "CP0" )
      }

      b64Decode( b64str, ByRef outBuf ) {
         static CryptStringToBinary := "crypt32\CryptStringToBinary" (A_IsUnicode ? "W" : "A")

         DllCall( CryptStringToBinary, "ptr", &b64str, "UInt", 0, "Uint", 1, "Ptr", 0, "UInt*", outLen, "ptr", 0, "ptr", 0 )
         VarSetCapacity( outBuf, outLen, 0 )
         DllCall( CryptStringToBinary, "ptr", &b64str, "UInt", 0, "Uint", 1, "Ptr", &outBuf, "UInt*", outLen, "ptr", 0, "ptr", 0 )

         return outLen
      }

      CreateUUID() {
         VarSetCapacity(puuid, 16, 0)
         if !(DllCall("rpcrt4.dll\UuidCreate", "ptr", &puuid))
            if !(DllCall("rpcrt4.dll\UuidToString", "ptr", &puuid, "uint*", suuid))
               return StrGet(suuid), DllCall("rpcrt4.dll\RpcStringFree", "uint*", suuid)
         return ""
      }

      Gdip_EncodeBitmapTo64string(pBitmap, ext, Quality=75) {

         if Ext not in BMP,DIB,RLE,JPG,JPEG,JPE,JFIF,GIF,TIF,TIFF,PNG
               return -1
         Extension := "." Ext

         DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", nCount, "uint*", nSize)
         VarSetCapacity(ci, nSize)
         DllCall("gdiplus\GdipGetImageEncoders", "uint", nCount, "uint", nSize, Ptr, &ci)
         if !(nCount && nSize)
            return -2



            Loop, %nCount%
            {
                  sString := StrGet(NumGet(ci, (idx := (48+7*A_PtrSize)*(A_Index-1))+32+3*A_PtrSize), "UTF-16")
                  if !InStr(sString, "*" Extension)
                     continue

                  pCodec := &ci+idx
                  break
            }


         if !pCodec
               return -3

         if (Quality != 75)
         {
               Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality
               if Extension in .JPG,.JPEG,.JPE,.JFIF
               {
                     DllCall("gdiplus\GdipGetEncoderParameterListSize", Ptr, pBitmap, Ptr, pCodec, "uint*", nSize)
                     VarSetCapacity(EncoderParameters, nSize, 0)
                     DllCall("gdiplus\GdipGetEncoderParameterList", Ptr, pBitmap, Ptr, pCodec, "uint", nSize, Ptr, &EncoderParameters)
                     Loop, % NumGet(EncoderParameters, "UInt")
                     {
                        elem := (24+(A_PtrSize ? A_PtrSize : 4))*(A_Index-1) + 4 + (pad := A_PtrSize = 8 ? 4 : 0)
                        if (NumGet(EncoderParameters, elem+16, "UInt") = 1) && (NumGet(EncoderParameters, elem+20, "UInt") = 6)
                        {
                              p := elem+&EncoderParameters-pad-4
                              NumPut(Quality, NumGet(NumPut(4, NumPut(1, p+0)+20, "UInt")), "UInt")
                              break
                        }
                     }
               }
         }

         DllCall("ole32\CreateStreamOnHGlobal", "ptr",0, "int",true, "ptr*",pStream)
         DllCall("gdiplus\GdipSaveImageToStream", "ptr",pBitmap, "ptr",pStream, "ptr",pCodec, "uint",p ? p : 0)

         DllCall("ole32\GetHGlobalFromStream", "ptr",pStream, "uint*",hData)
         pData := DllCall("GlobalLock", "ptr",hData, "uptr")
         nSize := DllCall("GlobalSize", "uint",pData)

         VarSetCapacity(Bin, nSize, 0)
         DllCall("RtlMoveMemory", "ptr",&Bin , "ptr",pData , "uint",nSize)
         DllCall("GlobalUnlock", "ptr",hData)
         DllCall(NumGet(NumGet(pStream + 0, 0, "uptr") + (A_PtrSize * 2), 0, "uptr"), "ptr",pStream)
         DllCall("GlobalFree", "ptr",hData)
         
         DllCall("Crypt32.dll\CryptBinaryToString", "ptr",&Bin, "uint",nSize, "uint",0x01, "ptr",0, "uint*",base64Length)
         VarSetCapacity(base64, base64Length*2, 0)
         DllCall("Crypt32.dll\CryptBinaryToString", "ptr",&Bin, "uint",nSize, "uint",0x01, "ptr",&base64, "uint*",base64Length)
         Bin := ""
         VarSetCapacity(Bin, 0)
         VarSetCapacity(base64, -1)

         return base64
      }

      Gdip_BitmapFromClientHWND(hwnd) {
         VarSetCapacity(rc, 16)
         DllCall("GetClientRect", "ptr", hwnd, "ptr", &rc)
      	hbm := CreateDIBSection(NumGet(rc, 8, "int"), NumGet(rc, 12, "int"))
         VarSetCapacity(rc, 0)
         hdc := CreateCompatibleDC()
         obm := SelectObject(hdc, hbm)
      	PrintWindow(hwnd, hdc, 1)
      	pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
      	SelectObject(hdc, obm), DeleteObject(hbm), DeleteDC(hdc)
      	return pBitmap
      }

      Gdip_CropBitmap(ByRef pBitmap, c, preserveOriginal:=false){
         w := Gdip_GetImageWidth(pBitmap), h := Gdip_GetImageHeight(pBitmap)
         pBitmap2 := Gdip_CloneBitmapArea(pBitmap, c.1, c.2, (c.1 + c.3 > w) ? w - c.1 : c.3 , (c.2 + c.4 > h) ? h - c.2 : c.4)
         (preserveOriginal) ? "" : Gdip_DisposeImage(pBitmap)
         pBitmap := pBitmap2
      }

      Gdip_isBitmapEqual(pBitmap1, pBitmap2, width:="", height:="") {
         ; Check if pointers are identical.
         if (pBitmap1 == pBitmap2)
            return true

         ; Assume both Bitmaps are equal in width and height.
         width := (width) ? width : Gdip_GetImageWidth(pBitmap1)
         height := (height) ? height : Gdip_GetImageHeight(pBitmap1)
         E1 := Gdip_LockBits(pBitmap1, 0, 0, width, height, Stride1, Scan01, BitmapData1)
         E2 := Gdip_LockBits(pBitmap2, 0, 0, width, height, Stride2, Scan02, BitmapData2)

         ; RtlCompareMemory preforms an unsafe comparison stopping at the first different byte.
         length := width * height * 4  ; ARGB = 4 bytes
         bytes := DllCall("RtlCompareMemory", "ptr", Scan01+0, "ptr", Scan02+0, "uint", length)

         Gdip_UnlockBits(pBitmap1, BitmapData1)
         Gdip_UnlockBits(pBitmap2, BitmapData2)
         return (bytes == length) ? true : false
      }

      RPath_Absolute(AbsolutPath, RelativePath, s="\") {

         len := InStr(AbsolutPath, s, "", InStr(AbsolutPath, s . s) + 2) - 1   ;get server or drive string length
         pr := SubStr(AbsolutPath, 1, len)                                     ;get server or drive name
         AbsolutPath := SubStr(AbsolutPath, len + 1)                           ;remove server or drive from AbsolutPath
         If InStr(AbsolutPath, s, "", 0) = StrLen(AbsolutPath)                 ;remove last \ from AbsolutPath if any
            StringTrimRight, AbsolutPath, AbsolutPath, 1

         If InStr(RelativePath, s) = 1                                         ;when first char is \ go to AbsolutPath of server or drive
            AbsolutPath := "", RelativePath := SubStr(RelativePath, 2)        ;set AbsolutPath to nothing and remove one char from RelativePath
         Else If InStr(RelativePath,"." s) = 1                                 ;when first two chars are .\ add to current AbsolutPath directory
            RelativePath := SubStr(RelativePath, 3)                           ;remove two chars from RelativePath
         Else If InStr(RelativePath,".." s) = 1 {                              ;otherwise when first 3 char are ..\
            StringReplace, RelativePath, RelativePath, ..%s%, , UseErrorLevel     ;remove all ..\ from RelativePath
            Loop, %ErrorLevel%                                                    ;for all ..\
               AbsolutPath := SubStr(AbsolutPath, 1, InStr(AbsolutPath, s, "", 0) - 1)  ;remove one folder from AbsolutPath
         } Else                                                                ;relative path does not need any substitution
            pr := "", AbsolutPath := "", s := ""                              ;clear all variables to just return RelativePath

         Return, pr . AbsolutPath . s . RelativePath                           ;concatenate server + AbsolutPath + separator + RelativePath
      }

      setSystemCursor(CursorID = "", cx = 0, cy = 0 ) { ; Thanks to Serenity - https://autohotkey.com/board/topic/32608-changing-the-system-cursor/
         static SystemCursors := "32512,32513,32514,32515,32516,32640,32641,32642,32643,32644,32645,32646,32648,32649,32650,32651"

         Loop, Parse, SystemCursors, `,
         {
               Type := "SystemCursor"
               CursorHandle := DllCall( "LoadCursor", "uInt",0, "Int",CursorID )
               %Type%%A_Index% := DllCall( "CopyImage", "uInt",CursorHandle, "uInt",0x2, "Int",cx, "Int",cy, "uInt",0 )
               CursorHandle := DllCall( "CopyImage", "uInt",%Type%%A_Index%, "uInt",0x2, "Int",0, "Int",0, "Int",0 )
               DllCall( "SetSystemCursor", "uInt",CursorHandle, "Int",A_Loopfield)
         }
      }

      ; toBase64() - Converts the input to a Base 64 string.
      ; Types of input accepted
      ; Objects: Rectangle Array (Screenshot)
      ; Strings: File, URL, Window Title (ahk_class...) OR hwnd (hex)
      ; Numbers: GDI Bitmap, GDI HBitmap
      ; Rawfile: Binary, base64
      toBase64(image, extension:="png", quality:="", crop:="", crop2:=""){
         Vis2.Graphics.Startup()

         ; Check if image is an array of 4 numbers
         if (image.1 ~= "^\d+$" && image.2 ~= "^\d+$" && image.3 ~= "^\d+$" && image.4 ~= "^\d+$") {
            pBitmap := Gdip_BitmapFromScreen(image.1 "|" image.2 "|" image.3 "|" image.4)
            base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(pBitmap, extension, quality)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image points to a valid file
         else if FileExist(image) {
            if !(crop) {
               file := FileOpen(image, "r")
               file.RawRead(data, file.length)
               base64 := Vis2.stdlib.b64Encode(data, file.length)
               file.Close()
            } else {
               pBitmap := Gdip_CreateBitmapFromFile(image)
               (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
               base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(pBitmap, extension, quality)
               Gdip_DisposeImage(pBitmap)
            }
         }
         ; Check if image points to a valid URL
         else if Vis2.stdlib.isURL(image) {
            static req := ComObjCreate("WinHttp.WinHttpRequest.5.1")
            req.Open("GET",image)
            req.Send()

            pStream := ComObjQuery(req.ResponseStream, "{0000000C-0000-0000-C000-000000000046}")
            if !(crop) {
               DllCall("ole32\GetHGlobalFromStream", "ptr",pStream, "uint*",hData)
               pData := DllCall("GlobalLock", "ptr",hData, "uptr")
               nSize := DllCall("GlobalSize", "uint",pData)

               VarSetCapacity(Bin, nSize, 0)
               DllCall("RtlMoveMemory", "ptr",&Bin , "ptr",pData , "uint",nSize)
               DllCall("GlobalUnlock", "ptr",hData)
               DllCall(NumGet(NumGet(pStream + 0, 0, "uptr") + (A_PtrSize * 2), 0, "uptr"), "ptr",pStream)
               DllCall("GlobalFree", "ptr",hData)

               DllCall("Crypt32.dll\CryptBinaryToString", "ptr",&Bin, "uint",nSize, "uint",0x01, "ptr",0, "uint*",base64Length)
               VarSetCapacity(base64, base64Length*2, 0)
               DllCall("Crypt32.dll\CryptBinaryToString", "ptr",&Bin, "uint",nSize, "uint",0x01, "ptr",&base64, "uint*",base64Length)
               Bin := ""
               VarSetCapacity(Bin, 0)
               VarSetCapacity(base64, -1)
            } else {
               DllCall("gdiplus\GdipCreateBitmapFromStream", "ptr",pStream, "uptr*",pBitmap)
               ObjRelease(pStream)
               (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
               base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(pBitmap, extension, quality)
               Gdip_DisposeImage(pBitmap)
            }
         }
         ; Check if image matches a window title OR is a valid handle to a window
         else if (DllCall("IsWindow", "ptr",image) || (hwnd := WinExist(image))) {
            hwnd := (DllCall("IsWindow", "ptr",image)) ? image : hwnd
            pBitmap := Vis2.stdlib.Gdip_BitmapFromClientHWND(hwnd)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(pBitmap, extension, quality)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image is a valid GDI Bitmap
         else if DeleteObject(Gdip_CreateHBITMAPFromBitmap(image)) {
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(image, crop, true) : ""
            base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(image, extension, quality)
            (crop) ? Gdip_DisposeImage(image) : ""
         }
         ; Check if image is a valid handle to a GDI Bitmap
         else if (DllCall("GetObjectType", "ptr",image) == 7) {
            pBitmap := Gdip_CreateBitmapFromHBITMAP(image)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            base64 := Vis2.stdlib.Gdip_EncodeBitmapTo64string(pBitmap, extension, quality)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image is a base64 string
         else if (image ~= "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$") {
            base64 := image
         }
         Vis2.Graphics.Shutdown()
         return base64
      }

      ; toFile() - Saves the image as a temporary file.
      toFile(image, outputFile:="", crop:=""){
         Vis2.Graphics.Startup()
         ; Check if image is an array of 4 numbers
         if (image.1 ~= "^\d+$" && image.2 ~= "^\d+$" && image.3 ~= "^\d+$" && image.4 ~= "^\d+$") {
            pBitmap := Gdip_BitmapFromScreen(image.1 "|" image.2 "|" image.3 "|" image.4)
            Gdip_SaveBitmapToFile(pBitmap, outputFile)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image points to a valid file
         else if FileExist(image) {
            Loop, Files, % image
            {
               if (A_LoopFileExt != "bmp" || IsObject(crop)) {
                  pBitmap := Gdip_CreateBitmapFromFile(A_LoopFileLongPath)
                  (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
                  Gdip_SaveBitmapToFile(pBitmap, outputFile)
                  Gdip_DisposeImage(pBitmap)
               }
               else outputFile := A_LoopFileLongPath
            }
         }
         ; Check if image points to a valid URL
         else if Vis2.stdlib.isURL(image) {
            static req := ComObjCreate("WinHttp.WinHttpRequest.5.1")
            req.Open("GET",image)
            req.Send()

            pStream := ComObjQuery(req.ResponseStream, "{0000000C-0000-0000-C000-000000000046}")
            DllCall("gdiplus\GdipCreateBitmapFromStream", "ptr",pStream, "uptr*",pBitmap)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            Gdip_SaveBitmapToFile(pBitmap, outputFile, 92)
            ObjRelease(pStream)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image matches a window title OR is a valid handle to a window
         else if (DllCall("IsWindow", "ptr",image) || (hwnd := WinExist(image))) {
            hwnd := (DllCall("IsWindow", "ptr",image)) ? image : hwnd
            pBitmap := Vis2.stdlib.Gdip_BitmapFromClientHWND(hwnd)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            Gdip_SaveBitmapToFile(pBitmap, outputFile)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image is a valid GDI Bitmap
         else if DeleteObject(Gdip_CreateHBITMAPFromBitmap(image)) {
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(image, crop, true) : ""
            Gdip_SaveBitmapToFile(image, outputFile)
         }
         ; Check if image is a valid handle to a GDI Bitmap
         else if (DllCall("GetObjectType", "ptr",image) == 7) {
            pBitmap := Gdip_CreateBitmapFromHBITMAP(image)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            Gdip_SaveBitmapToFile(pBitmap, outputFile)
            Gdip_DisposeImage(pBitmap)
         }
         ; Check if image is a base64 string
         else if (image ~= "^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$") {
            nSize := Vis2.stdlib.b64Decode(image, bin)
            hData := DllCall("GlobalAlloc", "uint",0x2, "ptr",nSize)
            pData := DllCall("GlobalLock", "ptr",hData)
            DllCall("RtlMoveMemory", "ptr",pData, "ptr",&bin, "ptr",nSize)
            DllCall("GlobalUnlock", "ptr",hData)
            DllCall("ole32\CreateStreamOnHGlobal", "ptr",hData, "int",1, "uptr*",pStream)
            DllCall("gdiplus\GdipCreateBitmapFromStream", "ptr",pStream, "uptr*",pBitmap)
            (crop) ? Vis2.stdlib.Gdip_CropBitmap(pBitmap, crop) : ""
            Gdip_SaveBitmapToFile(pBitmap, outputFile, 92)
            DllCall(NumGet(NumGet(pStream + 0, 0, "uptr") + (A_PtrSize * 2), 0, "uptr"), "ptr",pStream)
            DllCall("GlobalFree", "ptr",hData)
            ObjRelease(pStream)
            Gdip_DisposeImage(pBitmap)
         }

         if !(FileExist(outputFile))
            throw Exception("Could not find source image.")

         Vis2.Graphics.Shutdown()
         return outputFile
      }
   }

   class Text {

      copy() {
         AutoTrim Off
         c := ClipboardAll
         Clipboard := ""             ; Must start off blank for detection to work.
         Send, ^c
         ClipWait 0.5
         if ErrorLevel
            return
         t := Clipboard
         Clipboard := c
         VarSetCapacity(c, 0)
         return t
      }

      paste(t) {
         c := ClipboardAll
         Clipboard := t
         Send, ^v
         Sleep 50                    ; Don't change clipboard while it is pasted! (Sleep > 0)
         Clipboard := c
         VarSetCapacity(c, 0)        ; Free memory
         AutoTrim On
      }

      restore() {
         AutoTrim On
      }

      ; Based on this paper: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.81.8901
      rmgarbage(data := ""){
         ; If the input value is blank, send ^c to capture highlighted text.
         text := (data == "") ? Vis2.Text.copy() : data

         ; Split our text into strings, creating an array of words.
         strings := [], whitespaces := [], pos := 1
         while RegexMatch(text, "O)([^\s]+)", string, pos) {
            strings.push(string.value())
            pos := string.pos() + string.len()
            RegexMatch(text, "O)([\s]+)", whitespace, pos)
            whitespaces.push(whitespace.value)
         }

         for i in strings {
            ; (L) If a string is longer than 40 characters...
            ;strings[i] := RegExReplace(strings[i], "[^\s]{40,}", "")
            ; (A) If a string’s ratio of alphanumeric characters to total characters is less than 50%...
            alnum_thresholds := {1: 0     ; single chars can be non-alphanumeric
                    ,2: 0     ; so can doublets
                    ,3: 0.32  ; at least one of three should be alnum
                    ,4: 0.24  ; at least one of four should be alnum
                    ,5: 0.39}  ; at least two of five should be alnum
            strings[i] := (StrLen(RegExReplace(strings[i], "\W")) / StrLen(strings[i]) < 0.5) ? "" : strings[i]
            ; (R) If a string has 4 identical characters in a row...
            ;strings[i] := (strings[i] ~= "([^\s])\1\1\1") ? "" : strings[i]
            ; (V) If a string has nothing but alphabetic characters, look at the number of consonants and vowels.
            ;     If the number of one is less than 10% of the number of the other...
            ;     This includes a length threshold.

            /*
            def bad_consonant_vowel_ratio(self, string):
             """
             Rule V
             ======
             if a string has nothing but alphabetic characters, look at the
             number of consonants and vowels. If the number of one is less than 10%
             of the number of the other, then the string is garbage.
             This includes a length threshold.
             :param string: string to be tested
             :returns: either True or False
             """
             alpha_string = filter(str.isalpha, string)
             vowel_count = sum(1 for char in alpha_string if char in 'aeiouAEIOU')
             consonant_count = len(alpha_string) - vowel_count

             if (consonant_count > 0 and vowel_count > 0):
                 ratio = float(vowel_count)/consonant_count
                 if (ratio < 0.1 or ratio > 10):
                     return True
             elif (vowel_count == 0 and consonant_count > len('rhythms')):
                 return True
             elif (consonant_count == 0 and vowel_count > len('IEEE')):
                 return True

             return False
             */
            ;strings[i] := (strings[i] ~= "^(\w(?<=\D))+$") ? () : strings[i]
            ; (P) Strip off the first and last characters of a string. If there are two distinct
            ;     punctuation characters in the result...

            ; (C) If a string begins and ends with a lowercase letter, then if the string
            ;     contains an uppercase letter anywhere in between...
         }

         ; Reassemble the text
         text := ""
         for i, string in strings {
            text .= string . whitespaces[i]
         }

         return (data == "") ? Vis2.Text.paste(text) : text
      }

      clipboard(data := ""){
         text := (data == "") ? Vis2.Text.copy() : data
         clipboard := text
         return (data == "") ? Vis2.Text.restore() : text
      }

      google(data := "") {
         text := data
         if not RegExMatch(text, "^(http|ftp|telnet)")
            text := "https://www.google.com/search?&q=" . RegExReplace(text, "\s", "+")
         if (data)
            Run % text
         return data
      }
   }
}

Re: Vis2 - Image to Text OCR()

Posted: 29 Mar 2023, 00:38
by yoloo2000
iseahound wrote:
20 Aug 2017, 00:18
Rectangle array [x, y, w, h] - Takes a screenshot text := OCR([0, 0, 430, 150])
How make the x,y relative to the Client because it seems to follow the whole screen, not the window or client

Re: Vis2 - Image to Text OCR()

Posted: 29 Mar 2023, 10:03
by iseahound
Okay! I've updated Vis2 to use the latest version of the image capture library.

You have 2 options to get the client area:

Code: Select all

; For testing, you should probably use ImagePutWindow({screenshot: "ahk_class notepad", crop: [x, y, w, h]})
test := OCR({window: "ahk_class notepad", crop: [x, y, w, h]})
test := OCR({screenshot: "ahk_class notepad", crop: [x, y, w, h]})
Documentation to using crop: https://github.com/iseahound/ImagePut/wiki/Crop,-Scale,-&-Other-Flags#crop
Documentation to input types: https://github.com/iseahound/ImagePut/wiki/Input-Types-&-Output-Functions#input-types (scroll to screenshot or window)

Re: Vis2 - Image to Text OCR()

Posted: 29 Mar 2023, 10:11
by iseahound
Flowgun wrote:
24 Mar 2023, 07:30
I changed the way that Vis2.Ahk deals with directories to make it easier to include into other scripts:
I'm not sure what A_LineFile does although it seems to work. A_LineFile should designate the exact File itself, so if I'm understanding correctly,

Code: Select all

A_LineFile
C:/Vis2/lib/Vis2.ahk

A_LineFile\..\ImagePut.ahk
C:/Vis2/lib/ImagePut.ahk
So the .. lets you "go up a directory" on a File!?

Re: Vis2 - Image to Text OCR()

Posted: 29 Mar 2023, 16:40
by Flowgun
iseahound wrote:
29 Mar 2023, 10:11
Flowgun wrote:
24 Mar 2023, 07:30
I changed the way that Vis2.Ahk deals with directories to make it easier to include into other scripts:
I'm not sure what A_LineFile does although it seems to work. A_LineFile should designate the exact File itself, so if I'm understanding correctly,

Code: Select all

A_LineFile
C:/Vis2/lib/Vis2.ahk

A_LineFile\..\ImagePut.ahk
C:/Vis2/lib/ImagePut.ahk
So the .. lets you "go up a directory" on a File!?
Yes, and this allows the library to be used even when it's in a directory other than the one where the script is.

Re: Vis2 - Image to Text OCR()

Posted: 21 Jun 2023, 19:38
by Shajirr
I've been using this tool so far via the OCR([x, y, w, h]) function, and its mostly been working fine.

However, I found one case when it doesn't, with a white text on a blue background with a somewhat dotted pattern.
Doesn't recognise the text at all.

Also tried taking a screenshot, cropping the area, and feeding it to the function as a file - that also fails.

However, when I use demo.ahk and its interactive GUI function via Win+C - that identifies the exact same text every time no problem.
Why? What's the difference here?

Re: Vis2 - Image to Text OCR()

Posted: 22 Jun 2023, 02:58
by Shajirr
So I read that:
Fast is used in the interactive GUI implementation, while best will be used for other cases.
Is this why?
If so, then I am getting the opposite result to what should be happening - fast version successfully recognising the text,
while best version failing entirely.

Re: Vis2 - Image to Text OCR()

Posted: 06 Aug 2023, 12:16
by d1niel84
Hello, how can i use the the OCR() function without the overlayed text?
Just select the area and get the text as a return value.

Thanks
Daniel

Re: Vis2 - Image to Text OCR()

Posted: 06 Nov 2023, 22:01
by yzymanayzy
Seems that it is still to save the screen bits to a file, then use tesseract.exe to OCR the file.
can we ocr the hbm in the memory via tessearact API and skip the saving file?? not sure if it is possible...