Snipper - Window Snipping Tool

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

Re: Snipper - Window Snipping Tool

19 Apr 2023, 20:06

malcev wrote:
19 Apr 2023, 19:33
You should not call

Code: Select all

hdc := GetDC(Hwnd)
hdc should be saved after creating.
Therefore I wrote that if it is not Your process, You need to inject and hook.

Yes, it does work if I save the hdc. And don't SelectObject(hdc, obm) or DeleteDC(hdc). I was trying to avoid keeping a log of every Gui. I had a version where I just saved all the bitmaps but it was pretty memory intensive. Maybe this is not as bad. It is still far from a BitmapFromHwnd. Thanks for more knowledge.

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
iseahound
Posts: 1456
Joined: 13 Aug 2016, 21:04
Contact:

Re: Snipper - Window Snipping Tool

20 Apr 2023, 00:10

Does BitmapFromHwnd fail with layered windows without GDI+? Still curious as to why the window suddenly freezes—these things usually don't happen on modern versions of windows, last time these bugs were common was like Windows 98.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

20 Apr 2023, 01:18

FanaticGuru wrote:
19 Apr 2023, 13:40
Something about AHK gui windows and PrintWindow that messes up on one of my computers but not the other. Not all AHK gui windows, just ones that have been interacted with by GDIp. I think it is something with pGraphics, it seems if a pGraphics is involved the window no long will respond properly from PrintWindow.
does Gdip_BitmapFromScreen("hwnd:" hwnd) work?

FanaticGuru wrote:
19 Apr 2023, 18:15
@guest3456 which I think is mmikeww GitHub is only for v2 alpha
yeah this was meant to be backwards compatible so only one lib would be needed for both v1 and v2, but it has not been kept up to date

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Snipper - Window Snipping Tool

20 Apr 2023, 02:39

does Gdip_BitmapFromScreen("hwnd:" hwnd) work?
No.
PrintWindow with PW_RENDERFULLCONTENT flag on win11 does not work. May be microsoft changed something.
I tested and see that it will work if We add WS_EX_NOREDIRECTIONBITMAP style to our gui.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: Snipper - Window Snipping Tool

20 Apr 2023, 03:29

FanaticGuru wrote:
19 Apr 2023, 18:15
iseahound wrote:
19 Apr 2023, 16:24
That version of gdi+ hasn't been updated in ages.

Does the show window script I posted still work?

Is it the window styles? If you want to test your theory set the scale (variable s) to something other than 1 in my script. Then it will use GDI+ pGraphics to scale the object.

You script works regardless of scaling on its custom window, but not on a AHK created Gui that has been populated by a bitmap created with GDIp like the tic's standard examples.

I really wish I could find a good GDIp library for v2, especially a class or object based one.

@mcl has an object one but not v2. Never used a converter but maybe a v1 to v2 converter would get this one pretty close.
@guest3456 which I think is mmikeww GitHub is only for v2 alpha
@TheArkive collected some GDIp stuff together and looks like he started to make an object version.
@robodesign appears to be active with v1 but does not seem too interested in v2 update.
@buliasz v2 is years old.
@iseahound updated some stuff for ImagePut which is nice for a subset of GDIp functions.

Ok, threw out a whose who of GDIp to try to find the 'best' version of GDIp for current v2 or someone willing to update theirs.

FG
I am very much interested in v2, but my projects are so huge, that they lock me with v1. At some point I will abandon the projects and move forward. I do not know ...

I would like to convert all the libraries i made [ Gdip_All and FreeImage ] to v2, but they are so big...

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iseahound
Posts: 1456
Joined: 13 Aug 2016, 21:04
Contact:

Re: Snipper - Window Snipping Tool

20 Apr 2023, 08:21

Yes! I knew it had to be a window style. WS_EX_NOREDIRECTIONBITMAP

EDIT: The windows I create which operate well with PrintWindow do not have the WS_EX_NOREDIRECTIONBITMAP flag set.

Code: Select all

styles    94000000
extended: 80088
Your windows

Code: Select all

styles    940A0000
extended: 80088
To find out which window styles make up the flag 940A0000, you need to decompose it into its binary representation and then compare it with the binary values of each window style. The flag 940A0000 is 10010100000010100000000000000000 in binary. The window styles that have a corresponding bit set to 1 are:

WS_DISABLED (0x8000000 or 1000000000000000000000000 in binary)
WS_VISIBLE (0x10000000 or 10000000000000000000000000 in binary)
WS_MINIMIZE (0x20000000 or 100000000000000000000000000 in binary)
WS_CHILD (0x40000000 or 1000000000000000000000000000 in binary)
WS_POPUP (0x80000000 or 10000000000000000000000000000 in binary)
So the flag 940A0000 is a combination of WS_DISABLED, WS_VISIBLE, WS_MINIMIZE, WS_CHILD and WS_POPUP.
No Bing. You are wrong and useless.

940A0000 is equal to:

Code: Select all

WS_POPUP                  := 0x80000000
WS_VISIBLE                := 0x10000000
WS_CLIPSIBLINGS           :=  0x4000000
WS_SYSMENU                :=    0x80000
WS_GROUP                  :=    0x20000
Full Styles
User avatar
TheArkive
Posts: 1030
Joined: 05 Aug 2016, 08:06
Location: The Construct
Contact:

Re: Snipper - Window Snipping Tool

20 Apr 2023, 11:13

@FanaticGuru

My next project is to either make a UI class from scratch, using GDI to start, then expanding to GDIp, while also recreating / redrawing all the controls, or to expand on the existing Gui class using NMCUSTOMDRAW and OwnerDrawn styles.

No idea how long this will take. I'm still waffling between those two ideas. I know both are tall orders. We'll see where I end up.

If anyone has any ideas / requests along these lines I'll certainly consider them.

I'm probably going to start with exhausting my options using NMCSTOMDRAW and OwnerDrawn styles first. I just want a UI that I can customize as much as possible.
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

20 Apr 2023, 15:47

malcev wrote:
20 Apr 2023, 02:39
PrintWindow with PW_RENDERFULLCONTENT flag on win11 does not work. May be microsoft changed something.
I tested and see that it will work if We add WS_EX_NOREDIRECTIONBITMAP style to our gui.

Very nice find!

It does appear to fix the problem with my limited testing.

This basic script now works.

Code: Select all

; Click on Red Ellipse to make Active, then press F12
; On Windows 10 computer, works fine (meaning you don't really see anything happen but the pBitmap was created)
; On Windows 11 computer, entire Gui turns white and freezes up (you get a pointer, but it is to a solid white rectangle now)  [WS_EX_NOREDIRECTIONBITMAP appears to fix]
F12::
{
	Hwnd := WinExist('A')
	pBitmap := Gdip_BitmapFromHWND(Hwnd)
	Gdip_SetBitmapToClipboard(pBitmap)
}
OnMessage(0x201, WM_LBUTTONDOWN)
WM_LBUTTONDOWN(wParam, lParam, msg, hwnd)
{
	PostMessage(0xA1, 2, , hwnd)
}



If !pToken := Gdip_Startup()
{
	MsgBox "Gdiplus failed to start. Please ensure you have gdiplus on your system"
	ExitApp
}
OnExit(ExitFunc)

Width := 1400, Height := 1050

Gui1 := Gui("-Caption +E0x00200000 +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs") ; WS_EX_NOREDIRECTIONBITMAP
Gui1.Show("NA")
hwnd1 := WinExist()

hbm := CreateDIBSection(Width, Height)
hdc := CreateCompatibleDC()
obm := SelectObject(hdc, hbm)
G := Gdip_GraphicsFromHDC(hdc)
Gdip_SetSmoothingMode(G, 4)
pBrush := Gdip_BrushCreateSolid(0xffff0000)
Gdip_FillEllipse(G, pBrush, 100, 500, 200, 300)
Gdip_DeleteBrush(pBrush)
pBrush := Gdip_BrushCreateSolid(0x660000ff)
Gdip_FillRectangle(G, pBrush, 250, 80, 300, 200)
Gdip_DeleteBrush(pBrush)
UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)

SelectObject(hdc, obm)
DeleteObject(hbm)
DeleteDC(hdc)
Gdip_DeleteGraphics(G)

;#######################################################################

ExitFunc(ExitReason, ExitCode)
{
	Global
	; gdi+ may now be shutdown on exiting the program
	Gdip_Shutdown(pToken)
}
MSDN for WS_EX_NOREDIRECTIONBITMAP
The window does not render to a redirection surface. This is for windows that do not have visible content or that use mechanisms other than surfaces to provide their visual.

I guess GDIp is sneaking a visual on to the window in a way that Windows needs to be told about with this flag.

Wish I would have figured that out many hours of googling earlier but very happy to at least know it now. This might solve other oddities besides just Gdip_BitmapFromHWND that I was having.

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
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Snipper - Window Snipping Tool

20 Apr 2023, 16:30

I guess GDIp is sneaking a visual on to the window in a way that Windows needs to be told about with this flag.
I am not sure about this.
This flag is not considered to use with gdi+, it is for direct2d and direct3d, as I understand:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/june/windows-with-c-high-performance-window-layering-using-the-windows-composition-engine
On the other side nobody knows how works PrintWindow with PW_RENDERFULLCONTENT flag and what exactly changes with it in Windows 11.
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

20 Apr 2023, 16:44

Updated in First Post

Change Log: 2023 04 20
Borders are now created with Gui controls which eliminated the need for many GDIp functions. I was running into serious problems with GDIp which I believe could be solved with WS_EX_NOREDIRECTIONBITMAP suggestion from @malcev but I had already rewritten around the GDIp problem and a Gui control border seems better for this application.

The Snip gui is now basically a solid blue rectangle with a slightly smaller solid white rectangle with a slightly smaller solid blue rectangle with a picture in middle. This creates 3 thin lines around the picture when drawn in the proper order.

Added lines like Clipboard2PDF_Func := Clipboard2Acrobat, Clipboard2PDF_Name := 'Acrobat' in DEFAULT SETTING which would allow a #include file to overwrite this information and include something like this:

Code: Select all

; Snipper -Extension - Clipboard2PDF_Word.ahk
Clipboard2PDF_Func := Clipboard2Word, Clipboard2PDF_Name := 'Word'
Clipboard2Word(SavePath := '')
{
	; code by flyingDman
	fl2pdf := A_ScriptDir "\" A_Year . A_MM . A_DD "_" A_Hour . A_Min . A_Sec ".pdf"
	oWord := ComObject("Word.Application")
	doc := oWord.Documents.Add
	oWord.selection.Paste                        ; assume image is in the clipboard
	doc.InlineShapes.Item(1).ScaleWidth := 30
	doc.ExportAsFixedFormat(fl2pdf, 17)   ; https://learn.microsoft.com/en-us/office/vba/api/word.wdexportformat
	doc.close(0)
	oWord.quit()
	oWord := ""
	Run fl2pdf
}
Then #include this script in the #INCLUDE EXTENSION section like this.

Code: Select all

;; #INCLUDE EXTENSION
;{-----------------------------------------------
;

#include Snipper - Extension - Clipboard2PDF_Word.ahk

;}
Will allow you to have your own separate custom function. This is just a straight copy of @flyingDman code as an example. It could probably be improved upon. Maybe change the paper size to match the image so the PDF ends up without any white space but it is really about letting the user create their own function that does exactly what they want and I just used this code as an example.

I might look at adding over ways to help with extensions since @SpeedMaster has done so much in this area for v1 Screen Clipping.

Added minor changes to try to support Windows 7 but I am not sure how long Windows 7 can keep up. The changes have negligent effect on later Windows and at least make it run on Windows 7 even if some functionality is lost.

Other minor changes here and there as suggested.

FG
Last edited by FanaticGuru on 20 Apr 2023, 17:16, edited 1 time 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: 17206
Joined: 21 Dec 2014, 02:44

Re: Snipper - Window Snipping Tool

20 Apr 2023, 17:12

Nice update! Thanks for continuing to improve it. I use this tool all the time.
ahk7
Posts: 575
Joined: 06 Nov 2013, 16:35

Re: Snipper - Window Snipping Tool

22 Apr 2023, 02:55

fyi just saw OCR for v2 script viewtopic.php?p=518661#p518661
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

23 Apr 2023, 08:12

FanaticGuru wrote:
20 Apr 2023, 16:44
Added minor changes to try to support Windows 7 but I am not sure how long Windows 7 can keep up. The changes have negligent effect on later Windows and at least make it run on Windows 7 even if some functionality is lost.
Thanks for making the script compatible with win 7. :thumbup:
It works quite well :thumbup: .
However I noticed some problems with the advanced selection.
The window when moved does not always capture the right position (after pressing the enter key) :think: .

I corrected the problem by adding this fix to line 269:


guiSSR.GetPos(&x, &y, &w, &h) ; <--- the fix
guiSSR.Hide()
Return { X: X, Y: Y, W: W, H: H, X2: X + W, Y2: Y + H }



Saving png file with border does not work.(I get a black image) :think:

I suggest to move the "INCLUDE section" just below the "AUTO-EXECUTE" section because some include scripts may have their own internal hotkey or modify default settings first.

for example a script that sets SnipVisible to false or change tray icon.

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

Re: Snipper - Window Snipping Tool

23 Apr 2023, 17:19

SpeedMaster wrote:
23 Apr 2023, 08:12
However I noticed some problems with the advanced selection.
The window when moved does not always capture the right position (after pressing the enter key) :think: .

I corrected the problem by adding this fix to line 269:


guiSSR.GetPos(&x, &y, &w, &h) ; <--- the fix
guiSSR.Hide()
Return { X: X, Y: Y, W: W, H: H, X2: X + W, Y2: Y + H }



Saving png file with border does not work.(I get a black image) :think:

I suggest to move the "INCLUDE section" just below the "AUTO-EXECUTE" section because some include scripts may have their own internal hotkey or modify default settings first.

for example a script that sets SnipVisible to false or change tray icon.

In my next release version, put the additional GetPos in as a final check before returning Area.

Moving the Include section from under the SETTINGS to under the AUTO-EXECUTE really only changes if it is above or below the INITIALIZATION - GUI and it needs to be above that to change settings that effect the Gui.

I imagine the BitmapFromHWND is failing for you which is used in the snip with borders to clipboard and file. It is probably a Windows version problem as it works for me in Windows 11. What versions of Windows is it failing on?

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
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

24 Apr 2023, 15:59

FanaticGuru wrote:
23 Apr 2023, 17:19
In my next release version, put the additional GetPos in as a final check before returning Area.
It's great thanks. :thumbup:
FanaticGuru wrote:
23 Apr 2023, 17:19
Moving the Include section from under the SETTINGS to under the AUTO-EXECUTE really only changes if it is above or below the INITIALIZATION - GUI and it needs to be above that to change settings that effect the Gui.
Yes, that's right. I just forgot that keyboard shortcuts are like functions in version 2, so they don't block the script anymore. :roll:
FanaticGuru wrote:
23 Apr 2023, 17:19
It is probably a Windows version problem as it works for me in Windows 11. What versions of Windows is it failing on?

I could test it on windows 10, the backup with the border works well. I think it doesn't work only on win 7, fortunately the backup without the border works well.

I could also see that closing the snips only hides them on window 7. You can make them all reappear by pressing shift + printscreen on win 7.
I also think they still exist on windows 10 even if you can not make them reappear by pressing shift + print scr. The solution would be to use gui destroy() instead of winclose to close them permanently.

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

Re: Snipper - Window Snipping Tool

29 Apr 2023, 19:46

Snipper - Extensions

Snipper - Extension - Acrobat
This is an Adobe Acrobat extension for Snipper that adds an item to the context menu of a snipped image that allows for the image to be sent by the clipboard to Adobe Acrobat to create a PDF.

Code: Select all

; Snipper - Extension - Acrobat
Extensions.Push({ Acrobat: { Text: 'COPY:  Acrobat PDF', Func: Clipboard2Acrobat } })
Extensions.Push({ Acrobat: { Text: 'SAVE:  Acrobat PDF', Func: Clipboard2Acrobat.Bind(,GetFullPathName('.\Snipper - PDF\')) } })
Clipboard2Acrobat(Borders := false, SavePath := '')		; Adobe Acrobat must be installed
{
	; Put Active Snip on Clipboard
	Snip2Clipboard(Borders)
	; Open Adobe Acrobat
	Try App := ComObject('AcroExch.App')
	Catch
	{
		MsgBox 'Could not Open Adobe Acrobat COM'
		return
	}
	App.Show()
	App.MenuItemExecute('ImageConversion:Clipboard')
	; Save PDF
	If SavePath
	{
		If !FileExist(SavePath)
			DirCreate(SavePath)
		TimeStamp := FormatTime(, 'yyyy_MM_dd @ HH_mm_ss')
		FileName := TimeStamp '.PDF'
		AVDoc := App.GetActiveDoc()
		PVDoc := AVDoc.GetPDDoc()
		PDSaveIncremental := 0x0000		; write changes only
		PDSaveFull := 0x0001			; write entire file
		PDSaveCopy := 0x0002			; write copy w/o affecting current state
		PDSaveLinearized := 0x0004		; write the file linearized for
		PDSaveBinaryOK := 0x0010		; OK to store binary in file
		PDSaveCollectGarbage := 0x0020	; perform garbage collection on
		PVDoc.Save(PDSaveFull | PDSaveLinearized, SavePath FileName)
	}
}


Snipper - Extension - Word
This is an MS Word extension for Snipper that adds an item to the context menu of a snipped image that allows for the image to be sent by the clipboard to MS Word to insert the image or create a PDF.

Code: Select all

; Snipper - Extension - Word
Extensions.Push({ Word: { Text: 'COPY:  Word', Func: Clipboard2Word } })
Extensions.Push({ Word: { Text: 'SAVE:  Word PDF', Func: Clipboard2Word.Bind(,GetFullPathName('.\Snipper - Word\')) } })
Clipboard2Word(Borders := false, SavePath := '')
{
	; Put Active Snip on Clipboard
	Snip2Clipboard(Borders)
	; SavePath means to Create PDF
	If SavePath
	{
		; Get COM Application
		wdApp := ComObject('Word.Application')
		; Page Setup
		wdDoc := wdApp.Documents.Add
		wdDoc.PageSetup.PageHeight := 1584 ; Max Size Allowed by Word
		wdDoc.PageSetup.PageWidth := 1584 ; Max Size Allowed by Word
		wdDoc.PageSetup.TopMargin := wdApp.InchesToPoints(0)
		wdDoc.PageSetup.BottomMargin := wdApp.InchesToPoints(0)
		wdDoc.PageSetup.LeftMargin := wdApp.InchesToPoints(0)
		wdDoc.PageSetup.RightMargin := wdApp.InchesToPoints(0)
		; Insert Picture
		wdApp.Selection.PasteSpecial(,,,, wdPasteBitmap:=4)
		wdApp.Selection.Start -= 1
		wdPic := wdApp.Selection.InlineShapes(1)
		; Crop Page Size to Fit Picture
		wdDoc.PageSetup.PageHeight := wdPic.Height
		wdDoc.PageSetup.PageWidth := wdPic.Width
		; Save PDF
		If !FileExist(SavePath)
			DirCreate(SavePath)
		TimeStamp := FormatTime(, 'yyyy_MM_dd @ HH_mm_ss')
		FileName := 'Snipper - PDF Created with Word - ' TimeStamp '.PDF'
		wdDoc.ExportAsFixedFormat(SavePath FileName, 17)
		; Finish
		wdDoc.Close(0)
		wdApp.Quit()
		Run SavePath FileName
		return
	}
	Else
	{
		; Get COM Application
		Try
		{
			wdApp := ComObjActive('Word.Application')
			wdDoc := wdApp.ActiveDocument
		}
		Catch
		{
			wdApp := ComObject('Word.Application')
			wdDoc := wdApp.Documents.Add
			wdApp.Visible := true
		}
	}
	; Insert Picture
	PictureWidth := wdDoc.PageSetup.PageWidth - wdDoc.PageSetup.LeftMargin - wdDoc.PageSetup.RightMargin
	wdApp.Selection.EndKey(wdStory:=6)
	wdApp.Selection.Paste
	wdApp.Selection.Start -= 1
	wdPic := wdApp.Selection.InlineShapes(1)
	wdPic.LockAspectRatio := true
	wdPic.Width := PictureWidth ; Size to Side Margins
}


Snipper - Extension - Outlook
This is an Outlook extension for Snipper that adds an item to the context menu of a snipped image that allows for the image to be sent as a file to Outlook to add as an attached image to a created email.

Code: Select all

; Snipper - Extension - Outlook
Extensions.Push({ Outlook: { Text: 'SAVE:  ' Settings_SavePath_Image_Ext ' File and attach to Outlook Email', Func: File2Outlook.Bind(,Settings_SavePath_Image, Settings_SavePath_Image_Ext) } })
File2Outlook(Borders := false, SavePath:='', FileExt:='')		; Outlook must be installed
{
	File := Snip2File(Borders, SavePath,, FileExt)
	TimeStamp := FormatTime(RegExReplace(File, '^.*\\|[_ @]|\(.*$'), "dddd MMMM d, yyyy 'at' h:mm:ss tt")
	Try
		IsObject(olEmail := ComObjActive('Outlook.Application').CreateItem(olMailItem := 0)) ; Create if Outlook is open
	Catch
		olEmail := ComObject('Outlook.Application').CreateItem(olMailItem := 0) ; Create if Outlook is not open
	olEmail.BodyFormat := (olFormatHTML := 2)
	;~ olEmail.TO :='SomeEmail@SomeWhere.com'
	;~ olEmail.CC :='SomeEmail@SomeWhere.com'
	olEmail.Subject := 'Screenshot Taken: ' TimeStamp
	HTMLBody := '<HTML>Please find attached a screenshot taken on ' TimeStamp '</HTML>'
	olEmail.HTMLBody := HTMLBody
	olEmail.Attachments.Add(File)
	olEmail.Display
}


Snipper - Extension - OCR
This is an OCR extension for Snipper that adds an item to the context menu of a snipped image that allows for the image to be read with OCR (Optical Character Recognition) and the text returned put on the clipboard. The OCR library for v2 by @Descolada made this rather trivial to implement.

Code: Select all

; Snipper - Extension - OCR
; Version: 2023 05 05
Extensions.Push({ OCR: { Text: 'COPY:  OCR to Clipboard', Func: OCR2Clipboard } })
OCR2Clipboard(Borders := false)
{
	; Get Hwnd of Active Snip
	Hwnd := WinGetID('A')
	; Get hBitMap of Snip
	hBitMap := SendMessage(0x173, 0, 0, guiSnips[Hwnd].GuiObj.Pic)
	; OCR of hBitMap
	Result := OCR.FromBitmap(hBitMap)
	; Put Result.Text on Clipboard
	A_Clipboard := Result.Text
}

/** https://github.com/Descolada/OCR
 * OCR library: a wrapper for the the UWP Windows.Media.Ocr library.
 * Based on the UWP OCR function for AHK v1 by malcev.
 * 
 * Ways of initiating OCR:
 * OCR(IRandomAccessStream, lang?)
 * OCR.FromDesktop(lang?, scale:=1)
 * OCR.FromRect(X, Y, W, H, lang?, scale:=1)
 * OCR.FromWindow(WinTitle?, lang?, scale:=1, onlyClientArea:=0, mode:=2)
 * OCR.FromFile(FileName, lang?)
 * OCR.FromBitmap(HBitmap, lang?)
 * 
 * Additional methods:
 * OCR.GetAvailableLanguages()
 * OCR.LoadLanguage(lang:="FirstFromAvailableLanguages")
 * OCR.WaitText(needle, timeout:=-1, func?, casesense:=False, comparefunc?)
 *      Calls a func (the provided OCR method) until a string is found
 * OCR.WordsBoundingRect(words*)
 *      Returns the bounding rectangle for multiple words
 * 
 * Properties:
 * OCR.MaxImageDimension
 * MinImageDimension is not documented, but appears to be 40 pixels (source: user FanaticGuru in AutoHotkey forums)
 * 
 * OCR returns an OCR results object:
 * Result.Text         => All recognized text
 * Result.TextAngle    => Clockwise rotation of the recognized text 
 * Result.Lines        => Array of all Line objects
 * Result.Words        => Array of all Word objects
 * Result.ImageWidth   => Used image width
 * Result.ImageHeight  => Used image height
 * 
 * Result.FindString(needle, i:=1, casesense:=False, wordCompareFunc?)
 *      Finds a string in the result
 * Result.Click(Obj, WhichButton?, ClickCount?, DownOrUp?)
 *      Clicks an object (Word, FindString result etc)
 * Result.ControlClick(obj, WinTitle?, WinText?, WhichButton?, ClickCount?, Options?, ExcludeTitle?, ExcludeText?)
 *      ControlClicks an object (Word, FindString result etc)
 * Result.Highlight(obj?, showTime:=2000, color:="Red", d:=2)
 *      Highlights an object on the screen, or removes the highlighting
 * 
 * 
 * Line object:
 * Line.Text         => Recognized text of the line
 * Line.Words        => Array of Word objects for the Line
 * 
 * Word object:
 * Line.Text         => Recognized text of the word
 * Line.x,y,w,h      => Size and location of the Word. Coordinates are relative to the original image.
 * Line.BoundingRect => Bounding rectangle of the Word in format {x,y,w,h}. Coordinates are relative to the original image.
 * 
 * Additional notes:
 * Languages are recognized in BCP-47 language tags. Eg. OCR.FromFile("myfile.bmp", "en-AU")
 * Languages can be installed for example with PowerShell (run as admin): Install-Language <language-tag>
 *      or from Language settings in Settings.
 * Not all language packs support OCR though. A list of supported language can be gotten from 
 * Powershell (run as admin) with the following command: Get-WindowsCapability -Online | Where-Object { $_.Name -Like 'Language.OCR*' } 
 */
class OCR {
    static IID_IRandomAccessStream := "{905A0FE1-BC53-11DF-8C49-001E4FC686DA}"
         , IID_IPicture            := "{7BF80980-BF32-101A-8BBB-00AA00300CAB}"
         , IID_IAsyncInfo          := "{00000036-0000-0000-C000-000000000046}"

    class IBase {
        __New(ptr?) {
            if IsSet(ptr) && !ptr
                throw ValueError('Invalid IUnknown interface pointer', -2, this.__Class)
            this.DefineProp("ptr", {Value:ptr ?? 0})
        }
        __Delete() => this.ptr ? ObjRelease(this.ptr) : 0
    }

    static __New() {
        this.LanguageFactory := OCR.CreateClass("Windows.Globalization.Language", ILanguageFactory := "{9B0252AC-0C27-44F8-B792-9793FB66C63E}")
        this.BitmapEncoderStatics := OCR.CreateClass("Windows.Graphics.Imaging.BitmapEncoder", IBitmapEncoderStatics := "{A74356A7-A4E4-4EB9-8E40-564DE7E1CCB2}")
        this.BitmapDecoderStatics := OCR.CreateClass("Windows.Graphics.Imaging.BitmapDecoder", IBitmapDecoderStatics := "{438CCB26-BCEF-4E95-BAD6-23A822E58D01}")
        this.OcrEngineStatics := OCR.CreateClass("Windows.Media.Ocr.OcrEngine", IOcrEngineStatics := "{5BFFA85A-3384-3540-9940-699120D428A8}")
        ComCall(6, this.OcrEngineStatics, "uint*", &MaxImageDimension:=0)   ; MaxImageDimension
        this.MaxImageDimension := MaxImageDimension
    }

    /**
     * Returns an OCR results object for an IRandomAccessStream.
     * Images of other types should be first converted to this format (eg from file, from bitmap).
     * @param pIRandomAccessStream Pointer or an object containing a ptr to the stream
     * @param {String} lang OCR language. Default is first from available languages.
     * @returns {Ocr} 
     */
    __New(pIRandomAccessStream?, lang := "FirstFromAvailableLanguages") {
        if IsSet(lang) || !OCR.HasOwnProp("CurrentLanguage")
            OCR.LoadLanguage(lang?)
        ComCall(14, OCR.BitmapDecoderStatics, "ptr", pIRandomAccessStream, "ptr*", BitmapDecoder:=OCR.IBase())   ; CreateAsync
        OCR.WaitForAsync(&BitmapDecoder)
        BitmapFrame := ComObjQuery(BitmapDecoder, IBitmapFrame := "{72A49A1C-8081-438D-91BC-94ECFC8185C6}")
        ComCall(12, BitmapFrame, "uint*", &width:=0)   ; get_PixelWidth
        ComCall(13, BitmapFrame, "uint*", &height:=0)   ; get_PixelHeight
        if (width > OCR.MaxImageDimension) or (height > OCR.MaxImageDimension)
           throw ValueError("Image is too big - " width "x" height ".`nIt should be maximum - " OCR.MaxImageDimension " pixels")

        BitmapFrameWithSoftwareBitmap := ComObjQuery(BitmapDecoder, IBitmapFrameWithSoftwareBitmap := "{FE287C9A-420C-4963-87AD-691436E08383}")
        if width < 40 || height < 40 {
            BitmapTransform := OCR.CreateClass("Windows.Graphics.Imaging.BitmapTransform")
            scale := 40.0 / Min(width, height), this.ImageWidth := Ceil(width*scale), this.ImageHeight := Ceil(height*scale)
            ComCall(7, BitmapTransform, "int", this.ImageWidth) ; put_ScaledWidth
            ComCall(9, BitmapTransform, "int", this.ImageHeight) ; put_ScaledHeight
            ComCall(8, BitmapFrame, "uint*", &BitmapPixelFormat:=0) ; get_BitmapPixelFormat
            ComCall(9, BitmapFrame, "uint*", &BitmapAlphaMode:=0) ; get_BitmapAlphaMode
            ComCall(8, BitmapFrameWithSoftwareBitmap, "uint", BitmapPixelFormat, "uint", BitmapAlphaMode, "ptr", BitmapTransform, "uint", IgnoreExifOrientation := 0, "uint", DoNotColorManage := 0, "ptr*", SoftwareBitmap:=OCR.IBase()) ; GetSoftwareBitmapAsync
        } else {
            this.ImageWidth := width, this.ImageHeight := height
            ComCall(6, BitmapFrameWithSoftwareBitmap, "ptr*", SoftwareBitmap:=OCR.IBase())   ; GetSoftwareBitmapAsync
        }
        OCR.WaitForAsync(&SoftwareBitmap)

        ComCall(6, OCR.OcrEngine, "ptr", SoftwareBitmap, "ptr*", OcrResult:=OCR.IBase())   ; RecognizeAsync
        OCR.WaitForAsync(&OcrResult)

        ; Cleanup
        OCR.CloseIClosable(pIRandomAccessStream)
        OCR.CloseIClosable(SoftwareBitmap)

        this.ptr := OcrResult.ptr, ObjAddRef(OcrResult.ptr)
    }
    __Delete() => this.ptr ? ObjRelease(this.ptr) : 0

    ; Gets the recognized text.
    Text {
        get {
            ComCall(8, this, "ptr*", &hAllText:=0)   ; get_Text
            buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hAllText, "uint*", &length:=0, "ptr")
            this.DefineProp("Text", {Value:StrGet(buf, "UTF-16")})
            OCR.DeleteHString(hAllText)
            return this.Text
        }
    }

    ; Gets the clockwise rotation of the recognized text, in degrees, around the center of the image.
    TextAngle {
        get => (ComCall(7, this, "double*", &value:=0), value)
    }

    ; Returns all Line objects for the result.
    Lines {
        get {
            ComCall(6, this, "ptr*", LinesList:=OCR.IBase()) ; get_Lines
            ComCall(7, LinesList, "int*", &count:=0) ; count
            lines := []
            loop count {
                ComCall(6, LinesList, "int", A_Index-1, "ptr*", OcrLine:=OCR.OCRLine())               
                lines.Push(OcrLine)
            }
            this.DefineProp("Lines", {Value:lines})
            return lines
        }
    }

    ; Returns all Word objects for the result. Equivalent to looping over all the Lines and getting the Words.
    Words {
        get {
            words := []
            for line in this.Lines
                for word in line.Words
                    words.Push(word)
            this.DefineProp("Words", {Value:words})
            return words
        }
    }

    /**
     * Clicks an object
     * @param Obj The object to click, which can be a OCR result object, Line, Word, or Object {x,y,w,h}
     * If this object (the one Click is called from) contains a "Relative" property (this is
     * added by default with OCR.FromWindow) containing a Hwnd property, then that window will be activated,
     * otherwise the Relative properties values will be added to the x and y coordinates as offsets.
     */
    Click(Obj, WhichButton?, ClickCount?, DownOrUp?) {
        if !obj.HasOwnProp("x") && InStr(Type(obj), "OCR")
            obj := OCR.WordsBoundingRect(obj.Words)
        x := obj.x, y := obj.y, w := obj.w, h := obj.h
        if this.HasOwnProp("Relative") {
            if this.Relative.HasOwnProp("Hwnd") {
                if !WinActive(this.Relative.Hwnd) {
                    WinActivate(this.Relative.Hwnd)
                    WinWaitActive(this.Relative.Hwnd,,1)
                }
            } else
                x += this.Relative.x, y += this.Relative.y
        }
        oldCoordMode := A_CoordModeMouse
        CoordMode "Mouse", this.HasOwnProp("Relative") && this.Relative.HasOwnProp("Type") ? this.Relative.Type : "Screen"
        Click(x+w//2, y+h//2, WhichButton?, ClickCount?, DownOrUp?)
        CoordMode "Mouse", oldCoordMode
    }

    /**
     * ControlClicks an object
     * @param obj The object to click, which can be a OCR result object, Line, Word, or Object {x,y,w,h}
     * If this object (the one Click is called from) contains a "Relative" property (this is
     * added by default with OCR.FromWindow) containing a Hwnd property, then that window will be activated,
     * otherwise the Relative properties values will be added to the x and y coordinates as offsets.
     * @param WinTitle If WinTitle is set, then the coordinates stored in Obj will be converted to
     * client coordinates and ControlClicked.
     */
    ControlClick(obj, WinTitle?, WinText?, WhichButton?, ClickCount?, Options?, ExcludeTitle?, ExcludeText?) {
        if !obj.HasOwnProp("x") && InStr(Type(obj), "OCR")
            obj := OCR.WordsBoundingRect(obj.Words)
        x := obj.x, y := obj.y, w := obj.w, h := obj.h
        if this.HasOwnProp("Relative") && this.Relative.HasOwnProp("Type") {
            hWnd := this.Relative.hWnd
            if this.Relative.Type = "Window" {
                ; Window -> Client
                RECT := Buffer(16, 0), pt := Buffer(8, 0)
                DllCall("user32\GetWindowRect", "Ptr", hWnd, "Ptr", RECT)
                winX := NumGet(RECT, 0, "Int"), winY := NumGet(RECT, 4, "Int")
                NumPut("int", winX+x, "int", winY+y, pt)
                DllCall("user32\ScreenToClient", "Ptr", hWnd, "Ptr", pt)
                x := NumGet(pt,0,"int"), y := NumGet(pt,4,"int")
            }
        } else if IsSet(WinTitle) {
            hWnd := WinExist(WinTitle, WinText?, ExcludeTitle?, ExcludeText?)
            pt := Buffer(8), NumPut("int",x,pt), NumPut("int", y,pt,4)
            DllCall("ScreenToClient", "Int", Hwnd, "Ptr", pt)
            x := NumGet(pt,0,"int"), y := NumGet(pt,4,"int")
        } else
            throw TargetError("ControlClick needs to be called either after a OCR.FromWindow result or with a WinTitle argument")
            
        ControlClick("X" (x+w//2) " Y" (y+h//2), hWnd,, WhichButton?, ClickCount?, Options?)
    }

    /**
     * Highlights an object on the screen with a red box
     * @param obj The object to highlight. which can be a OCR result object, Line, Word, or Object {x,y,w,h}
     * If this object (the one Highlight is called from) contains a "Relative" property (this is
     * added by default with OCR.FromWindow), then its values will be added to the x and y coordinates as offsets.
     * @param {number} showTime Default is 2 seconds.
     * * Unset - if highlighting exists then removes the highlighting
     * * 0 - Indefinite highlighting
     * * Positive integer (eg 2000) - will highlight and pause for the specified amount of time in ms
     * * Negative integer - will highlight for the specified amount of time in ms, but script execution will continue
     * @param {string} color The color of the highlighting. Default is red.
     * @param {number} d The border thickness of the highlighting in pixels. Default is 2.
     * @returns {OCR}
     */
    Highlight(obj?, showTime:=2000, color:="Red", d:=2) {
        static guis := []
        if !IsSet(obj) {
            for _, r in guis
                r.Destroy()
            guis := []
            return this
        }
        if !guis.Length {
            Loop 4
                guis.Push(Gui("+AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000"))
        }
        if Type(obj) = "OCR.OCRLine" || Type(obj) = "OCR"
            obj := OCR.WordsBoundingRect(obj.Words*)
        x := obj.x, y := obj.y, w := obj.w, h := obj.h
        if this.HasOwnProp("Relative")
            x += this.Relative.x, y += this.Relative.y

        Loop 4 {
            i:=A_Index
            , x1:=(i=2 ? x+w : x-d)
            , y1:=(i=3 ? y+h : y-d)
            , w1:=(i=1 or i=3 ? w+2*d : d)
            , h1:=(i=2 or i=4 ? h+2*d : d)
            guis[i].BackColor := color
            guis[i].Show("NA x" . x1 . " y" . y1 . " w" . w1 . " h" . h1)
        }
        if showTime > 0 {
            Sleep(showTime)
            this.Highlight()
        } else if showTime < 0
            SetTimer(this.GetMethod("Highlight"), -Abs(showTime))
        return this
    }

    /**
     * Finds a string in the search results. Returns {x,y,w,h,Words} where Words contains an array of the matching Word objects.
     * @param needle The string to find
     * @param {number} i Which occurrence of needle to find
     * @param {number} casesense Comparison case-sensitivity. Default is False/Off.
     * @param wordCompareFunc Optionally a custom word comparison function. Accepts two arguments,
     *     neither of which should contain spaces
     * @returns {Object} 
     */
    FindString(needle, i:=1, casesense:=False, wordCompareFunc?) {
        splitNeedle := StrSplit(RegExReplace(needle, " +", " "), " "), needleLen := splitNeedle.Length
        if !IsSet(wordCompareFunc)
            wordCompareFunc := casesense ? ((arg1, arg2) => arg1 == arg2) : ((arg1, arg2) => arg1 = arg2)
        for line in this.Lines {
            if InStr(l := line.Text, needle, casesense) {
                counter := 0, found := []
                for word in line.Words {
                    t := word.Text, len := StrLen(t)
                    if wordCompareFunc(splitNeedle[found.Length+1], t) {
                        found.Push(word)
                        if found.Length == needleLen {
                            if ++counter == i {
                                result := OCR.WordsBoundingRect(found*)
                                result.Words := found
                                return result
                            } else
                                found := []
                        }
                    } else
                        found := []
                }
            }
        }
        throw TargetError('The target string "' needle '" was not found', -1)
    }

    class OCRLine extends OCR.IBase {
        ; Gets the recognized text for the line.
        Text {
            get {
                ComCall(7, this, "ptr*", &hText:=0)   ; get_Text
                buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", &length:=0, "ptr")
                text := StrGet(buf, "UTF-16")
                OCR.DeleteHString(hText)
                this.DefineProp("Text", {Value:text})
                return text
            }
        }

        ; Gets the Word objects for the line
        Words {
            get {
                ComCall(6, this, "ptr*", WordsList:=OCR.IBase())   ; get_Words
                ComCall(7, WordsList, "int*", &WordsCount:=0)   ; Words count
                words := []
                loop WordsCount {
                   ComCall(6, WordsList, "int", A_Index-1, "ptr*", OcrWord:=OCR.OCRWord())
                   words.Push(OcrWord)
                }
                this.DefineProp("Words", {Value:words})
                return words
            }
        }
    }

    class OCRWord extends OCR.IBase {
        ; Gets the recognized text for the word
        Text {
            get {
                ComCall(7, this, "ptr*", &hText:=0)   ; get_Text
                buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", &length:=0, "ptr")
                text := StrGet(buf, "UTF-16")
                OCR.DeleteHString(hText)
                this.DefineProp("Text", {Value:text})
                return text
            }
        }

        /**
         * Gets the bounding rectangle of the text in {x,y,w,h} format. 
         * The bounding rectangles coordinate system will be dependant on the image capture method.
         * For example, if the image was captured as a rectangle from the screen, then the coordinates
         * will be relative to the left top corner of the rectangle.
         */
        BoundingRect {
            get {
                ComCall(6, this, "ptr", RECT := Buffer(16, 0))   ; get_BoundingRect
                this.DefineProp("x", {Value:Integer(NumGet(RECT, 0, "float"))})
                , this.DefineProp("y", {Value:Integer(NumGet(RECT, 4, "float"))})
                , this.DefineProp("w", {Value:Integer(NumGet(RECT, 8, "float"))})
                , this.DefineProp("h", {Value:Integer(NumGet(RECT, 12, "float"))})
                return this.DefineProp("BoundingRect", {Value:{x:this.x, y:this.y, w:this.w, h:this.h}}).BoundingRect
            }
        }
        x {
            get => this.BoundingRect.x
        }
        y {
            get => this.BoundingRect.y
        }
        w {
            get => this.BoundingRect.w
        }
        h {
            get => this.BoundingRect.h
        }
    }

    /**
     * Returns an OCR results object for an image file. Locations of the words will be relative to
     * the top left corner of the image.
     * @param FileName Either full or relative (to A_ScriptDir) path to the file.
     * @param lang OCR language. Default is first from available languages.
     * @returns {Ocr} 
     */
    static FromFile(FileName, lang?) {
        if (SubStr(FileName, 2, 1) != ":")
            FileName := A_ScriptDir "\" FileName
         if !FileExist(FileName) or InStr(FileExist(FileName), "D")
            throw TargetError("File `"" FileName "`" doesn't exist", -1)
         GUID := OCR.CLSIDFromString(OCR.IID_IRandomAccessStream)
         DllCall("ShCore\CreateRandomAccessStreamOnFile", "wstr", FileName, "uint", Read := 0, "ptr", GUID, "ptr*", IRandomAccessStream:=OCR.IBase())
         return OCR(IRandomAccessStream, lang?)
    }

    /**
     * Returns an OCR results object for a given window. Locations of the words will be relative to the
     * window or client area, so for interactions use CoordMode "Window" or "Client".
     * @param WinTitle A window title or other criteria identifying the target window.
     * @param lang OCR language. Default is first from available languages.
     * @param scale The scaling factor to use.
     * @param {Number} onlyClientArea Whether only the client area or the whole window should be OCR-d
     * @param {Number} mode Different methods of capturing the window. 0 = uses GetDC with BitBlt, 2 = uses PrintWindow. 
     * Add 1 to make a transparent window totally opaque. 
     * @returns {Ocr} 
     */
    static FromWindow(WinTitle:="", lang?, scale:=1, onlyClientArea:=0, mode:=2) {
        if !(hWnd := WinExist(WinTitle))
            throw TargetError("Target window not found", -1)
        if DllCall("IsIconic", "uptr", hwnd)
            DllCall("ShowWindow", "uptr", hwnd, "int", 4)
        if mode&1 {
            oldStyle := WinGetExStyle(hwnd), i := 0
            WinSetTransparent(255, hwnd)
            While (WinGetTransparent(hwnd) != 255 && ++i < 30)
                Sleep 100
        }
        If onlyClientArea {
            DllCall("GetClientRect", "ptr", hwnd, "ptr", rc:=Buffer(16))
            W := NumGet(rc, 8, "int"), H := NumGet(rc, 12, "int")
            pt:=Buffer(8, 0), NumPut("int64", 0, pt)
            DllCall("ClientToScreen", "Ptr", hwnd, "Ptr", pt)
            X:=NumGet(pt,"int"), Y:=NumGet(pt,4,"int")
        } else {
            rect := Buffer(16, 0)
            DllCall("GetWindowRect", "UPtr", hwnd, "Ptr", rect, "UInt")
            X := NumGet(rect, 0, "Int"), Y := NumGet(rect, 4, "Int")
            x2 := NumGet(rect, 8, "Int"), y2 := NumGet(rect, 12, "Int")
            W := Abs(Max(X, X2) - Min(X, X2))
            H := Abs(Max(Y, Y2) - Min(Y, Y2))
        }
        hBitMap := OCR.CreateBitmap(X, Y, W, H, hWnd, scale, onlyClientArea, mode)
        ;OCR.DisplayHBitmap(hBitMap)
        if mode&1
            WinSetExStyle(oldStyle, hwnd)
        result := OCR(OCR.HBitmapToRandomAccessStream(hBitMap), lang?)
        result.Relative := {X:X, Y:Y, Type:(onlyClientArea ? "Client" : "Window"), Hwnd:hWnd}
        OCR.NormalizeCoordinates(result, scale)
        return result
    }

    /**
     * Returns an OCR results object for the whole desktop. Locations of the words will be relative to
     * the screen (CoordMode "Screen")
     * @param lang OCR language. Default is first from available languages.
     * @returns {Ocr} 
     */
    static FromDesktop(lang?, scale:=1) => OCR.FromRect(0, 0, A_ScreenWidth, A_ScreenHeight, lang?, scale)

    /**
     * Returns an OCR results object for a region of the screen. Locations of the words will be relative
     * to the top left corner of the rectangle.
     * @param x Screen x coordinate
     * @param y Screen y coordinate
     * @param w Region width. Maximum is OCR.MaxImageDimension; minimum is 40 pixels (source: user FanaticGuru in AutoHotkey forums), smaller images will be scaled to at least 40 pixels.
     * @param h Region height. Maximum is OCR.MaxImageDimension; minimum is 40 pixels, smaller images will be scaled accordingly.
     * @param lang OCR language. Default is first from available languages.
     * @param scale The scaling factor to use. Larger number (eg 2) might improve the accuracy
     *     of the OCR, at the cost of speed.
     * @returns {Ocr} 
     */
    static FromRect(x, y, w, h, lang?, scale:=1) {
        hBitmap := OCR.CreateBitmap(X, Y, W, H,,scale)
        result := OCR(OCR.HBitmapToRandomAccessStream(hBitmap), lang?)
        result.Relative := {x:x, y:y}
        return OCR.NormalizeCoordinates(result, scale)
    }

    /**
     * Returns an OCR results object from a hBitmap object. Locations of the words will be relative
     * to the top left corner of the bitmap.
     * @param hBitmap An hBitmap pointer or an object with a ptr property
     * @param lang OCR language. Default is first from available languages.
     * @returns {ocr} 
     */
    static FromBitmap(hBitmap, lang?) => OCR(OCR.HBitmapToRandomAccessStream(hBitmap), lang?)

    /**
     * Returns all available languages as a string, where the languages are separated by newlines.
     * @returns {String} 
     */
    static GetAvailableLanguages() {
        static GlobalizationPreferencesStatics
        if !IsSet(GlobalizationPreferencesStatics)
            GlobalizationPreferencesStatics := OCR.CreateClass("Windows.System.UserProfile.GlobalizationPreferences", IGlobalizationPreferencesStatics := "{01BF4326-ED37-4E96-B0E9-C1340D1EA158}")
        ComCall(9, GlobalizationPreferencesStatics, "ptr*", &LanguageList:=0)   ; get_Languages
        ComCall(7, LanguageList, "int*", &count:=0)   ; count
        Loop count {
            ComCall(6, LanguageList, "int", A_Index-1, "ptr*", &hString:=0)   ; get_Item
            ComCall(6, this.LanguageFactory, "ptr", hString, "ptr*", &LanguageTest:=0)   ; CreateLanguage
            ComCall(8, this.OcrEngineStatics, "ptr", LanguageTest, "int*", &bool:=0)   ; IsLanguageSupported
            if (bool = 1) {
                ComCall(6, LanguageTest, "ptr*", &hText:=0)
                buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", &length:=0, "ptr")
                text .= StrGet(buf, "UTF-16") "`n"
            }
            ObjRelease(LanguageTest)
        }
        ObjRelease(LanguageList)
        return text
    }

    /**
     * Loads a new language which will be used with subsequent OCR calls.
     * @param {string} lang OCR language. Default is first from available languages.
     * @returns {void} 
     */
    static LoadLanguage(lang:="FirstFromAvailableLanguages") {
        if this.HasOwnProp("CurrentLanguage") && this.HasOwnProp("OcrEngine") && this.CurrentLanguage = lang
            return
        if (lang = "FirstFromAvailableLanguages")
            ComCall(10, this.OcrEngineStatics, "ptr*", OcrEngine:=OCR.IBase())   ; TryCreateFromUserProfileLanguages
        else {
            hString := OCR.CreateHString(lang)
            ComCall(6, this.LanguageFactory, "ptr", hString, "ptr*", Language:=OCR.IBase())   ; CreateLanguage
            OCR.DeleteHString(hString)
            ComCall(9, this.OcrEngineStatics, "ptr", Language, "ptr*", OcrEngine:=OCR.IBase())   ; TryCreateFromLanguage
        }
        if (OcrEngine.ptr = 0)
            Throw Error("Can not use language `"" lang "`" for OCR, please install language pack.")
        this.OcrEngine := OcrEngine, this.CurrentLanguage := lang
    }

    /**
     * Returns a bounding rectangle {x,y,w,h} for the provided Word objects
     * @param words Word object arguments (at least 1)
     * @returns {Object}
     */
    static WordsBoundingRect(words*) {
        if !words.Length
            throw ValueError("This function requires at least one argument")
        X1 := 100000000, Y1 := 100000000, X2 := -100000000, Y2 := -100000000
        for word in words {
            X1 := Min(word.x, X1), Y1 := Min(word.y, Y1), X2 := Max(word.x+word.w, X2), Y2 := Max(word.y+word.h, Y2)
        }
        return {X:X1, Y:Y1, W:X2-X1, H:Y2-Y1, X2:X2, Y2:Y2}
    }
    
    /**
     * Waits text to appear on screen. If the method is successful, then Func's return value is returned.
     * Otherwise nothing is returned.
     * @param needle The searched text
     * @param {number} timeout Timeout in milliseconds. Less than 0 is indefinite wait (default)
     * @param func The function to be called for the OCR. Default is OCR.FromDesktop
     * @param casesense Text comparison case-sensitivity
     * @param comparefunc A custom string compare/search function, that accepts two arguments: haystack and needle.
     *      Default is InStr. If a custom function is used, then casesense is ignored.
     * @returns {OCR} 
     */
    static WaitText(needle, timeout:=-1, func?, casesense:=False, comparefunc?) {
        endTime := A_TickCount+timeout
        if !IsSet(func)
            func := OCR.FromDesktop
        if !IsSet(comparefunc)
            comparefunc := InStr.Bind(,,casesense)
        While timeout > 0 ? (A_TickCount < endTime) : 1 {
            result := func()
            if comparefunc(result.Text, needle)
                return result
        }
        return
    }

    ;; Only internal methods ahead

    static CreateDIBSection(w, h, hdc?, bpp:=32, &ppvBits:=0) {
        hdc2 := IsSet(hdc) ? hdc : DllCall("GetDC", "Ptr", 0, "UPtr")
        bi := Buffer(40, 0)
        NumPut("int", 40, "int", w, "int", h, "ushort", 1, "ushort", bpp, "int", 0, bi)
        hbm := DllCall("CreateDIBSection", "uint", hdc2, "ptr" , bi, "uint" , 0, "uint*", &ppvBits:=0, "uint" , 0, "uint" , 0)
        if !IsSet(hdc)
            DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdc2)
        return hbm
    }

    static CreateBitmap(X, Y, W, H, hWnd := 0, scale:=1, onlyClientArea:=0, mode:=2) {
        static CAPTUREBLT
        if !IsSet(CAPTUREBLT) {
            DllCall("Dwmapi\DwmIsCompositionEnabled", "Int*", &compositionEnabled:=0)
            CAPTUREBLT:= compositionEnabled ? 0 : 0x40000000
        }
        sW := W*scale, sH := H*scale
        if hWnd {
            if mode < 2 {
                X := 0, Y := 0
                HDC := DllCall("GetDCEx", "Ptr", hWnd, "Ptr", 0, "int", 2|!onlyClientArea, "Ptr")
            } else {
                hbm := OCR.CreateDIBSection(W, H)
                hdc := DllCall("CreateCompatibleDC", "Ptr", 0, "UPtr")
                obm := DllCall("SelectObject", "Ptr", HDC, "Ptr", HBM)
                DllCall("PrintWindow", "uint", hwnd, "uint", hdc, "uint", 2|!!onlyClientArea)
                if scale != 1 {
                    PDC := DllCall("CreateCompatibleDC", "Ptr", HDC, "UPtr")
                    hbm2 := DllCall("CreateCompatibleBitmap", "Ptr", HDC, "Int", sW, "Int", sH, "UPtr")
                    DllCall("SelectObject", "Ptr", PDC, "Ptr", HBM2)
                    DllCall("StretchBlt", "Ptr", PDC, "Int", 0, "Int", 0, "Int", sW, "Int", sH, "Ptr", HDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "UInt", 0x00CC0020 | CAPTUREBLT) ; SRCCOPY
                    DllCall("DeleteDC", "Ptr", PDC)
                    DllCall("DeleteObject", "UPtr", HBM)
                    hbm := hbm2
                }
                DllCall("DeleteDC", "Ptr", HDC)
                return OCR.IBase(HBM).DefineProp("__Delete", {call:(*)=>DllCall("DeleteObject", "UPtr", HBM)})
            }
        } else {
            HDC := DllCall("GetDC", "Ptr", 0, "UPtr")
        }
        HBM := DllCall("CreateCompatibleBitmap", "Ptr", HDC, "Int", Max(40,sW), "Int", Max(40,sH), "UPtr")
        PDC := DllCall("CreateCompatibleDC", "Ptr", HDC, "UPtr")
        DllCall("SelectObject", "Ptr", PDC, "Ptr", HBM)
        if sW < 40 || sH < 40 ; Fills the bitmap so it's at least 40x40, which seems to improve recognition
            DllCall("StretchBlt", "Ptr", PDC, "Int", 0, "Int", 0, "Int", Max(40,sW), "Int", Max(40,sH), "Ptr", HDC, "Int", X, "Int", Y, "Int", 1, "Int", 1, "UInt", 0x00CC0020 | CAPTUREBLT) ; SRCCOPY. 
        DllCall("StretchBlt", "Ptr", PDC, "Int", 0, "Int", 0, "Int", sW, "Int", sH, "Ptr", HDC, "Int", X, "Int", Y, "Int", W, "Int", H, "UInt", 0x00CC0020 | CAPTUREBLT) ; SRCCOPY
        DllCall("DeleteDC", "Ptr", PDC)
        DllCall("ReleaseDC", "Ptr", 0, "Ptr", HDC)
        return OCR.IBase(HBM).DefineProp("__Delete", {call:(*)=>DllCall("DeleteObject", "UPtr", HBM)})
    }

    static HBitmapToRandomAccessStream(hBitmap) {
        static PICTYPE_BITMAP := 1
             , BSOS_DEFAULT   := 0
             , sz := 8 + A_PtrSize*2
             
        DllCall("Ole32\CreateStreamOnHGlobal", "Ptr", 0, "UInt", true, "Ptr*", pIStream:=OCR.IBase(), "UInt")
        
        PICTDESC := Buffer(sz, 0)
        NumPut("uint", sz, "uint", PICTYPE_BITMAP, "ptr", IsInteger(hBitmap) ? hBitmap : hBitmap.ptr, PICTDESC)
        riid := OCR.CLSIDFromString(OCR.IID_IPicture)
        DllCall("OleAut32\OleCreatePictureIndirect", "Ptr", PICTDESC, "Ptr", riid, "UInt", 0, "Ptr*", pIPicture:=OCR.IBase(), "UInt")
        ; IPicture::SaveAsFile
        ComCall(15, pIPicture, "Ptr", pIStream, "UInt", true, "uint*", &size:=0, "UInt")
        riid := OCR.CLSIDFromString(OCR.IID_IRandomAccessStream)
        DllCall("ShCore\CreateRandomAccessStreamOverStream", "Ptr", pIStream, "UInt", BSOS_DEFAULT, "Ptr", riid, "Ptr*", pIRandomAccessStream:=OCR.IBase(), "UInt")
        Return pIRandomAccessStream
    }

    static DisplayHBitmap(hBitmap, W:=640, H:=640) {
        gImage := Gui()
        hPic := gImage.Add("Text", "0xE w" W " h" H)
        SendMessage(0x172, 0, hBitmap,, hPic.Hwnd)
        gImage.Show()
        WinWaitClose gImage
    }

    static CreateClass(str, interface?) {
        hString := OCR.CreateHString(str)
        if !IsSet(interface) {
            result := DllCall("Combase.dll\RoActivateInstance", "ptr", hString, "ptr*", cls:=OCR.IBase(), "uint")
        } else {
            GUID := OCR.CLSIDFromString(interface)
            result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", GUID, "ptr*", cls:=OCR.IBase(), "uint")
        }
        if (result != 0) {
            if (result = 0x80004002)
                throw Error("No such interface supported", -1, interface)
            else if (result = 0x80040154)
                throw Error("Class not registered", -1)
            else
                throw Error(result)
        }
        OCR.DeleteHString(hString)
        return cls
    }
    
    static CreateHString(str) => (DllCall("Combase.dll\WindowsCreateString", "wstr", str, "uint", StrLen(str), "ptr*", &hString:=0), hString)
    
    static DeleteHString(hString) => DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
    
    static WaitForAsync(&obj) {
        AsyncInfo := ComObjQuery(obj, OCR.IID_IAsyncInfo)
        Loop {
            ComCall(7, AsyncInfo, "uint*", &status:=0)   ; IAsyncInfo.Status
            if (status != 0) {
                if (status != 1) {
                    ComCall(8, ASyncInfo, "uint*", &ErrorCode:=0)   ; IAsyncInfo.ErrorCode
                    throw Error("AsyncInfo failed with status error " ErrorCode, -1)
                }
             break
          }
          Sleep 10
        }
        ComCall(8, obj, "ptr*", ObjectResult:=OCR.IBase())   ; GetResults
        obj := ObjectResult
    }

    static CloseIClosable(pClosable) {
        static IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}"
        Close := ComObjQuery(pClosable, IClosable)
        ComCall(6, Close)   ; Close
        if !IsObject(pClosable)
            ObjRelease(pClosable)
    }

    static CLSIDFromString(IID) {
        CLSID := Buffer(16)
        if res := DllCall("ole32\CLSIDFromString", "WStr", IID, "Ptr", CLSID, "UInt")
           throw Error("CLSIDFromString failed. Error: " . Format("{:#x}", res))
        Return CLSID
    }

    static NormalizeCoordinates(result, scale) {
        if scale != 1 {
            for word in result.Words
                word.x := Integer(word.x / scale), word.y := Integer(word.y / scale), word.w := Integer(word.w / scale), word.h := Integer(word.h / scale), word.BoundingRect := {X:word.x, Y:word.y, W:word.w, H:word.h}
        }
        return result
    }
}


Each of these files can be added to Snipper by including them in the same directory as the Snipper script and then the appropriate #include line needs to be added or uncommented in the #INCLUDE EXTENSION section of the Snipper script.
#Include Snipper - Extension - Acrobat.ahk
#Include Snipper - Extension - Word.ahk
#Include Snipper - Extension - Outlook.ahk
#include Snipper - Extension - OCR.ahk

FG
Last edited by FanaticGuru on 05 May 2023, 15:21, edited 2 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
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

29 Apr 2023, 21:01

Updated in First Post

Change Log: 2023 04 29
  • Added Settings that are saved to ini file and loaded on script startup
  • Dynamic creation of context menu
  • Streamlined context menu where the saving or coping of borders is indicated by a checkmark that can be controlled through the Settings dialog
  • More modular design to allow for easier #include of separate extension files

The main thing is the much more modular design and how I approached extensions that can be added to the context menu.

I got to thinking that many people probably do not have access to all the software that I have. Like the full paid version of Adobe Acrobat which I use for creating PDFs. Word and Outlook are much more common but not available for everyone.

So, although my example Extensions use this software, it at least added the capability for others to write their own custom function following this template:

Code: Select all

Extensions.Push({ Acrobat: { Text: 'COPY:  Acrobat PDF', Func: Clipboard2Acrobat } })
Extensions.Push({ Acrobat: { Text: 'SAVE:  Acrobat PDF', Func: Clipboard2Acrobat.Bind(,GetFullPathName('.\Snipper - PDF\')) } })
Clipboard2Acrobat(Borders := false, SavePath := '')
{
; Custom Function Stuff
}
This will added two items to the context menu with the Text shown that call the same custom Func but with different parameters. Then the actual function called is defined below.

Push an object on to Extensions array of an 'application' object with properties for Text and Func. The first parameter in the callback function is always Borders which is hard coded into Snipper, then any other parameters that you might want to bind leaving the first blank for Borders.

The custom function can also call Snipper functions like Snip2Clipboard(Borders) and Snip2File(Borders, SavePath, FileNameOnly, FileExt) depending on if those are needed to get information to the application.

I hope it will add a lot of flexibility down the road. The context menu could have a dozen or more extensions added with #include and it should be able to handle it dynamically. Each user can then customize the extensions to match their needs and available software.

The next extension I will probably work on will be OCR.

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
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

01 May 2023, 07:20

FanaticGuru wrote:
29 Apr 2023, 21:01
Updated in First Post

Change Log: 2023 04 29
  • Added Settings that are saved to ini file and loaded on script startup
  • Dynamic creation of context menu
  • Streamlined context menu where the saving or coping of borders is indicated by a checkmark that can be controlled through the Settings dialog
  • More modular design to allow for easier #include of separate extension files
Thank you for all these new improvements. 8-)
The script is easier to use now and for the few that I have tested I can say that it works well on windows 7. :thumbup:
I have converted some extensions from version 1. See below...
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

01 May 2023, 07:21

SM - Snipper - Extensions

Snipper - Extension - MoveClip.ahk

Usage:
Press arrow keys or Ctrl + arrow keys to move the clip image by 1 or 10 pixel

Code: Select all

 ; Snipper - Extension - MoveClip
/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] MoveClip (by Speedmaster)
File Name : Snipper - Extension - MoveClip.ahk
Version : 2023 05 01

Usage : Press Arrow Keys or Ctrl + Arrow Keys to move the clip by 10 or 1 pixel


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


; DEFAULT SHORTCUTS ---------------------------------------------------

; Press arrow keys or Ctrl + arrow keys to move the clip by 10 or 1 pixel

#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
right::
{
	WinGetPos(&x, &y, , , "A")
	x += 1
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
left::
{
	WinGetPos(&x, &y, , , "A")
	x -= 1
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
Up::
{
	WinGetPos(&x, &y, , , "A")
	y -= 1
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
Down::
{
	WinGetPos(&x, &y, , , "A")
	y += 1
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
^right::
{
	WinGetPos(&x, &y, , , "A")
	x += 10
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
^left::
{
	WinGetPos(&x, &y, , , "A")
	x -= 10
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
^Up::
{
	WinGetPos(&x, &y, , , "A")
	y -= 10
	WinMove(x, y, , , "A")
	try colorselected()
	return
}
^Down::
{
	WinGetPos(&x, &y, , , "A")
	y += 10
	WinMove(x, y, , , "A")
	try colorselected()
	return
}

#HotIf


Snipper - Extension - OpenSnipFolder.ahk

Usage:
Use the context menu item to open the snip image folder

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Open Snip Images Folder (by Speedmaster)
File Name : Snipper - Extension - OpenSnipFolder.ahk
Version : 2023 05 02

Usage : Use the context menu to browse the snip image folder

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

; CONTEXT MENU -------------------------------------------------------
Extensions.Push({ SM: { Text: 'OPEN:  Snipper - Images Folder', Func: OpenSnipFolder } })

OpenSnipFolder(borders := false)
{
	try run "explore " Settings_SavePath_Image
}


Snipper - Extension - ToggleBorder.ahk

Usage:
Use context menu item or press B to hide or show snipped image border

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Toggle Border (by Speedmaster)
File Name : Snipper - Extension - ToggleBorder.ahk
Version : 2023 05 01

Usage : Use context menu item or press B to hide or show snipped image border

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


; CONTEXT MENU -------------------------------------------------------
Extensions.Push({ SM: { Text: 'TOGGLE: Border', Func: SM_Borders } })

; DEFAULT SHORTCUTS ---------------------------------------------------
#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
b::SM_Borders()
#HotIf

SM_Borders(yorder:=false) {
	Hwnd := WinGetID('A')
	 Style := WinGetStyle("ahk_id" Hwnd)

	if (Style & 0x2000)  ; toggle selected
	{
		WinSetStyle("-0x2000", "ahk_id " Hwnd)
		WinSetRegion(, "ahk_id " Hwnd)
		return false
	}
	else                 ; retore selected
	{
		WinGetPos(&X, &Y, &W, &H, "ahk_id " Hwnd)
		W -= 6, H -= 6
		WinSetStyle("+0x2000", "ahk_id " Hwnd)
		WinSetRegion("3-3 W" W " H" H,  Hwnd)
		return true
	}
}


Snipper - Extension - ToggleAlwaysOnTop.ahk

Usage:
Press PageDown to make selected clip image not more always on top.
Press PageUp to restore selected clip image to always on top.
Use context menu item to toggle AlwaysOnTop.

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Toggle Always On Top (by Speedmaster)
File Name : Snipper - Extension - ToggleAlwaysOnTop.ahk
Version : 2023 05 01

Usage : Press PageUp / PageDown to activate / desactivate Always On top

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


; CONTEXT MENU -------------------------------------------------------
Extensions.Push({ SM: { Text: 'TOGGLE: AlwaysOnTop', Func: SM_SwitchAlwaysontop } })


; DEFAULT SHORTCUTS ---------------------------------------------------
#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
PGDN::SM_AlwaysOnBottom()
PGUP::SM_AlwaysOnTop()
#HotIf


;----- FUNCTIONS ------------------------------------------
SM_AlwaysOnBottom(){
Hwnd := WinGetID("A")
	guisnip:=GuiFromHwnd(hwnd)
	guiSnip.Opt("-AlwaysOnTop")
}

SM_AlwaysOnTop(){
Hwnd := WinGetID("A")
	guisnip:=GuiFromHwnd(hwnd)
	guiSnip.Opt("+AlwaysOnTop")
}

SM_SwitchAlwaysontop(border:=""){

	Hwnd := WinGetID('A')
	 Style := WinGetExStyle("ahk_id" Hwnd)

	if (Style & 0x8)  ; toggle selected
	{
		SM_AlwaysOnBottom()
		return false
	}
	else                 ; retore selected
	{
		SM_AlwaysOnTop()
		return true
	}

}


Snipper - Extension - Duplicate.ahk

Usage:
Use the context menu item or press D to use current selected clip area to make a new clip

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Duplicate Area (by Speedmaster)
File Name : Snipper - Extension - Duplicate.ahk
Version : 2023 05 01

Usage : Press D to use current selected clip area to make a new clip


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

; CONTEXT MENU -------------------------------------------------------
Extensions.Push({ SM: { Text: 'Duplicate Area', Func: SM_Duplicate } })


; DEFAULT SHORTCUT ----- (#if winactive section)----------------------------------------------
#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
d::SM_Duplicate()
#HotIf

;-----FUNCTION DUPLICATE AREA------------------------------------------
SM_Duplicate(border:=""){
	global guiSnips
	sleep  400
	If !IsSet(Hwnd)
	Hwnd := WinGetID('A')
	WinGetPos(&X, &Y, &W, &H, 'ahk_id ' Hwnd)
		X += 3, Y += 3, W -= 6, H -= 6
	Area := { X: X, Y: Y, W: W, H: H, X2: X + W, Y2: Y + H }
	SnipArea(Area, false, true, true, &guiSnips)
}



Snipper - Extension - SmartDelete.ahk

Usage :
Press Delete key to delete current selected clip
Press Ctrl + Delete to delete all others clip images except current selected one

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Smart Delete (by Speedmaster)
File Name : Snipper - Extension - SmartDelete.ahk
Version : 2023 05 01

Usage : Press Delete key to delete current selected clip
		Press Ctrl + Delete to delete all others clip images except current selected one

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

; DEFAULT SHORTCUTS ---------------------------------------------------
#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')

Delete::CloseSnip()    ; close current selected clip

^Delete::              ; close all clip except current
{
Hwnd := WinGetID('A')
ohwnd := WinGetList('SnipperWindow ahk_class AutoHotkeyGUI',,,)
	Loop ohwnd.Length
	{
		if (ohwnd[A_Index]!=hwnd)
			CloseSnip(ohwnd[A_Index])
	}
}

#Hotif


Snipper - Extension - BackgroundBox.ahk

Usage:
Press Backspace to transform current selected clip into a colored and resizable background box
Press Ctrl + Backspace to switch between different colors
Press Backspace again to hide or show the resizable borders

Code: Select all

/* ;--------------------------------------------------------------------------------------------------------------------
[Extension] Background Box (by Speedmaster)
File Name : Snipper - Extension - BackgroundBox.ahk
Version : 2023 05 01

Usage : Press [c]Backspace[/c] to transform current selected clip into a colored and resizable background box
		Press [c]Ctrl + Backspace[/c] to switch between different colors
		Press [c]Backspace[/c] again to hide or show the resizable borders

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

; DEFAULT SHORTCUTS ---------------------------------------------------
#HotIf WinActive('SnipperWindow ahk_class AutoHotkeyGUI')
backspace::
{
	global guisnips
	static btog:=false
		Hwnd := WinGetID('A')
		guiSnip := GuiFromHwnd(Hwnd)
	btog:=!btog
	if btog
guisnip.opt("-E0x80000 +resize -alwaysontop")
	else
		guisnip.opt("-E0x80000 -resize")
(guisnip.BackColor="") && (guisnip.BackColor := "212121")

for i, in guisnips[hwnd].guiobj {
		guisnips[hwnd].guiobj[i].opt("+hidden")
	}
}

^backspace::
c::
{
static cbc
cbc ?? cbc:="black"
blist:="212121|Black|white|Red|Green|Blue"

	Switch cbc
	{
		Case "black"  : cbc:="white"
		Case "white"  : cbc:="red"
		Case "red"    : cbc:="green"
		Case "green"  : cbc:="blue"
		Case "blue"   : cbc:="212121"
		Case "212121" : cbc:="black"
	}

	Hwnd := WinGetID('A')
	guiSnip:= GuiFromHwnd(Hwnd)
guiSnip.BackColor := cbc
}

#HotIf


Each of these files can be added to Snipper by including them in the same directory as the Snipper script and then the appropriate #include line needs to be added or uncommented in the #INCLUDE EXTENSION section of the Snipper script.
#Include Snipper - Extension - MoveClip.ahk
#Include Snipper - Extension - OpenSnipFolder.ahk
#Include Snipper - Extension - ToggleBorder.ahk
#Include Snipper - Extension - ToggleAlwaysOnTop.ahk
#Include Snipper - Extension - Duplicate.ahk
#Include Snipper - Extension - SmartDelete.ahk
#Include Snipper - Extension - BackgroundBox.ahk

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

Re: Snipper - Window Snipping Tool

02 May 2023, 10:31

New Extension Update :

Open Snip Folder : viewtopic.php?f=83&t=115622&p=519894#p519894
(added "explore " to the run command)

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: 8LWXpg, Google [Bot], hoppfrosch and 12 guests