Compass - Measure angles and scale distances with your mouse

Post your working scripts, libraries and tools
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Compass - Measure angles and scale distances with your mouse

24 Apr 2015, 11:25

Compass
Measure angles and scale distances with your mouse.
Demo Gif

Code: Select all

^!LButton::Compass.Measure(1)           ; Ctrl+Alt+LButton
^#LButton::Compass.Measure(2)           ; Ctrl+Win+LButton

; Measure a known distance. Sets the scale factor for measuring subsequent distances.
^!RButton::Compass.SetScale(1, 1)       ; Ctrl+Alt+RButton

; Clear the scale factor. Distance will not be calculated for subsequent measurements.
^#RButton::Compass.SetScale(1, 2)       ; Ctrl+Win+RButton

; Toggle snap to horizontal/vertical when within +/-3 degrees.
^F12::Compass.ToggleSnap()              ; Ctrl+F12


/*  Compass
 *  By kon - ahkscript.org/boards/viewtopic.php?t=7225
 *  Translation to stand-alone Gdip thanks to jNizM - ahkscript.org/boards/viewtopic.php?&t=7225&p=45589#p45570
 *  Updated May 18, 2015
 *      Measure angles and scale distances with your mouse. Press the hotkeys and drag your mouse between two points.
 *  Note:
 *      *If you change the Measure/SetScale hotkeys, also change the keys GetKeyState monitors in the while-loop.
 *      **If you have multiple monitors, adjust the static variables w and h to match your resolution.
 *  Methods:
 *      Measure         Measure angles and scale distances with your mouse.
 *      SetScale        Sets the scale factor to use for subsequent measurements.
 *      ToggleSnap      Toggle snap to horizontal/vertical when within +/-3 degrees.
 *  Methods (Internal):
 *      SetScaleGui     Displays a GUI which prompts the user for the distance measured.
 *      pxDist          Returns the distance between two points in pixels.
 *      DecFtToArch     Converts decimal feet to architectural.
 *      gcd             Euclidean GCD.
 */
class Compass {
    static Color1 := 0x55000000, Color2 := 0x990066ff, Lineweight1 := 3
    , ScaleFactor := "", Units := "ft", Snap := false
    , w := A_ScreenWidth    ;**The area in which this script is displayed, which should be your full resolution.
    , h := A_ScreenHeight

    /*  Measure
     *      Measure angles and scale distances with your mouse.
     *  Parameters:
     *      Orientation     1       Up=90, Down=-90, Left=180, Right=0
     *                      2       Up=0,  Down=180, Left=270, Right=90
     *  Returns:            The distance, in pixels, between the two points. (Used internally by the SetScale method.)
     */
    Measure(Orientation:=1) {
        Conv := 180 / (4 * ATan(1)) * (Orientation = 1 ? -1 : 1)
        if !(DllCall("kernel32.dll\GetModuleHandle", "Str", "gdiplus", "Ptr"))
            hGDIPLUS := DllCall("kernel32.dll\LoadLibrary", "Str", "gdiplus.dll", "Ptr")
        VarSetCapacity(SI, 24, 0), NumPut(1, SI, 0, "UInt")
        DllCall("gdiplus.dll\GdiplusStartup", "UPtrP", pToken, "Ptr", &SI, "Ptr", 0)
        if !(pToken) {
            MsgBox, GDIPlus could not be started.`nCheck the availability of GDIPlus on your system.
            return
        }
        CoordMode, Mouse, Screen
        MouseGetPos, StartX, StartY
        Gui, Compass: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs
        Gui, Compass: Show, NA
        hWnd := WinExist()
        , VarSetCapacity(BI, 40, 0), NumPut(this.w, BI, 4, "UInt"), NumPut(this.h, BI, 8, "UInt"), NumPut(40, BI, 0, "UInt")
        , NumPut(1, BI, 12, "UShort"), NumPut(0, BI, 16, "UInt"), NumPut(32, BI, 14, "UShort")
        , hbm := DllCall("gdi32.dll\CreateDIBSection", "Ptr", hdc := DllCall("user32.dll\GetDC", "Ptr", 0, "Ptr"), "UPtr", &BI, "UInt", 0, "UPtr*", 0, "Ptr", 0, "UInt", 0, "Ptr")
        , DllCall("user32.dll\ReleaseDC", "Ptr", hWnd, "Ptr", hdc)
        , hdc := DllCall("gdi32.dll\CreateCompatibleDC", "Ptr", 0, "Ptr")
        , obm := DllCall("gdi32.dll\SelectObject", "Ptr", hdc, "Ptr", hbm)
        , DllCall("gdiplus.dll\GdipCreateFromHDC", "Ptr", hdc, "PtrP", G)
        , DllCall("gdiplus.dll\GdipSetSmoothingMode", "Ptr", G, "Int", 4)
        , DllCall("gdiplus.dll\GdipCreatePen1", "UInt", this.Color1, "Float", this.Lineweight1, "Int", 2, "PtrP", pPen)
        , DllCall("gdiplus.dll\GdipCreateSolidFill", "Int", this.Color2, "PtrP", pBrush)
        , MouseKey := SubStr(A_ThisHotkey, 3), PrevX := "", PrevY := ""
        , hMSVCRT := DllCall("kernel32.dll\LoadLibrary", "Str", "msvcrt.dll", "Ptr")
        while (GetKeyState("Ctrl", "P") && GetKeyState(MouseKey, "P")) {    ; *Loop until Ctrl or L/RButton is released.
            MouseGetPos, CurrentX, CurrentY
            if (PrevX != CurrentX) || (PrevY != CurrentY) {
                PrevX := CurrentX, PrevY := CurrentY
                if (this.Snap) {
                    if ((ADiff := Abs(CurrentY - StartY) / Abs(CurrentX - StartX))  < 0.052407 && ADiff)
                        CurrentY := StartY
                    else if (ADiff > 19.081136)
                        CurrentX := StartX
                }
                DllCall("gdiplus.dll\GdipDrawLine", "Ptr", G, "Ptr", pPen, "Float", StartX, "Float", StartY, "Float", CurrentX, "Float", CurrentY)
                , DllCall("gdiplus.dll\GdipFillEllipse", "Ptr", G, "Ptr", pBrush, "Float", StartX - 3, "Float", StartY - 3, "Float", 7, "Float", 7)
                , DllCall("gdiplus.dll\GdipFillEllipse", "Ptr", G, "Ptr", pBrush, "Float", CurrentX - 3, "Float", CurrentY - 3, "Float", 7, "Float", 7)
                , VarSetCapacity(PT, 8), NumPut(0, PT, 0, "UInt"), NumPut(0, PT, 4, "UInt")
                , DllCall("user32.dll\UpdateLayeredWindow", "Ptr", hWnd, "Ptr", 0, "UPtr", &PT, "Int64*", this.w | this.h << 32, "Ptr", hdc, "Int64*", 0, "UInt", 0, "UInt*", 0x1FF0000, "UInt", 2)
                , DllCall("gdiplus.dll\GdipGraphicsClear", "Ptr", G, "UInt", 0x00FFFFFF)
                , Angle := DllCall("msvcrt.dll\atan2", "Double", CurrentY - StartY, "Double", CurrentX - StartX, "CDECL Double") * Conv
                if (Orientation = 2)
                    Angle += Angle < -90 ? 450 : 90
                if (this.ScaleFactor) {
                    Dist := Format("{1:0.3f}", this.pxDist(CurrentX, StartX, CurrentY, StartY) * this.ScaleFactor)
                    if (this.Units = "ft")
                        Dist .= " ft (" . this.DecFtToArch(Dist) . ")"
                    else 
                        Dist .= " " . this.Units
                }
                ToolTip, % Format("{1:0.3f}", Angle) . "°" . (Dist ? "`n" Dist : "")
            }
        }
        DllCall("kernel32.dll\FreeLibrary", "Ptr", hMSVCRT)
        , DllCall("gdiplus.dll\GdipDeletePen", "Ptr", pPen)
        , DllCall("gdiplus.dll\GdipDeleteBrush", "Ptr", pBrush)
        , DllCall("gdi32.dll\SelectObject", "Ptr", hdc, "Ptr", obm)
        , DllCall("gdi32.dll\DeleteObject", "Ptr", hbm)
        , DllCall("gdi32.dll\DeleteDC", "Ptr", hdc)
        , DllCall("gdiplus.dll\GdipDeleteGraphics", "Ptr", G)
        , DllCall("gdiplus.dll\GdiplusShutdown", "UPtr", pToken)
        , DllCall("kernel32.dll\FreeLibrary", "Ptr", hGDIPLUS)
        ToolTip
        Gui, Compass: Destroy
        return this.pxDist(CurrentX, StartX, CurrentY, StartY)
    }

    /*  SetScale
     *      Enter the known distance measured to set the scale factor to be used with all subsequent calls to Measure.
     *  Parameters:
     *      Orientation     1       Up=90, Down=-90, Left=180, Right=0
     *                      2       Up=0,  Down=180, Left=270, Right=90
     *      Action          1       The user will be prompted to enter the real world dimension corresponding to the
     *                              current measurement. Distance will be calculated for any subsequent measurements.
     *                      2       Clear the scale factor. Distance will not be calculated for subsequent measurements.
     */
    SetScale(Orientation:=1, Action:=1) {
        if (Action = 2)
            this.ScaleFactor := ""
        pxDist := this.Measure(Orientation)
        if (Action = 1)
            this.SetScaleGui(pxDist)
        return
    }
    
    /*  ToggleSnap
     *      Toggle snap to horizontal/vertical when within +/-3 degrees.
     */
    ToggleSnap() {
        this.Snap := !this.Snap
        TrayTip, Compass, % "Snap: " (this.Snap ? "On" : "Off")
    }

    SetScaleGui(pxLen) {
        static Text1, Text2, Text3, Edit1, Edit2, Button1
        Gui, SetScale: -Caption +Owner +LastFound
        WinID := WinExist()
        Gui, SetScale: Add, Text, vText1 x10 y10, Length
        Gui, SetScale: Add, Text, vText2 x110 y10, Units
        Gui, SetScale: Add, Edit, vEdit1 x10 y+10 w90
        GuiControlGet, Edit1, SetScale:pos, Edit1
        Gui, SetScale: Add, Edit, vEdit2 x+10 w90 y%Edit1Y%, % this.Units
        Gui, SetScale: Add, Text, vText3 x10 y+10, Enter the length of this measurement.
        Gui, SetScale: Add, Button, vButton1 x10 y+10 w190 gSetScaleOK Default, Ok
        Gui, SetScale: Show,, Set Scale
        WinSet AlwaysOnTop
        GuiControl, Focus, Edit1
        WinWaitNotActive, ahk_id %WinID%
        SetScaleGuiEscape:
        Gui, SetScale: Destroy
        return

        SetScaleOK:
        Gui, SetScale: Submit
        if Edit1 is number
            this.ScaleFactor := Edit1 / pxLen
        if (Edit2)
            this.Units := Edit2
        return
    }

    pxDist(x1, x2, y1, y2) {
        DiffX := x1 - x2, DiffY := y1 - y2
        return Sqrt(DiffX * DiffX + DiffY * DiffY)
    }

    DecFtToArch(DecFt, Precision:=16) {
        Ft := Floor(DecFt), In := 12 * Mod(DecFt, 1)
        , UpperLimit := 1 - (HalfPrecision := 0.5 / Precision)
        , Fraction := Mod(In, 1), In := Floor(In)
        if (Fraction >= UpperLimit) {
            In++, Fraction := ""
            if (In = 12)
                In := 0, Ft++
        }
        else if (Fraction < HalfPrecision)
            Fraction := ""
        else {
            Step := 1 / Precision
            loop % Precision - 1 {
                if (Fraction >= UpperLimit - (A_Index * Step)) {
                    gcd := this.gcd(Precision, n := Precision - A_Index)
                    , Fraction := " " n // gcd "/" Precision // gcd
                    break
                }
            }
        }
        return LTrim((Ft ? Ft "'-" : "") In Fraction """", " 0")
    }

    gcd(a, b) {
        while (b)
            b := Mod(a | 0x0, a := b)
        return a
    }
}
Last edited by kon on 18 May 2015, 15:57, edited 14 times in total.
User avatar
joedf
Posts: 7696
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: [Function] Compass - Measure angles with your mouse

25 Apr 2015, 09:46

Interesting concept! :0
toralf
Posts: 792
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: [Function] Compass - Measure angles with your mouse

25 Apr 2015, 10:39

I'm curious, kon.
Why did you script this?
Besides measuring the angle.
What do you do with the angle?
ciao
toralf
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: [Function] Compass - Measure angles with your mouse

25 Apr 2015, 11:13

It was originally an "Ask for help" request. [Link] I thought it was interesting.
Also, I sometimes need to calculate angles from incomplete information. ie plans in .pdf format. I will use this as a quick double-check in those situations. It's already come in handy :)
toralf
Posts: 792
Joined: 27 Apr 2014, 21:08
Location: Germany

Re: [Function] Compass - Measure angles with your mouse

25 Apr 2015, 11:51

Thanks for satisfying my curiosity.
ciao
toralf
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: Compass - Measure angles and scale distances with your m

28 Apr 2015, 12:46

Update: Added the ability to scale distances.
User avatar
joedf
Posts: 7696
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: Compass - Measure angles and scale distances with your m

28 Apr 2015, 18:25

Oh oh Oohh!!!! Add an awesome Gif!! :D
KevShaw
Posts: 11
Joined: 15 May 2015, 08:17

Re: Compass - Measure angles and scale distances with your mouse

15 May 2015, 08:24

Great Idea. Wish I'd have thought of it!! This is something I can use for sure. Getting an error on line 046, call to nonexistent function.
User avatar
jNizM
Posts: 2639
Joined: 30 Sep 2013, 01:33
GitHub: jNizM
Contact:

Re: Compass - Measure angles and scale distances with your mouse

15 May 2015, 09:49

@KevShaw:
A: You need to include Gdip.ahk (Lib)
or
B: You use the version without extra lib Compass.ahk
[AHK] 1.1.32.00 x64 Unicode | [WIN] 10 Pro (Version 2004) x64 | [GitHub] Profile
Donations are appreciated if I could help you
KevShaw
Posts: 11
Joined: 15 May 2015, 08:17

Re: Compass - Measure angles and scale distances with your mouse

15 May 2015, 11:40

Alright, where could a copy of the Gdip.ahk (Lib) found?
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: Compass - Measure angles and scale distances with your mouse

15 May 2015, 13:38

KevShaw, Gdip is available here. Currently I recommend getting the "Gdip All" version, rename it to gdip.ahk, and place it in your Lib folder.

Good idea about the Gif joedf. You may have seen that I added one shortly after you suggested it.

Nice job with the alternate version jNizM. (Edit: DllCall("gdiplus.dll\GdipSetSmoothingMode", "Ptr", pGraphics, "Int", 4) should be -> DllCall("gdiplus.dll\GdipSetSmoothingMode", "Ptr", G, "Int", 4) though.)
vasili111
Posts: 747
Joined: 21 Jan 2014, 02:04
Location: Georgia

Re: Compass - Measure angles and scale distances with your mouse

16 May 2015, 01:20

Thanks for sharing! I think it will be useful for making mouse gestures.
DRAKON-AutoHotkey: Visual programming for AutoHotkey.
User avatar
jNizM
Posts: 2639
Joined: 30 Sep 2013, 01:33
GitHub: jNizM
Contact:

Re: Compass - Measure angles and scale distances with your mouse

18 May 2015, 07:45

Thx for the hint. Updated. I changed 1-2 small things since last time.
[AHK] 1.1.32.00 x64 Unicode | [WIN] 10 Pro (Version 2004) x64 | [GitHub] Profile
Donations are appreciated if I could help you
kon
Posts: 1756
Joined: 29 Sep 2013, 17:11

Re: Compass - Measure angles and scale distances with your mouse

18 May 2015, 12:38

Update:
  • Incorporated translation to stand-alone Gdip thanks to jNizM - http://ahkscript.org/boards/viewtopic.p ... 860#p45570
  • Added new method, ToggleSnap, and adjusted existing method Measure. When Snap is on, the measurement will snap to horizontal/vertical when within +/-3 degrees. New default hotkey, Ctrl+F12, will toggle snap on/off.
(Quick Edit: fixed a divide-by-zero bug introduced by the new snap feature.)
FanaticGuru
Posts: 1411
Joined: 30 Sep 2013, 22:25

Re: Compass - Measure angles and scale distances with your mouse

19 May 2015, 17:54

Nice script. Useful for my job.

I added hotkeys to paste the last results from a measurement. Since you were already using F12 for toggle I went with Win+F12 paste decimal and Win+Alt+F12 paste architectural.

Not that it matters much but here is an example of how I do decimal feet to architectural measurements.

Code: Select all

DecFtToArch(Decimal,P:=16)
{
	Feet := Floor(Decimal), Decimal -= Feet, Inches := Floor(Decimal * 12), Decimal := Decimal * 12 - Inches
	RegExMatch(Decimal,"\.(\d+)0*", Match), N := Match1
	D := 10 ** StrLen(N)
	N := Round(N / D * P), D := P
	while X != 1
		X := GCD(N,D), N := N / X, D := D / X
	if (N = D)
		Inches ++, N := 0
	if (Inches = 12)
		Feet++, Inches := 0
	return Feet "'-" Inches (N and D ? " " Floor(N) "/" Floor(D):"")"""" 
}

GCD(A, B) 
{
    while B 
	   B := Mod(A|0x0, A:=B)
    return A
}
Here is a post that offer more options along the same vein:
Decimal2Fraction and Fraction2Decimal
The Highlighted to Fraction example is useful to convert measurements back and forth between decimal and architectural in other programs through the clipboard.

Also there are these functions about reducing terms in fractions:
Reduce Fraction Terms

Great job. If you ever decide to add to the script any more that would be great.

An option to leave the measurements displayed until erased might be nice. Multiply measurements would be nice but even if it was just one it would still be useful.

An option to measure areas.

An option to change the precision.

A GUI to choose options.

You get the idea. :bravo:

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts

AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon

[Function] Timer - Create and Manage Timers
User avatar
joedf
Posts: 7696
Joined: 29 Sep 2013, 17:08
Facebook: J0EDF
Google: +joedf
GitHub: joedf
Location: Canada
Contact:

Re: Compass - Measure angles and scale distances with your mouse

28 May 2015, 09:43

That's an awesome gif! :D
iilabs
Posts: 17
Joined: 07 Jun 2020, 16:57

Re: Compass - Measure angles and scale distances with your mouse

05 Aug 2020, 11:40

Very interesting tool. How do you get the lines to show up on the screen. Ive added GDIP to my folder but does it need to be in a specific library folder in the program files section where AHK program file is? Also, is there a way to keep the lines persist for a time or a way to delete it off the screen later. This will allow me to take a snapshot of all the measurements.

Thank you!
awel20
Posts: 206
Joined: 19 Mar 2018, 14:09

Re: Compass - Measure angles and scale distances with your mouse

05 Aug 2020, 16:23

Hi iilabs. :)
iilabs wrote:
05 Aug 2020, 11:40
How do you get the lines to show up on the screen. Ive added GDIP to my folder but does it need to be in a specific library folder in the program files section where AHK program file is?
You don't need gdip.ahk for this since the script calls all the gdip functions directly with DllCall. I think you were not seeing the lines because you are using multiple monitors; I adjusted the script to detect that.
iilabs wrote:
05 Aug 2020, 11:40
Also, is there a way to keep the lines persist for a time or a way to delete it off the screen later. This will allow me to take a snapshot of all the measurements.
I went ahead and made some adjustments to this script based on the script in your Ask For Help topic (https://www.autohotkey.com/boards/viewtopic.php?f=76&t=78930). So now it will keep the lines on screen until you call Compass.DeleteLines().

Code: Select all

#SingleInstance Force
#NoEnv
SetBatchLines, -1
MsgBox, 64, Screen Scale Instructions,
(
Hold down the keys while measuring.

Ctrl + Alt + RightMouse
    Measure a known distance. Sets the scale factor for measuring subsequent distances.

Ctrl + Alt + LeftMouse
    Measure a distance or angle.

Ctrl + Win + RightMouse
    Clear the scale factor. Distance will not be calculated for subsequent measurements.

Ctrl + F12
    Toggle snap to horizontal/vertical when within +/-3 degrees.
)
Compass.ToggleSnap() ; Turn Snap on at startup (snaps to right angles when within 3 degrees)
return

; Measure a known distance. Sets the scale factor for measuring subsequent distances.
^!RButton::Compass.SetScale()   ; Ctrl+Alt+RButton

^!LButton::                     ; Ctrl+Alt+LButton
    Compass.Measure() ; measure distance
    if (Compass.Lines.MaxIndex() = 3) { ; if this is the 3rd line
        Clipboard := "This measures " Round(Compass.Lines.1.Len, 1)
            . ", " Round(Compass.Lines.2.Len, 1)
            . ", " Round(Compass.Lines.3.Len, 1) " " Compass.Units "."
        MsgBox % "Measurements were copied to the clipboard`n`n" Clipboard
        Compass.DeleteLines() ; delete the 3 lines on screen
    }
return
 
; Clear the scale factor. Distance will not be calculated for subsequent measurements.
^#RButton::Compass.ClearScale() ; Ctrl+Win+RButton
 
; Toggle snap to horizontal/vertical when within +/-3 degrees.
^F12::Compass.ToggleSnap()      ; Ctrl+F12

/*  Compass
 *  By kon - ahkscript.org/boards/viewtopic.php?t=7225
 *  Translation to stand-alone Gdip thanks to jNizM - ahkscript.org/boards/viewtopic.php?&t=7225&p=45589#p45570
 *  Updated May 18, 2015
 *      Measure angles and scale distances with your mouse. Press the hotkeys and drag your mouse between two points.
 *  Note:
 *      *If you change the Measure/SetScale hotkeys, also change the keys GetKeyState monitors in the while-loop.
 *  Methods:
 *      Measure             Measure angles and scale distances with your mouse.
 *      SetScale            Sets the scale factor to use for subsequent measurements.
 *      ToggleSnap          Toggle snap to horizontal/vertical when within +/-3 degrees.
 *  Methods (Internal):
 *      SetScaleGui         Displays a GUI which prompts the user for the distance measured.
 *      pxDist              Returns the distance between two points in pixels.
 *      DecFtToArch         Converts decimal feet to architectural.
 *      gcd                 Euclidean GCD.
 *  Added methods 2020-08-05:
 *      ClearScale()        Clear the scale factor. Distance will not be calculated for subsequent measurements.
 *      AdjustSnap()        Adjusts points as required for snap.
 *      GDIP_Start()        Start GDIP.
 *      GDIP_Close()        Close GDIP.
 *      GDIP_Setup()        Setup window, brush, graphics, etc for GDIP.
 *      GDIP_Cleanup()      Delete brush, graphics, etc.
 *      GDIP_CreatePen()    Create pen.
 *      GDIP_DeletePen()    Delete pen.
 *      GDIP_CreateBrush()  Create brush.
 *      GDIP_DeleteBrush()  Delete brush.
 *      GDIP_DrawLine()     Draw a line.
 *      GDIP_UpdateWindow() Refresh the window.
 *      DeleteLines()       Delete all lines.
 *      SetOrientation()    Set the orientation of the angle
 *      UpdateLines()       Redraw the lines in Convert.Lines and update the window.
 *      GetWinArea()        Get the total resolution of all the monitors.
 */
class Compass {
    static Color1 := 0x55000000, Color2 := 0x990066ff, Lineweight1 := 3
    , ScaleFactor := "", Units := "ft", Snap := false
    , WinW := A_ScreenWidth ; this will be updated by Compass.GetWinArea() in case there are multiple monitors
    , WinH := A_ScreenHeight
    , Orientation := 1
    , Lines := []
    
    ; Measure angles and scale distances with your mouse. Returns the distance, in pixels, between the two points.
    ; The return value is used internally by the SetScale method.
    Measure() {
        Conv := 180 / (4 * ATan(1)) * (this.Orientation = 1 ? -1 : 1)
        if (this.pToken = "") {
            this.GetWinArea()
            this.GDIP_Start()
        }
        TTNum := this.Lines.MaxIndex() ? this.Lines.MaxIndex() + 1 : 1
        CoordMode, Mouse, Screen
        MouseGetPos, StartX, StartY
        MouseKey := SubStr(A_ThisHotkey, 3), PrevX := "", PrevY := ""
        , hMSVCRT := DllCall("kernel32.dll\LoadLibrary", "Str", "msvcrt.dll", "Ptr")
        while (GetKeyState("Ctrl", "P") && GetKeyState(MouseKey, "P")) {    ; *Loop until Ctrl or L/RButton is released.
            MouseGetPos, CurrentX, CurrentY
            if (PrevX != CurrentX) || (PrevY != CurrentY) {
                PrevX := CurrentX, PrevY := CurrentY
                if (this.Snap)
                    this.AdjustSnap(StartX, CurrentX, StartY, CurrentY)
                this.GDIP_DrawLine(StartX, CurrentX, StartY, CurrentY)
                , Angle := DllCall("msvcrt.dll\atan2", "Double", CurrentY - StartY, "Double", CurrentX - StartX, "CDECL Double") * Conv
                if (this.Orientation = 2)
                    Angle += Angle < -90 ? 450 : 90
                if (this.ScaleFactor) {
                    Dist := Format("{1:0.3f}", this.pxDist(CurrentX, StartX, CurrentY, StartY) * this.ScaleFactor)
                    if (this.Units = "ft")
                        Dist .= " ft (" . this.DecFtToArch(Dist) . ")"
                    else 
                        Dist .= " " . this.Units
                }
                ToolTip, % Format("{1:0.3f}", Angle) . "°" . (Dist ? "`n" Dist : ""),,, % TTNum
                this.UpdateLines()
            }
        }
        DllCall("kernel32.dll\FreeLibrary", "Ptr", hMSVCRT)
        if (this.Snap)
            this.AdjustSnap(StartX, CurrentX, StartY, CurrentY)
        this.Lines.Push({"StartX": StartX, "EndX": CurrentX, "StartY": StartY, "EndY": CurrentY, "Angle": Format("{1:0.3f}", Angle) . "°", "Len": Dist})
        return this.pxDist(StartX, CurrentX, StartY, CurrentY)
    }

    ; Enter the known distance measured to set the scale factor to be used with all subsequent calls to Measure. The
    ; user will be prompted to enter the real world dimension corresponding to the current measurement. Distance will be
    ; calculated for any subsequent measurements.
    SetScale() {
        this.SetScaleGui(pxDist := this.Measure())
        ; Pop the most recent line from this.Lines and redraw the remaining lines. (We don't want this line to remain
        ; on screen, or count towards the measurement total in this.Lines.MaxIndex().)
        ToolTip,,,, % this.Lines.MaxIndex()
        this.Lines.Pop()
        this.UpdateLines()
        if (this.Lines.MaxIndex() < 1)
            this.GDIP_Close()
    }

    ; Clear the scale factor. Distance will not be calculated for subsequent measurements.
    ClearScale() {
        this.ScaleFactor := ""
    }
    
    SetScaleGui(pxLen) {
        static Text1, Text2, Text3, Edit1, Edit2, Button1
        Gui, SetScale: -Caption +Owner +LastFound +AlwaysOnTop
        WinID := WinExist()
        Gui, SetScale: Add, Text, vText1 x10 y10, Length
        Gui, SetScale: Add, Text, vText2 x110 y10, Units
        Gui, SetScale: Add, Edit, vEdit1 x10 y+10 w90
        GuiControlGet, Edit1, SetScale:pos, Edit1
        Gui, SetScale: Add, Edit, vEdit2 x+10 w90 y%Edit1Y%, % this.Units
        Gui, SetScale: Add, Text, vText3 x10 y+10, Enter the length of this measurement.
        Gui, SetScale: Add, Button, vButton1 x10 y+10 w190 gSetScaleOK Default, Ok
        Gui, SetScale: Show,, Set Scale
        WinSet AlwaysOnTop
        GuiControl, Focus, Edit1
        WinWaitNotActive, ahk_id %WinID%
        SetScaleGuiEscape:
        Gui, SetScale: Destroy
        return

        SetScaleOK:
        Gui, SetScale: Submit
        if Edit1 is number
            this.ScaleFactor := Edit1 / pxLen
        if (Edit2)
            this.Units := Edit2
        return
    }
    
    AdjustSnap(StartX, ByRef CurrentX, StartY, ByRef CurrentY) {
        if ((ADiff := Abs(CurrentY - StartY) / Abs(CurrentX - StartX))  < 0.052407 && ADiff)
            CurrentY := StartY
        else if (ADiff > 19.081136)
            CurrentX := StartX
    }
    
    ; Toggle snap to horizontal/vertical when within +/-3 degrees.
    ToggleSnap() {
        this.Snap := !this.Snap
        TrayTip, Compass, % "Snap: " (this.Snap ? "On" : "Off")
    }

    pxDist(x1, x2, y1, y2) {
        return Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
    }

    DecFtToArch(DecFt, Precision:=16) {
        Ft := Floor(DecFt), In := 12 * Mod(DecFt, 1)
        , UpperLimit := 1 - (HalfPrecision := 0.5 / Precision)
        , Fraction := Mod(In, 1), In := Floor(In)
        if (Fraction >= UpperLimit) {
            In++, Fraction := ""
            if (In = 12)
                In := 0, Ft++
        }
        else if (Fraction < HalfPrecision)
            Fraction := ""
        else {
            Step := 1 / Precision
            loop % Precision - 1 {
                if (Fraction >= UpperLimit - (A_Index * Step)) {
                    gcd := this.gcd(Precision, n := Precision - A_Index)
                    , Fraction := " " n // gcd "/" Precision // gcd
                    break
                }
            }
        }
        return LTrim((Ft ? Ft "'-" : "") In Fraction """", " 0")
    }

    gcd(a, b) {
        while (b)
            b := Mod(a | 0x0, a := b)
        return a
    }
    
    GDIP_Start() {
        if !(DllCall("kernel32.dll\GetModuleHandle", "Str", "gdiplus", "Ptr"))
            this.hGDIPLUS := DllCall("kernel32.dll\LoadLibrary", "Str", "gdiplus.dll", "Ptr")
        VarSetCapacity(SI, 24, 0), NumPut(1, SI, 0, "UInt")
        DllCall("gdiplus.dll\GdiplusStartup", "UPtrP", pToken, "Ptr", &SI, "Ptr", 0)
        if !(this.pToken := pToken) {
            MsgBox, GDIPlus could not be started.`nCheck the availability of GDIPlus on your system.
            return
        }
        this.GDIP_Setup()
    }
    
    GDIP_Close() {
        this.GDIP_Cleanup()
        , DllCall("gdiplus.dll\GdiplusShutdown", "UPtr", this.pToken)
        , DllCall("kernel32.dll\FreeLibrary", "Ptr", this.hGDIPLUS)
        , this.pToken := "", this.hGDIPLUS := "" ; Clear these values to indicate that GDIP is not started
    }
    
    GDIP_Setup() {
        Gui, Compass: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs
        Gui, Compass: Show, NA
        this.hWnd := WinExist()
        , VarSetCapacity(BI, 40, 0), NumPut(this.WinW, BI, 4, "UInt"), NumPut(this.WinH, BI, 8, "UInt"), NumPut(40, BI, 0, "UInt")
        , NumPut(1, BI, 12, "UShort"), NumPut(0, BI, 16, "UInt"), NumPut(32, BI, 14, "UShort")
        , this.hbm := DllCall("gdi32.dll\CreateDIBSection", "Ptr", this.hdc := DllCall("user32.dll\GetDC", "Ptr", 0, "Ptr"), "UPtr", &BI, "UInt", 0, "UPtr*", 0, "Ptr", 0, "UInt", 0, "Ptr")
        , DllCall("user32.dll\ReleaseDC", "Ptr", this.hWnd, "Ptr", this.hdc)
        , this.hdc := DllCall("gdi32.dll\CreateCompatibleDC", "Ptr", 0, "Ptr")
        , this.obm := DllCall("gdi32.dll\SelectObject", "Ptr", this.hdc, "Ptr", this.hbm)
        , DllCall("gdiplus.dll\GdipCreateFromHDC", "Ptr", this.hdc, "PtrP", Graphics)
        , this.Graphics := Graphics
        , DllCall("gdiplus.dll\GdipSetSmoothingMode", "Ptr", this.Graphics, "Int", 4)
        , this.GDIP_CreatePen()
        , this.GDIP_CreateBrush()
    }
    
    GDIP_Cleanup() {
        this.GDIP_DeletePen()
        , this.GDIP_DeleteBrush()
        , DllCall("gdi32.dll\SelectObject", "Ptr", this.hdc, "Ptr", this.obm)
        , DllCall("gdi32.dll\DeleteObject", "Ptr", this.hbm)
        , DllCall("gdi32.dll\DeleteDC", "Ptr", this.hdc)
        , DllCall("gdiplus.dll\GdipDeleteGraphics", "Ptr", this.Graphics)
        , this.hdc := "", this.obm := "", this.hdc := "", this.Graphics := ""
        Gui, Compass: Destroy
    }
    
    GDIP_CreatePen() {
        DllCall("gdiplus.dll\GdipCreatePen1", "UInt", this.Color1, "Float", this.Lineweight1, "Int", 2, "PtrP", pPen)
        , this.pPen := pPen
    }
    
    GDIP_DeletePen() {
        DllCall("gdiplus.dll\GdipDeletePen", "Ptr", this.pPen)
        , this.pPen := ""
    }
    
    GDIP_CreateBrush() {
        DllCall("gdiplus.dll\GdipCreateSolidFill", "Int", this.Color2, "PtrP", pBrush)
        , this.pBrush := pBrush
    }
    
    GDIP_DeleteBrush() {
        DllCall("gdiplus.dll\GdipDeleteBrush", "Ptr", this.pBrush)
        , this.pBrush := ""
    }
    
    GDIP_DrawLine(StartX, CurrentX, StartY, CurrentY) {
        DllCall("gdiplus.dll\GdipDrawLine", "Ptr", this.Graphics, "Ptr", this.pPen, "Float", StartX, "Float", StartY, "Float", CurrentX, "Float", CurrentY)
        , DllCall("gdiplus.dll\GdipFillEllipse", "Ptr", this.Graphics, "Ptr", this.pBrush, "Float", StartX - 3, "Float", StartY - 3, "Float", 7, "Float", 7)
        , DllCall("gdiplus.dll\GdipFillEllipse", "Ptr", this.Graphics, "Ptr", this.pBrush, "Float", CurrentX - 3, "Float", CurrentY - 3, "Float", 7, "Float", 7)
    }
    
    GDIP_UpdateWindow() {
        VarSetCapacity(PT, 8), NumPut(0, PT, 0, "UInt"), NumPut(0, PT, 4, "UInt")
        , DllCall("user32.dll\UpdateLayeredWindow", "Ptr", this.hWnd, "Ptr", 0, "UPtr", &PT, "Int64*", this.WinW | this.WinH << 32, "Ptr", this.hdc, "Int64*", 0, "UInt", 0, "UInt*", 0x1FF0000, "UInt", 2)
        , DllCall("gdiplus.dll\GdipGraphicsClear", "Ptr", this.Graphics, "UInt", 0x00FFFFFF)
    }
    
    ; Clears all the visible lines
    DeleteLines() {
        for i, v in this.Lines
            ToolTip,,,, % i
        this.Lines := []
        , this.GDIP_Close()
    }
    
    ; Orientation   Angle
    ; 1             Up=90, Down=-90, Left=180, Right=0
    ; 2             Up=0,  Down=180, Left=270, Right=90
    SetOrientation(Orientation:=1) {
        this.Orientation := Orientation
    }
    
    ; Redraw the lines in this.Lines and update the window
    UpdateLines() {
        for i, Line in this.Lines
            this.GDIP_DrawLine(Line.StartX, Line.EndX, Line.StartY, Line.EndY)
        this.GDIP_UpdateWindow()
    }
    
    ; Gets the max coordinates of all the monitors.
    GetWinArea() {
        SysGet, MonCnt, MonitorCount
        Loop, % MonCnt
        {
            SysGet, Mon, Monitor, % A_Index
            if (MonRight > this.WinW)
                this.WinW := MonRight
            if (MonBottom > this.WinH)
                this.WinH := MonBottom
        }
    }
}
Last edited by awel20 on 07 Aug 2020, 13:38, edited 5 times in total.
gwarble
Posts: 427
Joined: 30 Sep 2013, 15:01

Re: Compass - Measure angles and scale distances with your mouse

05 Aug 2020, 17:16

nice update, small bug:
when snap is on, the second line created moves the endpoint of the previous line to its unsnapped position

for me some trivial changes, i removed the msgbox and let it add up to 10 lines to the screen before deleting, or deleting on Esc::, this is very cool and useful, thanks kon and awel
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
iilabs
Posts: 17
Joined: 07 Jun 2020, 16:57

Re: Compass - Measure angles and scale distances with your mouse

05 Aug 2020, 18:47

Thank you all for the improvement. Appreciate it.

I reloaded the script but still dont see any lines being drawn. I have a 4 monitor setup. Thanks Awel20 for picking that up. I do see the measurements so at least I know its working like a champ. Any way to get it to draw lines and get angle as well?

Return to “Scripts and Functions”

Who is online

Users browsing this forum: gwarble and 21 guests