[GDI+ Object] Subtitle.Render() - Beautiful Text on Screen

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

[GDI+ Object] Subtitle.Render() - Beautiful Text on Screen

28 Aug 2017, 01:40

2022-02-26 This project has been superseded by TextRender for subtitles and text/image rendering.
2019-08-04 This project has been superseded by Graphics. It is compatible with v1 and v2.

Subtitle.Render() on Github

Subtitle.Render() has many features.
  • Outline Text
  • Shadows
  • Fast!!! - See examples
Code
Image

code 2
Image

code 3
Image
How to write the code that makes beautiful text?
Syntax
Tips and Tricks
List Of Methods
Frequently Asked Questions
Old Examples

If you have any suggestions or comments, feel free to suggest them in the comments! Especially if you find the syntax not intuitive (syntax is based off CSS.)


Get the latest version on GitHub.
For a real example of Subtitle being used: Vis2
If you need to load a custom font, I reccomend CustomFont by tmplinshi
You will need Gdip_All

Full Script

Code: Select all

; Script:    Subtitle.ahk
; Author:    iseahound
; Version:   2018-04-17 (April 2018)
; Recent:    2018-05-15

#include <Gdip_All>


class Subtitle {

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

   __New(title := "") {
      global pToken
      if !(this.outer.Startup())
         if !(pToken)
            if !(this.pToken := Gdip_Startup())
               throw Exception("Gdiplus failed to start. Please ensure you have gdiplus on your system.")

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

   __Delete() {
      global pToken
      if (this.outer.pToken)
         return this.outer.Shutdown()
      if (pToken)
         return
      if (this.pToken)
         return Gdip_Shutdown(this.pToken)
   }

   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(i := 8) {
      DllCall("ShowWindow", "ptr",this.hwnd, "int",i)
      return this
   }

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

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

   AlwaysOnTop() {
      WinSet, AlwaysOnTop, Toggle, % "ahk_id" this.hwnd
      return this
   }

   Bottom() {
      WinSet, Bottom,, % "ahk_id" this.hwnd
      return this
   }

   ClickThrough() {
      _dhw := A_DetectHiddenWindows
      DetectHiddenWindows On
      WinGet, ExStyle, ExStyle, % "ahk_id" this.hwnd
      if (ExStyle & 0x20)
         WinSet, ExStyle, -0x20, % "ahk_id" this.hwnd
      else
         WinSet, ExStyle, +0x20, % "ahk_id" this.hwnd
      DetectHiddenWindows %_dhw%
      return this
   }

   Desktop() {
      ; Based on: https://www.codeproject.com/Articles/856020/Draw-Behind-Desktop-Icons-in-Windows-plus?msg=5478543#xx5478543xx
      DllCall("SendMessage", "ptr",WinExist("ahk_class Progman"), "uint",0x052C, "ptr",0x0000000D, "ptr",0)
      DllCall("SendMessage", "ptr",WinExist("ahk_class Progman"), "uint",0x052C, "ptr",0x0000000D, "ptr",1) ; Post-Creator's Update Windows 10.
      WinGet, windows, List, ahk_class WorkerW
      Loop, %windows%
         if (DllCall("FindWindowEx", "ptr",windows%A_Index%, "ptr",0, "str","SHELLDLL_DefView", "ptr",0) != 0)
            WorkerW := DllCall("FindWindowEx", "ptr",0, "ptr",windows%A_Index%, "str","WorkerW", "ptr",0)

      if (WorkerW) {
         this.Destroy()
         this.hwnd := WorkerW
         DllCall("SetWindowPos", "uint",WorkerW, "uint",1, "int",0, "int",0, "int",this.ScreenWidth, "int",this.ScreenHeight, "uint",0)
         this.base.FreeMemory := ObjBindMethod(this, "DesktopFreeMemory")
         this.base.Destroy := ObjBindMethod(this, "DesktopDestroy")
         this.hdc := DllCall("GetDCEx", "ptr",WorkerW, "ptr",0, "int",0x403)
         this.G := Gdip_GraphicsFromHDC(this.hdc)
      }
      return this
   }

   DesktopFreeMemory() {
      ReleaseDC(this.hdc)
      Gdip_DeleteGraphics(this.G)
      return this
   }

   DesktopDestroy() {
      this.FreeMemory()
      DllCall("SendMessage", "ptr",WinExist("ahk_class Progman"), "uint",0x052C, "ptr",0x0000000D, "ptr",0)
      DllCall("SendMessage", "ptr",WinExist("ahk_class Progman"), "uint",0x052C, "ptr",0x0000000D, "ptr",1)
      return this
   }

   Normal() {
      WinSet, AlwaysOnTop, Off, % "ahk_id" this.hwnd
      return this
   }

   DetectScreenResolutionChange(width := "", height := "") {
      width := (width) ? width : A_ScreenWidth
      height := (height) ? height : A_ScreenHeight
      if (width != this.ScreenWidth || height != this.ScreenHeight) {
         this.ScreenWidth := width, this.ScreenHeight := height
         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)
         loop % this.layers.maxIndex()
            this.Draw(this.layers[A_Index].1, this.layers[A_Index].2, this.layers[A_Index].3, pGraphics)
      }
   }

   Bitmap(x := "", y := "", w := "", h := "") {
      x := (x != "") ? x : this.x
      y := (y != "") ? y : this.y
      w := (w != "") ? w : this.xx - this.x
      h := (h != "") ? h : this.yy - this.y

      pBitmap := Gdip_CreateBitmap(this.ScreenWidth, this.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)
      pBitmapCopy := Gdip_CloneBitmapArea(pBitmap, x, y, w, h)
      Gdip_DisposeImage(pBitmap)
      return pBitmapCopy ; Please dispose of this image responsibly.
   }

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

   Save(filename := "", quality := 92) {
      filename := (filename ~= "i)\.(bmp|dib|rle|jpg|jpeg|jpe|jfif|gif|tif|tiff|png)$") ? filename
               : (filename != "") ? filename ".png" : this.title ".png"
      pBitmap := this.Bitmap()
      Gdip_SaveBitmapToFile(pBitmap, filename, quality)
      Gdip_DisposeImage(pBitmap)
      return this
   }

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

   Render(text := "", style1 := "", style2 := "", update := true) {
      if !(this.hwnd){
         _subtitle := (this.outer) ? new this.outer.Subtitle() : new Subtitle()
         return _subtitle.Render(text, style1, style2, update)
      }
      else {
         Critical On
         this.Draw(text, style1, style2)
         this.DetectScreenResolutionChange()
         if (this.allowDrag == true)
            this.Reposition()
         if (update == true) {
            UpdateLayeredWindow(this.hwnd, this.hdc, 0, 0, this.ScreenWidth, this.ScreenHeight)
         }
         if (this.time) {
            self_destruct := ObjBindMethod(this, "Destroy")
            SetTimer, % self_destruct, % -1 * this.time
         }
         this.rendered := true
         Critical Off
         return this
      }
   }

   RenderToBitmap(text := "", style1 := "", style2 := "") {
      if !(this.hwnd){
         _subtitle := (this.outer) ? new this.outer.Subtitle() : new Subtitle()
         return _subtitle.RenderToBitmap(text, style1, style2)
      } else {
         this.Render(text, style1, style2, false)
         return this.Bitmap()
      }
   }

   RenderToHBitmap(text := "", style1 := "", style2 := "") {
      if !(this.hwnd){
         _subtitle := (this.outer) ? new this.outer.Subtitle() : new Subtitle()
         return _subtitle.RenderToHBitmap(text, style1, style2)
      } else {
         this.Render(text, style1, style2, false)
         return this.hBitmap()
      }
   }

   Reposition() {
      CoordMode, Mouse, Screen
      MouseGetPos, x_mouse, y_mouse
      this.LButton := GetKeyState("LButton", "P") ? 1 : 0
      this.keypress := (this.LButton && DllCall("GetForegroundWindow") == this.hwnd) ? ((!this.keypress) ? 1 : -1) : ((this.keypress == -1) ? 2 : 0)

      if (this.keypress == 1) {
         this.x_mouse := x_mouse, this.y_mouse := y_mouse
         this.hbm2 := CreateDIBSection(A_ScreenWidth, A_ScreenHeight)
         this.hdc2 := CreateCompatibleDC()
         this.obm2 := SelectObject(this.hdc2, this.hbm2)
         this.G2 := Gdip_GraphicsFromHDC(this.hdc2)
      }

      if (this.keypress == -1) {
         dx := x_mouse - this.x_mouse
         dy := y_mouse - this.y_mouse
         safe_x := (0 + dx <= 0) ? 0 : 0 + dx
         safe_y := (0 + dy <= 0) ? 0 : 0 + dy
         safe_w := (0 + this.ScreenWidth + dx >= this.ScreenWidth) ? this.ScreenWidth : 0 + this.ScreenWidth + dx
         safe_h := (0 + this.ScreenHeight + dy >= this.ScreenHeight) ? this.ScreenHeight : 0 + this.ScreenHeight + dy
         source_x := (dx < 0) ? -dx : 0
         source_y := (dy < 0) ? -dy : 0
         ;Tooltip % dx ", " dy "`n" safe_x ", " safe_y ", " safe_w ", " safe_h
         Gdip_GraphicsClear(this.G2)
         BitBlt(this.hdc2, safe_x, safe_y, safe_w, safe_h, this.hdc, source_x, source_y)
         UpdateLayeredWindow(this.hwnd, this.hdc2, 0, 0, this.ScreenWidth, this.ScreenHeight)
      }

      if (this.keypress == 2) {
         Gdip_DeleteGraphics(this.G)
         SelectObject(this.hdc, this.obm)
         DeleteObject(this.hbm)
         DeleteDC(this.hdc)
         this.hdc := this.hdc2
         this.obm := this.obm2
         this.hbm := this.hbm2
         this.G := Gdip_GraphicsFromHDC(this.hdc2)
      }

      Reposition := ObjBindMethod(this, "Reposition")
      SetTimer, % Reposition, -10
   }

   Draw(text := "", style1 := "", style2 := "", pGraphics := "") {
      ; If the image was previously rendered, reset everything like a new Subtitle object.
      if (pGraphics == "") {
         if (this.rendered == true) {
            this.rendered := false
            this.layers := {}
            this.x := this.y := this.xx := this.yy := "" ; not 0!
            Gdip_GraphicsClear(this.G)
         }
         this.layers.push([text, style1, style2]) ; Saves each call of Draw()
         pGraphics := this.G
      }

      ; Remove excess whitespace. This is required for proper RegEx detection.
      style1 := !IsObject(style1) ? RegExReplace(style1, "\s+", " ") : style1
      style2 := !IsObject(style2) ? RegExReplace(style2, "\s+", " ") : style2

      ; Load saved styles if and only if both styles are blank.
      if (style1 == "" && style2 == "")
         style1 := this.style1, style2 := this.style2
      else
         this.style1 := style1, this.style2 := style2 ; Remember styles so that they can be loaded next time.

      ; RegEx help? https://regex101.com/r/xLzZzO/2
      static q1 := "(?i)^.*?\b(?<!:|:\s)\b"
      static q2 := "(?!(?>\([^()]*\)|[^()]*)*\))(:\s*)?\(?(?<value>(?<=\()([\s:#%_a-z\-\.\d]+|\([\s:#%_a-z\-\.\d]*\))*(?=\))|[#%_a-z\-\.\d]+).*$"

      ; Extract the time variable and save it for later when we Render() everything.
      this.time := (style1.time) ? style1.time : (style1.t) ? style1.t
         : (!IsObject(style1) && (___ := RegExReplace(style1, q1 "(t(ime)?)" q2, "${value}")) != style1) ? ___
         : (style2.time) ? style2.time : (style2.t) ? style2.t
         : (!IsObject(style2) && (___ := RegExReplace(style2, q1 "(t(ime)?)" q2, "${value}")) != style2) ? ___
         : this.time

      ; Extract styles from the background styles parameter.
      if IsObject(style1) {
         _a  := (style1.anchor != "")   ? style1.anchor   : style1.a
         _x  := (style1.left != "")     ? style1.left     : style1.x
         _y  := (style1.top != "")      ? style1.top      : style1.y
         _w  := (style1.width != "")    ? style1.width    : style1.w
         _h  := (style1.height != "")   ? style1.height   : style1.h
         _r  := (style1.radius != "")   ? style1.radius   : style1.r
         _c  := (style1.color != "")    ? style1.color    : style1.c
         _m  := (style1.margin != "")   ? style1.margin   : style1.m
         _p  := (style1.padding != "")  ? style1.padding  : style1.p
         _q  := (style1.quality != "")  ? style1.quality  : (style1.q) ? style1.q : 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) ? ___ : ""
      }

      ; Extract styles from the text styles parameter.
      if IsObject(style2) {
         a  := (style2.anchor != "")      ? style2.anchor      : style2.a
         x  := (style2.left != "")        ? style2.left        : style2.x
         y  := (style2.top != "")         ? style2.top         : style2.y
         w  := (style2.width != "")       ? style2.width       : style2.w
         h  := (style2.height != "")      ? style2.height      : style2.h
         m  := (style2.margin != "")      ? style2.margin      : style2.m
         f  := (style2.font != "")        ? style2.font        : style2.f
         s  := (style2.size != "")        ? style2.size        : style2.s
         c  := (style2.color != "")       ? style2.color       : style2.c
         b  := (style2.bold != "")        ? style2.bold        : style2.b
         i  := (style2.italic != "")      ? style2.italic      : style2.i
         u  := (style2.underline != "")   ? style2.underline   : style2.u
         j  := (style2.justify != "")     ? style2.justify     : style2.j
         n  := (style2.noWrap != "")      ? style2.noWrap      : style2.n
         z  := (style2.condensed != "")   ? style2.condensed   : style2.z
         d  := (style2.dropShadow != "")  ? style2.dropShadow  : style2.d
         o  := (style2.outline != "")     ? style2.outline     : style2.o
         q  := (style2.quality != "")     ? style2.quality     : (style2.q) ? style2.q : 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) ? ___ : ""
      }

      ; These are the type checkers.
      static valid := "^\s*(\-?\d+(?:\.\d*)?)\s*(%|pt|px|vh|vmin|vw)?\s*$"
      static valid_positive := "^\s*(\d+(?:\.\d*)?)\s*(%|pt|px|vh|vmin|vw)?\s*$"

      ; Define viewport width and height. This is the visible screen area.
      this.vw := 0.01 * this.ScreenWidth    ; 1% of viewport width.
      this.vh := 0.01 * this.ScreenHeight   ; 1% of viewport height.
      this.vmin := (this.vw < this.vh) ? this.vw : this.vh ; 1vw or 1vh, whichever is smaller.

      ; Get Rendering Quality.
      _q := (_q >= 0 && _q <= 4) ? _q : 4          ; Default SmoothingMode is 4 if radius is set. See Draw 1.
      q  := (q >= 0 && q <= 5) ? q : 4             ; Default TextRenderingHint is 4 (antialias).

      ; Get Font size.
      s  := (s ~= valid_positive) ? RegExReplace(s, "\s", "") : "2.23vh"           ; Default font size is 2.23vh.
      s  := (s ~= "(pt|px)$") ? SubStr(s, 1, -2) : s                               ; Strip spaces, px, and pt.
      s  := (s ~= "vh$") ? RegExReplace(s, "vh$", "") * this.vh : s                ; Relative to viewport height.
      s  := (s ~= "vw$") ? RegExReplace(s, "vw$", "") * this.vw : s                ; Relative to viewport width.
      s  := (s ~= "(%|vmin)$") ? RegExReplace(s, "(%|vmin)$", "") * this.vmin : s  ; Relative to viewport minimum.

      ; Get Bold, Italic, Underline, NoWrap, and Justification of text.
      style += (b) ? 1 : 0         ; bold
      style += (i) ? 2 : 0         ; italic
      style += (u) ? 4 : 0         ; underline
      style += (strikeout) ? 8 : 0 ; strikeout, not implemented.
      n  := (n) ? 0x4000 | 0x1000 : 0x4000
      j  := (j ~= "i)cent(er|re)") ? 1 : (j ~= "i)(far|right)") ? 2 : 0   ; Left/near, center/centre, far/right.

      ; Later when text x and w are finalized and it is found that x + ReturnRC[3] exceeds the screen,
      ; then the _redrawBecauseOfCondensedFont flag is set to true.
      if (this._redrawBecauseOfCondensedFont == true)
         f:=z, z:=0, this._redrawBecauseOfCondensedFont := false

      ; Create Font.
      hFamily := (___ := Gdip_FontFamilyCreate(f)) ? ___ : Gdip_FontFamilyCreate("Arial") ; Default font is Arial.
      hFont := Gdip_FontCreate(hFamily, s, style)
      hFormat := Gdip_StringFormatCreate(n)
      Gdip_SetStringFormatAlign(hFormat, j)  ; Left = 0, Center = 1, Right = 2

      ; Simulate string width and height. This will get the exact width and height of the text.
      CreateRectF(RC, 0, 0, 0, 0)
      Gdip_SetSmoothingMode(pGraphics, _q)     ; None = 3, AntiAlias = 4
      Gdip_SetTextRenderingHint(pGraphics, q)  ; Anti-Alias = 4, Cleartype = 5 (and gives weird effects.)
      ReturnRC := Gdip_MeasureString(pGraphics, Text, hFont, hFormat, RC)
      ReturnRC := StrSplit(ReturnRC, "|")      ; Contains the values for measured x, y, w, h text.

      ; Get background width and height. Default width and height are simulated width and height.
      _w := (_w ~= valid_positive) ? RegExReplace(_w, "\s", "") : ReturnRC[3]
      _w := (_w ~= "(pt|px)$") ? SubStr(_w, 1, -2) : _w
      _w := (_w ~= "(%|vw)$") ? RegExReplace(_w, "(%|vw)$", "") * this.vw : _w
      _w := (_w ~= "vh$") ? RegExReplace(_w, "vh$", "") * this.vh : _w
      _w := (_w ~= "vmin$") ? RegExReplace(_w, "vmin$", "") * this.vmin : _w
      ; Output is a decimal with pixel units.

      _h := (_h ~= valid_positive) ? RegExReplace(_h, "\s", "") : ReturnRC[4]
      _h := (_h ~= "(pt|px)$") ? SubStr(_h, 1, -2) : _h
      _h := (_h ~= "vw$") ? RegExReplace(_h, "vw$", "") * this.vw : _h
      _h := (_h ~= "(%|vh)$") ? RegExReplace(_h, "(%|vh)$", "") * this.vh : _h
      _h := (_h ~= "vmin$") ? RegExReplace(_h, "vmin$", "") * this.vmin : _h
      ; Output is a decimal with pixel units.

      ; Get background anchor. This is where the origin of the image is located.
      ; The default origin is the top left corner. Default anchor is 1.
      _a := RegExReplace(_a, "\s", "")
      _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 anchor is top-left.

      ; _x and _y can be specified as locations (left, center, right, top, bottom).
      ; These location words in _x and _y take precedence over the values in _a.
      _a  := (_x ~= "i)left") ? 1+(((_a-1)//3)*3) : (_x ~= "i)cent(er|re)") ? 2+(((_a-1)//3)*3) : (_x ~= "i)right") ? 3+(((_a-1)//3)*3) : _a
      _a  := (_y ~= "i)top") ? 1+(mod(_a-1,3)) : (_y ~= "i)cent(er|re)") ? 4+(mod(_a-1,3)) : (_y ~= "i)bottom") ? 7+(mod(_a-1,3)) : _a

      ; Convert English words to numbers. Don't mess with these values any further.
      _x  := (_x ~= "i)left") ? 0 : (_x ~= "i)cent(er|re)") ? 0.5*this.ScreenWidth : (_x ~= "i)right") ? this.ScreenWidth : _x
      _y  := (_y ~= "i)top") ? 0 : (_y ~= "i)cent(er|re)") ? 0.5*this.ScreenHeight : (_y ~= "i)bottom") ? this.ScreenHeight : _y

      ; Get _x value.
      _x := (_x ~= valid) ? RegExReplace(_x, "\s", "") : 0  ; Default _x is 0.
      _x := (_x ~= "(pt|px)$") ? SubStr(_x, 1, -2) : _x
      _x := (_x ~= "(%|vw)$") ? RegExReplace(_x, "(%|vw)$", "") * this.vw : _x
      _x := (_x ~= "vh$") ? RegExReplace(_x, "vh$", "") * this.vh : _x
      _x := (_x ~= "vmin$") ? RegExReplace(_x, "vmin$", "") * this.vmin : _x

      ; Get _y value.
      _y := (_y ~= valid) ? RegExReplace(_y, "\s", "") : 0  ; Default _y is 0.
      _y := (_y ~= "(pt|px)$") ? SubStr(_y, 1, -2) : _y
      _y := (_y ~= "vw$") ? RegExReplace(_y, "vw$", "") * this.vw : _y
      _y := (_y ~= "(%|vh)$") ? RegExReplace(_y, "(%|vh)$", "") * this.vh : _y
      _y := (_y ~= "vmin$") ? RegExReplace(_y, "vmin$", "") * this.vmin : _y

      ; Now let's modify the _x and _y values with the _anchor, so that the image has a new point of origin.
      ; We need our calculated _width and _height for this.
      _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.


      ; Get the text width and text height.
      ; Note that there are two new lines. Matching a percent symbol (%) will give text width/height
      ; that is relative to the background width/height. This is undesirable behavior, and so
      ; the user should use "vh" and "vw" whenever possible.
      w  := ( w ~= valid_positive) ? RegExReplace( w, "\s", "") : ReturnRC[3] ; Default is simulated text width.
      w  := ( w ~= "(pt|px)$") ? SubStr( w, 1, -2) :  w
      w  := ( w ~= "vw$") ? RegExReplace( w, "vw$", "") * this.vw :  w
      w  := ( w ~= "vh$") ? RegExReplace( w, "vh$", "") * this.vh :  w
      w  := ( w ~= "vmin$") ? RegExReplace( w, "vmin$", "") * this.vmin :  w
      w  := ( w ~= "%$") ? RegExReplace( w, "%$", "") * 0.01 * _w :  w

      h  := ( h ~= valid_positive) ? RegExReplace( h, "\s", "") : ReturnRC[4] ; Default is simulated text height.
      h  := ( h ~= "(pt|px)$") ? SubStr( h, 1, -2) :  h
      h  := ( h ~= "vw$") ? RegExReplace( h, "vw$", "") * this.vw :  h
      h  := ( h ~= "vh$") ? RegExReplace( h, "vh$", "") * this.vh :  h
      h  := ( h ~= "vmin$") ? RegExReplace( h, "vmin$", "") * this.vmin :  h
      h  := ( h ~= "%$") ? RegExReplace( h, "%$", "") * 0.01 * _h :  h

      ; If text justification is set but x is not, align the justified text relative to the center
      ; or right of the backgound, after taking into account the text width.
      if (x == "")
         x  := (j = 1) ? _x + (_w/2) - (w/2) : (j = 2) ? _x + _w - w : x

      ; Get anchor.
      a  := RegExReplace( a, "\s", "")
      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 anchor is top-left.

      ; Text x and text y can be specified as locations (left, center, right, top, bottom).
      ; These location words in text x and text y take precedence over the values in the text anchor.
      a  := ( x ~= "i)left") ? 1+((( a-1)//3)*3) : ( x ~= "i)cent(er|re)") ? 2+((( a-1)//3)*3) : ( x ~= "i)right") ? 3+((( a-1)//3)*3) :  a
      a  := ( y ~= "i)top") ? 1+(mod( a-1,3)) : ( y ~= "i)cent(er|re)") ? 4+(mod( a-1,3)) : ( y ~= "i)bottom") ? 7+(mod( a-1,3)) :  a

      ; Convert English words to numbers. Don't mess with these values any further.
      ; Also, these values are relative to the background.
      x  := ( x ~= "i)left") ? _x : (x ~= "i)cent(er|re)") ? _x + 0.5*_w : (x ~= "i)right") ? _x + _w : x
      y  := ( y ~= "i)top") ? _y : (y ~= "i)cent(er|re)") ? _y + 0.5*_h : (y ~= "i)bottom") ? _y + _h : y

      ; Validate text x and y, convert to pixels.
      x  := ( x ~= valid) ? RegExReplace( x, "\s", "") : _x ; Default text x is background x.
      x  := ( x ~= "(pt|px)$") ? SubStr( x, 1, -2) :  x
      x  := ( x ~= "vw$") ? RegExReplace( x, "vw$", "") * this.vw :  x
      x  := ( x ~= "vh$") ? RegExReplace( x, "vh$", "") * this.vh :  x
      x  := ( x ~= "vmin$") ? RegExReplace( x, "vmin$", "") * this.vmin :  x
      x  := ( x ~= "%$") ? RegExReplace( x, "%$", "") * 0.01 * _w :  x

      y  := ( y ~= valid) ? RegExReplace( y, "\s", "") : _y ; Default text y is background y.
      y  := ( y ~= "(pt|px)$") ? SubStr( y, 1, -2) :  y
      y  := ( y ~= "vw$") ? RegExReplace( y, "vw$", "") * this.vw :  y
      y  := ( y ~= "vh$") ? RegExReplace( y, "vh$", "") * this.vh :  y
      y  := ( y ~= "vmin$") ? RegExReplace( y, "vmin$", "") * this.vmin :  y
      y  := ( y ~= "%$") ? RegExReplace( y, "%$", "") * 0.01 * _h :  y

      ; Modify text x and text y values with the anchor, so that the text has a new point of origin.
      ; The text anchor is relative to the text width and height before margin/padding.
      ; This is NOT relative to the background width and height.
      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

      ; Define margin and padding. Both parameters will leave the text unchanged,
      ; expanding the background box. The difference between the two is NON-EXISTENT.
      ; What does matter is if the margin/padding is a background style, the position of the text will not change.
      ; If the margin/padding is a text style, the text position will change.
      ; THERE REALLY IS NO DIFFERENCE BETWEEN MARGIN AND PADDING.
      _p := this.margin_and_padding(_p)
      _m := this.margin_and_padding(_m)
      p  := this.margin_and_padding( p)
      m  := this.margin_and_padding( m)

      ; Modify _x, _y, _w, _h with margin and padding, increasing the size of the background.
      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.4 + _p.4)
         _y  -= (_m.1 + _p.1)
      }

      ; If margin/padding are defined in the text parameter, shift the position of the text.
      x  += (m.4 + p.4)
      y  += (m.1 + p.1)

      ; Re-run: Condense Text using a Condensed Font if simulated text width exceeds screen width.
      if (Gdip_FontFamilyCreate(z)) {
         if (ReturnRC[3] + x > this.ScreenWidth) {
            this._redrawBecauseOfCondensedFont := true
            return this.Draw(text, style1, style2, pGraphics)
         }
      }

      ; Define radius of rounded corners.
      _r := (_r ~= valid_positive) ? RegExReplace(_r, "\s", "") : 0  ; Default radius is 0, or square corners.
      _r := (_r ~= "(pt|px)$") ? SubStr(_r, 1, -2) : _r
      _r := (_r ~= "vw$") ? RegExReplace(_r, "vw$", "") * this.vw : _r
      _r := (_r ~= "vh$") ? RegExReplace(_r, "vh$", "") * this.vh : _r
      _r := (_r ~= "vmin$") ? RegExReplace(_r, "vmin$", "") * this.vmin : _r
      ; percentage is defined as a percentage of the smaller background width/height.
      _r := (_r ~= "%$") ? RegExReplace(_r, "%$", "") * 0.01 * ((_w > _h) ? _h : _w) : _r
      ; the radius cannot exceed the half width or half height, whichever is smaller.
      _r  := (_r <= ((_w > _h) ? _h : _w) / 2) ? _r : 0

      ; Define color.
      _c := this.color(_c, 0xDD424242) ; Default background color is transparent gray.
      SourceCopy := (c ~= "i)(delete|eraser?|overwrite|sourceCopy)") ? 1 : 0 ; Eraser brush for text.
      c  := (SourceCopy) ? 0x00000000 : this.color( c, 0xFFFFFFFF) ; Default text color is white.

      ; Define outline and dropShadow.
      o := this.outline(o, s, c)
      d := this.dropShadow(d, ReturnRC[3], ReturnRC[4], s)

      ; 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)
      }

      ; Round 10 - Finalize _x, _y, _w, _h
      _x  := Round(_x)
      _y  := Round(_y)
      _w  := Round(_w)
      _h  := Round(_h)

      ; Define image boundaries using the background boundaries.
      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

      ; Define image boundaries using the text boundaries + outline boundaries.
      artifacts := Ceil(o.3 + 0.5*o.1)
      this.x  := (x - artifacts < this.x) ? x - artifacts : this.x
      this.y  := (y - artifacts < this.y) ? y - artifacts : this.y
      this.xx := (x + w + artifacts > this.xx) ? x + w + artifacts : this.xx
      this.yy := (y + h + artifacts > this.yy) ? y + h + artifacts : this.yy

      ; Define image boundaries using the dropShadow boundaries.
      artifacts := Ceil(d.3 + d.6 + 0.5*o.1)
      this.x  := (x + d.1 - artifacts < this.x) ? x + d.1 - artifacts : this.x
      this.y  := (y + d.2 - artifacts < this.y) ? y + d.2 - artifacts : this.y
      this.xx := (x + d.1 + w + artifacts > this.xx) ? x + d.1 + w + artifacts : this.xx
      this.yy := (y + d.2 + h + artifacts > this.yy) ? y + d.2 + h + artifacts : this.yy

      ; Round to the nearest integer.
      this.x := Floor(this.x)
      this.y := Floor(this.y)
      this.xx := Ceil(this.xx)
      this.yy := Ceil(this.yy)

      ; Draw 1 - Background
      if (_w && _h && _c && (_c & 0xFF000000)) {
         if (_r == 0)
            Gdip_SetSmoothingMode(pGraphics, 1) ; Turn antialiasing off if not a rounded rectangle.
         pBrushBackground := Gdip_BrushCreateSolid(_c)
         Gdip_FillRoundedRectangle(pGraphics, pBrushBackground, _x, _y, _w, _h, _r) ; DRAWING!
         Gdip_DeleteBrush(pBrushBackground)
         if (_r == 0)
            Gdip_SetSmoothingMode(pGraphics, _q) ; Turn antialiasing on for text rendering.
      }

      ; Draw 2 - DropShadow
      if (!d.void) {
         offset2 := d.3 + d.6 + Ceil(0.5*o.1)

         if (d.3) {
            DropShadow := Gdip_CreateBitmap(w + 2*offset2, h + 2*offset2)
            DropShadowG := Gdip_GraphicsFromImage(DropShadow)
            Gdip_SetSmoothingMode(DropShadowG, _q)
            Gdip_SetTextRenderingHint(DropShadowG, q)
            CreateRectF(RC, offset2, offset2, w + 2*offset2, h + 2*offset2)
         } else {
            CreateRectF(RC, x + d.1, y + d.2, w, h)
            DropShadowG := pGraphics
         }

         ; Use Gdip_DrawString if and only if there is a horizontal/vertical offset.
         if (o.void && d.6 == 0)
         {
            pBrush := Gdip_BrushCreateSolid(d.4)
            Gdip_DrawString(DropShadowG, Text, hFont, hFormat, pBrush, RC) ; DRAWING!
            Gdip_DeleteBrush(pBrush)
         }
         else ; Otherwise, use the below code if blur, size, and opacity are set.
         {
            ; Draw the outer edge of the text string.
            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, 2*d.6 + o.1)
            DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPen, "uint",2)
            DllCall("gdiplus\GdipDrawPath", "ptr",DropShadowG, "ptr",pPen, "ptr",pPath)
            Gdip_DeletePen(pPen)

            ; Fill in the outline. Turn off antialiasing and alpha blending so the gaps are 100% filled.
            pBrush := Gdip_BrushCreateSolid(d.4)
            Gdip_SetCompositingMode(DropShadowG, 1) ; Turn off alpha blending
            Gdip_SetSmoothingMode(DropShadowG, 3)   ; Turn off anti-aliasing
            Gdip_FillPath(DropShadowG, pBrush, pPath)
            Gdip_DeleteBrush(pBrush)
            Gdip_DeletePath(pPath)
            Gdip_SetCompositingMode(DropShadowG, 0)
            Gdip_SetSmoothingMode(DropShadowG, _q)
         }

         if (d.3) {
            Gdip_DeleteGraphics(DropShadowG)
            this.GaussianBlur(DropShadow, d.3, d.5)
            Gdip_SetInterpolationMode(pGraphics, 5) ; NearestNeighbor
            Gdip_SetSmoothingMode(pGraphics, 3) ; Turn off anti-aliasing
            Gdip_DrawImage(pGraphics, DropShadow, x + d.1 - offset2, y + d.2 - offset2, w + 2*offset2, h + 2*offset2) ; DRAWING!
            Gdip_SetSmoothingMode(pGraphics, _q)
            Gdip_DisposeImage(DropShadow)
         }
      }

      ; 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 glow effect around the edges.
         if (o.3) {
            Gdip_SetClipPath(pGraphics, pPath, 3) ; Exclude original text region from being drawn on.
            pPenGlow := Gdip_CreatePen(Format("0x{:02X}",((o.4 & 0xFF000000) >> 24)/o.3) . Format("{:06X}",(o.4 & 0x00FFFFFF)), 1)
            DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPenGlow, "uint",2)

            loop % o.3
            {
               DllCall("gdiplus\GdipSetPenWidth", "ptr",pPenGlow, "float",o.1 + 2*A_Index)
               DllCall("gdiplus\GdipDrawPath", "ptr",pGraphics, "ptr",pPenGlow, "ptr",pPath) ; DRAWING!
            }
            Gdip_DeletePen(pPenGlow)
            Gdip_ResetClip(pGraphics)
         }

         ; Draw outline text.
         if (o.1) {
            pPen := Gdip_CreatePen(o.2, o.1)
            DllCall("gdiplus\GdipSetPenLineJoin", "ptr",pPen, "uint",2)
            DllCall("gdiplus\GdipDrawPath", "ptr",pGraphics, "ptr",pPen, "ptr",pPath) ; DRAWING!
            Gdip_DeletePen(pPen)
         }

         ; Fill outline text.
         pBrush := Gdip_BrushCreateSolid(c)
         Gdip_SetCompositingMode(pGraphics, SourceCopy)
         Gdip_FillPath(pGraphics, pBrush, pPath) ; DRAWING!
         Gdip_SetCompositingMode(pGraphics, 0)
         Gdip_DeleteBrush(pBrush)
         Gdip_DeletePath(pPath)
      }

      ; Draw Text if outline is not are not specified.
      if (text != "" && o.void) {
         CreateRectF(RC, x, y, w, h)
         pBrushText := Gdip_BrushCreateSolid(c)
         Gdip_SetCompositingMode(pGraphics, SourceCopy)
         Gdip_DrawString(pGraphics, A_IsUnicode ? text : wtext, hFont, hFormat, pBrushText, RC) ; DRAWING!
         Gdip_SetCompositingMode(pGraphics, 0)
         Gdip_DeleteBrush(pBrushText)
      }

      ; Delete Font Objects.
      Gdip_DeleteStringFormat(hFormat)
      Gdip_DeleteFont(hFont)
      Gdip_DeleteFontFamily(hFamily)

      return (pGraphics == "") ? this : ""
   }

   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
   }

   dropShadow(d, x_simulated, y_simulated, font_size) {
      static valid := "^\s*(\-?\d+(?:\.\d*)?)\s*(%|pt|px|vh|vmin|vw)?\s*$"
      static q1 := "(?i)^.*?\b(?<!:|:\s)\b"
      static q2 := "(?!(?>\([^()]*\)|[^()]*)*\))(:\s*)?\(?(?<value>(?<=\()([\s:#%_a-z\-\.\d]+|\([\s:#%_a-z\-\.\d]*\))*(?=\))|[#%_a-z\-\.\d]+).*$"

      if IsObject(d) {
         d.1 := (d.1) ? d.1 : (d.horizontal != "") ? d.horizontal : d.h
         d.2 := (d.2) ? d.2 : (d.vertical   != "") ? d.vertical   : d.v
         d.3 := (d.3) ? d.3 : (d.blur       != "") ? d.blur       : d.b
         d.4 := (d.4) ? d.4 : (d.color      != "") ? d.color      : d.c
         d.5 := (d.5) ? d.5 : (d.opacity    != "") ? d.opacity    : d.o
         d.6 := (d.6) ? d.6 : (d.size       != "") ? d.size       : d.s
      } else if (d != "") {
         _ := RegExReplace(d, ":\s+", ":")
         _ := RegExReplace(_, "\s+", " ")
         _ := StrSplit(_, " ")
         _.1 := ((___ := RegExReplace(d, q1    "(h(orizontal)?)"    q2, "${value}")) != d) ? ___ : _.1
         _.2 := ((___ := RegExReplace(d, q1    "(v(ertical)?)"      q2, "${value}")) != d) ? ___ : _.2
         _.3 := ((___ := RegExReplace(d, q1    "(b(lur)?)"          q2, "${value}")) != d) ? ___ : _.3
         _.4 := ((___ := RegExReplace(d, q1    "(c(olor)?)"         q2, "${value}")) != d) ? ___ : _.4
         _.5 := ((___ := RegExReplace(d, q1    "(o(pacity)?)"       q2, "${value}")) != d) ? ___ : _.5
         _.6 := ((___ := RegExReplace(d, q1    "(s(ize)?)"          q2, "${value}")) != d) ? ___ : _.6
         d := _
      }
      else return {"void":true, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0}

      for key, value in d {
         if (key = 4) ; Don't mess with color data.
            continue
         d[key] := (d[key] ~= valid) ? RegExReplace(d[key], "\s", "") : 0 ; Default for everything is 0.
         d[key] := (d[key] ~= "(pt|px)$") ? SubStr(d[key], 1, -2) : d[key]
         d[key] := (d[key] ~= "vw$") ? RegExReplace(d[key], "vw$", "") * this.vw : d[key]
         d[key] := (d[key] ~= "vh$") ? RegExReplace(d[key], "vh$", "") * this.vh : d[key]
         d[key] := (d[key] ~= "vmin$") ? RegExReplace(d[key], "vmin$", "") * this.vmin : d[key]
      }

      d.1 := (d.1 ~= "%$") ? SubStr(d.1, 1, -1) * 0.01 * x_simulated : d.1
      d.2 := (d.2 ~= "%$") ? SubStr(d.2, 1, -1) * 0.01 * y_simulated : d.2
      d.3 := (d.3 ~= "%$") ? SubStr(d.3, 1, -1) * 0.01 * font_size : d.3
      d.4 := this.color(d.4, 0xFFFF0000) ; Default color is red.
      d.5 := (d.5 ~= "%$") ? SubStr(d.5, 1, -1) / 100 : d.5
      d.5 := (d.5 <= 0 || d.5 > 1) ? 1 : d.5 ; Range Opacity is a float from 0-1.
      d.6 := (d.6 ~= "%$") ? SubStr(d.6, 1, -1) * 0.01 * font_size : d.6
      return d
   }

   font(f, default := "Arial"){

   }

   margin_and_padding(m, default := 0) {
      static valid := "^\s*(\-?\d+(?:\.\d*)?)\s*(%|pt|px|vh|vmin|vw)?\s*$"
      static q1 := "(?i)^.*?\b(?<!:|:\s)\b"
      static q2 := "(?!(?>\([^()]*\)|[^()]*)*\))(:\s*)?\(?(?<value>(?<=\()([\s:#%_a-z\-\.\d]+|\([\s:#%_a-z\-\.\d]*\))*(?=\))|[#%_a-z\-\.\d]+).*$"

      if IsObject(m) {
         m.1 := (m.top    != "") ? m.top    : m.t
         m.2 := (m.right  != "") ? m.right  : m.r
         m.3 := (m.bottom != "") ? m.bottom : m.b
         m.4 := (m.left   != "") ? m.left   : m.l
      } else if (m != "") {
         _ := RegExReplace(m, ":\s+", ":")
         _ := RegExReplace(_, "\s+", " ")
         _ := StrSplit(_, " ")
         _.1 := ((___ := RegExReplace(m, q1    "(t(op)?)"           q2, "${value}")) != m) ? ___ : _.1
         _.2 := ((___ := RegExReplace(m, q1    "(r(ight)?)"         q2, "${value}")) != m) ? ___ : _.2
         _.3 := ((___ := RegExReplace(m, q1    "(b(ottom)?)"        q2, "${value}")) != m) ? ___ : _.3
         _.4 := ((___ := RegExReplace(m, q1    "(l(eft)?)"          q2, "${value}")) != m) ? ___ : _.4
         m := _
      }
      else return {1:default, 2:default, 3:default, 4:default}

      ; Follow CSS guidelines for margin!
      if (m.2 == "" && m.3 == "" && m.4 == "")
         m.4 := m.3 := m.2 := m.1, exception := true
      if (m.3 == "" && m.4 == "")
         m.4 := m.2, m.3 := m.1
      if (m.4 == "")
         m.4 := m.2

      for key, value in m {
         m[key] := (m[key] ~= valid) ? RegExReplace(m[key], "\s", "") : default
         m[key] := (m[key] ~= "(pt|px)$") ? SubStr(m[key], 1, -2) : m[key]
         m[key] := (m[key] ~= "vw$") ? RegExReplace(m[key], "vw$", "") * this.vw : m[key]
         m[key] := (m[key] ~= "vh$") ? RegExReplace(m[key], "vh$", "") * this.vh : m[key]
         m[key] := (m[key] ~= "vmin$") ? RegExReplace(m[key], "vmin$", "") * this.vmin : m[key]
      }
      m.1 := (m.1 ~= "%$") ? SubStr(m.1, 1, -1) * this.vh : m.1
      m.2 := (m.2 ~= "%$") ? SubStr(m.2, 1, -1) * (exception ? this.vh : this.vw) : m.2
      m.3 := (m.3 ~= "%$") ? SubStr(m.3, 1, -1) * this.vh : m.3
      m.4 := (m.4 ~= "%$") ? SubStr(m.4, 1, -1) * (exception ? this.vh : this.vw) : m.4
      return m
   }

   outline(o, font_size, font_color) {
      static valid_positive := "^\s*(\d+(?:\.\d*)?)\s*(%|pt|px|vh|vmin|vw)?\s*$"
      static q1 := "(?i)^.*?\b(?<!:|:\s)\b"
      static q2 := "(?!(?>\([^()]*\)|[^()]*)*\))(:\s*)?\(?(?<value>(?<=\()([\s:#%_a-z\-\.\d]+|\([\s:#%_a-z\-\.\d]*\))*(?=\))|[#%_a-z\-\.\d]+).*$"

      if IsObject(o) {
         o.1 := (o.1) ? o.1 : (o.stroke != "") ? o.stroke : o.s
         o.2 := (o.2) ? o.2 : (o.color  != "") ? o.color  : o.c
         o.3 := (o.3) ? o.3 : (o.glow   != "") ? o.glow   : o.g
         o.4 := (o.4) ? o.4 : (o.tint   != "") ? o.tint   : o.t
      } else if (o != "") {
         _ := RegExReplace(o, ":\s+", ":")
         _ := RegExReplace(_, "\s+", " ")
         _ := StrSplit(_, " ")
         _.1 := ((___ := RegExReplace(o, q1    "(s(troke)?)"        q2, "${value}")) != o) ? ___ : _.1
         _.2 := ((___ := RegExReplace(o, q1    "(c(olor)?)"         q2, "${value}")) != o) ? ___ : _.2
         _.3 := ((___ := RegExReplace(o, q1    "(g(low)?)"          q2, "${value}")) != o) ? ___ : _.3
         _.4 := ((___ := RegExReplace(o, q1    "(t(int)?)"          q2, "${value}")) != o) ? ___ : _.4
         o := _
      }
      else return {"void":true, 1:0, 2:0, 3:0, 4:0}

      for key, value in o {
         if (key = 2) || (key = 4) ; Don't mess with color data.
            continue
         o[key] := (o[key] ~= valid_positive) ? RegExReplace(o[key], "\s", "") : 0 ; Default for everything is 0.
         o[key] := (o[key] ~= "(pt|px)$") ? SubStr(o[key], 1, -2) : o[key]
         o[key] := (o[key] ~= "vw$") ? RegExReplace(o[key], "vw$", "") * this.vw : o[key]
         o[key] := (o[key] ~= "vh$") ? RegExReplace(o[key], "vh$", "") * this.vh : o[key]
         o[key] := (o[key] ~= "vmin$") ? RegExReplace(o[key], "vmin$", "") * this.vmin : o[key]
      }

      o.1 := (o.1 ~= "%$") ? SubStr(o.1, 1, -1) * 0.01 * font_size : o.1
      o.2 := this.color(o.2, font_color) ; Default color is the text font color.
      o.3 := (o.3 ~= "%$") ? SubStr(o.3, 1, -1) * 0.01 * font_size : o.3
      o.4 := this.color(o.4, o.2) ; Default color is outline color.
      return o
   }

   colorMap(c) {
      static map

      if !(map) {
      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"
      map := color
      }

      return map[c]
   }

   GaussianBlur(ByRef pBitmap, radius, opacity := 1) {
      static x86 := "
      (LTrim
      VYnlV1ZTg+xci0Uci30c2UUgx0WsAwAAAI1EAAGJRdiLRRAPr0UYicOJRdSLRRwP
      r/sPr0UYiX2ki30UiUWoi0UQjVf/i30YSA+vRRgDRQgPr9ONPL0SAAAAiUWci0Uc
      iX3Eg2XE8ECJVbCJRcCLRcSJZbToAAAAACnEi0XEiWXk6AAAAAApxItFxIllzOgA
      AAAAKcSLRaiJZcjHRdwAAAAAx0W8AAAAAIlF0ItFvDtFFA+NcAEAAItV3DHAi12c
      i3XQiVXgAdOLfQiLVdw7RRiNDDp9IQ+2FAGLTcyLfciJFIEPtgwDD69VwIkMh4tN
      5IkUgUDr0THSO1UcfBKLXdwDXQzHRbgAAAAAK13Q6yAxwDtFGH0Ni33kD7YcAQEc
      h0Dr7kIDTRjrz/9FuAN1GItF3CtF0AHwiceLRbg7RRx/LDHJO00YfeGLRQiLfcwB
      8A+2BAgrBI+LfeQDBI+ZiQSPjTwz933YiAQPQevWi0UIK0Xci03AAfCJRbiLXRCJ
      /itdHCt13AN14DnZfAgDdQwrdeDrSot1DDHbK3XcAf4DdeA7XRh9KItV4ItFuAHQ
      A1UID7YEGA+2FBop0ItV5AMEmokEmpn3fdiIBB5D69OLRRhBAUXg66OLRRhDAUXg
      O10QfTIxyTtNGH3ti33Ii0XgA0UID7YUCIsEjynQi1XkAwSKiQSKi1XgjTwWmfd9
      2IgED0Hr0ItF1P9FvAFF3AFF0OmE/v//i0Wkx0XcAAAAAMdFvAAAAACJRdCLRbAD
      RQyJRaCLRbw7RRAPjXABAACLTdwxwItdoIt10IlN4AHLi30Mi1XcO0UYjQw6fSEP
      thQBi33MD7YMA4kUh4t9yA+vVcCJDIeLTeSJFIFA69Ex0jtVHHwSi13cA10Ix0W4
      AAAAACtd0OsgMcA7RRh9DYt95A+2HAEBHIdA6+5CA03U68//RbgDddSLRdwrRdAB
      8InHi0W4O0UcfywxyTtNGH3hi0UMi33MAfAPtgQIKwSPi33kAwSPmYkEj408M/d9
      2IgED0Hr1otFDCtF3ItNwAHwiUW4i10Uif4rXRwrddwDdeA52XwIA3UIK3Xg60qL
      dQgx2yt13AH+A3XgO10YfSiLVeCLRbgB0ANVDA+2BBgPthQaKdCLVeQDBJqJBJqZ
      933YiAQeQ+vTi0XUQQFF4Ouji0XUQwFF4DtdFH0yMck7TRh97Yt9yItF4ANFDA+2
      FAiLBI+LfeQp0ItV4AMEj4kEj408Fpn3fdiIBA9B69CLRRj/RbwBRdwBRdDphP7/
      //9NrItltA+Fofz//9no3+l2PzHJMds7XRR9OotFGIt9CA+vwY1EBwMx/zt9EH0c
      D7Yw2cBHVtoMJFrZXeTzDyx15InyiBADRRjr30MDTRDrxd3Y6wLd2I1l9DHAW15f
      XcM=
      )"
      static x64 := "
      (LTrim
      VUFXQVZBVUFUV1ZTSIHsqAAAAEiNrCSAAAAARIutkAAAAIuFmAAAAESJxkiJVRhB
      jVH/SYnPi42YAAAARInHQQ+v9Y1EAAErvZgAAABEiUUARIlN2IlFFEljxcdFtAMA
      AABIY96LtZgAAABIiUUID6/TiV0ESIld4A+vy4udmAAAAIl9qPMPEI2gAAAAiVXQ
      SI0UhRIAAABBD6/1/8OJTbBIiVXoSINl6PCJXdxBifaJdbxBjXD/SWPGQQ+v9UiJ
      RZhIY8FIiUWQiXW4RInOK7WYAAAAiXWMSItF6EiJZcDoAAAAAEgpxEiLRehIieHo
      AAAAAEgpxEiLRehIiWX46AAAAABIKcRIi0UYTYn6SIll8MdFEAAAAADHRdQAAAAA
      SIlFyItF2DlF1A+NqgEAAESLTRAxwEWJyEQDTbhNY8lNAflBOcV+JUEPthQCSIt9
      +EUPthwBSItd8IkUhw+vVdxEiRyDiRSBSP/A69aLVRBFMclEO42YAAAAfA9Ii0WY
      RTHbMdtNjSQC6ytMY9oxwE0B+0E5xX4NQQ+2HAMBHIFI/8Dr7kH/wUQB6uvGTANd
      CP/DRQHoO52YAAAAi0W8Ro00AH82SItFyEuNPCNFMclJjTQDRTnNftRIi1X4Qg+2
      BA9CKwSKQgMEiZlCiQSJ930UQogEDkn/wevZi0UQSWP4SAN9GItd3E1j9kUx200B
      /kQpwIlFrEiJfaCLdaiLRaxEAcA580GJ8XwRSGP4TWPAMdtMAf9MA0UY60tIi0Wg
      S408Hk+NJBNFMclKjTQYRTnNfiFDD7YUDEIPtgQPKdBCAwSJmUKJBIn3fRRCiAQO
      Sf/B69r/w0UB6EwDXQjrm0gDXQhB/8FEO00AfTRMjSQfSY00GEUx20U53X7jSItF
      8EMPthQcQosEmCnQQgMEmZlCiQSZ930UQogEHkn/w+vXi0UEAUUQSItF4P9F1EgB
      RchJAcLpSv7//0yLVRhMiX3Ix0UQAAAAAMdF1AAAAACLRQA5RdQPja0BAABEi00Q
      McBFichEA03QTWPJTANNGEE5xX4lQQ+2FAJIi3X4RQ+2HAFIi33wiRSGD69V3ESJ
      HIeJFIFI/8Dr1otVEEUxyUQ7jZgAAAB8D0iLRZBFMdsx202NJALrLUxj2kwDXRgx
      wEE5xX4NQQ+2HAMBHIFI/8Dr7kH/wQNVBOvFRANFBEwDXeD/wzudmAAAAItFsEaN
      NAB/NkiLRchLjTwjRTHJSY00A0U5zX7TSItV+EIPtgQPQisEikIDBImZQokEifd9
      FEKIBA5J/8Hr2YtFEE1j9klj+EwDdRiLXdxFMdtEKcCJRaxJjQQ/SIlFoIt1jItF
      rEQBwDnzQYnxfBFNY8BIY/gx20gDfRhNAfjrTEiLRaBLjTweT40kE0UxyUqNNBhF
      Oc1+IUMPthQMQg+2BA8p0EIDBImZQokEifd9FEKIBA5J/8Hr2v/DRANFBEwDXeDr
      mkgDXeBB/8FEO03YfTRMjSQfSY00GEUx20U53X7jSItF8EMPthQcQosEmCnQQgME
      mZlCiQSZ930UQogEHkn/w+vXSItFCP9F1EQBbRBIAUXISQHC6Uf+////TbRIi2XA
      D4Ui/P//8w8QBQAAAAAPLsF2TTHJRTHARDtF2H1Cicgx0kEPr8VImEgrRQhNjQwH
      McBIA0UIO1UAfR1FD7ZUAQP/wvNBDyrC8w9ZwfNEDyzQRYhUAQPr2kH/wANNAOu4
      McBIjWUoW15fQVxBXUFeQV9dw5CQkJCQkJCQkJCQkJAAAIA/
      )"
      width := Gdip_GetImageWidth(pBitmap)
      height := Gdip_GetImageHeight(pBitmap)
      clone := Gdip_CloneBitmapArea(pBitmap, 0, 0, width, height)
      E1 := Gdip_LockBits(pBitmap, 0, 0, width, height, Stride1, Scan01, BitmapData1)
      E2 := Gdip_LockBits(clone, 0, 0, width, height, Stride2, Scan02, BitmapData2)

      DllCall("crypt32\CryptStringToBinary", "str",(A_PtrSize == 8) ? x64 : x86, "uint",0, "uint",0x1, "ptr",0, "uint*",s, "ptr",0, "ptr",0)
      p := DllCall("GlobalAlloc", "uint",0, "ptr",s, "ptr")
      if (A_PtrSize == 8)
         DllCall("VirtualProtect", "ptr",p, "ptr",s, "uint",0x40, "uint*",op)
      DllCall("crypt32\CryptStringToBinary", "str",(A_PtrSize == 8) ? x64 : x86, "uint",0, "uint",0x1, "ptr",p, "uint*",s, "ptr",0, "ptr",0)
      value := DllCall(p, "ptr",Scan01, "ptr",Scan02, "uint",width, "uint",height, "uint",4, "uint",radius, "float",opacity)
      DllCall("GlobalFree", "ptr", p)

      Gdip_UnlockBits(pBitmap, BitmapData1)
      Gdip_UnlockBits(clone, BitmapData2)
      Gdip_DisposeImage(clone)
      return value
   }

   outer[]
   {
      get {
         ; Determine if there is a parent class. this.__class will retrive the
         ; current instance's class name. Array notation [] will dereference.
         ; Returns void if this function is not nested in at least 2 classes.
         if ((_class := RegExReplace(this.__class, "^(.*)\..*$", "$1")) != this.__class)
            Loop, Parse, _class, .
               outer := (A_Index=1) ? %A_LoopField% : outer[A_LoopField]
         return outer
      }
   }

   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
   }
} ; End of Subtitle class.
Last edited by iseahound on 26 Feb 2022, 13:06, edited 53 times in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Subtitle() - Show text with style

28 Aug 2017, 03:31

Very nice function and demo :bravo:
My gdi skills are subpar, would it be possible to return a bitmap handle (HBITMAP) instead of displaying it in a gui? Maybe a spearate function would be more appropriate for that :think:

Thanks for sharing, cheers!
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle() - Show text with style

28 Aug 2017, 05:53

Is there anything beneficial to return a HBITMAP? I can make it so that you input a pGraphics, then manually call update layer window.

There's a function to get a pBitmap from a hwnd, and I'm assuming it's easy to grab the hBitmap from there.
User avatar
kczx3
Posts: 1677
Joined: 06 Oct 2015, 21:39

Re: Subtitle() - Show text with style

28 Aug 2017, 19:21

Helgef wrote:Very nice function and demo :bravo:
+1
Fry

Re: Subtitle() - Show text with style

28 Aug 2017, 20:15

Nice, like it.
Suggestions for now:

-background:
  • w0 should set it to invisible and blank to auto size, sometimes a user might need to just show text with no background.
-Text:
  • options to add drop shadow and all the possible options that come with it, like thickness, distance transparency, color etc, something like d(t10% d5%...) <=== % of the text size.
    Same for outline.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Subtitle() - Show text with style

29 Aug 2017, 15:55

iseahound wrote:Is there anything beneficial to return a HBITMAP? I can make it so that you input a pGraphics, then manually call update layer window.
Hello, thanks for responding. It would be beneficial to have a hBitmap if it refered to something looking as nice as in the demo :lol: My vision was to
1) use hBitmaps for ahk gui picture controls, eg, gui.addPic(,"hBitmap:" hbitmap)
2) convert the hBitmaps hIcon.

Your function is black magic to me, maybe the bitmap handles in it aren't useable for my ideas.
iseahound wrote:There's a function to get a pBitmap from a hwnd, and I'm assuming it's easy to grab the hBitmap from there.
I may try this when my eyes aren't half shut :|

Cheers.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Subtitle() - Show text with style

29 Aug 2017, 16:59

The variable hbm is an hBitmap, is that any good?
I often do tests when using Gdip functions like so:

Code: Select all

SplashImage, % "HBITMAP:" hBitmap, B ;B: borderless
Sleep, 3000
SplashImage, Off
[EDIT:] It seems when I tried this that the image included black all around the main image.

Warning:
Btw I had some problems while testing the script, to experiment with hBitmap, where parts of the screen were not updating (not anyone's fault, just a consequence of fiddling with the code). To get out of this situation I found two ways: I clicked on the arrow to show system tray icons, and closed the script. Also, I right-clicked on the taskbar, Start Task Manager, and then closed the script. I had the 'Command Line' column showing, which helped identify the right script, but the screen updating issue, meant I couldn't do this to add the column if it wasn't already added: 'View, Select Columns..., Command Line'.

So yeah, Helgef, I had a bit of scare there, trying to play about with hBitmap, and there was a little while before I came up with a solution, thankfully I avoided doing a restart.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle() - Show text with style

30 Aug 2017, 06:19

jeeswg wrote:The variable hbm is an hBitmap, is that any good?
In that case returning the variable hbm should do the trick.

Questions: (to everyone)

1) What's the difference between margin and padding? I'm using CSS as a template, and currently I've coded all margins and padding to do the same thing: Extend the width and height of the background box. No changes in x or y offsets.

2) Is anyone using this a lot? The blinking you see in the demo is due to me deleting the Graphics, hbm, hdc and obm and recreating them over and over. I can release a class version that alleviates this. (It already exists as a part of my Vis2 project.)

Finally I have received Fry's suggestions and am testing them out. One new feature anchor points has been completed. Bugfixes to allow negative percentages have been completed on my end, they will be released whenever.
User avatar
kczx3
Posts: 1677
Joined: 06 Oct 2015, 21:39

Re: Subtitle() - Show text with style

30 Aug 2017, 06:46

margin is added spaced outside the boundary of the element. This gives space between two elements where if they both had a border, those borders would no longer touch with a positive margin. Padding is internal space to the element. If you were to have text inside the element and a positive padding value, there would be space between the text and its own border.
https://i.stack.imgur.com/PeSIJ.gif
User avatar
kczx3
Posts: 1677
Joined: 06 Oct 2015, 21:39

Re: Subtitle() - Show text with style

30 Aug 2017, 06:52

Is there an option for a border?

Also, supporting so many different ways of passing options really seems like a waste of time and creates a lot of fluff code. I prefer object notation coming from a JavaScript background.
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle() - Show text with style

30 Aug 2017, 07:08

kczx3 wrote:Is there an option for a border?

Also, supporting so many different ways of passing options really seems like a waste of time and creates a lot of fluff code. I prefer object notation coming from a JavaScript background.
I might as well try coding a border too. Supporting so many ways of passing options is because I like it to be flexable. Syntax like "x500 y200" are taken from the AHK GUI. Whereas I also prefer {"x":500, "y":200}

better yet:

Code: Select all

background := {"x":500, "y":200}
background.color := 0x3F627F
if (mouseTouchesBackground()}
   background.y := 900
else 
   background.y := 200
Subtitle("Pickles and Jam", background, "jCenter")
Is how it's actually meant to be used. Objects for switching values before rendering (dynamic). Strings for simple tasks (static).
User avatar
kczx3
Posts: 1677
Joined: 06 Oct 2015, 21:39

Re: Subtitle() - Show text with style

30 Aug 2017, 07:27

While we are on the topic, is it possible to draw with gdip that has color which changes in a fluid manner? The best example I can think of is the focus border around options on the Nintendo Switch. The blue color of it is constantly changing and would be neat to replicate that.

EDIT:

This is the best screenshot I could find of the blue border - https://res.cloudinary.com/lmn/image/up ... -a57b4.jpg

MORE INFO:

I see the ColorAnimation class but I'm fairly confident this can't be used with AHK directly - ColorAnimation
guest3456
Posts: 3477
Joined: 09 Oct 2013, 10:31

Re: Subtitle() - Show text with style

30 Aug 2017, 09:00

screenshot of the subtitle effect?

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Subtitle() - Show text with style

30 Aug 2017, 11:12

@ jeeswg. I did try to use hbm before I posted, but it showed a bitmap as big as the whole screen with the text oddly placed, and it was mostly white, no blackness. Blackness and or whiteness is not a problem per se, more the size of it. :think: Thanks for almost rebooting for the sake of helping a fellow forum member :angel:
What is your reboot time? Convert A_TickCount. I'm on 5 days, very recent for me.
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle() - Show text with style

12 Sep 2017, 13:00

Helgef wrote:@ jeeswg. I did try to use hbm before I posted, but it showed a bitmap as big as the whole screen with the text oddly placed, and it was mostly white, no blackness. Blackness and or whiteness is not a problem per se, more the size of it. :think: Thanks for almost rebooting for the sake of helping a fellow forum member :angel:
What is your reboot time? Convert A_TickCount. I'm on 5 days, very recent for me.

Try this:

Code: Select all

#include class_Subtitle.ahk
#include <Gdip_All>
pToken := Gdip_Startup()
hBitmap := Subtitle.RenderToHBitmap("Centered Text", "w500 h500", "anchor:center font:Veranda x50% y50%")
SplashImage, % "HBITMAP:" hBitmap, B
Esc:: ExitApp
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Subtitle() - Show text with style

12 Sep 2017, 16:18

iseahound wrote: Try this:
:superhappy: :bravo:

I will probably return with questions.
User avatar
moefr01
Posts: 119
Joined: 25 Nov 2015, 09:01
Location: Germany

Re: Subtitle.Render() - Show text with style (Easy, painless Gdip interpreter)

13 Sep 2017, 15:03

Thanks for your Great Code, iseahound... very usable for informative devs.
I am looking for a possibilty to exchange the formatted text by a timer, eg a clock, countdown ...
With gdip you can define a prush, which erases the already displayed string every tick, so its easy to create a smooth Desktopclock. Andy hint how to do it with subtitle.render? :?:
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle.Render() - Show text with style (Easy, painless Gdip interpreter)

14 Sep 2017, 14:07

Simple Timer

Code: Select all

#include class_Subtitle.ahk
#include <Gdip_All>
pToken := Gdip_Startup()

a := new Subtitle()
SetTimer, label, 1000

label:
FormatTime, d,, h:mm:ss
a.Render(d)
return
Esc:: ExitApp
Divergent Clock

Code: Select all

#include class_Subtitle.ahk
#include <Gdip_All>

; Create Subtitle Object.
pToken := Gdip_Startup()
a := new Subtitle()

; Define background and text styles using object notation.
background := {"x":"center", "y":"center", "padding":"10%", "color":"LemonChiffon"}
text := {"x":"50%", "anchor":2, "font":"Calibri", "size":"192", "color":"Transparent"}

; Define text outline
text.outline := {"width":2, "color":"Black"}

; Start Timer
SetTimer, label, 1000


label:
; Fetch Time
FormatTime, d,, h:mm:ss
; Use the Subtitle.Draw() function to draw without rendering text to the screen.
a.Draw(d, background, text)
; Make another background object.
b2 := {"width":0, "y":"center", "anchor":"center"}

; Change the x position depending on the # of seconds.
if (A_Sec > 10) {
   diverge := (A_Sec - 10) / 100
   b2.x := 50 + diverge "%"
} else
   b2.x := "50%"

; Draw to the screen and render it.
a.Draw(d, b2, text)
a.Render() ; you could do a.Render(d, b2, text) too.
return

Esc:: ExitApp
iseahound
Posts: 1582
Joined: 13 Aug 2016, 21:04
Contact:

Re: Subtitle.Render() - Show text with style (Easy, painless Gdip interpreter)

14 Sep 2017, 18:12

Updated Subtitle
Fixed anchor, and Save() to be more reliable.
#2: Fixed Memory leak.
This will be the last update for a long time.


Old Version I originally posted. For archival purposes.
Spoiler

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 137 guests