Reputation: 387
After hours of googling I managed to "write" this:
import win32gui
from ctypes import windll
hwnd = win32gui.FindWindow(None, 'Steam')
hdc = win32gui.GetDC(hwnd)
hdcMem = win32gui.CreateCompatibleDC(hdc)
hbitmap = win32ui.CreateBitmap()
hbitmap = win32gui.CreateCompatibleBitmap(hdcMem, 500, 500)
win32gui.SelectObject(hdcMem, hbitmap)
windll.user32.PrintWindow(hwnd, hdcMem, 0)
Is this a correct way to do this and how would I save an image?
Upvotes: 23
Views: 43819
Reputation: 3345
ALT + PrtSc
.It prints the full window with its 1px border. (Or no border if maximized)
We can copy it to the clipboard just like the combo does.
Or we can convert it to a PIL Image and save it.
import ctypes, win32con, win32gui
import win32clipboard as w32clip
from struct import pack, calcsize
from ctypes import windll, wintypes
from PIL import Image
user32,gdi32 = windll.user32,windll.gdi32
PW_RENDERFULLCONTENT = 2
def getWindowBMAP(hwnd,returnImage=False):
# get Window size and crop pos/size
L,T,R,B = win32gui.GetWindowRect(hwnd); W,H = R-L,B-T
x,y,w,h = (8,8,W-16,H-16) if user32.IsZoomed(hwnd) else (7,0,W-14,H-7)
# create dc's and bmp's
dc = user32.GetWindowDC(hwnd)
dc1,dc2 = gdi32.CreateCompatibleDC(dc),gdi32.CreateCompatibleDC(dc)
bmp1,bmp2 = gdi32.CreateCompatibleBitmap(dc,W,H),gdi32.CreateCompatibleBitmap(dc,w,h)
# render dc1 and dc2 (bmp1 and bmp2) (uncropped and cropped)
obj1,obj2 = gdi32.SelectObject(dc1,bmp1),gdi32.SelectObject(dc2,bmp2) # select bmp's into dc's
user32.PrintWindow(hwnd,dc1,PW_RENDERFULLCONTENT) # render window to dc1
gdi32.BitBlt(dc2,0,0,w,h,dc1,x,y,win32con.SRCCOPY) # copy dc1 (x,y,w,h) to dc2 (0,0,w,h)
gdi32.SelectObject(dc1,obj1); gdi32.SelectObject(dc2,obj2) # restore dc's default obj's
if returnImage: # create Image from bmp2
data = ctypes.create_string_buffer((w*4)*h)
bmi = ctypes.c_buffer(pack("IiiHHIIiiII",calcsize("IiiHHIIiiII"),w,-h,1,32,0,0,0,0,0,0))
gdi32.GetDIBits(dc2,bmp2,0,h,ctypes.byref(data),ctypes.byref(bmi),win32con.DIB_RGB_COLORS)
img = Image.frombuffer('RGB',(w,h),data,'raw','BGRX')
# clean up
gdi32.DeleteObject(bmp1) # delete bmp1 (uncropped)
gdi32.DeleteDC(dc1); gdi32.DeleteDC(dc2) # delete created dc's
user32.ReleaseDC(hwnd,dc) # release retrieved dc
return (bmp2,w,h,img) if returnImage else (bmp2,w,h)
def copyBitmap(hbmp): # copy HBITMAP to clipboard
w32clip.OpenClipboard(); w32clip.EmptyClipboard()
w32clip.SetClipboardData(w32clip.CF_BITMAP,hbmp); w32clip.CloseClipboard()
def copySnapshot(hwnd): # copy Window HBITMAP to clipboard
hbmp,w,h = getWindowBMAP(hwnd); copyBitmap(hbmp); gdi32.DeleteObject(hbmp)
def getSnapshot(hwnd): # get Window HBITMAP as Image
hbmp,w,h,img = getWindowBMAP(hwnd,True); gdi32.DeleteObject(hbmp); return img
hwnd = windll.kernel32.GetConsoleWindow() # Console
if not hwnd: hwnd = user32.FindWindowW("Notepad",None) # Notepad
if not hwnd: hwnd = user32.FindWindowW("Chrome_WidgetWin_1",None) # Chrome
if not hwnd: hwnd = user32.FindWindowW("CabinetWClass",None) # Windows Explorer
if not hwnd: print("Couldn't find Window")
elif user32.IsIconic(hwnd): print("Window is minimized")
else:
print(win32gui.GetWindowText(hwnd))
print(win32gui.GetClassName(hwnd))
copySnapshot(hwnd)
img = getSnapshot(hwnd)
img.save("snapshot.png")
#img.show()
print("Snapshot Saved!")
If we did a basic PrintWindow
, it renders a Windows 7 style frame/border which is 7 pixels thicker at the Left/Right/Bottom. It's also at the top but only when maximized.
We can use the hidden flag PW_RENDERFULLCONTENT
to render correctly, but that extra border space still exists and remains blank. So our image is padded with thick black borders.
So we need to crop our HBITMAP.
To do this we can create a new bitmap and use BitBlt
with our first bitmap's DC as the source.
From here we can use win32clipboard
to copy the HBITMAP just like ALT + PrtSc
.
We can use GetDIBits
to get the raw pixel data in an array of size w*h*4
.
It needs the BITMAPINFOHEADER
struct to recieve the width
, height
, planes
, bitCount
.
One way to do it:
class BITMAPINFOHEADER(ctypes.Structure):
_fields_ = [
("biSize", wintypes.DWORD),
("biWidth", ctypes.c_long),
("biHeight", ctypes.c_long),
("biPlanes", wintypes.WORD),
("biBitCount", wintypes.WORD),
("biCompression", wintypes.DWORD),
("biSizeImage", wintypes.DWORD),
("biXPelsPerMeter", ctypes.c_long),
("biYPelsPerMeter", ctypes.c_long),
("biClrUsed", wintypes.DWORD),
("biClrImportant", wintypes.DWORD)
]
def __init__(self,w,h,p=1,b=32):
self.biSize = ctypes.sizeof(BITMAPINFOHEADER)
self.biWidth,self.biHeight,self.biPlanes,self.biBitCount = w,h,p,b
bmi = BITMAPINFOHEADER(w,-h) # we use -h to flip the image also
But a much more compact way:
bmi = ctypes.c_buffer(pack("IiiHHIIiiII",calcsize("IiiHHIIiiII"),w,-h,1,32,0,0,0,0,0,0))
We use struct
to pack values as specific data types.
i = int (c_long)
I = unsigned int (DWORD)
H = unsigned short (WORD)
We use calcsize("IiiHHIIiiII")
to do the equivalent of ctypes.sizeof(BITMAPINFOHEADER)
.
Here's how to get the Client only:
PW_CLIENTONLY,PW_RENDERFULLCONTENT = 1,2
def getClientBMAP(hwnd,returnImage=False):
# get Client size
L,T,R,B = win32gui.GetClientRect(hwnd); w,h = R-L,B-T
# create dc's and bmp's
dc = user32.GetWindowDC(hwnd)
dc1 = gdi32.CreateCompatibleDC(dc)
bmp1 = gdi32.CreateCompatibleBitmap(dc,w,h)
# render dc1 (bmp1)
obj1 = gdi32.SelectObject(dc1,bmp1) # select bmp into dc
user32.PrintWindow(hwnd,dc1,PW_CLIENTONLY|PW_RENDERFULLCONTENT) # render window to dc1
gdi32.SelectObject(dc1,obj1) # restore dc's default obj
if returnImage: # create Image from bmp1
data = ctypes.create_string_buffer((w*4)*h)
bmi = ctypes.c_buffer(pack("IiiHHIIiiII",calcsize("IiiHHIIiiII"),w,-h,1,32,0,0,0,0,0,0))
gdi32.GetDIBits(dc1,bmp1,0,h,ctypes.byref(data),ctypes.byref(bmi),win32con.DIB_RGB_COLORS)
img = Image.frombuffer('RGB',(w,h),data,'raw','BGRX')
# clean up
gdi32.DeleteDC(dc1) # delete created dc
user32.ReleaseDC(hwnd,dc) # release retrieved dc
return (bmp1,w,h,img) if returnImage else (bmp1,w,h)
def getClientSnapshot(hwnd): # get Client HBITMAP as Image
hbmp,w,h,img = getClientBMAP(hwnd,True); gdi32.DeleteObject(hbmp); return img
Upvotes: 0
Reputation: 1258
After lots of searching and trying various different methods, the following worked for me.
import win32gui
import win32ui
from ctypes import windll
from PIL import Image
hwnd = win32gui.FindWindow(None, 'Calculator')
# Uncomment the following line if you use a high DPI display or >100% scaling size
# windll.user32.SetProcessDPIAware()
# Change the line below depending on whether you want the whole window
# or just the client area.
#left, top, right, bot = win32gui.GetClientRect(hwnd)
left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
#result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
print result
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
#PrintWindow Succeeded
im.save("test.png")
Upvotes: 71