Python Screenshot application window at any size - python

I'm trying to get a full screenshots from an application window even when minimized, maximized, or any window shape. I've looked at other questions like this but havn't found the answer that I'm looking for.
I've tried the code below and it works, but has limited capability with what I want it to do.
def screenshot(hwnd = None):
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)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
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(r"c:\python27\programs\check.bmp")
Using this code with a window that is maximized yields a great result!
But when the window is shrinked.......not so much
I tried editing this line but end up with an akward result :/ . saveBitMap.CreateCompatibleBitmap(mfcDC, w+100, h+100)
Does anyone know how to take a screenshot of a fully windowed application without maximizing then windowing again? Maybe something along the lines of using win32con.SW_MAXIMIZE.

Why not use pywinauto + Pillow / PIL?
from pywinauto import application
app = application.Application().start("notepad.exe")
app.Untitled_Notepad.capture_as_image().save('window.png')

Related

CreateCompatibleDC() or DeleteDC() fail in continues loop in Python - possible memory leak?

I am feeding an opencv window in a loop with this specific window screen capture routine below.
PROBLEM: after hundreds of cycles in the loop, it suddenly fail at either one of the two FAIL POINTS marked below in the code.
I am suspecting possible memory leak, but if I am not mistaken, I do delete and release what's required as well as I (re)select object before I delete it.
(The reason I am using this method, because it is important for me to be able to capture the specific window even if it is inactive and in the background and I did not found any other module/method actually works.)
What am I overlooking?
import win32gui
import win32ui
from PIL import Image
import numpy as np
import cv2
while True:
target_window = win32gui.FindWindow(None, ("Analytics dashboard - Google Chrome"))
hwndDC = win32gui.GetWindowDC(target_window)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC() #### <-- RANDOM FAIL POINT 1: win32ui.error: CreateCompatibleDC failed
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, screen_width, screen_height)
saveDC.SelectObject(saveBitMap)
result = windll.user32.PrintWindow(target_window, saveDC.GetSafeHdc(), 3)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
screen_image = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
mfcDC.DeleteDC() #### <-- RANDOM FAIL POINT 2: win32ui.error: DeleteDC failed
saveDC.DeleteDC()
win32gui.DeleteObject(saveBitMap.GetHandle())
win32gui.ReleaseDC(target_window, hwndDC)
image = cv2.cvtColor(np.array(screen_image), cv2.IMREAD_ANYCOLOR)
I tried the above code with ctypes.windll.user32.PrintWindow and there were no GDI leaks. PrintWindow's third argument should be PW_CLIENTONLY (1), or there is the undocumented PW_RENDERFULLCONTENT (2) option. Undocumented code is not reliable. I don't know what the constant (3) refers to.
If Chrome is the top window you should just take screen shot of desktop. This would be compliant.
It might help if you remove some of the code outside the loop, it will be more efficient at least.
import ctypes
import win32gui
import win32ui
import win32con
from PIL import Image
hdesktop = win32gui.GetDesktopWindow()
(l, r, width, height) = win32gui.GetClientRect(hdesktop)
hdc = win32gui.GetWindowDC(hdesktop)
dc = win32ui.CreateDCFromHandle(hdc)
memdc = dc.CreateCompatibleDC()
bitmap = win32ui.CreateBitmap()
bitmap.CreateCompatibleBitmap(dc, width, height)
memdc.SelectObject(bitmap)
while True:
hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", None)
if hwnd == 0:
break
result = ctypes.windll.user32.PrintWindow(hwnd, memdc.GetSafeHdc(), 2)
if result == 1:
bytes = bitmap.GetBitmapBits(True)
img = Image.frombuffer('RGB', (width, height), bytes, 'raw', 'BGRX', 0, 1)
img.save("file.bmp")
#break
dc.DeleteDC()
memdc.DeleteDC()
win32gui.DeleteObject(bitmap.GetHandle())
win32gui.ReleaseDC(hwnd, hdc)
You can also add ctypes.windll.shcore.SetProcessDpiAwareness(2) on top

CreateDCFromHandle select object TOP coordinate in Python

I need to capture a specific application window on Windows, even if it is not focused or in the foreground, WITHOUT the window's top header bar, which height I specify manually in 'application_window_topbar_size' variable to keep it simple.
monitor_info = GetMonitorInfo(MonitorFromPoint((0,0))) # get screen information
work_area = monitor_info.get("Work")
screen_width = work_area[2] # if taskbar is on the left
screen_height = work_area[3] # if taskbar is on the left
target_window_hwnd = win32gui.FindWindow(None, ("Math Analysis - Google Chrome"))
application_window_topbar_size = 200 # just roughly for testing
hwndDC = win32gui.GetWindowDC(target_window_hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, screen_width, screen_height)
saveDC.SelectObject(saveBitMap)
# saveDC.BitBlt((0, 0), (screen_width, screen_height), mfcDC, (0, application_window_topbar_size),win32con.SRCCOPY)
result = windll.user32.PrintWindow(target_window_hwnd, saveDC.GetSafeHdc(), 3)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
screen_image = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(target_window_hwnd, hwndDC)
The capture routine should works for windows handle "WM_PRINT" (thanks for the comment of IInspectable), BUT I am unable to figure it out how to set the TOP coordinate of the capture area. In other words, I would like to capture the target window without the top bar. Changing the height is one thing, as logically, if the capture rectangle coordinates start at 0,0 then it cuts only the bottom area of the window, not the top. I tried to use BitBlt() but I failed.
----------------- Alternative version below based on IInspectable comment
target_window_hwnd = win32gui.FindWindow(None, ("Math Analysis - Google Chrome"))
application_window_topbar_size = 200 # just roughly for testing
hwndDC = win32gui.GetWindowDC(target_window_hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
l, t, r, b = win32gui.GetClientRect(target_window_hwnd)
screen_width = r - l
screen_height = b -t
saveBitMap.CreateCompatibleBitmap(mfcDC, screen_width, screen_height)
saveDC.SelectObject(saveBitMap)
result = windll.user32.PrintWindow(target_window_hwnd, saveDC.GetSafeHdc(), 3)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
screen_image = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(target_window_hwnd, hwndDC)
Still, altering top coordinate only effect height, not captured content (to be able to capture only the 'document area' of the window, excluding the address bar/toolbar).
After long search and trials, I realized that the most obvious solution is to CROP the screenshot after it made. This way, the unwanted part (in my case the top bar) can be easily get rid off.
Cropping is easy with PIL Image:
box = (left, top, right, bottom)
screen_image.crop(box)

Capturing screenshots with win32api python returns black image

I've used the following code examples to capture a screenshot:
https://stackoverflow.com/a/3260811
https://stackoverflow.com/a/24352388/5858697
When taking a screenshot of Firefox or chrome, they return a blank black image. Capturing a screenshot of notepad works fine. I've done some research on this and I think it's because they're gpu accelerated. Other screenshot libraries work but I need to have it so I can capture a screenshot of an application even if it's not currently visible.
Has anyone solved a similar problem or could someone point me in the right direction? Thank you.
Based on the #Barmak's previous answer, I converted C + + code to python, and now it works.
import win32gui
import win32ui
import win32con
from ctypes import windll
from PIL import Image
import time
import ctypes
hwnd_target = 0x00480362 #Chrome handle be used for test
left, top, right, bot = win32gui.GetWindowRect(hwnd_target)
w = right - left
h = bot - top
win32gui.SetForegroundWindow(hwnd_target)
time.sleep(1.0)
hdesktop = win32gui.GetDesktopWindow()
hwndDC = win32gui.GetWindowDC(hdesktop)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
result = saveDC.BitBlt((0, 0), (w, h), mfcDC, (left, top), win32con.SRCCOPY)
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(hdesktop, hwndDC)
if result == None:
#PrintWindow Succeeded
im.save("test.png")
Please note: Firefox uses Windowless Controls.
If you want to get the handle of Firefox, you may need UI Automation.
For a detailed explanation, please refer to #IInspectable's answer.

Screenshot function does not work on other machines

I am trying to take window screenshot while the windows is in background. The code runs perfectly on my system(HP, win7). The same code fails to give proper output on another system(win7, Lenovo and Dell). It does give the screenshot, but it is not clear. There is black color wherever there is empty area in the window.
Click here to see image
def capture_screen(win_name,outloc,imagename,h):
hwnd = win32gui.FindWindow(None,win_name)
# Get window bounds
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)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
#print(result)
bmp_info = saveBitMap.GetInfo()
bmp_str = saveBitMap.GetBitmapBits(True)
#print(bmp_str)
im = Image.frombuffer('RGB',(bmp_info['bmWidth'], bmp_info['bmHeight']),
bmp_str, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
im.save(outloc+imagename)
To capture any hidden window (Win32, UWP, ...), you can use DWM (DwmRegisterThumbnail )

Screenshot of inactive window PrintWindow + win32gui

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?
After lots of searching and trying various different methods, the following worked for me.
import win32gui
import win32ui
from ctypes import windll
import Image
hwnd = win32gui.FindWindow(None, 'Calculator')
# 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")

Categories