hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

Post your working scripts, libraries and tools
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

06 Sep 2019, 09:40

hWnd_to_hBmp( hWnd, Client, Array )
Returns hBitmap when capture was successful.
Returns 0 for invalid hWnd or if window is in minimized/hidden state.

hWnd : Handle to any window/control that is visible. Windows shouldn't be in minimized state. -1 will result in a fullscreen capture.
Client: When true, only the client area of the Window/Control is captured. Does not apply for fullscreen capture.
Array: User defined values for X, Y, Width, Height either as array [0,0,640,480] or as a string "0,0,640,480"
Cursor: Plugin function to draw custom/global cursor into the result, (Yet to be implemented)

Sample calls


The function with simple example:
Example requires SavePicture() and GDIP()
Thanks to @iPhilip. BitBlt ROP fixed.

Code: Select all

hWnd_to_hBmp( hWnd:=-1, Client:=0, A:="", C:="" ) {      ; By SKAN C/M on D295|D299 @ bit.ly/2lyG0sN

; Capture fullscreen, Window, Control or user defined area of these

  A      := IsObject(A) ? A : StrLen(A) ? StrSplit( A, ",", A_Space ) : {},     A.tBM := 0
  Client := ( ( A.FS := hWnd=-1 ) ? False : !!Client ), A.DrawCursor := "DrawCursor"
  hWnd   := ( A.FS ? DllCall( "GetDesktopWindow", "UPtr" ) : WinExist( "ahk_id" . hWnd ) )

  A.SetCapacity( "WINDOWINFO", 62 ),  A.Ptr := A.GetAddress( "WINDOWINFO" ) 
  A.RECT := NumPut( 62, A.Ptr, "UInt" ) + ( Client*16 )  

  If  ( DllCall( "GetWindowInfo",   "Ptr",hWnd, "Ptr",A.Ptr )
    &&  DllCall( "IsWindowVisible", "Ptr",hWnd )
    &&  DllCall( "IsIconic",        "Ptr",hWnd ) = 0 ) 
    {
        A.L := NumGet( A.RECT+ 0, "Int" ),     A.X := ( A.1 <> "" ? A.1 : (A.FS ? A.L : 0) )  
        A.T := NumGet( A.RECT+ 4, "Int" ),     A.Y := ( A.2 <> "" ? A.2 : (A.FS ? A.T : 0 )) 
        A.R := NumGet( A.RECT+ 8, "Int" ),     A.W := ( A.3  >  0 ? A.3 : (A.R - A.L - Round(A.1)) ) 
        A.B := NumGet( A.RECT+12, "Int" ),     A.H := ( A.4  >  0 ? A.4 : (A.B - A.T - Round(A.2)) )
        
        A.sDC := DllCall( Client ? "GetDC" : "GetWindowDC", "Ptr",hWnd, "UPtr" )
        A.mDC := DllCall( "CreateCompatibleDC", "Ptr",A.sDC, "UPtr")
        A.tBM := DllCall( "CreateCompatibleBitmap", "Ptr",A.sDC, "Int",A.W, "Int",A.H, "UPtr" )

        DllCall( "SaveDC", "Ptr",A.mDC )
        DllCall( "SelectObject", "Ptr",A.mDC, "Ptr",A.tBM )
        DllCall( "BitBlt",       "Ptr",A.mDC, "Int",0,   "Int",0, "Int",A.W, "Int",A.H
                               , "Ptr",A.sDC, "Int",A.X, "Int",A.Y, "UInt",0x40CC0020 )  

        A.R := ( IsObject(C) || StrLen(C) ) && IsFunc( A.DrawCursor ) ? A.DrawCursor( A.mDC, C ) : 0
        DllCall( "RestoreDC", "Ptr",A.mDC, "Int",-1 )
        DllCall( "DeleteDC",  "Ptr",A.mDC )   
        DllCall( "ReleaseDC", "Ptr",hWnd, "Ptr",A.sDC )
    }        
Return A.tBM
}
:)
iPhilip
Posts: 413
Joined: 02 Oct 2013, 12:21

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

06 Sep 2019, 18:21

Hi @SKAN,

Thank you for this function. I did some testing with the following hotkeys and it generally works. The one issue I found is that, when capturing a single window, it suffers from Aero effects. It is most notable with Explorer windows. When capturing the full screen, I don't see any side effects. As a suggestion, you might want to add the CAPTUREBLT = 0x40000000 flag to the BitBlt DllCall to deal with translucent/alpha/layered windows.

Code: Select all

F11::  ; Suffers from Aero effects
   File := A_Now ".jpg"
   hBM := hWnd_to_hBmp(WinExist("A"))  ; Capture active window
   GDIP("Startup")
   SavePicture(hBM, File)
   GDIP("Shutdown")
   DllCall("DeleteObject", "Ptr", hBM)
   Run %File%
Return

F12::
   File := A_Now ".jpg"
   hBM := hWnd_to_hBmp()  ; Capture fullscreen
   GDIP("Startup")
   SavePicture(hBM, File)
   GDIP("Shutdown")
   DllCall("DeleteObject", "Ptr", hBM)
   Run %File%
Return
Finally, the following code does the same as the above but without any side effects from Aero. It requires the Gdip library.

Code: Select all

^F11::
   File := A_Now ".jpg"
   Capture("Active Window", File)
   Run %File%
Return

^F12::
   File := A_Now ".jpg"
   Capture("Fullscreen", File)
   Run %File%
Return

Capture(What, File) {  ; Capture active window or fullscreen
   ClipboardBackup := ClipboardAll
   Clipboard := ""
   SendInput, % (SubStr(What,1,1) = "A" ? "!" : "") "{PrintScreen}"
   ClipWait, 1, 1
   pToken  := Gdip_Startup()
   pBitmap := Gdip_CreateBitmapFromClipboard()
   if ClipboardBackup
      Clipboard := ClipboardBackup
   Gdip_SaveBitmapToFile(pBitmap, File)
   Gdip_DisposeImage(pBitmap)
   Gdip_Shutdown(pToken)
}
Cheers!

- iPhilip
Last edited by iPhilip on 09 Sep 2019, 10:14, edited 1 time in total.
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

06 Sep 2019, 19:19

Hi @iPhilip

Thanks for testing and the valuable feedback.
Initially I put an additional parameter for bitblt ROP but later removed it.
I had tested it with Notepad and Calculator. but not Explorer.
I'll investigate and amend the code.

Thanks again.
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

09 Sep 2019, 14:37

iPhilip wrote: The one issue I found is that, when capturing a single window, it suffers from Aero effects. It is most notable with Explorer windows.
I tested. Indeed there seems to be no solution. I also referred Sean's wonderful ScreenCapture.ahk
He just gets DC to entire desktop and blits only the relevant part as per function's parameters.
The same can be achieved as follows

Code: Select all

WinGetPos, X, Y, W, H, A
Array := [X,Y,W,H]
hBM := hWnd_to_hBmp( -1, False, Array ) 
iPhilip wrote: As a suggestion, you might want to add the CAPTUREBLT = 0x40000000 flag to the BitBlt DllCall to deal with translucent/alpha/layered windows.
Thank you.. Fixed.
I overlooked CAPTUREBLT as I was only testing it with AHK GUI and its controls, mainly ActiveX control (for my HTML to PNG needs).

iPhilip wrote: following code does the same as the above but without any side effects from Aero. It requires the Gdip library.
Thanks for sharing.
Inspired by you, I've posted my standalone version @
SavePicture() : Save hBitmap as BMP, JPG, GIF, PNG, TIF
(See the last spoiler)

:)
User avatar
cyruz
Posts: 295
Joined: 30 Sep 2013, 13:31

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

10 Sep 2019, 03:58

Hello SKAN,

missed your code format style a lot :D :D

Regarding this function, I experimented with this stuff and looks like the only clean way to have a screenshot of a window it's the PrintWindow function. Using the screen DC it's a solution but you must put the windows in foreground before taking the screenshot.

There are other considerations: one is related to window coordinates (the composition effect around the window will be included in the screenshot) and another is related to the performance (bit block transfers with composition enabled are considerably slower than with composition disabled).
ABCza on the old forum.
My GitHub.
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

11 Sep 2019, 02:52

Hi @cyruz :)
Regarding this function, I experimented with this stuff and looks like the only clean way to have a screenshot of a window it's the PrintWindow function.
At least, Explorer windows are a problem. Here are screenshots for comparison. (I wrote standalone version of PrintWindow() and will post soon.)
Note how caption buttons differ and missing data around address bar in PrintWindow() capture.
  • Image
Using the screen DC it's a solution but you must put the windows in foreground before taking the screenshot.
Yes.. and too add more, the view of foreground window shouldn't be obstructed by a always-on-top window (like AHK SPY).
This is the exact result you can expect with Alt+PrintScreen shortcut.
There are other considerations: one is related to window coordinates (the composition effect around the window will be included in the screenshot) and another is related to the performance (bit block transfers with composition enabled are considerably slower than with composition disabled).
I've heard about it in our forum.. but, I don't see the effect in above posted Screenshot. I'm only using WinGetPos.
From what I've read, PrintWindow() is slower, but I have to test to confirm.

:)
User avatar
cyruz
Posts: 295
Joined: 30 Sep 2013, 13:31

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

13 Sep 2019, 07:01

SKAN wrote:
11 Sep 2019, 02:52
...

I'm trying on Windows 10 and actually almost no part of the window get screenshotted correctly, moreover there are some issues regarding window size:


Image


On Windows 10 composition cannot be disabled so it should be taken into account when creating the screenshot.

I'm having a little more success with this code, but it still suffers from composition artifacts on Windows 10. It works well instead on Windows 7 with composition disabled (please don't mind all the commas, the code should be run in a loop, so I'm trying to squeeze out any little bit of performance):

Code: Select all

    #SingleInstance force

	bNC := True
    WinGet, hWnd, ID, ahk_class CabinetWClass

    ; *** Gdi32.dll and User32.dll are loaded by default by the interpreter.
	
	; *** Coordinates must be adjusted for the StretchBlt operation, because it considers the top-left corner as 0-0.
	; *** If the composition is enabled, 0-0 is the top-left corner starting from the composition effect,
	; *** otherwise with composition disabled, 0-0 is the top-left corner of the NON-CLIENT area.
	; *** We don't want the composition to be part of the screenshot so we have to fix the coordinates.

	If ( bNC )
	{
	    ; *** For the NON-CLIENT area we have 2 possible cases: with or without composition.
		; *** GetWindowRect gets the window screen cordinates, including the composition effect.
		; *** DwmGetWindowAttribute gets the window screen coordinates, excluding the composition effect.
        ; *** We use first GetWindowRect, fixing X-Y to 0-0, calculating W-H and saving our top-left corner values.
	    ; *** Then we run DwmGetWindowAttribute, that works only if composition is enabled, recalculating our W-H
		; *** and shifting down X-Y coordinates removing the values of the top-left corner we got before.

		VarSetCapacity(_RECT, 16, 0)
      , DllCall("GetWindowRect", "Ptr",hWnd, "Ptr",&_RECT)
	  , bltX := bltY := 0
	  , bltW := NumGet(_RECT,  8, "Int") - (bltL := NumGet(_RECT, 0, "Int"))
	  , bltH := NumGet(_RECT, 12, "Int") - (bltT := NumGet(_RECT, 4, "Int"))
		
		If ( !DllCall("dwmapi\DwmGetWindowAttribute", Ptr,hWnd, UInt,9, Ptr,&_RECT, UInt,16) )	  
		    bltW := NumGet(_RECT,  8, "Int") - NumGet(_RECT, 0, "Int")
		  , bltH := NumGet(_RECT, 12, "Int") - NumGet(_RECT, 4, "Int")
	      , bltX := NumGet(_RECT, 0, "Int") - bltL
		  , bltY := NumGet(_RECT, 4, "Int") - bltT

	  
	    VarSetCapacity(_RECT, 0)
	}
	Else
	{
        ; *** For the CLIENT area we perform the shift down of the top-left corner to remove the NON-CLIENT part
	    ; *** and the eventual composition. The same amount must be removed when calculating width and height.
        
		VarSetCapacity(_WINFO, 60, 0), NumPut(60, &_WINFO, 0, "UInt")
	  , DllCall("GetWindowInfo", "Ptr",hWnd, "Ptr",&_WINFO)
		
      , bltX  := NumGet(_WINFO, 20, "Int") - (bltL := NumGet(_WINFO,  4, "Int"))
      , bltY  := NumGet(_WINFO, 24, "Int") - (bltT := NumGet(_WINFO,  8, "Int"))
      , bltW  := NumGet(_WINFO, 28, "Int") - (bltL + bltX)
      , bltH  := NumGet(_WINFO, 32, "Int") - (bltT + bltY)
	  
	  , VarSetCapacity(_WINFO, 0)
	}
   
    msgbox, % bltX " | " bltY " | " bltW " | " bltH
   
    nW := bltW
  , nH := bltH
   
  , hDCsrc := DllCall("GetWindowDC", "Ptr",hWnd, "Ptr")
  , hDCdst := DllCall("CreateCompatibleDC", "Ptr",hDCsrc, "Ptr")
  , hBmp   := DllCall("CreateCompatibleBitmap", "Ptr",hDCsrc, "Int",nW, "Int",nH, "Ptr")
  , oBmp   := DllCall("SelectObject", "Ptr",hDCdst, "Ptr",hBmp, "Ptr")
   
  , DllCall("SetStretchBltMode", "Ptr",hDCdst, "Int",0x04)
  , DllCall("SetBrushOrgEx", "Ptr",hDCdst, "Int",0, "Int",0, "Ptr",0)
  , DllCall("StretchBlt", "Ptr",hDCdst, "Int",0,    "Int",0,    "Int",nW,   "Int",nH
                        , "Ptr",hDCsrc, "Int",bltX, "Int",bltY, "Int",bltW, "Int",bltH 
                        , "UInt",0x00CC0020|0x40000000)

  , DllCall("DeleteObject", "Ptr",oBmp)
  , DllCall("DeleteDC",  "Ptr",hDCdst)
  , DllCall("ReleaseDC", "Ptr",hDCsrc)

	Gui, 1:Add, Picture, w%nW% h%nH%, HBITMAP:%hBmp%
	Gui, 1:Show,, StretchBlt

Image
ABCza on the old forum.
My GitHub.
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

13 Sep 2019, 15:27

cyruz wrote: I'm trying on Windows 10 and actually almost no part of the window get screenshotted correctly, moreover there are some issues regarding window size:
I just tested in Win10.
Everything seems to work fine except trying to capture a WS_POPUP with caption bar.
I now only saw the problem with window pos retrieved with WinGetPos .
I'll try to write a simplified version of WinGetPosEx()
cyruz wrote:On Windows 10 composition cannot be disabled so it should be taken into account when creating the screenshot.
Thanks. It is enabled in Win 7 for me.
This creates an another problem for me. I use a -Caption +Resize GUI to mark screen area for capture.
In windows 7, border size is equal on all sides, but in win10 the top side is more. :(
cyruz wrote:I'm having a little more success with this code, but it still suffers from composition artifacts on Windows 10. It works well instead on Windows 7 with composition disabled (please don't mind all the commas, the code should be run in a loop, so I'm trying to squeeze out any little bit of performance):
Thanks. I'll try StretchBlt instead of BitBlt. I have problems in capturing controls when screen bit-depth is less than 32 BPP.

Thanks again for all the input. :)

PS: I'm rewriting the function to be more efficient.
User avatar
cyruz
Posts: 295
Joined: 30 Sep 2013, 13:31

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

13 Sep 2019, 20:47

SKAN wrote:
13 Sep 2019, 15:27
I just tested in Win10.
Everything seems to work fine except trying to capture a WS_POPUP with caption bar.
This is strange, which Windows 10 version are you using? I'm on 1803 and that's the result :(
ABCza on the old forum.
My GitHub.
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

13 Sep 2019, 21:33

cyruz wrote:This is strange, which Windows 10 version are you using? I'm on 1803 and that's the result :(
I'll let you know next time I boot into it. I guess I installed about 4 years ago.
Never updated it. I have to unplug net before booting into it.
No software installed other than my editor + AHK..
I just use it to test my functions.

BTW, it is possible to disable Window composition per window in Win 7 / Wn 10.
But capture works only in Win 7 ( exact PrintWindow() output. )
Win 10 produces a black/blank image.

Code: Select all

DllCall("dwmapi\DwmSetWindowAttribute","Ptr",hwnd, "UInt",2, "PtrP",1, "UInt",4) ; Disable
Sleep 500 ; Allow time for window redraw
; capture screen
DllCall("dwmapi\DwmSetWindowAttribute","Ptr",hWnd, "Uint",2, "PtrP",0, "UInt",4 ) ; Enable
Ref: Code by Lexikos
User avatar
cyruz
Posts: 295
Joined: 30 Sep 2013, 13:31

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

13 Sep 2019, 22:26

SKAN wrote:
13 Sep 2019, 21:33
BTW, it is possible to disable Window composition per window in Win 7 / Wn 10.
But capture works only in Win 7 ( exact PrintWindow() output. )
Win 10 produces a black/blank image.

Code: Select all

DllCall("dwmapi\DwmSetWindowAttribute","Ptr",hwnd, "UInt",2, "PtrP",1, "UInt",4) ; Disable
Sleep 500 ; Allow time for window redraw
; capture screen
DllCall("dwmapi\DwmSetWindowAttribute","Ptr",hWnd, "Uint",2, "PtrP",0, "UInt",4 ) ; Enable
Ref: Code by Lexikos

Yeah tried that. If you disable the composition for a specific window on Windows 10, the window part that used the composition starts flickering.

Finding a clean and performing cross-platform solution is really difficult. I wonder why they didn't introduce some dedicated functions in the dwm api (like they did for the thumbnail preview feature).
ABCza on the old forum.
My GitHub.
kkleinfelter
Posts: 18
Joined: 21 Dec 2018, 10:59

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

18 Sep 2019, 14:24

OK. I think I've got a similar, but not identical problem. I understand the Aero changes to the title bar and the width of the shadow around the window accounts for the narrow black stripe, but when I turn off the Address Bar in IE, I get a *big* black stripe at the bottom of the window. The narrow stripe appears to be handled by WinGetPosEx, but the big stripe isn't.

I can live with the stripe, but I need to know its size. For reasons not worth explaining, I need to align the content at the *bottom* of the image. If I can't screen shot the whole window, I'll just blit the content downward by the height of the stripe -- but I need to know it's height.

Or I need a way to calculate difference between the WinGetPosEx origin and the screenshotted origin. That's part of the issue we're all struggling with here -- how to get the different XY for the multiple definitions of 'origin'.

I think my problem with IE might be that I'm using the wrong window. Maybe I'm using the tab window and I need to find the frame window?

Narrow stripe:
yes-address.png
yes-address.png (11.27 KiB) Viewed 461 times
Broad stripe:
no-address.png
no-address.png (5.48 KiB) Viewed 461 times
kkleinfelter
Posts: 18
Joined: 21 Dec 2018, 10:59

Re: hWnd_to_hBmp() : Capture full screen, Window, Control or user defined area of these

23 Sep 2019, 07:53

My solution was to combine WinGetPosEx and ClientToWin. ClientToWin addressed the bulk of the big black strip, and WinGetPosEx addressed the remaining sliver. As a reminder, my complaint was NOT about the stripe itself, but about being able to identify an 'accurate' XY coordinate within the screenshot, so that I could draw a mark to show where the mouse click happened.
User avatar
SKAN
Posts: 384
Joined: 29 Sep 2013, 16:58

WinGetVPos() Alt-name: WinGetVisualPos()

03 Oct 2019, 04:42

I wrote: I'll try to write a simplified version of WinGetPosEx()
Straight-forward approach: Call GetWindowRect() only if DwmGetWindowAttribute( DWMWA_EXTENDED_FRAME_BOUNDS ) fails.

Quick tested only in Win 7 and Win 10. Can somebody please test this win Win 8.1?

Code: Select all

WinGetVPos(hWnd, ByRef X:=0, ByRef Y:=0, ByRef W:=0, ByRef H:=0) {          ; By SKAN on D2A3 @ bit.ly/2lyG0sN
Local R:=VarSetCapacity(R,16,0), OK := []
  OK.1 := DllCall("IsTopLevelWindow", "Ptr",hWnd),                             ; DWMWA_EXTENDED_FRAME_BOUNDS=9
  OK.2 := OK.1 ? ( DllCall("dwmapi\DwmGetWindowAttribute", "Ptr",hWnd, "UInt",9, "Ptr",&R, "UInt",16)==0 ) : 0
  OK.3 := ( OK.1 && Ok.2=0 ? DllCall("GetWindowRect", "Ptr",hWnd, "Ptr",&R) : 0 ),         ErrorLevel := !OK.1
Return OK.1 ? [X:=NumGet(R,"Int"), Y:=NumGet(R,4,"Int"), W:=NumGet(R,8,"Int")-X, H:=NumGet(R,12,"Int")-Y] : ""   
}

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 65 guests