capturing windows with WM_PRINT

BlitzMax Forums/BlitzMax Beginners Area/capturing windows with WM_PRINT

I have been trying to make some code to capture all the open windows on the desktop into pixmaps, but failed to find a way to do so.

I found a page explaining that in order to capture the windows that are partially hidden or completely hidden, you should be able to use sendmessage with WM_Print.

The page I found is located here: http://www.fengyuan.com/article/wmprint.html

But I have not been able to get any code working in bmax that uses this sendmessage command and capturing windows into pixmaps.

So what I am asking is if someone with more knowledge of the winapi could take a look at that page, and tell me if its possible at all, and perhaps how to go about it.

I tried using the WM_PRINT method, but couldnt get it to work :(

You could try this instead, it needs the window to be visible on screen though so it may not be what you want...
SuperStrict

Import PUB.Win32

Extern "Win32"
	Function ReleaseDC:Int( hwnd:Int, dc:Int)
	Function GetDIBits:Int( dc:Int, bm:Int, x:Int,y:Int, src:Byte Ptr, bmi:Byte Ptr, flags:Int)
	Function FindWindowA:Int( classname$z, windowtitle$z)
	Function BitBlt:Int( destdc:Int, destx:Int,desty:Int, width:Int,height:Int, srcdc:Int, srcx:Int,srcy:Int, rop:Int)
EndExtern

Local hwnd:Int = FindWindowA( Null, "Calculator")
If hwnd Then
	Local pix:TPixmap = GrabWindowPixmap( hwnd)
	If pix Then
		Print "saving png..."
		SavePixmapPNG pix, "c:\test.png"
	Else
		Print "could not capture window"
	EndIf
Else
	Print "could not find window"
EndIf

Function GrabWindowPixmap:TPixmap( hwnd:Int)
	If hwnd = 0 Then Return Null
	
	' need to show the window to capture it
	SetWindowPos( hwnd, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE)
	SetWindowPos( hwnd, HWND_NOTOPMOST, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE)
	ShowWindow( hwnd, SW_SHOW)	
	Delay 50
	
	' get window size
	Local rect:Int[4]
	GetClientRect( hwnd, rect)
	Local w:Int = rect[2] - rect[0]
	Local h:Int = rect[3] - rect[1]	
	
	Local srcdc:Int = GetDC(hwnd)
	Local memdc:Int = CreateCompatibleDC(srcdc)
	Local bmp:Int = CreateCompatibleBitmap( srcdc, w,h)
	Local oldbmp:Int = SelectObject( memdc, bmp)
	
	BitBlt( memdc, 0,0,w,h, srcdc,0,0, ROP_SRCCOPY)
	
	Local pixmap:TPixmap, bits:Int = GetDeviceCaps( srcdc, BITSPIXEL)
	Select bits
		Case 24	pixmap = CreatePixmap( w,h, PF_BGR888)
		Case 32	pixmap = CreatePixmap( w,h, PF_BGRA8888)
		Default
			Throw "GrabWindowPixmap() supports only 24 and 32 bits"
	EndSelect

	Local bmphdr:BITMAPINFOHEADER = New BITMAPINFOHEADER
	bmphdr.biSize = SizeOf(bmphdr)
	bmphdr.biWidth = pixmap.width
	bmphdr.biHeight = pixmap.height
	bmphdr.biPlanes = 1
	bmphdr.biBitCount = bits
	bmphdr.biCompression = BI_RGB

	Local p:Byte Ptr = pixmap.pixels
	For Local y:Int = 0 Until pixmap.height
		GetDIBits( memdc,bmp, pixmap.height - y - 1,1, p, bmphdr, DIB_RGB_COLORS)	
		p :+ pixmap.pitch
	Next
	' make sure its visible
	If pixmap.Format =  PF_BGRA8888 Then
		Local pp:Byte Ptr = pixmap.pixels
		While pp < p
			pp[3] = $FF
			pp :+ 4
		Wend
	EndIf
	
	SelectObject( memdc, oldbmp)
	DeleteObject(bmp)
	DeleteDC(memdc)
	ReleaseDC( hwnd, srcdc)
	
	Return pixmap
EndFunction


Yes I need to capture windows that are possibly behind other windows as well. And that page mentions that its possible, I just could not get any of it to work.

You could remember the state of each window, show it on screen and capture it, then restore its state.

Instead of using the BitBlt function in your code above I found PrintWindow which seems to work.

http://msdn2.microsoft.com/en-us/library/ms535695.aspx

This is how I set it up in bmax:

Function PrintWindow( hwnd:Int, hdcBlt:Int, nFlags:Int)


And I call it like this:

PrintWindow(hwnd,memdc,0)


Now I have to find out, how I get the handles of all windows on the desktop. But I am not having much luck with EnumWindows(), which seems to give me handles for all running processes, and not just windows on the desktop.

Cool! it works on some windows but not all though :/
Many come out black, but that could be an incompatability between the DC/BITMAP or something.

EnumWindows isnt supposed to do that (there are other APIs for enumerating processes), it only enums toplevel windows.

I believe that I have found a way to get windows. The following code seems to do the trick.

Extern "win32"
	Function EnumWindows(Callbackptr:Byte Ptr,lParam)
	Function GetWindowText(hWnd,lpString:Byte Ptr,nMaxCount) = "GetWindowTextA@12"
	Function IsWindowVisible(hwnd:Int)
End Extern

Global CharBuffer:Byte[255]

Function EnumWindowsProc(hwnd,lParam)
	GetWindowText(hwnd,CharBuffer,255)
	If String.FromCString(CharBuffer) <> "" And String.FromCString(CharBuffer) <> "Program Manager" Then
		If IsWindowVisible(hwnd) Then
			Print "("+hwnd+") / "+String.FromCString(CharBuffer)
		End If
	End If
	Return True
End Function

EnumWindows(EnumWindowsProc,0)


I just managed to make an alternative way of getting the handles without using enumwindows.

Extern "win32"
	Function GetWindowText(hWnd:Int,lpString:Byte Ptr,nMaxCount) = "GetWindowTextA@12"
	Function IsWindowVisible(hwnd:Int)
	Function GetWindow(hwnd:Int,uCmd:Int)
	Function GetDesktopWindow()
End Extern

Const GW_CHILD:Int = 5
Const GW_HWNDNEXT:Int = 2

Global CharBuffer:Byte[255]

Function getWindowHandles()
	Local hwnd:Int = GetWindow(GetDesktopWindow(),GW_CHILD)
	Repeat
		If IsWindowVisible(hwnd) Then
			GetWindowText(hwnd,CharBuffer,255)
			If String.FromCString(CharBuffer) <> "" And String.FromCString(CharBuffer) <> "Program Manager" Then
				Print "("+hwnd+") / "+String.FromCString(CharBuffer)
			End If
		End If
		hwnd = GetWindow(hwnd,GW_HWNDNEXT)
	Until hwnd = 0
End Function

getWindowHandles()


I hope the above code is useful to others as well. It hasn't been easy finding out how to code it, so at least it will be easier for others looking to do the same.

[EDIT]
It also grabs tool windows, like those loose window in drawing packages, so its not 100% yet, but I will keep looking to see if I can't fix that.