16

With python 3, I'd like to get a handle to another window (not part of my application) such that I can either:

  1. directly capture that window as a screenshot, or
  2. determine its position and size and capture it some other way

In case it is important, I am using Windows XP (edit: works in Windows 7 also).

I found this solution, but it is not quite what I need since it is full screen and more importantly, PIL to the best of my knowledge does not support 3.x yet.

martineau
  • 119,623
  • 25
  • 170
  • 301
KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • 1
    I'm marking ars as the solution, but check the self answer I posted for a complete working example. – KobeJohn Feb 01 '12 at 13:28
  • News Flash: The [`Pillow`](https://pypi.org/project/Pillow/) fork of the PIL *does* support Python 3 and features a working `ImageGrab` module, and also has excellent [documentation](https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html). – martineau Jan 24 '22 at 18:07
  • If somebody adds that as an answer, I'd move the selected answer and update the question. It's certainly the best option today. – KobeJohn Jan 26 '22 at 01:41
  • 1
    KobeJohn: Pillow currently doesn't have a way to capture to capture a single application's window, just the whole screen, so the currently accepted answer is still relevant at least for the Windows OS. Unfortunately what's really needed is a portable way to determine the window location of applications. – martineau Jan 26 '22 at 10:13

4 Answers4

29

Here's how you can do it using PIL on win32. Given a window handle (hwnd), you should only need the last 4 lines of code. The preceding simply search for a window with "firefox" in the title. Since PIL's source is available, you should be able to poke around the ImageGrab.grab(bbox) method and figure out the win32 code you need to make this happen.

from PIL import ImageGrab
import win32gui

toplist, winlist = [], []
def enum_cb(hwnd, results):
    winlist.append((hwnd, win32gui.GetWindowText(hwnd)))
win32gui.EnumWindows(enum_cb, toplist)

firefox = [(hwnd, title) for hwnd, title in winlist if 'firefox' in title.lower()]
# just grab the hwnd for first window matching firefox
firefox = firefox[0]
hwnd = firefox[0]

win32gui.SetForegroundWindow(hwnd)
bbox = win32gui.GetWindowRect(hwnd)
img = ImageGrab.grab(bbox)
img.show()
ars
  • 120,335
  • 23
  • 147
  • 134
  • That looks great if I understand it correctly. PIL won't work directly but I can use win32gui in Python 3k to get the window as you showed and then extract the win32 code from PIL to do the grab. Is that right? I'll try it out as soon as I can set aside the time. – KobeJohn Jul 16 '10 at 02:27
  • 1
    I'm still new to both python and win32, but this certainly seems to be going the right direction. I'll post more specific code here if I figure it out for anyone else that might need it. – KobeJohn Jul 16 '10 at 09:37
  • So this is not working out so well for me at the moment. I was able to get a handle to the window as you showed. Thanks very much for that. However, when I dug down, I believe ImageGrab ends up in a c file named display.c that comes with PIL. The function there is PyImaging_GrabScreenWin32. I'm going to research whether I can use that myself, but in the meantime, does anyone know a way to get a screenshot in Python 3k without needing to compile and interface to a c library myself? – KobeJohn Jul 17 '10 at 10:45
  • 2
    You could try the code on this page, but I haven't done so myself: http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-05/2531.html – ars Jul 17 '10 at 18:01
  • That worked! Those are quite similar to what happens in display.c. I didn't realize that much of the interface was available in Python already through the other libraries. Thanks for the invaluable follow-up ars. – KobeJohn Jul 18 '10 at 21:21
  • Please see a modern answer with PIL and py3 here: https://stackoverflow.com/a/64556845/377366 – KobeJohn Nov 20 '20 at 08:45
14

Ars gave me all the pieces. I am just putting the pieces together here for anyone else who needs to get a screenshot in python 3.x. Next I need to figure out how to work with a win32 bitmap without having PIL to lean on.

Get a Screenshot (pass hwnd for a window instead of full screen):

def screenshot(hwnd = None):
    import win32gui
    import win32ui
    import win32con
    from time import sleep
    if not hwnd:
        hwnd=win32gui.GetDesktopWindow()
    l,t,r,b=win32gui.GetWindowRect(hwnd)
    h=b-t
    w=r-l
    hDC = win32gui.GetWindowDC(hwnd)
    myDC=win32ui.CreateDCFromHandle(hDC)
    newDC=myDC.CreateCompatibleDC()

    myBitMap = win32ui.CreateBitmap()
    myBitMap.CreateCompatibleBitmap(myDC, w, h)

    newDC.SelectObject(myBitMap)

    win32gui.SetForegroundWindow(hwnd)
    sleep(.2) #lame way to allow screen to draw before taking shot
    newDC.BitBlt((0,0),(w, h) , myDC, (0,0), win32con.SRCCOPY)
    myBitMap.Paint(newDC)
    myBitMap.SaveBitmapFile(newDC,'c:\\tmp.bmp')

Get a Window Handle by title (to pass to the above function):

def _get_windows_bytitle(title_text, exact = False):
    def _window_callback(hwnd, all_windows):
        all_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
    windows = []
    win32gui.EnumWindows(_window_callback, windows)
    if exact:
        return [hwnd for hwnd, title in windows if title_text == title]
    else:
        return [hwnd for hwnd, title in windows if title_text in title]
KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • Please see a modern answer with PIL and py3 here: https://stackoverflow.com/a/64556845/377366 – KobeJohn Nov 20 '20 at 08:45
  • 1
    Sorry for the very late comment, but I found out that the window doesn't appear to be in the foreground at all to be captured by this method. – GiantTree Apr 07 '21 at 21:46
0

The solution here gets a screenshot of a single Window (so can work if the Window is in the background).

Other solutions of this page take picture of the part of the screen the window is on, and thus need to bring the Window to the front first.

Python Screenshot of inactive window PrintWindow + win32gui

Stuart Axon
  • 1,844
  • 1
  • 26
  • 44
0

This will take a new opened window and make a screenshot of it and then crop it with PIL also possible to find your specific window with pygetwindow.getAllTitles() and then fill in your window name in z3 to get screenshot of only that window.

If you definitely not want to use PIL you can maximize window with pygetwindow module and then make a screenshot with pyautogui module.

Note: not tested on Windows XP (but tested on Windows 10)

import pygetwindow
import time
import os
import pyautogui
import PIL

# get screensize
x,y = pyautogui.size()
print(f"width={x}\theight={y}")

x2,y2 = pyautogui.size()
x2,y2=int(str(x2)),int(str(y2))
print(x2//2)
print(y2//2)

# find new window title
z1 = pygetwindow.getAllTitles()
time.sleep(1)
print(len(z1))
# test with pictures folder
os.startfile("C:\\Users\\yourname\\Pictures")
time.sleep(1)
z2 = pygetwindow.getAllTitles()
print(len(z2))
time.sleep(1)
z3 = [x for x in z2 if x not in z1]
z3 = ''.join(z3)
time.sleep(3)

# also able to edit z3 to specified window-title string like: "Sublime Text (UNREGISTERED)"
my = pygetwindow.getWindowsWithTitle(z3)[0]
# quarter of screen screensize
x3 = x2 // 2
y3 = y2 // 2
my.resizeTo(x3,y3)
# top-left
my.moveTo(0, 0)
time.sleep(3)
my.activate()
time.sleep(1)

# save screenshot
p = pyautogui.screenshot()
p.save(r'C:\\Users\\yourname\\Pictures\\\\p.png')

# edit screenshot
im = PIL.Image.open('C:\\Users\\yourname\\Pictures\\p.png')
im_crop = im.crop((0, 0, x3, y3))
im_crop.save('C:\\Users\\yourname\\Pictures\\p.jpg', quality=100)

# close window
time.sleep(1)
my.close()
tinus
  • 149
  • 1
  • 7
  • 1
    This is great. My answer is 10 years old and this is probably a better way today. PIL didn't even support py3 at the time! That is why I needed a non-PIL solution. – KobeJohn Nov 20 '20 at 08:45