Snipper - Window Snipping Tool

Post your working scripts, libraries and tools.
iseahound
Posts: 1456
Joined: 13 Aug 2016, 21:04
Contact:

Re: Snipper - Window Snipping Tool

17 Apr 2023, 21:30

wow. I've never encountered that before. You're right, I'm getting a white window as well. It's something with your code, I'm not sure what.
Try this code to capture the contents of a window:

Code: Select all

#include ImagePut.ahk ; https://github.com/iseahound/ImagePut/blob/master/ImagePut.ahk
a:: ImagePutWindow("A")
It works on every window on my OS except yours. You can create a window by pressing a, and create a copy of that window by pressing a again. It works for the windows created by my script.

In addition if you run KeyHistory or something like that, or click the tray icon to bring up AutoHotkey's main window, and use my script, it works fine.

So it's not AutoHotkey, it's your code. You are creating "defective" windows somehow that don't interoperate with the PrintWindow API. Or does this apply to every GDI+ window in AutoHotkey? I didn't test this idea, but you can run tic's tutorials and find out.
iseahound
Posts: 1456
Joined: 13 Aug 2016, 21:04
Contact:

Re: Snipper - Window Snipping Tool

17 Apr 2023, 21:37

iseahound wrote:
17 Apr 2023, 21:30
run tic's tutorials and find out.
Yeah, it happens with tic's tutorials as well.
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

18 Apr 2023, 10:27

iseahound wrote:
17 Apr 2023, 21:37
iseahound wrote:
17 Apr 2023, 21:30
run tic's tutorials and find out.
Yeah, it happens with tic's tutorials as well.

All of my functions are just derivatives of tic's Gdip work.

No wonder I could not find a solution to getting the bitmap, it was not so much a problem with my method for getting the bitmap, it was more a problem of how the window was created.

At least now, I know I need to look more closely at how the window is being created.

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

18 Apr 2023, 19:21

Another alternative is to put the pBitmap into the GWL_USERDATA field of the window. You can store exactly one pointer there (and if you need more, you can GlobalAlloc your own memory and free it, or store the pointer of an AutoHotkey Buffer Object). You seem to collect the windows in an array in your script, but you also tend to use Hwnd := WinGetID('A'). I highly suggest sticking to one model or the other: (1) You collect every hwnd in some dictionary (2) Each window is self-contained with a buffer holding all of its important values.

(To add on to (2), every window can hold an AutoHotkey object as well: You can use the functions ObjPtr() and ObjAddRef() to store the pointer in GWLP_Userdata and recover the autohotkey object with ObjFromPtr. Then in WM_Destroy you can check for the AutoHotkey object, and manually call ObjRelease())

Also if you do figure out exactly why my window creation works and tic's does not, please share! I'm very interested knowing, as the code for manual window creation is honestly very difficult to understand and is much too long.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

19 Apr 2023, 01:09

iseahound wrote:
17 Apr 2023, 20:00
You need to use something like this:
since he's already using GDIP, he can just migrate the GdipBitmapFromHwnd() func, which calls PrintWindow. just need to add that -3 flag which the gdip lib omits

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

Re: Snipper - Window Snipping Tool

19 Apr 2023, 01:59

iseahound wrote:
18 Apr 2023, 19:21
Another alternative is to put the pBitmap into the GWL_USERDATA field of the window. You can store exactly one pointer there (and if you need more, you can GlobalAlloc your own memory and free it, or store the pointer of an AutoHotkey Buffer Object). You seem to collect the windows in an array in your script, but you also tend to use Hwnd := WinGetID('A'). I highly suggest sticking to one model or the other: (1) You collect every hwnd in some dictionary (2) Each window is self-contained with a buffer holding all of its important values.

(To add on to (2), every window can hold an AutoHotkey object as well: You can use the functions ObjPtr() and ObjAddRef() to store the pointer in GWLP_Userdata and recover the autohotkey object with ObjFromPtr. Then in WM_Destroy you can check for the AutoHotkey object, and manually call ObjRelease())

Also if you do figure out exactly why my window creation works and tic's does not, please share! I'm very interested knowing, as the code for manual window creation is honestly very difficult to understand and is much too long.

The guiSnips map was only recently added when I added a feature to show/hide all snips, so I had to keep track of their Hwnd. I was trying to keep all the function very modular with no globals but that is proving more difficult. Now that I am keeping track of the Hwnd, I might as well keep track of other things like Area. I did create a version where I kept all the bitmaps and only disposed of them when the window was destroyed. It worked but was pretty memory intensive to keep basically an extra copy in memory.

Still a mystery with why gui created with a bitmap manipulated by gdip library ends up flawed and a bitmap cannot be then produced from PrintWindow.

Something like this works:

Code: Select all

	guiObj := Gui('-Caption +E0x80000 +E0x02000000 +AlwaysOnTop +OwnDialogs', 'SnipperWindow')
	guiObj.MarginX := 0, guiObj.MarginY := 0
	pBitmap := GDIp.BitmapFromScreen(Area)
	hBitmap := GDIp.CreateHBITMAPFromBitmap(pBitmap)
	guiObjPic := guiObj.Add('Picture', , 'HBITMAP:' hBitmap)
	guiObj.Show()

But if I try to create a graphic and then draw an image on to it, then get a hBitmap back and put in the Picture then it ends up defective.

I can pull a pBitmap from somewhere and put it in the gui but I cannot create a bitmap like a simple box drawing with Gdip and put in the gui. I can only create defective pBitmap that end up as solid white rectangles.

There is like 5 steps: hbm, hdc, obm, pGraphics and the Drawing. One of those steps appears to introduce a problem.

Even if I just do this:

Code: Select all

	DllCall("gdiplus\GdipGetImageGraphicsContext", "UPtr", pBitmap, "UPtr*", &pGraphics := 0)
	DllCall("gdiplus\GdipCreateBitmapFromGraphics", "int", Width, "int", Height, "UPtr", pGraphics, "UPtr*", &pBitmap := 0)
to convert the pBitmap to a pGraphics and then back, the pBitmap is now flawed.

Your knowledge is greater than mine, if you can figure it out that would be greatly appreciated.

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: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

19 Apr 2023, 02:54

guest3456 wrote:
19 Apr 2023, 01:09
iseahound wrote:
17 Apr 2023, 20:00
You need to use something like this:
since he's already using GDIP, he can just migrate the GdipBitmapFromHwnd() func, which calls PrintWindow. just need to add that -3 flag which the gdip lib omits

Man, I am really confused now. I just tried GdipBitmapFromHwnd() on an older computer because everyone just keeps saying to just use the flag on PrintWindow.

And it worked. After I deep dived for two days when it would not work on my main computer. I was very familiar with the flags after googling every version under the sun of getting a bitmap by hwnd from a layered window.

@iseahoud was getting the same white rectangle problem as me.

One is Windows 10, the other is Windows 11 that I try to keep everything totally up to date. The total up to date computer is the one having problems. So at least I got a totally different angle to look at this problem from.

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

19 Apr 2023, 04:11

Version 2023 04 15 (main) is not working on Windows 7.

Error at line 77. (Call to non existant function)

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

Error at line 185. (this setting is not supported by win 7)

I fixed the error with A_OSVersion


if instr(A_OSVersion, "6.1") ; win 7
WinSetTransparent(Transparent, guiSSR)
else
WinSetTransparent(Transparent, guiSSR.Background)



The lite version works fine.
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: Snipper - Window Snipping Tool

19 Apr 2023, 05:32

iseahound wrote:
17 Apr 2023, 20:00
Yeah same issue here. The images when I save to file are all cropped with black borders...
I work around the problem by creating another GUI that shows where the selected snip is.
You can destroy it by clicking on it. I think it would be possible to use the same idea to create external buttons.


[PLUGIN] Show Selected

how to install this plugin ?
1° create a "Lib" folder in the script directory and put the plugin "ShowSelected.ahk" in it
2° Edit main script "snipper.ahk" and insert this line #Include <ShowSelected> in the ';; AUTO-EXECUTE section' of the script.
3° insert try ShowSelected(false) in the CloseSnip() funtion of the main script to close the sticky gui.
Spoiler

file name: ShowSelected.ahk

Code: Select all

/*
;--------------------------------------------------------------------------------------------------------------------
[Plugin] Show Selected (by speedmaster)
usage: this plugin create a sticky gui to show selected snip image.
File Name : ShowSelected.ahk 
version: 2023 04 19 .001
topic: https://www.autohotkey.com/boards/viewtopic.php?f=83&t=115622
Help:
how to install this plugin ?
1° create a "Lib" folder in the script directory and put the plugin "ShowSelected.ahk" in it
2° Edit main script "snipper.ahk" and insert this line '#Include <ShowSelected>' in the ';; AUTO-EXECUTE section' of the script.
3° insert "try ShowSelected(false)" in the  "CloseSnip()" funtion of the main script to close the sticky gui.

CloseSnip()
{
	Hwnd := WinGetID('A')
	WinClose('A')
	try ShowSelected(false)
}

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

~LButton::ShowSelected()


ShowSelected(show:=true, color:="lime")
{
sleep 50
try Hwnd := WinGetID('A')

	static mygui
	try mygui.destroy()
if show
	mygui:=Gui('+AlwaysOnTop -caption +Border +ToolWindow -DPIScale')
try MyGui.BackColor := color

ohwnd := WinGetList('SnipperWindow ahk_class AutoHotkeyGUI',,,)
	Loop ohwnd.Length
	{
		try if (ohwnd[A_Index]=hwnd) {
			WinGetPos(&X, &Y, &W, &H, ohwnd[A_Index])
			mygui.show( "x" x-1 "  y" y-6 " w" 25 " h" 3 " Na")
		}
		else 
			WinGetPos(&X, &Y, &W, &H, ohwnd[A_Index])
	}
}
Regards


[Mod edit: Fixed quote tags.]
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Snipper - Window Snipping Tool

19 Apr 2023, 11:30

FanaticGuru wrote:
19 Apr 2023, 02:54
guest3456 wrote:
19 Apr 2023, 01:09
iseahound wrote:
17 Apr 2023, 20:00
You need to use something like this:
since he's already using GDIP, he can just migrate the GdipBitmapFromHwnd() func, which calls PrintWindow. just need to add that -3 flag which the gdip lib omits

Man, I am really confused now. I just tried GdipBitmapFromHwnd() on an older computer because everyone just keeps saying to just use the flag on PrintWindow.

And it worked. After I deep dived for two days when it would not work on my main computer. I was very familiar with the flags after googling every version under the sun of getting a bitmap by hwnd from a layered window.

@iseahoud was getting the same white rectangle problem as me.

One is Windows 10, the other is Windows 11 that I try to keep everything totally up to date. The total up to date computer is the one having problems. So at least I got a totally different angle to look at this problem from.

FG
the PW_RENDERFULLCONTENT flag was introduced on win8.1, and it usually only comes into play on hardware accelerated windows, such as those with Chrome based window classes (like Chrome itself and ElectronJS apps like Skype). on many normal windows, it has no effect and is not needed, if i remember correctly. but still, there are some game windows that it doenst work on, and maybe its related to how the window is created as iseahound is alluding to

iseahound
Posts: 1456
Joined: 13 Aug 2016, 21:04
Contact:

Re: Snipper - Window Snipping Tool

19 Apr 2023, 12:21

I double checked tic's Gdip_BitmapFromHWND and it does not work either. I just unwrapped the Gdip calls into the DllCall form, and added some code to allow multi-monitor setups, restrict to the client area, and added the 3 flag to work with hardware accelerated windows.

So it's still a mystery why the AHK gdip windows turn white and freeze huh?
FanaticGuru wrote:
19 Apr 2023, 01:59
Even if I just do this:

Code: Select all

	DllCall("gdiplus\GdipGetImageGraphicsContext", "UPtr", pBitmap, "UPtr*", &pGraphics := 0)
	DllCall("gdiplus\GdipCreateBitmapFromGraphics", "int", Width, "int", Height, "UPtr", pGraphics, "UPtr*", &pBitmap := 0)
to convert the pBitmap to a pGraphics and then back, the pBitmap is now flawed.
Q: Can a DC or a BITMAP be retrieved from a Graphics object?
A: Yes, but they will be write-only as a graphics object is write-only. Any drawn pixels cannot be retrieved.

https://github.com/iseahound/ImagePut/wiki/Engineering-Challenges-Q&A#q-can-a-dc-or-a-bitmap-be-retrieved-from-a-graphics-object
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

19 Apr 2023, 13:40

guest3456 wrote:
19 Apr 2023, 11:30
the PW_RENDERFULLCONTENT flag was introduced on win8.1, and it usually only comes into play on hardware accelerated windows, such as those with Chrome based window classes (like Chrome itself and ElectronJS apps like Skype). on many normal windows, it has no effect and is not needed, if i remember correctly. but still, there are some game windows that it doenst work on, and maybe its related to how the window is created as iseahound is alluding to

Here is an example of the problem. Scripts works as expected on my older Windows 10 computer but fails on my totally up-to-date Windows 11 computer.

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)
F12::
{
	Hwnd := WinExist('A')
	pBitmap := Gdip_BitmapFromHWND(Hwnd)
}

; gdi+ ahk tutorial 1 written by tic (Tariq Porter)
; Requires Gdip.ahk either in your Lib folder as standard library or using #Include
;
; Tutorial to draw a single ellipse and rectangle to the screen

#SingleInstance Force
;#NoEnv
;SetBatchLines -1

; Uncomment if Gdip.ahk is not in your standard library
; #Include ../Gdip_All.ahk

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

; Set the width and height we want as our drawing area, to draw everything in. This will be the dimensions of our bitmap
Width :=1400, Height := 1050

; Create a layered window (+E0x80000 : must be used for UpdateLayeredWindow to work!) that is always on top (+AlwaysOnTop), has no taskbar entry or caption
;AHK v1
;Gui, 1: -Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs
;Gui, 1: Show, NA
; Gui1 := Gui("-Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs")
Gui1 := Gui("-Caption +E0x80000 +LastFound")
Gui1.Show("NA")

; Get a handle to this window we have created in order to update it later
hwnd1 := WinExist()

; Create a gdi bitmap with width and height of what we are going to draw into it. This is the entire drawing area for everything
hbm := CreateDIBSection(Width, Height)

; Get a device context compatible with the screen
hdc := CreateCompatibleDC()

; Select the bitmap into the device context
obm := SelectObject(hdc, hbm)

; Get a pointer to the graphics of the bitmap, for use with drawing functions
G := Gdip_GraphicsFromHDC(hdc)

; Set the smoothing mode to antialias = 4 to make shapes appear smother (only used for vector drawing and filling)
Gdip_SetSmoothingMode(G, 4)

; Create a fully opaque red brush (ARGB = Transparency, red, green, blue) to draw a circle
pBrush := Gdip_BrushCreateSolid(0xffff0000)

; Fill the graphics of the bitmap with an ellipse using the brush created
; Filling from coordinates (100,50) an ellipse of 200x300
Gdip_FillEllipse(G, pBrush, 100, 500, 200, 300)

; Delete the brush as it is no longer needed and wastes memory
Gdip_DeleteBrush(pBrush)

; Create a slightly transparent (66) blue brush (ARGB = Transparency, red, green, blue) to draw a rectangle
pBrush := Gdip_BrushCreateSolid(0x660000ff)

; Fill the graphics of the bitmap with a rectangle using the brush created
; Filling from coordinates (250,80) a rectangle of 300x200
Gdip_FillRectangle(G, pBrush, 250, 80, 300, 200)

; Delete the brush as it is no longer needed and wastes memory
Gdip_DeleteBrush(pBrush)

; Update the specified window we have created (hwnd1) with a handle to our bitmap (hdc), specifying the x,y,w,h we want it positioned on our screen
; So this will position our gui at (0,0) with the Width and Height specified earlier
UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)


; Select the object back into the hdc
SelectObject(hdc, obm)

; Now the bitmap may be deleted
DeleteObject(hbm)

; Also the device context related to the bitmap may be deleted
DeleteDC(hdc)

; The graphics may now be deleted
Gdip_DeleteGraphics(G)
Return

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

ExitFunc(ExitReason, ExitCode)
{
   global
   ; gdi+ may now be shutdown on exiting the program
   Gdip_Shutdown(pToken)
}

;#######################################################################
I use the library from: https://github.com/marius-sucan/AHK-GDIp-Library-Compilation/tree/master/ahk-v2
Which seems to be about the most up-to-date Gdip for current v2. Still has a few flaws but has the PW_RENDERFULLCONTENT flag for PrintWindow.

Standard example script with added hotkey at top. https://github.com/marius-sucan/AHK-GDIp-Library-Compilation/blob/master/ahk-v2/Examples-ahk-v2/Gdip.Tutorial.1-Draw.Shapes.ahk

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.

If would be nice to know if this script works for other people and what version of Windows is running. I know way back 7 and 8 major things have changed but 10 to 11 seems strange.

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

19 Apr 2023, 16:24

That version of gdi+ hasn't been updated in ages.

Does the show window script I posted still work?

Code: Select all

#Requires AutoHotkey v2.0


         DllCall("LoadLibrary", "str", "gdiplus")
         si := Buffer(A_PtrSize = 4 ? 16:24, 0) ; sizeof(GdiplusStartupInput) = 16, 24
            NumPut("uint", 0x1, si)
         DllCall("gdiplus\GdiplusStartup", "ptr*", &pToken:=0, "ptr", si, "ptr", 0)



show(from_screenshot([0, 0, 100, 100]))


   show(pBitmap, title := "", pos := "", style := 0x90000000, styleEx := 0x80088, parent := "") {
      ; Window Styles - https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
      WS_POPUP                  := 0x80000000   ; Allow small windows.
      WS_VISIBLE                := 0x10000000   ; Show on creation.

      ; Extended Window Styles - https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
      WS_EX_TOPMOST             :=        0x8   ; Always on top.
      WS_EX_TOOLWINDOW          :=       0x80   ; Hides from Alt+Tab menu. Removes small icon.
      WS_EX_LAYERED             :=    0x80000   ; For UpdateLayeredWindow.

      ; Default styles can be overwritten by previous functions.
      (style == "") && style := WS_POPUP | WS_VISIBLE
      (styleEx == "") && styleEx := WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED

      ; Get Bitmap width and height.
      DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", &width:=0)
      DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", &height:=0)

      ; Get Screen width and height with DPI awareness.
      dpi := DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
      ScreenWidth := A_ScreenWidth
      ScreenHeight := A_ScreenHeight
      DllCall("SetThreadDpiAwarenessContext", "ptr", dpi, "ptr")

      ; If both dimensions exceed the screen boundaries, compare the aspect ratio of the image
      ; to the aspect ratio of the screen to determine the scale factor. Default scale is 1.
      s  := (width > ScreenWidth) && (width / height > ScreenWidth / ScreenHeight) ? ScreenWidth / width
         : (height > ScreenHeight) && (width / height <= ScreenWidth / ScreenHeight) ? ScreenHeight / height
         : 1

      w  := IsObject(pos) && pos.Has(3) ? pos[3] : s * width
      h  := IsObject(pos) && pos.Has(4) ? pos[4] : s * height

      x  := IsObject(pos) && pos.Has(1) ? pos[1] : 0.5*(ScreenWidth - w)
      y  := IsObject(pos) && pos.Has(2) ? pos[2] : 0.5*(ScreenHeight - h)

      ; Resolve dependent coordinates first, coordinates second, and distances last.
      x2 := Round(x + w)
      y2 := Round(y + h)
      x  := Round(x)
      y  := Round(y)
      w  := x2 - x
      h  := y2 - y

      ; Convert the source pBitmap into a hBitmap manually.
      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      bi := Buffer(40, 0)                    ; sizeof(bi) = 40
         NumPut(  "uint",        40, bi,  0) ; Size
         NumPut(   "int",         w, bi,  4) ; Width
         NumPut(   "int",        -h, bi,  8) ; Height - Negative so (0, 0) is top-left.
         NumPut("ushort",         1, bi, 12) ; Planes
         NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")

      ; Case 1: Image is not scaled.
      if (s = 1) {
         ; Transfer data from source pBitmap to an hBitmap manually.
         Rect := Buffer(16, 0)                  ; sizeof(Rect) = 16
            NumPut(  "uint",   width, Rect,  8) ; Width
            NumPut(  "uint",  height, Rect, 12) ; Height
         BitmapData := Buffer(16+2*A_PtrSize, 0)         ; sizeof(BitmapData) = 24, 32
            NumPut(   "int",  4 * width, BitmapData,  8) ; Stride
            NumPut(   "ptr",      pBits, BitmapData, 16) ; Scan0
         DllCall("gdiplus\GdipBitmapLockBits"
                  ,    "ptr", pBitmap
                  ,    "ptr", Rect
                  ,   "uint", 5            ; ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly
                  ,    "int", 0xE200B      ; Format32bppPArgb
                  ,    "ptr", BitmapData)  ; Contains the pointer (pBits) to the hbm.
         DllCall("gdiplus\GdipBitmapUnlockBits", "ptr", pBitmap, "ptr", BitmapData)
      }

      ; Case 2: Image is scaled.
      else {
         ; Create a graphics context from the device context.
         DllCall("gdiplus\GdipCreateFromHDC", "ptr", hdc , "ptr*", &pGraphics:=0)

         ; Set settings in graphics context.
         DllCall("gdiplus\GdipSetPixelOffsetMode",    "ptr", pGraphics, "int", 2) ; Half pixel offset.
         DllCall("gdiplus\GdipSetCompositingMode",    "ptr", pGraphics, "int", 1) ; Overwrite/SourceCopy.
         DllCall("gdiplus\GdipSetInterpolationMode",  "ptr", pGraphics, "int", 7) ; HighQualityBicubic

         ; Draw Image.
         DllCall("gdiplus\GdipCreateImageAttributes", "ptr*", &ImageAttr:=0)
         DllCall("gdiplus\GdipSetImageAttributesWrapMode", "ptr", ImageAttr, "int", 3) ; WrapModeTileFlipXY
         DllCall("gdiplus\GdipDrawImageRectRectI"
                  ,    "ptr", pGraphics
                  ,    "ptr", pBitmap
                  ,    "int", 0, "int", 0, "int", w,     "int", h      ; destination rectangle
                  ,    "int", 0, "int", 0, "int", width, "int", height ; source rectangle
                  ,    "int", 2
                  ,    "ptr", ImageAttr
                  ,    "ptr", 0
                  ,    "ptr", 0)
         DllCall("gdiplus\GdipDisposeImageAttributes", "ptr", ImageAttr)

         ; Clean up the graphics context.
         DllCall("gdiplus\GdipDeleteGraphics", "ptr", pGraphics)
      }

      dpi := DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
      hwnd := DllCall("CreateWindowEx"
               ,   "uint", styleEx | WS_EX_LAYERED  ; dwExStyle
               ,    "str", WindowClass()       ; lpClassName
               ,    "str", title                    ; lpWindowName
               ,   "uint", style                    ; dwStyle
               ,    "int", x
               ,    "int", y
               ,    "int", w
               ,    "int", h
               ,    "ptr", (parent != "") ? parent : A_ScriptHwnd
               ,    "ptr", 0                        ; hMenu
               ,    "ptr", 0                        ; hInstance
               ,    "ptr", 0                        ; lpParam
               ,    "ptr")
      DllCall("SetThreadDpiAwarenessContext", "ptr", dpi, "ptr")

      ; Draw the contents of the device context onto the layered window.
      DllCall("UpdateLayeredWindow"
               ,    "ptr", hwnd                     ; hWnd
               ,    "ptr", 0                        ; hdcDst
               ,    "ptr", 0                        ; *pptDst
               ,"uint64*", w | h << 32              ; *psize
               ,    "ptr", hdc                      ; hdcSrc
               , "int64*", 0                        ; *pptSrc
               ,   "uint", 0                        ; crKey
               ,  "uint*", 0xFF << 16 | 0x01 << 24  ; *pblend
               ,   "uint", 2)                       ; dwFlags

      ; Cleanup the hBitmap and device contexts.
      DllCall("SelectObject", "ptr", hdc, "ptr", obm)
      DllCall("DeleteObject", "ptr", hbm)
      DllCall("DeleteDC",     "ptr", hdc)

      return hwnd
   }

   WindowClass(style := 0) {
      ; The window class shares the name of this class.
      cls := "iseahound's window code"
      wc := Buffer(A_PtrSize = 4 ? 48:80) ; sizeof(WNDCLASSEX) = 48, 80

      ; Check if the window class is already registered.
      hInstance := DllCall("GetModuleHandle", "ptr", 0, "ptr")
      if DllCall("GetClassInfoEx", "ptr", hInstance, "str", cls, "ptr", wc)
         return cls

      ; Create window data.
      pWndProc := CallbackCreate(WindowProc)
      hCursor := DllCall("LoadCursor", "ptr", 0, "ptr", 32512, "ptr") ; IDC_ARROW
      hBrush := DllCall("GetStockObject", "int", 5, "ptr") ; Hollow_brush

      ; struct tagWNDCLASSEXA - https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
      ; struct tagWNDCLASSEXW - https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexw
      _ := (A_PtrSize = 4)
         NumPut(  "uint",     wc.size, wc,         0) ; cbSize
         NumPut(  "uint",       style, wc,         4) ; style
         NumPut(   "ptr",    pWndProc, wc,         8) ; lpfnWndProc
         NumPut(   "int",           0, wc, _ ? 12:16) ; cbClsExtra
         NumPut(   "int",          40, wc, _ ? 16:20) ; cbWndExtra
         NumPut(   "ptr",           0, wc, _ ? 20:24) ; hInstance
         NumPut(   "ptr",           0, wc, _ ? 24:32) ; hIcon
         NumPut(   "ptr",     hCursor, wc, _ ? 28:40) ; hCursor
         NumPut(   "ptr",      hBrush, wc, _ ? 32:48) ; hbrBackground
         NumPut(   "ptr",           0, wc, _ ? 36:56) ; lpszMenuName
         NumPut(   "ptr", StrPtr(cls), wc, _ ? 40:64) ; lpszClassName
         NumPut(   "ptr",           0, wc, _ ? 44:72) ; hIconSm

      ; Registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function.
      DllCall("RegisterClassEx", "ptr", wc, "ushort")

      ; Return the class name as a string.
      return cls

      ; Define window behavior.
      WindowProc(hwnd, uMsg, wParam, lParam) {

         ; Prevent the script from exiting early.
         static active_windows := Persistent()

         ; WM_CREATE
         if (uMsg = 0x1)
            Persistent(++active_windows)

         ; WM_DESTROY
         if (uMsg = 0x2) {
            Persistent(--active_windows)
         }

         ; WM_LBUTTONDOWN
         if (uMsg = 0x201) {
            return DllCall("DefWindowProc", "ptr", hwnd, "uint", 0xA1, "uptr", 2, "ptr", 0, "ptr")
         }

         ; WM_RBUTTONUP
         if (uMsg = 0x205) {
            DllCall("DestroyWindow", "ptr", hwnd)
            return 0
         }

         ; Must return
         return DllCall("DefWindowProc", "ptr", hwnd, "uint", uMsg, "uptr", wParam, "ptr", lParam, "ptr")
      }
   }


   from_screenshot(image) {
      ; Thanks tic - https://www.autohotkey.com/boards/viewtopic.php?t=6517

      if !IsObject(image) {
         dpi := DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
         WinGetClientPos &x, &y, &w, &h, image
         DllCall("SetThreadDpiAwarenessContext", "ptr", dpi, "ptr")
         image := [x, y, w, h]
      }


      

      ; struct BITMAPINFOHEADER - https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
      hdc := DllCall("CreateCompatibleDC", "ptr", 0, "ptr")
      bi := Buffer(40, 0)                    ; sizeof(bi) = 40
         NumPut(  "uint",        40, bi,  0) ; Size
         NumPut(   "int",  image[3], bi,  4) ; Width
         NumPut(   "int", -image[4], bi,  8) ; Height - Negative so (0, 0) is top-left.
         NumPut("ushort",         1, bi, 12) ; Planes
         NumPut("ushort",        32, bi, 14) ; BitCount / BitsPerPixel
      hbm := DllCall("CreateDIBSection", "ptr", hdc, "ptr", bi, "uint", 0, "ptr*", &pBits:=0, "ptr", 0, "uint", 0, "ptr")
      obm := DllCall("SelectObject", "ptr", hdc, "ptr", hbm, "ptr")

      ; Retrieve the device context for the screen.
      sdc := DllCall("GetDC", "ptr", 0, "ptr")

      ; Copies a portion of the screen to a new device context.
      DllCall("gdi32\BitBlt"
               , "ptr", hdc, "int", 0, "int", 0, "int", image[3], "int", image[4]
               , "ptr", sdc, "int", image[1], "int", image[2], "uint", 0x00CC0020 | 0x40000000) ; SRCCOPY | CAPTUREBLT

      ; Release the device context to the screen.
      DllCall("ReleaseDC", "ptr", 0, "ptr", sdc)

      ; Convert the hBitmap to a Bitmap using a built in function as there is no transparency.
      DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "ptr", hbm, "ptr", 0, "ptr*", &pBitmap:=0)

      ; Cleanup the hBitmap and device contexts.
      DllCall("SelectObject", "ptr", hdc, "ptr", obm)
      DllCall("DeleteObject", "ptr", hbm)
      DllCall("DeleteDC",     "ptr", hdc)

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

Re: Snipper - Window Snipping Tool

19 Apr 2023, 17:26

FanaticGuru, why do You want to get screenshot for Your own window with PrintWindow, but not with BitBlt?
PrintWindow with PW_RENDERFULLCONTENT flag on win11 does not work. May be microsoft changed something.
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

19 Apr 2023, 17:40

malcev wrote:
19 Apr 2023, 17:26
FanaticGuru, why do You want to get screenshot for Your own window with PrintWindow, but not with BitBlt?
PrintWindow with PW_RENDERFULLCONTENT flag on win11 does not work. May be microsoft changed something.

Yea, I have about concluded that something changed with PW_RENDERFULLCONTENT and PrintWindow does not work the same in Windows 11.

If you can show me how to get a Bitmap of a Gui window or control, that would be great.

I can't find a good way to get a bitmap from a layered window gui that has something on top of it or is partially off screen. I just want a BitmapFromHWND that actually works. A bitmap I can put on the clipboard, save to a file, draw stuff on top of and then put it back to the gui, or a different gui, etc. All the fun stuff you can do with a bitmap.

Actually I don't even need a BitmapFromHWND, I just need a BitmapFromHWNDofAHK

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

19 Apr 2023, 18:12

Algorithm can be like this::

Code: Select all

hCaptureDC := CreateCompatibleDC(hdc)
hCaptureBitmap := CreateCompatibleBitmap(hdc, width, height)
SelectObject(hCaptureDC, hCaptureBitmap)
BitBlt(hCaptureDC, 0, 0, width, height, hdc, 0, 0)
pBitmap := Gdip_CreateBitmapFromHBITMAP(hCaptureBitmap)
If it is not Your process then I think need to inject and hook UpdateLayeredWindow.
User avatar
FanaticGuru
Posts: 1907
Joined: 30 Sep 2013, 22:25

Re: Snipper - Window Snipping Tool

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
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

19 Apr 2023, 19:29

malcev wrote:
19 Apr 2023, 18:12
Algorithm can be like this::

Code: Select all

hCaptureDC := CreateCompatibleDC(hdc)
hCaptureBitmap := CreateCompatibleBitmap(hdc, width, height)
SelectObject(hCaptureDC, hCaptureBitmap)
BitBlt(hCaptureDC, 0, 0, width, height, hdc, 0, 0)
pBitmap := Gdip_CreateBitmapFromHBITMAP(hCaptureBitmap)

Works for typical Gui but not Gui populated by GDIp.

Code: Select all

F12:: Gui2Clipboard()
Gui2Clipboard(Hwnd?)
{
	Global
	If !IsSet(Hwnd)
		Hwnd := WinGetID('A')
	WinGetPos(&X, &Y, &Width, &Height, 'ahk_id ' Hwnd)

	hdc := GetDC(Hwnd)
	hCaptureDC := CreateCompatibleDC(hdc)
	hCaptureBitmap := CreateCompatibleBitmap(hdc, Width, Height)
	SelectObject(hCaptureDC, hCaptureBitmap)
	BitBlt(hCaptureDC, 0, 0, Width, Height, hdc, 0, 0)
	pBitmap := Gdip_CreateBitmapFromHBITMAP(hCaptureBitmap)

	Gdip_SetBitmapToClipboard(pBitmap)

	Gdip_DisposeImage(pBitmap)
}
Something like this added to tic's example scripts will only produce a black rectangle on the clipboard. Other Gui are put on the clipboard fine.

Once UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height) is done, something about the nature of the Gui changes. Or it could just be gui with the +E0x00080000 flag althought gui with +E0x02000000 +E0x00080000 are clipped fine but UpdateLayeredWindow does not work on those windows for drawing on them.

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

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.
buliasz
Posts: 26
Joined: 10 Oct 2016, 14:31
Contact:

Re: Snipper - Window Snipping Tool

19 Apr 2023, 20:01

FanaticGuru wrote:
19 Apr 2023, 18:15
I really wish I could find a good GDIp library for v2, especially a class or object based one.
...
@buliasz v2 is years old.
I have just pushed changes that I made since last version in case you'd like to use it. It is compatible with AHK v2.0.2 but be aware that I didn't verify all the functions there. It is not showing any error but I only use a subset of functions for my needs. Honestly I created this fork only because on that time when I was creating it there were no version that would run with an updated AHK v2 beta.
BTW I like the idea of making it objective. Should be easy to do. Let me know if you'd need a help with that.
Last edited by buliasz on 20 Apr 2023, 07:08, edited 1 time in total.

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: No registered users and 26 guests