Snipper - Window Snipping Tool

Post your working scripts, libraries and tools.
User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Snipper - Window Snipping Tool

Post by FanaticGuru » 31 Mar 2023, 21:09

Snipper

This is a snipping tool to convert an area of the screen into a picture.

Then the picture can be moved around the screen, copied to the clipboard, saved as a file, converted to a PDF, attached to an email, etc.

This is very much like Screen Clipping from AHK v1.

Snipper

Code: Select all

; Snipper
; Fanatic Guru
;
; Version 2024 01 09
;
; #Requires AutoHotkey v2
;
; Copy Area of Screen
;
;{-----------------------------------------------
;
; AutoHotkey alternate to Windows Snipping Tool with additional features
; Creates Gui of Snip that can be manipulated onscreen
;
;	Credits:
;	The work of dozens of people inspired this script.
;	Many of them listed in the threads below:
;
;	Screen Clipping		https://www.autohotkey.com/boards/viewtopic.php?f=6&t=12088
;	Gdip				https://www.autohotkey.com/boards/viewtopic.php?t=72011
;
;}

;; INITIALIZATION - ENVIROMENT
;{-----------------------------------------------
;
#Requires AutoHotkey v2
#Warn All, Off
#SingleInstance force
DetectHiddenWindows true
SetWinDelay(0)
;}

;; INITIALIZATION - FUNCTIONS
;{-----------------------------------------------
;
ObjectAutoProperty()
ObjectAutoProperty() => Object.Prototype.DefineProp('__Get', { Call: (this, Name, Params) => this.%Name% := {} })
;}

;; DEFAULT SETTING - VARIABLES
;{-----------------------------------------------
;
Settings_SavePath_Image := GetFullPathName('.\Snipper - Images\')
Settings_SavePath_Image_Ext := 'PNG' 	; BMP|DIB|RLE|JPG|JPEG|JPE|JFIF|GIF|TIF|TIFF|PNG
Settings_Image_Quality := 75			; JPG|JPEG|JPE|JFIF
Settings_ToolWindow := false			; True to Hide Snips from taskbar
;}

;; INITIALIZATION - VARIABLES
;{-----------------------------------------------
;
guiSnips := Map(), SnipVisible := true, Extensions := Array()
;}

;; #INCLUDE EXTENSION
;{-----------------------------------------------
;
; #Include Snipper - Extension - Acrobat.ah2
; #Include Snipper - Extension - Word.ah2
; #Include Snipper - Extension - Outlook.ah2
;}

;; INITIALIZATION - GUI
;{-----------------------------------------------
;
; Data for Context Menu Gui
ContextSnipMenuData := [], PrevApp := ''
ContextSnipMenuData.Push('SETTINGS  |  SNIP INFO', '', '', 'COPY:  Clipboard', '', 'SAVE:  ' Settings_SavePath_Image_Ext ' File',)
For Index, Item in Extensions
	For App, AppObj in Item.OwnProps()
	{
		If App != PrevApp
			ContextSnipMenuData.Push('')
		PrevApp := App
		ContextSnipMenuData.Push({ Text: AppObj.Text, Func: AppObj.Func })
	}
ContextSnipMenuData.Push('', '', 'CLOSE:      SNIP IMAGE')
; Create Context Menu Gui
ContextSnipMenu := Menu()
For Index, Item in ContextSnipMenuData
{
	IsObject(Item) ? MenuText := Item.Text : MenuText := Item
	MenuText ? ContextSnipMenu.Add(MenuText, ContextSnipMenu_Handler) : ContextSnipMenu.Add('')
}
ContextSnipMenu.SetIcon('1&', A_WinDir '\system32\shell32.dll', 260, 32)
ContextSnipMenu.SetIcon(ContextSnipMenuData.Length '&', A_WinDir '\system32\shell32.dll', 220)
; Settings Gui
guiSettings := Gui('+AlwaysOnTop -MinimizeBox'), guiSettings.Check := {}
guiSettings.SetFont('s10 bold underline')
guiSettings.Text.Heading := guiSettings.Add('Text', 'x40', 'Snip Settings')
guiSettings.SetFont()
guiSettings.Add('Text', 'x20 yp+10')
For ItemPos, Item in RangeArray(ContextSnipMenuData, 2, ContextSnipMenuData.Length - 1)
{
	IsObject(Item) ? ItemName := Item.Text : ItemName := Item
	(ItemName && guiSettings.Check.%ItemPos% := guiSettings.Add('CheckBox', ' yp+20', ' Border with ' ItemName))
}
(guiSettings.ButtonOK := guiSettings.Add('Button', 'yp+40 x50 w100 Default', 'Ok')).OnEvent('Click', guiSettings_Click)
(guiSettings.ButtonCancel := guiSettings.Add('Button', 'yp xp+125 w100', 'Cancel')).OnEvent('Click', guiSettings_Click)
guiSettings.ButtonOK.Focus
;}

;; AUTO-EXECUTE
;{-----------------------------------------------
;
Settings_Load
TraySetIcon(A_WinDir '\system32\shell32.dll', 260) ; Scissors
Try DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
;}

;; HOTKEYS
;{-----------------------------------------------
;
#Lbutton::		;	<-- Snip Image Only
{
	Global guiSnips
	Area := SelectScreenRegion('LButton')
	If (Area.W > 8 and Area.H > 8)
		SnipArea(Area, false, true, SnipVisible, &guiSnips)
}

#^Lbutton::	;	<-- Snip Image and Copy to Clipboard
{
	Global guiSnips
	Area := SelectScreenRegion('LButton')
	If (Area.W > 8 and Area.H > 8)
		SnipArea(Area, true, true, SnipVisible, &guiSnips)
}

#!Lbutton::	;	<-- Copy to Clipboard Only
{
	Area := SelectScreenRegion('LButton')
	SnipArea(Area, true, false)
}

+PrintScreen:: ;	<-- Toggle Show/Hide of Snips
{
	Global SnipVisible
	SnipVisible := !SnipVisible
	If SnipVisible
		For Hwnd, guiSnip in guiSnips
			guiSnip.GuiObj.Show('NA')
	If !SnipVisible
		For Hwnd, guiSnip in guiSnips
			guiSnip.GuiObj.Hide
}

~RButton::	;	<-- @@ Click for Context Menu
{
	MouseGetPos(, , &OutputVarWin)
	If WinGetTitle('ahk_id' OutputVarWin) = 'SnipperWindow'
	{
		WinActivate('ahk_id ' OutputVarWin)
		WinGetPos(&X, &Y, &W, &H, 'ahk_id ' OutputVarWin)
		ContextSnipMenu.Rename('1&', ContextSnipMenuData[1] ':    ' W - 6 ' Width by ' H - 6 ' Height')
		ContextSnipMenu.Parent.Hwnd := OutputVarWin
		ContextSnipMenu.Show()
	}
}

#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')

Esc:: CloseSnip()	;	<-- @@ Close Active Snip

#HotIf

;}

;; CLASSES & FUNCTIONS - GUI
;{-----------------------------------------------
;
;{ ContextSnipMenu_Handler
ContextSnipMenu_Handler(ItemName, ItemPos, MyMenu)
{
	(GetKeyState('Shift', 'p') ? Inverse := true : Inverse := false)
	; User Defined ClipboardApps
	If ContextSnipMenuData[ItemPos].HasProp('Func')
		ContextSnipMenuData[ItemPos].Func.Call(Borders := Inverse ^ guiSettings.Check.%ItemPos%.Value)
	; Standard Actions
	Switch ItemPos
	{
		Case 1: WinGetPos(&X, &Y, &W, &H, 'A'), SettingsDialog({ X: X, Y: Y, W: W, H: H })										; Settings
		Case 4: Snip2Clipboard(Borders := Inverse ^ guiSettings.Check.4.Value)													; Clipboard
		Case 6: Snip2File(Borders := Inverse ^ guiSettings.Check.6.Value, Settings_SavePath_Image, , Settings_SavePath_Image_Ext, , Settings_Image_Quality)	; File
		Case ContextSnipMenuData.Length: CloseSnip																														; Snip Image
	}
}
;}

SettingsDialog(Area)
{
	guiSettings.Show('x' Area.X + (Area.W / 3) ' y' Area.Y + (Area.H / 3))
}

guiSettings_Click(GuiCtrlObj, Info)
{
	If GuiCtrlObj = guiSettings.ButtonOK
	{
		For ItemPos, GuiCtrlCheck in guiSettings.Check.OwnProps()
			If GuiCtrlCheck.Value
				ContextSnipMenu.Check(ItemPos '&')
			Else
				ContextSnipMenu.Uncheck(ItemPos '&')
		Settings_Save
	}
	guiSettings.GetPos(&X, &Y, &W, &H), X *= A_ScreenDPI / 96, Y *= A_ScreenDPI / 96
	guiSettings.Hide
	WinActivate(ContextSnipMenu.Parent)
	CoordMode('Menu', 'Screen'), ContextSnipMenu.Show(X, Y)
}

;{ CloseSnip
CloseSnip(Hwnd?)
{
	If !IsSet(Hwnd)
		Hwnd := WinGetID('A')
	guiSnips[Hwnd].GuiObj.Destroy()
	guiSnips.Delete(Hwnd)
}
;}
;}

;; CLASSES & FUNCTIONS
;{-----------------------------------------------
;
;{ SelectScreenRegion
SelectScreenRegion(Key, Color := 'Lime', Color_Inactive := 'Red', Transparent := 80)
{
	; Initialize
	Static guiSSR, guiInfoDialog, DisplayWH := true, X, Y, W := 0, H := 0
	If !IsSet(guiSSR)
	{
		guiSSR := Gui('+AlwaysOnTop -Caption +Border +ToolWindow +LastFound -DPIScale +Resize', 'SnipperSelect')
		guiSSR.MarginX := 0, guiSSR.MarginY := 0
		guiSSR.BackColor := 1, WinSetTransColor(1, guiSSR)
		guiSSR.OnEvent('Size', guiSSR_Size)
		guiSSR.Background := guiSSR.Add('Text', 'w' A_ScreenWidth ' h' A_ScreenHeight ' Background' Color)
		If InStr(A_OSVersion, '6.1') ; win 7
			WinSetTransparent(Transparent, guiSSR)
		Else
			WinSetTransparent(Transparent, guiSSR.Background)
		guiSSR.InfoWH := guiSSR.Add('Text', 'Background' Color ' w175 h75 Center', 'XXXX x YYYY')
		guiSSR.AspectRatio := false
		OnMessage(0x84, WM_NCHITTEST)
		OnMessage(0x83, WM_NCCALCSIZE)
		OnMessage(0x214, WM_SIZING)
		OnMessage(0x0232, WM_EXITSIZEMOVE)
		OnExit((*) => OnMessage(0x83, WM_NCCALCSIZE, 0)) ; WM_NCCALCSIZE will cause error if Gui exist while closing
	}
	If !IsSet(guiInfoDialog)
	{
		guiInfoDialog := Gui('+AlwaysOnTop -MinimizeBox')
		guiInfoDialog.TextW := guiInfoDialog.Add('Text', 'y20', 'Width')
		guiInfoDialog.EditW := guiInfoDialog.Add('Edit', 'r1 w40 x50 yp')
		guiInfoDialog.TextH := guiInfoDialog.Add('Text', 'xp+60 yp', 'Height')
		guiInfoDialog.EditH := guiInfoDialog.Add('Edit', 'r1 w40 xp+50 yp')
		(guiInfoDialog.CheckRatio := guiInfoDialog.Add('CheckBox', 'xp+60 yp-10', 'Lock Ratio')).OnEvent('Click', guiInfoDialog_CheckRatio)
		(guiInfoDialog.CheckWH := guiInfoDialog.Add('CheckBox', 'xp yp+20 Checked' DisplayWH, 'Display W x H')).OnEvent('Click', guiInfoDialog_CheckWH)
		(guiInfoDialog.ButtonApply := guiInfoDialog.Add('Button', 'Default xm yp+40 w80', 'Apply')).OnEvent('Click', guiInfoDialog_Click)
		(guiInfoDialog.ButtonOK := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Ok')).OnEvent('Click', guiInfoDialog_Click)
		(guiInfoDialog.ButtonCancel := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Cancel')).OnEvent('Click', guiInfoDialog_Click)
	}

	guiSSR.AdvanceSelectMode := false, guiSSR.AspectRatio := false
	ControlSetChecked(0, guiInfoDialog.CheckRatio)

	; Drag Selection
	CoordMode('Mouse', 'Screen')
	MouseGetPos(&sX, &sY)
	guiSSR.Show('x' sX ' y' sY ' w1 h1')
	Wprev := Hprev := 0
	Loop
	{
		MouseGetPos(&eX, &eY)
		W := Abs(sX - eX), H := Abs(sY - eY)
		X := Min(sX, eX), Y := Min(sY, eY)
		If A_Index < 50
			guiSSR.Background.Redraw()
		guiSSR.Move(X, Y, W, H)
		Display_WH(false)
		Sleep 10
	} Until !GetKeyState(Key, 'p')

	; Advanced Selection
	If GetKeyState("Shift")
	{
		guiSSR.AdvanceSelectMode := true, guiSSR_Active := true
		ControlFocus(A_ScriptHwnd)
		Loop
		{
			Display_WH()
			If GetKeyState('RButton', 'P')
				InfoWH_RButton
			If WinActive('A') = A_ScriptHwnd and !guiSSR_Active
			{
				guiSSR.InfoWH.Opt('Background' Color)
				guiSSR.InfoWH.Redraw()
				guiSSR_Active := true
			}
			Else If WinActive('A') != A_ScriptHwnd and guiSSR_Active
			{
				guiSSR.InfoWH.Opt('Background' Color_Inactive)
				guiSSR.InfoWH.Redraw()
				guiSSR_Active := false
			}
			Sleep 10
		} Until GetKeyState('Enter', 'P') and WinActive('A') = A_ScriptHwnd
	}

	guiSSR.GetPos(&X, &Y, &W, &H)
	guiSSR.Hide()
	Return { X: X, Y: Y, W: W, H: H, X2: X + W, Y2: Y + H }

	Display_WH(MarkAspectRatio := true)
	{
		If GetKeyState('Shift', 'P') or DisplayWH and (W > 60 and H > 35)
		{
			guiSSR.GetPos(&X, &Y, &W, &H)
			If W != Wprev or H != Hprev
			{
				FontSize := MinMax(Min(W // 25, H // 5), 8, 20)
				PosWHx := Max(1, (W - FontSize * 6) // 2), PosWHy := Max(1, (H - FontSize * 2) // 2)
				guiSSR.InfoWH.SetFont('s' FontSize)
				guiSSR.InfoWH.Text := (guiSSR.AspectRatio && MarkAspectRatio ? '[' W 'x' H ']' : W 'x' H)
				guiSSR.InfoWH.Move(PosWHx, PosWHy, FontSize ** 1.7 + 15, FontSize * 2)
				guiSSR.InfoWH.Visible := true
				Wprev := W, Hprev := H
			}
		}
		Else
		{
			guiSSR.InfoWH.Visible := false
			Wprev := Hprev := 0
			guiSSR.Background.Redraw
		}
	}

	InfoWH_RButton()
	{
		MouseGetPos(, , , &OutputVarControl, 2)
		If OutputVarControl = guiSSR.InfoWH.Hwnd
			guiInfoDialog.Show('AutoSize')
	}

	InfoWH_LButton()
	{
		MouseGetPos(, , , &OutputVarControl, 2)
		If OutputVarControl = guiSSR.InfoWH.Hwnd
			WinActivate('ahk_id' A_ScriptHwnd)
	}

	guiInfoDialog_Click(GuiCtrlObj, Info)
	{
		If GuiCtrlObj.Text = 'Apply'
		{
			If guiInfoDialog.EditW.Value and guiInfoDialog.EditH.Value
			{
				guiSSR.Move(X, Y, guiInfoDialog.EditW.Value, guiInfoDialog.EditH.Value)
				(guiInfoDialog.CheckRatio.Value && guiSSR.AspectRatio := guiInfoDialog.EditW.Value / guiInfoDialog.EditH.Value)
			}
			Else
			(!guiInfoDialog.EditW.Value ? guiInfoDialog.EditW.Focus() : guiInfoDialog.EditH.Focus())
			Return
		}
		If GuiCtrlObj.Text = 'Ok'
		{
			If guiInfoDialog.EditW.Value and guiInfoDialog.EditH.Value
			{
				guiSSR.Move(X, Y, guiInfoDialog.EditW.Value, guiInfoDialog.EditH.Value)
				(guiInfoDialog.CheckRatio.Value && guiSSR.AspectRatio := guiInfoDialog.EditW.Value / guiInfoDialog.EditH.Value)
			}
			Else
			{
				(!guiInfoDialog.EditW.Value ? guiInfoDialog.EditW.Focus() : guiInfoDialog.EditH.Focus())
				Return
			}
		}
		guiInfoDialog.Hide
	}

	guiInfoDialog_CheckWH(GuiCtrlObj, Info)
	{
		DisplayWH := GuiCtrlObj.Value
	}

	guiInfoDialog_CheckRatio(GuiCtrlObj, Info)
	{
		If GuiCtrlObj.Value
			guiSSR.AspectRatio := W / H, guiSSR.InfoWH.Text := '[' W 'x' H ']'
		Else
			guiSSR.AspectRatio := false, guiSSR.InfoWH.Text := W 'x' H
	}

	guiSSR_Size(guiSSR, WindowMinMax, Width, Height)
	{
		If guiSSR.AdvanceSelectMode
		{
			guiSSR.Background.Move(0, 0), guiSSR.Background.Redraw()
			Display_WH()
		}
	}

	;{ WM Event Functions - Selection
	WM_SIZING(wParam, lParam, msg, hwnd)
	{
		If guiSSR.AspectRatio
		{
			L := NumGet(lParam, 0, "Int"), T := NumGet(lParam, 4, "Int")
			r := NumGet(lParam, 8, "Int"), B := NumGet(lParam, 12, "Int")
			Switch wParam
			{
				Case 1, 2, 7, 8: NumPut('int', T + ((r - L) / guiSSR.AspectRatio), lParam, 12)	; L, R, BL, BR = adjust B
				Case 3, 6: NumPut('int', L + ((B - T) * guiSSR.AspectRatio), lParam, 8)			; T, B = adjust R
				Case 4, 5: NumPut('int', B - ((r - L) / guiSSR.AspectRatio), lParam, 4)			; T,TR = adjust T
			}
		}
	}

	WM_EXITSIZEMOVE(wParam, lParam, msg, hwnd)
	{
		If hwnd = guiSSR.Hwnd
		{
			ControlFocus(A_ScriptHwnd)
		}
	}

	WM_NCCALCSIZE(wParam, lParam, msg, hwnd)
	{
		If hwnd != guiSSR.Hwnd
			Return

		If guiSSR.AdvanceSelectMode
			Return 0x0300	; WVR_REDRAW
		Return 0
	}

	WM_NCHITTEST(wParam, lParam, msg, hwnd)
	{
		Static Border_Size := 10

		If hwnd != guiSSR.Hwnd or !guiSSR.AdvanceSelectMode
			Return

		X := lParam << 48 >> 48, Y := lParam << 32 >> 48
		WinGetPos(&gX, &gY, &gW, &gH)

		hit_Left := X < gX + Border_Size
		hit_Right := X >= gX + gW - Border_Size
		hit_Top := Y < gY + Border_Size
		hit_Bottom := Y >= gY + gH - Border_Size

		If hit_Top
		{
			If hit_Left
				Return 0xD
			Else If hit_Right
				Return 0xE
			Else
				Return 0xC
		}
		Else If hit_Bottom
		{
			If hit_Left
				Return 0x10
			Else If hit_Right
				Return 0x11
			Else
				Return 0xF
		}
		Else If hit_Left
			Return 0xA
		Else If hit_Right
			Return 0xB

		; else default hit-testing
	}
	;}
}
;}
;{ WM Event Functions - General
OnMessage(0x200, WM_MOUSEMOVE)
WM_MOUSEMOVE(wParam, lParam, msg, hwnd)
{
	If WinGetTitle('ahk_id' hwnd) = 'SnipperSelect'
		WinActivate('ahk_id ' A_ScriptHwnd)
}

OnMessage(0x201, WM_LBUTTONDOWN)
WM_LBUTTONDOWN(wParam, lParam, msg, hwnd)
{
	PostMessage(0xA1, 2, , hwnd)
}

OnMessage(0x6, WM_ACTIVATE)
WM_ACTIVATE(wParam, lParam, msg, hwnd)
{
	Global guiSnips
	If WinGetTitle('ahk_id' hwnd) = 'SnipperWindow'
		If (Activated := wParam << 48 >> 48)
			SnipWinBorderColor(guiSnips[hwnd].GuiObj, 'Green')
		Else
			SnipWinBorderColor(guiSnips[hwnd].GuiObj, 'Blue')
}
;}
;{ SnipArea
SnipArea(Area, SetClipboard := false, CreateWindow := true, ShowWindow := true, &ObjMap?, BorderColorOut := 'Blue', BorderColorIn := 'White')
{
	GDIp.Startup()
	pBitmap := GDIp.BitmapFromScreen(Area)
	If SetClipboard
		GDIp.SetBitmapToClipboard(pBitmap)
	If CreateWindow and IsSet(ObjMap)
	{
		guiObj := Gui('-Caption +AlwaysOnTop +OwnDialogs -DPIScale +E0x80000 +E0x02000000', 'SnipperWindow')
		if Settings_ToolWindow
			guiObj.Opt('+ToolWindow')
		ObjMap[guiObj.hwnd] := { GuiObj: guiObj, Area: Area }
		guiObj.MarginX := 0, guiObj.MarginY := 0
		hBitmap := GDIp.CreateHBITMAPFromBitmap(pBitmap)
		guiObj.Pic := guiObj.Add('Picture', 'x3 y3', 'HBITMAP:' hBitmap)
		guiObj.Border1 := guiObj.Add('Text', 'x2 y2 w' Area.W + 2 ' h' Area.H + 2 ' Background' BorderColorOut)
		guiObj.Border2 := guiObj.Add('Text', 'x1 y1 w' Area.W + 4 ' h' Area.H + 4 ' Background' BorderColorIn)
		guiObj.Border3 := guiObj.Add('Text', 'x0 y0 w' Area.W + 6 ' h' Area.H + 6 ' Background' BorderColorOut)
		If ShowWindow
			guiObj.Show('NA x' Area.X - 3 ' y' Area.Y - 3)
		Else
			guiObj.Show('Hide x' Area.X - 3 ' y' Area.Y - 3)
		GDIp.DisposeImage(pBitmap)
		GDIp.DeleteObject(hBitmap)
		Return guiObj.hwnd
	}
	GDIp.DisposeImage(pBitmap)
	GDIp.Shutdown()
	Return false
}
;}
;{ SnipWinBorderColor
SnipWinBorderColor(guiObj, BorderColorOut := 'Blue', BorderColorIn := 'White')
{
	guiObj.Border1.Opt('Background' BorderColorOut), guiObj.Border1.Redraw()
	guiObj.Border2.Opt('Background' BorderColorIn), guiObj.Border2.Redraw()
	guiObj.Border3.Opt('Background' BorderColorOut), guiObj.Border3.Redraw()
}
;}
;{ Snip2Clipboard
Snip2Clipboard(Borders := false, Hwnd?)
{
	If !IsSet(Hwnd)
		Hwnd := WinGetID('A')
	If Borders
	{
		GDIp.Startup()
		If InStr(A_OSVersion, '6.1') ; win 7
		{
			guiSnips[Hwnd].GuiObj.GetPos(&X, &Y, &W, &H)
			pBitmap := GDIp.BitmapFromScreen({ X: X, Y: Y, W: W, H: H })
		}
		Else
			pBitmap := GDIp.BitmapFromHWND(Hwnd)
		GDIp.SetBitmapToClipboard(pBitmap)
		GDIp.DisposeImage(pBitmap)
		GDIp.Shutdown()
	}
	Else
	{
		hBitMap := SendMessage(0x173, 0, 0, guiSnips[Hwnd].GuiObj.Pic)
		GDIp.Startup()
		GDIp.SetHBITMAPToClipboard(hBitMap)
		GDIp.Shutdown()
	}
}
;}
;{ Snip2File
Snip2File(Borders := false, SavePath?, FileNameOnly?, FileExt?, Hwnd?, Quality?)
{
	If !FileExist(SavePath)
		DirCreate(SavePath)
	If !IsSet(Hwnd)
		Hwnd := WinGetID('A')
	If !IsSet(Quality)
		Quality := 75
	If Borders
	{
		GDIp.Startup()
		If InStr(A_OSVersion, '6.1') ; win 7
		{
			guiSnips[Hwnd].GuiObj.GetPos(&X, &Y, &W, &H)
			pBitmap := GDIp.BitmapFromScreen({ X: X, Y: Y, W: W, H: H })
		}
		Else
			pBitmap := GDIp.BitmapFromHWND(Hwnd)
		TimeStamp := FormatTime(, 'yyyy_MM_dd @ HH_mm_ss')
		If IsSet(FileNameOnly)
			FileName := FileNameOnly '.' FileExt
		Else
			FileName := TimeStamp ' (' guiSnips[Hwnd].Area.W + 6 'x' guiSnips[Hwnd].Area.H + 6 ').' FileExt
		GDIp.SaveBitmapToFile(pBitmap, SavePath FileName, Quality)
		GDIp.DisposeImage(pBitmap)
		GDIp.Shutdown()
	}
	Else
	{
		TimeStamp := FormatTime(, 'yyyy_MM_dd @ HH_mm_ss')
		FileName := TimeStamp ' (' guiSnips[Hwnd].Area.W 'x' guiSnips[Hwnd].Area.H ').' FileExt
		hBitMap := SendMessage(0x173, 0, 0, guiSnips[Hwnd].GuiObj.Pic)
		GDIp.Startup()
		pBitmap := GDIp.CreateBitmapFromHBITMAP(hBitMap)
		GDIp.SaveBitmapToFile(pBitmap, SavePath FileName, Quality)
		GDIp.DisposeImage(pBitmap)
		GDIp.Shutdown()
	}
	Return SavePath FileName
}
;}
;{ Settings Save / Load
Settings_Save(FileName := 'Snipper.ini')
{
	Try FileMove(FileName, FileName '.bak', true)
	For ItemPos, GuiCtrlCheck in guiSettings.Check.OwnProps()
		IniWrite GuiCtrlCheck.Value, FileName, 'Check', GuiCtrlCheck.Text
}
Settings_Load(FileName := 'Snipper.ini')
{
	For ItemPos, GuiCtrlCheck in guiSettings.Check.OwnProps()
		Try If GuiCtrlCheck.Value := IniRead(FileName, 'Check', GuiCtrlCheck.Text)
			ContextSnipMenu.Check(ItemPos '&')
		Else
			ContextSnipMenu.Uncheck(ItemPos '&')
}
;}
;}

;; LIBRARY
;{-----------------------------------------------
;
;{ MinMax
MinMax(Num, MinNum, MaxNum) => Min(Max(Num, MinNum), MaxNum)
;}
;{ ArrayRange
RangeArray(Arr, Start := 1, Stop := unset, Step := 1) {
	If !IsSet(Stop)
		Stop := Arr.Length
	If Start > Stop and Step = 1
		Step := -1
	Enumerate(&p1, &p2 := false) {
		If IsSet(p2) {
			p2 := Start, Start += Step
			Try p1 := Arr[p2]
			Return Step > 0 ? p2 <= Stop : p2 >= Stop
		}
		Else {
			p1 := Start, Start += Step
			Try p2 := Arr[p1]
			Return Step > 0 ? p1 <= Stop : p1 >= Stop
		}
	}
	Return Enumerate
}
;}
;{ GetFullPathName
GetFullPathName(path)
{
	cc := DllCall('GetFullPathNameW', 'str', path, 'uint', 0, 'ptr', 0, 'ptr', 0, 'uint')
	buf := Buffer(cc * 2)
	DllCall('GetFullPathNameW', 'str', path, 'uint', cc, 'ptr', buf, 'ptr', 0, 'uint')
	Return StrGet(buf)
}
;}
;{ GDIp Class - Select GDIp library functions converted to a class specifically for this script
#DllLoad 'GdiPlus'
Class GDIp
{
	;{ Startup
	Static Startup()
	{
		If (this.HasProp("Token"))
			Return
		input := Buffer((A_PtrSize = 8) ? 24 : 16, 0)
		NumPut("UInt", 1, input)
		DllCall("gdiplus\GdiplusStartup", "UPtr*", &pToken := 0, "UPtr", input.ptr, "UPtr", 0)
		this.Token := pToken
	}
	;}
	;{ Shutdown
	Static Shutdown()
	{
		If (this.HasProp("Token"))
			DllCall("Gdiplus\GdiplusShutdown", "UPtr", this.DeleteProp("Token"))
	}
	;}
	;{ BitmapFromHWND
	Static BitmapFromHWND(hwnd, clientOnly := 0)
	{
		If DllCall("IsIconic", "UPtr", hwnd)
			DllCall("ShowWindow", "UPtr", hwnd, "int", 4)

		thisFlag := 0
		If (clientOnly = 1)
		{
			rc := Buffer(16, 0)
			DllCall("GetClientRect", "UPtr", hwnd, "UPtr", rc.ptr)
			Width := NumGet(rc, 8, "int")
			Height := NumGet(rc, 12, "int")
			thisFlag := 1
		} Else this.GetWindowRect(hwnd, &Width, &Height)

		hbm := this.CreateDIBSection(Width, Height)
		hdc := this.CreateCompatibleDC(), obm := this.SelectObject(hdc, hbm)
		this.PrintWindow(hwnd, hdc, 2 + thisFlag)
		pBitmap := this.CreateBitmapFromHBITMAP(hbm)
		this.SelectObject(hdc, obm), this.DeleteObject(hbm), this.DeleteDC(hdc)
		Return pBitmap
	}
	;}
	;{ PrintWindow
	Static PrintWindow(hwnd, hdc, Flags := 2)
	{
		Return DllCall("PrintWindow", "UPtr", hwnd, "UPtr", hdc, "uint", Flags)
	}
	;}
	;{ GetWindowRect
	Static GetWindowRect(hwnd, &W, &H)
	{
		rect := Buffer(16, 0)
		er := DllCall("dwmapi\DwmGetWindowAttribute"
			, "UPtr", hwnd        ; HWND  hwnd
			, "UInt", 9           ; DWORD dwAttribute (DWMWA_EXTENDED_FRAME_BOUNDS)
			, "UPtr", rect.ptr    ; PVOID pvAttribute
			, "UInt", rect.size   ; DWORD cbAttribute
			, "UInt")             ; HRESULT

		If er
			DllCall("GetWindowRect", "UPtr", hwnd, "UPtr", rect.ptr, "UInt")

		r := {}
		r.x1 := NumGet(rect, 0, "Int"), r.y1 := NumGet(rect, 4, "Int")
		r.x2 := NumGet(rect, 8, "Int"), r.y2 := NumGet(rect, 12, "Int")
		r.w := Abs(Max(r.x1, r.x2) - Min(r.x1, r.x2))
		r.h := Abs(Max(r.y1, r.y2) - Min(r.y1, r.y2))
		W := r.w, H := r.h
		Return r
	}
	;}
	;{ BitmapFromScreen
	Static BitmapFromScreen(Area)
	{
		chdc := this.CreateCompatibleDC()
		hbm := this.CreateDIBSection(Area.W, Area.H, chdc)
		obm := this.SelectObject(chdc, hbm)
		hhdc := this.GetDC()
		this.BitBlt(chdc, 0, 0, Area.W, Area.H, hhdc, Area.X, Area.Y)
		this.ReleaseDC(hhdc)
		pBitmap := this.CreateBitmapFromHBITMAP(hbm)
		this.SelectObject(chdc, obm), this.DeleteObject(hbm), this.DeleteDC(hhdc), this.DeleteDC(chdc)
		Return pBitmap
	}
	;}
	;{ SetHBITMAPToClipboard
	Static SetHBITMAPToClipboard(hBitmap)
	{

		off1 := A_PtrSize = 8 ? 52 : 44
		off2 := A_PtrSize = 8 ? 32 : 24

		pid := DllCall("GetCurrentProcessId", "uint")
		hwnd := WinExist("ahk_pid " . pid)
		r1 := DllCall("OpenClipboard", "UPtr", hwnd)
		If !r1
			Return -1

		r2 := DllCall("EmptyClipboard")
		If !r2
		{
			this.DeleteObject(hBitmap)
			DllCall("CloseClipboard")
			Return -2
		}

		oi := Buffer((A_PtrSize = 8) ? 104 : 84, 0)
		DllCall("GetObject", "UPtr", hBitmap, "int", oi.size, "UPtr", oi.ptr)
		hdib := DllCall("GlobalAlloc", "uint", 2, "UPtr", 40 + NumGet(oi, off1, "UInt"), "UPtr")
		pdib := DllCall("GlobalLock", "UPtr", hdib, "UPtr")
		DllCall("RtlMoveMemory", "UPtr", pdib, "UPtr", oi.ptr + off2, "UPtr", 40)
		DllCall("RtlMoveMemory", "UPtr", pdib + 40, "UPtr", NumGet(oi, off2 - A_PtrSize, "UPtr"), "UPtr", NumGet(oi, off1, "UInt"))
		DllCall("GlobalUnlock", "UPtr", hdib)
		r3 := DllCall("SetClipboardData", "uint", 8, "UPtr", hdib) ; CF_DIB = 8
		DllCall("CloseClipboard")
		DllCall("GlobalFree", "UPtr", hdib)
		E := r3 ? 0 : -4    ; 0 - success
		Return E
	}
	;}
	;{ SetBitmapToClipboard
	Static SetBitmapToClipboard(pBitmap)
	{
		off1 := A_PtrSize = 8 ? 52 : 44
		off2 := A_PtrSize = 8 ? 32 : 24

		pid := DllCall("GetCurrentProcessId", "uint")
		hwnd := WinExist("ahk_pid " . pid)
		r1 := DllCall("OpenClipboard", "UPtr", hwnd)
		If !r1
			Return -1

		hBitmap := this.CreateHBITMAPFromBitmap(pBitmap, 0)
		If !hBitmap
		{
			DllCall("CloseClipboard")
			Return -3
		}

		r2 := DllCall("EmptyClipboard")
		If !r2
		{
			this.DeleteObject(hBitmap)
			DllCall("CloseClipboard")
			Return -2
		}

		oi := Buffer((A_PtrSize = 8) ? 104 : 84, 0)
		DllCall("GetObject", "UPtr", hBitmap, "int", oi.size, "UPtr", oi.ptr)
		hdib := DllCall("GlobalAlloc", "uint", 2, "UPtr", 40 + NumGet(oi, off1, "UInt"), "UPtr")
		pdib := DllCall("GlobalLock", "UPtr", hdib, "UPtr")
		DllCall("RtlMoveMemory", "UPtr", pdib, "UPtr", oi.ptr + off2, "UPtr", 40)
		DllCall("RtlMoveMemory", "UPtr", pdib + 40, "UPtr", NumGet(oi, off2 - A_PtrSize, "UPtr"), "UPtr", NumGet(oi, off1, "UInt"))
		DllCall("GlobalUnlock", "UPtr", hdib)
		this.DeleteObject(hBitmap)
		r3 := DllCall("SetClipboardData", "uint", 8, "UPtr", hdib) ; CF_DIB = 8
		DllCall("CloseClipboard")
		DllCall("GlobalFree", "UPtr", hdib)
		E := r3 ? 0 : -4    ; 0 - success
		Return E
	}
	;}
	;{ CreateCompatibleDC
	Static CreateCompatibleDC(hdc := 0)
	{
		Return DllCall("CreateCompatibleDC", "UPtr", hdc)
	}
	;}
	;{ CreateDIBSection
	Static CreateDIBSection(w, h, hdc := "", bpp := 32, &ppvBits := 0, Usage := 0, hSection := 0, Offset := 0)
	{
		hdc2 := hdc ? hdc : this.GetDC()
		bi := Buffer(40, 0)
		NumPut("UInt", 40, bi, 0)
		NumPut("UInt", w, bi, 4)
		NumPut("UInt", h, bi, 8)
		NumPut("UShort", 1, bi, 12)
		NumPut("UShort", bpp, bi, 14)
		NumPut("UInt", 0, bi, 16)

		hbm := DllCall("CreateDIBSection"
			, "UPtr", hdc2
			, "UPtr", bi.ptr    ; BITMAPINFO
			, "uint", Usage
			, "UPtr*", &ppvBits
			, "UPtr", hSection
			, "uint", Offset, "UPtr")

		If !hdc
			this.ReleaseDC(hdc2)
		Return hbm
	}
	;}
	;{ SelectObject
	Static SelectObject(hdc, hgdiobj)
	{
		Return DllCall("SelectObject", "UPtr", hdc, "UPtr", hgdiobj)
	}
	;}
	;{ BitBlt
	Static BitBlt(ddc, dx, dy, dw, dh, sdc, sx, sy, raster := "")
	{
		Return DllCall("gdi32\BitBlt"
			, "UPtr", ddc
			, "int", dx, "int", dy
			, "int", dw, "int", dh
			, "UPtr", sdc
			, "int", sx, "int", sy
			, "uint", raster ? raster : 0x00CC0020)
	}
	;}
	;{ CreateBitmapFromHBITMAP
	Static CreateBitmapFromHBITMAP(hBitmap, hPalette := 0)
	{
		DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "UPtr", hBitmap, "UPtr", hPalette, "UPtr*", &pBitmap := 0)
		Return pBitmap
	}
	;}
	;{ CreateHBITMAPFromBitmap
	Static CreateHBITMAPFromBitmap(pBitmap, Background := 0xffffffff)
	{
		DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "UPtr", pBitmap, "UPtr*", &hBitmap := 0, "int", Background)
		Return hBitmap
	}
	;}
	;{ DeleteObject
	Static DeleteObject(hObject)
	{
		Return DllCall("DeleteObject", "UPtr", hObject)
	}
	;}
	;{ ReleaseDC
	Static ReleaseDC(hdc, hwnd := 0)
	{
		Return DllCall("ReleaseDC", "UPtr", hwnd, "UPtr", hdc)
	}
	;}
	;{ DeleteDC
	Static DeleteDC(hdc)
	{
		Return DllCall("DeleteDC", "UPtr", hdc)
	}
	;}
	;{ DisposeImage
	Static DisposeImage(pBitmap, noErr := 0)
	{
		If (StrLen(pBitmap) <= 2 && noErr = 1)
			Return 0

		r := DllCall("gdiplus\GdipDisposeImage", "UPtr", pBitmap)
		If (r = 2 || r = 1) && (noErr = 1)
			r := 0
		Return r
	}
	;}
	;{ GetDC
	Static GetDC(hwnd := 0)
	{
		Return DllCall("GetDC", "UPtr", hwnd)
	}
	;}
	;{ SaveBitmapToFile
	Static SaveBitmapToFile(pBitmap, sOutput, Quality := 75, toBase64 := 0)
	{
		_p := 0

		SplitPath sOutput, , , &Extension
		If !RegExMatch(Extension, "^(?i:BMP|DIB|RLE|JPG|JPEG|JPE|JFIF|GIF|TIF|TIFF|PNG)$")
			Return -1

		Extension := "." Extension
		DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", &nCount := 0, "uint*", &nSize := 0)
		ci := Buffer(nSize)
		DllCall("gdiplus\GdipGetImageEncoders", "uint", nCount, "uint", nSize, "UPtr", ci.ptr)
		If !(nCount && nSize)
			Return -2

		Static IsUnicode := StrLen(Chr(0xFFFF))
		If (IsUnicode)
		{
			StrGet_Name := "StrGet"
			Loop nCount
			{
				sString := %StrGet_Name%(NumGet(ci, (idx := (48 + 7 * A_PtrSize) * (A_Index - 1)) + 32 + 3 * A_PtrSize, "UPtr"), "UTF-16")
				If !InStr(sString, "*" Extension)
					Continue

				pCodec := ci.ptr + idx
				Break
			}
		} Else
		{
			Loop nCount
			{
				Location := NumGet(ci, 76 * (A_Index - 1) + 44, "UPtr")
				nSize := DllCall("WideCharToMultiByte", "uint", 0, "uint", 0, "uint", Location, "int", -1, "uint", 0, "int", 0, "uint", 0, "uint", 0)
				sString := Buffer(nSize)
				DllCall("WideCharToMultiByte", "uint", 0, "uint", 0, "uint", Location, "int", -1, "str", sString, "int", nSize, "uint", 0, "uint", 0)
				If !InStr(sString, "*" Extension)
					Continue

				pCodec := ci.ptr + 76 * (A_Index - 1)
				Break
			}
		}

		If !pCodec
			Return -3

		If (Quality != 75)
		{
			Quality := (Quality < 0) ? 0 : (Quality > 100) ? 100 : Quality
			If (Quality > 90 && toBase64 = 1)
				Quality := 90

			If RegExMatch(Extension, "^\.(?i:JPG|JPEG|JPE|JFIF)$")
			{
				DllCall("gdiplus\GdipGetEncoderParameterListSize", "UPtr", pBitmap, "UPtr", pCodec, "uint*", &nSize)
				EncoderParameters := Buffer(nSize, 0)
				DllCall("gdiplus\GdipGetEncoderParameterList", "UPtr", pBitmap, "UPtr", pCodec, "uint", nSize, "UPtr", EncoderParameters.ptr)
				nCount := NumGet(EncoderParameters, "UInt")
				Loop nCount
				{
					elem := (24 + A_PtrSize) * (A_Index - 1) + 4 + (pad := (A_PtrSize = 8) ? 4 : 0)
					If (NumGet(EncoderParameters, elem + 16, "UInt") = 1) && (NumGet(EncoderParameters, elem + 20, "UInt") = 6)
					{
						_p := elem + EncoderParameters.ptr - pad - 4
						NumPut("UInt", Quality, NumGet(NumPut("UInt", 4, NumPut("UPtr", 1, _p + 0) + 20), "UPtr"))
						Break
					}
				}
			}
		}

		If (toBase64 = 1)
		{
			DllCall("ole32\CreateStreamOnHGlobal", "UPtr", 0, "int", true, "UPtr*", &pStream := 0)
			_E := DllCall("gdiplus\GdipSaveImageToStream", "UPtr", pBitmap, "UPtr", pStream, "UPtr", pCodec, "uint", _p)
			If _E
				Return -6

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

			bin := Buffer(nSize, 0)
			DllCall("RtlMoveMemory", "UPtr", bin.ptr, "UPtr", pData, "uptr", nSize)
			DllCall("GlobalUnlock", "UPtr", hData)
			ObjRelease(pStream)
			DllCall("GlobalFree", "UPtr", hData)

			DllCall("Crypt32.dll\CryptBinaryToStringA", "UPtr", bin.ptr, "uint", nSize, "uint", 0x40000001, "UPtr", 0, "uint*", &base64Length := 0)
			base64 := Buffer(base64Length, 0)
			_E := DllCall("Crypt32.dll\CryptBinaryToStringA", "UPtr", bin.ptr, "uint", nSize, "uint", 0x40000001, "UPtr", &base64, "uint*", base64Length)
			If !_E
				Return -7

			bin := Buffer(0)
			Return StrGet(base64, base64Length, "CP0")
		}

		_E := DllCall("gdiplus\GdipSaveImageToFile", "UPtr", pBitmap, "WStr", sOutput, "UPtr", pCodec, "uint", _p)
		Return _E ? -5 : 0
	}
	;}
}
;}
;}

Hotkeys and Basic Instructions

Code: Select all

__Create Snip__
Win+LButton Drag:			Snip an area only
Win+Ctrl+LButton Drag:		Snip an area and copy to clipboard
Win+Alt+LButton Drag:		Copy to clipboard only
Shift:						when releasing LButton on Drag Selection enters Advanced Selection Mode
 
__Advanced Selection__
LButton Drag on Dimensions:	Move
LButton Drag on Border:		Resize
RButton on Dimensions:		Selection Dialog (Specify Dimensions, Lock Aspect Ratio, Toggle Always Display of Dimensions)
Shift:						Display Dimensions if Always Display is Toggled Off 
Enter:						If Selection Window is Active will Snip Image and Exit Advanced Selection

__With Snip Image__
LButton Drag:				Move Snip Image
RButton:					Context Menu
Esc:						Close Snip Image
Shift+ScreenPrint			toggles all Snip Images Show/Hide

__Snip Context Menu
Settings					Opens Settings Dialog where checkboxes can be set to control whether borders are used with each item (Checkmark means borders are included)
Shift						When clicking on an item inverses the setting for borders for that one click, if borders are off, shift will create borders and vice versa

The border around an active Snip Image will be green, and blue when not active. The background of the Dimensions text of a Selection will be green when active and red when not active.

Settings are saved in an ini file when Ok is clicked in the Snip Settings dialog and automatically loaded on script startup.

Snipper Extensions
Extensions created by me can be found here to add functionality to the Snipper script: Snipper Extensions
These extensions just need to be added with #include in the #INCLUDE EXTENSION section.

I plan to add more features and extensions over time.

Additional extensions by @SpeedMaster can also be found here: viewtopic.php?p=519894#p519894

Custom GDIp library with just the functions needed and in the form of a class. Modified a few to accept parameters differently. So, Snipper does not need Gdip or any other library dependencies.

UPDATE: 2023 04 04 - Added Tray Icon, Setting for Image File Type, Added SetThreadDpiAwarenessContext
UPDATE: 2023 04 05 - Fixed context menu issue, static guiSSR, default paths, minor naming convention changes
UPDATE: 2023 04 10 - Added some error handling, clipboard only hotkey, removed any global variables from functions
UPDATE: 2023 04 15 - Major changes that moved beta version as main, previous version has become Snipper Lite hidden below
UPDATE: 2023 04 20 - Borders are created with Gui controls, added support for defining different functions for PDF and Email, minor changes from user feedback
UPDATE: 2023 04 29 - Added Settings saved to ini file, Dynamic Context Menu, More modular for #include Extensions
UPDATE: 2024 01 09 - Added Settings for Image Quality and ToolWindow, also fixed bug in Gdip image quality code

Snipper Lite - 2023 04 15

FG
Last edited by FanaticGuru on 06 Feb 2024, 14:39, edited 18 times in total.
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

User avatar
boiler
Posts: 16962
Joined: 21 Dec 2014, 02:44

Re: Snipper - Window Snipping Tool

Post by boiler » 31 Mar 2023, 21:44

Nice. Thanks for sharing. Already using this in place of the original Screen Clipping. I like that it only puts it on the clipboard if you ask it to, and I like the context menu. Never used any of the other features in the original because they are used so infrequently (if ever) I would never have remembered the hotkeys for them. Perhaps a future added feature is a tray icon other than the standard v2 icon.

User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

Post by SpeedMaster » 02 Apr 2023, 11:48

Thank you very much for making your script compatible for version 2. 8-)
Although I myself don't use many extension plugins, I missed the possibility to resize the image with the mouse wheel.
So I adapted a previous plugin script to make that. :)

how to install this extention plugin ?

1° download ImagePut.ahk (for v2) library at https://github.com/iseahound/ImagePut/archive/refs/heads/master.zip
2° create a "Lib" folder in the snipper.ahk script directory and put the library in it
3° put also the script plugin SmResizer.ahk (see below) in the lib folder
4° Edit main script Snipper.ahk and insert this line #Include <SMResizer> in the ;; AUTO-EXECUTE section of the script.
Spoiler
Note this extention include plugin creates a " cache " folder in the script directory so In the main script " snipper.ahk " remplace CloseSnip() funtion with this one (see below) to automatically remove unused cached images:
Spoiler

Keyboard Shortcuts:
use the mouse wheel or numpad add/Sub to resize selected clip image

SmResizer.ahk

Code: Select all

/*
;--------------------------------------------------------------------------------------------------------------------
Resizer Plugin for Snipper.ahk
File Name : SMResizer.ahk by speedmaster
usage: resize the selected clip image with the mouse wheel or numpad
version: 2023 04 11 .001
topic: https://www.autohotkey.com/boards/viewtopic.php?f=83&t=115622
Help:
how to install this plugin ?
1° download script Snipper.ahk by FanaticGuru at https://www.autohotkey.com/boards/viewtopic.php?f=83&t=115622
2° download "ImagePut.ahk" (for v2) library  at https://github.com/iseahound/ImagePut/archive/refs/heads/master.zip
3° create a "Lib" folder in the script directory and put the library in it
4° put also this script plugin "SMResizer.ahk" in the lib folder
5° Edit main script "snipper.ahk" and insert this line '#Include <SMResizer>' in the ';; AUTO-EXECUTE section' of the script.

Note this include plugin create a "cache" folder in the script dir

remplace "CloseSnip()" funtion with this one to auto delete unused cached images:

CloseSnip()
{
	Hwnd := WinGetID('A')
	WinClose('A')
	try guiSnips.Delete(Hwnd)
	try FileDelete(A_ScriptDir '\cache\' hwnd '.png')
}

; ---------------------------------------------------------------------------------------------------------------------
*/

#Warn All, Off
#include <imageput>      ; (for V2)

#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
~numpadadd::
~NumpadSub::
~wheelup::
~wheeldown::
{
	static sca, prevhwnd

	sca ?? sca:=1

	Hwnd := WinGetID('A')
	prevHwnd ?? prevHwnd:=Hwnd
	(Hwnd!=prevHwnd)&&(sca:=1)&&prevHwnd:=Hwnd

		If !FileExist(A_ScriptDir '\cache' Hwnd ".png")
		DirCreate(A_ScriptDir '\cache')
	(sca<0.2&&sca:=0.2)

if (instr(a_thishotkey, "up")||instr(a_thishotkey, "Add"))
	sca += 0.1

else if (instr(a_thishotkey, "down")||instr(a_thishotkey, "Sub"))
	sca -= 0.1

	Snip2Scale(A_ScriptDir '\cache\' hwnd '.png',,,sca)
	tooltip round(sca*100) " %"

	settimer(hidett, -1000)

	hidett() => ToolTip()
}
#HotIf

UpdateLayeredWinMod(pBitmap, Area, BorderColorA := 0xff6666ff, BorderColorB := 0xffffffff)
{

	If !IsSet(Hwnd)
	Hwnd := WinGetID('A')
	hbm := GDIp.CreateDIBSection(Area.W + 6, Area.H + 6), hdc := GDIp.CreateCompatibleDC(), obm := GDIp.SelectObject(hdc, hbm)
	G := GDIp.GraphicsFromHDC(hdc), GDIp.SetSmoothingMode(G, 4), GDIp.SetInterpolationMode(G, 7)
	GDIp.DrawImage(G, pBitmap, 3, 3, Area.W, Area.H)
	GDIp.DisposeImage(pBitmap)
	pPen1 := GDIp.CreatePen(BorderColorA, 3), pPen2 := GDIp.CreatePen(BorderColorB, 1)
	GDIp.DrawRectangle(G, pPen1, 1, 1, Area.W + 3, Area.H + 3)
	GDIp.DrawRectangle(G, pPen2, 1, 1, Area.W + 3, Area.H + 3)
	GDIp.DeletePen(pPen1), GDIp.DeletePen(pPen2)
	GDIp.UpdateLayeredWindow(hwnd, hdc, Area.X - 3, Area.Y - 3, Area.W + 6, Area.H + 6)
	GDIp.SelectObject(hdc, obm), GDIp.DeleteObject(hbm), GDIp.DeleteDC(hdc), GDIp.DeleteGraphics(G)
	Return
}

Snip2Scale(SavePath, Borders := false, Hwnd?, sc:=1)
{
	If !FileExist(A_ScriptDir '\cache')
		DirCreate(A_ScriptDir '\cache')
	If !IsSet(Hwnd)
		Hwnd := WinGetID('A')
	WinGetPos(&X, &Y, &W, &H, 'ahk_id ' Hwnd)
	If !Borders
		X += 3, Y += 3, W -= 6, H -= 6

	imageput.gdiplusStartup()
	If !FileExist(A_ScriptDir '\cache\' hwnd '.png') {
		gb := GDIp.BitmapFromScreen({ X: X, Y: Y, W: W, H: H })
		GDIp.SaveBitmapToFile(gb, SavePath)
	}
	else
		gb := imageput.from_file(A_ScriptDir '\cache\' hwnd '.png')

	gdip.GetImageDimension(gb, &w, &h)

	UpdateLayeredWinMod(gb, { X: X, Y: Y, W: round(W*sc), H: round(H*sc) })
	imageput.gdiplusShutdown()
	Return SavePath
}
Regards
Last edited by SpeedMaster on 11 Apr 2023, 03:31, edited 1 time in total.

guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

Post by guest3456 » 03 Apr 2023, 13:25

what are advantages over Windows built-in snipping tool (WIN + SHIFT + S) ?


User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Snipper - Window Snipping Tool

Post by kczx3 » 03 Apr 2023, 14:18

I think the primary advantage is speed. At least for me, the WIN + SHIFT + S tool takes forever to load and actually allow me to take a snip. The benefit of it over this tool are the markup capabilities, and probably a few other things.

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

Post by FanaticGuru » 03 Apr 2023, 14:38

guest3456 wrote:
03 Apr 2023, 13:25
what are advantages over Windows built-in snipping tool (WIN + SHIFT + S) ?

Have you even tried it?

One huge advantage as @boiler commented about it not even putting the snipped area on the clipboard but that it makes it into a picture that then can be very easily dragged around the screen. Most of the time when I snip an area it is not even to put it on the clipboard but to just create a snip that freezes a region of the screen. I can then drag multiples of these snips where I want them. Often so I can then see changes. It becomes second nature in my workflow. Any words or numbers on my screen that I want to save temporarily in a picture that I can drag around the screen. It is just so quick to create snips and move snips.

I snip an area of an Excel sheet, then change some formulas, compare the new result. Snip that, change some more formulas, compare new result to last two. Snip a picture of a playing video maybe to study a graph while the video continues to play. Snip the important part of a receipt page of a website purchase, convert to a PDF to save a record, then print it. 95% of the time, it is not even about putting the snip on the clipboard. It is more about converting an area of the screen to a gui window with a picture that can be dragged to reposition.

I have not added all the features that I have in the v1 version to this v2 yet, but the v1 version that I use is probably my favorite most used utility AHK script of all time.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

User avatar
boiler
Posts: 16962
Joined: 21 Dec 2014, 02:44

Re: Snipper - Window Snipping Tool

Post by boiler » 03 Apr 2023, 15:58

FanaticGuru wrote: ...it makes it into a picture that then can be very easily dragged around the screen. Most of the time when I snip an area it is not even to put it on the clipboard but to just create a snip that freezes a region of the screen. I can then drag multiples of these snips where I want them. Often so I can then see changes. It becomes second nature in my workflow. Any words or numbers on my screen that I want to save temporarily in a picture that I can drag around the screen. It is just so quick to create snips and move snips.
Yes, this is the only reason I use it. And those snips stay on top of everything without the screen area overhead of a window caption, menus, buttons, etc. Like snip something in a browser, scroll down, and that snip is still there just taking up that small area and always visible. It's like FG said, you need to try it. Since I first tried it, I have been using it all the time. I don't use it at all for what the Snipping Tool is for. If I'm going to save stuff, I'll use that, although now with FG's version with the context menu, maybe I'll use this more exclusively even when I want to save stuff or paste it somewhere.

guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

Post by guest3456 » 03 Apr 2023, 16:06

thanks i never considered this workflow


Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: Snipper - Window Snipping Tool

Post by Krd » 04 Apr 2023, 10:04

This is a dream! So easy so much better than windows build in!

Is it possible to save the snip as jpg/png ect?

Would be awesome if you could add that to context menu!


Thank you so much!

User avatar
flyingDman
Posts: 2817
Joined: 29 Sep 2013, 19:01

Re: Snipper - Window Snipping Tool

Post by flyingDman » 04 Apr 2023, 10:45

@FanaticGuru I had created something similar and it has built in OCR which would be a nice addition to your script as well. Your script as well as mine fail to accurately "snip" on a second monitor that has a different resolution / dpi. Do you have a solution for that?
14.3 & 1.3.7

guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

Post by guest3456 » 04 Apr 2023, 11:01

flyingDman wrote:
04 Apr 2023, 10:45
Your script as well as mine fail to accurately "snip" on a second monitor that has a different resolution / dpi. Do you have a solution for that?
the resolution shouldnt matter. it should only fail if the 2nd monitor uses a different scaling%/dpi. you likely need to add this to the top of your script:

Code: Select all

DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
see here:
viewtopic.php?t=13810


User avatar
flyingDman
Posts: 2817
Joined: 29 Sep 2013, 19:01

Re: Snipper - Window Snipping Tool

Post by flyingDman » 04 Apr 2023, 11:06

@guest3456 That worked very nicely. Thank you! (it works both in FG's script as well as mine).
14.3 & 1.3.7

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

Post by FanaticGuru » 04 Apr 2023, 13:18

Krd wrote:
04 Apr 2023, 10:04
Is it possible to save the snip as jpg/png ect?

Would be awesome if you could add that to context menu!

The context menu already had a "File" option which saved the snip as a PNG. Added an option in the "DEFAULT SETTING - VARIABLES" section where this can be changed to other file types. I very much prefer PNG as it is lossless compression. In that same section, the save path can be set. Also made the file type displayed in the context menu.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

Post by FanaticGuru » 04 Apr 2023, 14:32

flyingDman wrote:
04 Apr 2023, 10:45
@FanaticGuru I had created something similar and it has built in OCR which would be a nice addition to your script as well. Your script as well as mine fail to accurately "snip" on a second monitor that has a different resolution / dpi. Do you have a solution for that?


I only use one huge monitor at the moment so have not tested on multiple monitors. When researching code, I saw code dealing with sorting out multiple monitors and DPI scaling but didn't try to implement any of it. The SetThreadDpiAwarenessContext solves part of the problem.

I plan to add OCR also. I like this example code by @teadrinker for OCR on a screenshot viewtopic.php?f=6&t=72674

It is light weight as it uses the builtin WinAPI but it is for v1.

But right now I am working on a more robust selection, where if you hold "Shift" when you let off the mouse when dragging, it will not clip it immediately but leave a semi-transparent area that can be moved, and resized with dimensions text before committing to the snip. There is lots of potential there with inputting exact dimensions, specific width/height ratio, locking ratios, taking multiple snips with the same selection area, etc.

But it is proving much harder than I thought to get
  • one Gui
  • semi-transparent rectangle
  • completely opaque text over rectangle
  • no borders
  • draggable
  • resizeable
It boils down to I have found no good way to have a semi-transparent rectangle with opaque text on top where the rectangle CANNOT be clicked through. That makes the dragging and resizing very difficult as the mouse cannot grab anything. Semi-transparent rectangle with semi-transparent text is easy but does not look very good.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Snipper - Window Snipping Tool

Post by kczx3 » 04 Apr 2023, 15:21

A few optimizations I've made are to simply create the guiSSR GUI once at script startup. In my opinion, it isn't a big deal to have the window around for the length of the script's process and it eliminates the need to recreate the window every time. I also added SetWinDelay(-1) to the SelectScreenRegion function. Lastly, I changed the guiSnip GUI in the CreateLayeredWinMod function to remove the NA option as it suites me better.

One issue I found is that it is possible to right-click a snip that is not the active snip and choosing the CLOSE option from the context menu instead closes the active snip. Adding a WinActivate for the window under the mouse prior to showing the context menu should suffice.

one1tick
Posts: 32
Joined: 04 Mar 2022, 04:26

Re: Snipper - Window Snipping Tool

Post by one1tick » 05 Apr 2023, 12:33

FanaticGuru wrote:
04 Apr 2023, 14:32
I only use one huge monitor at the moment so have not tested on multiple monitors. When researching code, I saw code dealing with sorting out multiple monitors and DPI scaling but didn't try to implement any of it. The SetThreadDpiAwarenessContext solves part of the problem.
Hi @FanaticGuru and big thanks for this script! :thumbup:

It works well on my 2 monitors setup with different resolutions and scales.

User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

Post by FanaticGuru » 05 Apr 2023, 14:18

@kczx3 appreciate the constructive feedback. Implemented some of the suggestions in the original post.

@one1tick that is good to know as I do not have two monitors to test.

Also, added an 'Alpha' version in the original post. Not ready for primetime but some of the techniques might be of use or interest to people.

Basically changed it from using a semi-transparent background for the Gui. To a completely transparent background for gui and a semi-transparent background for a control that covers the entire gui. So, you can have a semi-transparent rectangle with completely opaque text. The z-order of controls seems difficult to control. I would like to z-order them so I can then double buffer and redraw them in reverse order to reduce flickering, but that has been elusive.

Then jumped through a whole lot of hoops to be able to drag and resize a gui that is mostly invisible to clicks. I would prefer none of the gui be click through but can't quite figure that wrinkle out and still have the semi-transparent background and solid text.

There are multiple loops and events that are being called often. Some of these loops and events could probably be combined or streamlined.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: Snipper - Window Snipping Tool

Post by Krd » 06 Apr 2023, 11:50

Latest version is awesome! Thanks for adding PNG option!

And OCR would make it even a more complete package!

User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

Post by SpeedMaster » 09 Apr 2023, 09:01

Version 2023 04 05 , not working on Windows 7.
Error at line 71. (Call to non existant function)

I fixed the error with "try"
try DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")

guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

Post by guest3456 » 09 Apr 2023, 12:23

SpeedMaster wrote:
09 Apr 2023, 09:01
Version 2023 04 05 , not working on Windows 7.
Error at line 71. (Call to non existant function)

I fixed the error with "try"
try DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
yes that func only exists in win10 or later
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreaddpiawarenesscontext


Post Reply

Return to “Scripts and Functions (v2)”