How do I capture a screenshot of a window, and all the windows on top of it?
Solution 1
Use import
with the -screen
option, like
import -screen -window 'id' test.png
Solution 2
Using shutter
and wmctrl
, an edited version of this script does pretty much exactly what you describe: it takes a screenshot of the area, a specific window covers on your screen, no matter if and how the window is (partially) below other windows.
The marge around the window, to be included in the screenshot, is arbitrary; set it to zero if you like.
In practice
- I have an
Inkscape
window on my screen, with id0x0520000e
, partially covered by a fewgedit
windows. -
I run the script with the window id and the marge (in
px
) around the window as arguments:python3 <script> 0x0520000e 10 10 10 10
(where
10 10 10 10
is the marge inpx
around the window on the left/right/top/bottom. Set to0
to have no marge in the image)The result:
The script
#!/usr/bin/env python3
import subprocess
import time
import sys
"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---
get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
time.sleep(0.5)
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])
command = "shutter -s="+(",").join([x,y,w,h])+" -e"
subprocess.call(["/bin/bash", "-c", command])
How to use
-
The script uses
Shutter
andwmctrl
:sudo apt-get install wmctrl shutter
Copy the script below into an empty file, save it as
custom_screenshot.py
.-
Run it by the command:
python3 /path/to/custom_screenshot.py <window_id> <left> <right> <top> <bottom>
where ,
<left> <right> <top> <bottom>
are the marges you'd like to keep in the image around the window, like in this answer.Example command:
python3 /path/to/custom_screenshot.py 0x0520000e 20 20 20 20
Explanation
In
Shutter
, it is possible to take a screenshot of a defined area of the desktop.With the window id as an argument, the script looks up the window's exact position with the help of
wmctrl
(wmctrl -lG
to be precise), and the output ofxprop -id <window_id>
(in the line_NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 28, 0
for example).Subsequently, a screenshot is taken from the found area, with an arbitrary marge.
Note
The script does not overwrite existing screenshots. New screenshots are named:
outputfile_1.png
outputfile_2.png
outputfile_3.png
and so on...
EDIT
Since you mentioned in a comment that speed is an issue:
Based on this script, if we do exactly the same trick, but use Scrot
instead of Shutter
, we can skip the sleep 0.5
and make it a lot faster:
The script
#!/usr/bin/env python3
import subprocess
import sys
import os
"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---
get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])
# setting default directories / filenames
home = os.environ["HOME"]
temp = home+"/"+".scrot_images"
img_in = temp+"/in.png"
# if you prefer, you can change the two line below:
output_directory = home+"/"+"scrot_images" # output directory
filename = "outputfile" # filename
# creating needed directories
for dr in [temp, output_directory]:
if not os.path.exists(dr):
os.mkdir(dr)
# creating filename (-number) to prevent overwriting previous shots
n = 1
while True:
img_out = output_directory+"/"+filename+"_"+str(n)+".png"
if os.path.exists(img_out):
n = n+1
else:
break
# Take screnshot, crop image
subprocess.call(["scrot", img_in])
subprocess.Popen(["convert", img_in, "-crop", w+"x"+h+"+"+x+"+"+y, "+repage", img_out])
To use
Use it exactly like the first script, only:
-
This script needs
scrot
,imagemagick
andwmctrl
sudo apt-get install imagemagick wmctrl scrot
images will be stored in
~/scrot_images
Explanation
While the first script uses the command line option of Shutter
to shoot a defined section of the desktop, Scrot
does not support that. It only takes a screenshot of the whole screen.
We can combine however imagemagick
's option to make an out-take of an image, with the method to find the exact window's coordinates we used in the first script, and crop the image accordingly.
Since Scrot
is extremely light weight and quick, even combined with imagemagick
's crop action, we have a pretty fast way of making screen shots of a window's area.
Still not fast enough?
Not sure if it is needed, but with a bit of rewriting (see script below), it would be possible to make a series of shots even faster by first shoot the whole series, then (afterwards) do the cropping. Assuming the window would stay in its position, this would save a considearble amount of the time:
-
Shooting only with
Scrot
(no cropping):real 0m0.263s user 0m0.205s sys 0m0.037s
-
Shooting, including cropping:
real 0m0.363s user 0m0.293s sys 0m0.040s
Serial shooting
Finally, as an example to make a series of screenshots, the script below, as suggested in the EDIT.
This one first shoots all images in a row, then does the cropping on all created images at once.
Use the script like the second one, but with one additional argument: the number of shoots in a row, for example:
python3 /path/to/custom_screenshot.py 0x0520000e 0 0 0 0 20
to make 20 screenshots of window 0x0520000e
at a row (could be hundreds), no marge around the window.
The script
#!/usr/bin/env python3
import subprocess
import sys
import os
"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---
get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])
# setting default directories / filenames
home = os.environ["HOME"]
temp = home+"/"+".scrot_images"
# if you prefer, you can change the two line below:
output_directory = home+"/"+"scrot_images" # output directory
filename = "outputfile" # filename
# creating needed directories
for dr in [temp, output_directory]:
if not os.path.exists(dr):
os.mkdir(dr)
# do the shooting
t = 0; l = []; shots = int(sys.argv[6])
while t < shots:
img_temp = temp+"/"+str(t)+"in.png"
l.append(img_temp)
# reading arguments,arranging commands to perform
subprocess.call(["scrot", img_temp])
t += 1
# do the cropping on all images in a row
for img in l:
n = 1
while True:
img_out = output_directory+"/"+filename+"_"+str(n)+".png"
if os.path.exists(img_out):
n = n+1
else:
break
subprocess.call(["convert", img , "-crop", w+"x"+h+"+"+x+"+"+y, "+repage", img_out])
Related videos on Youtube
Clément
PhD Student at MIT CSAIL. Author of Create Synchronicity, YìXué Dictionary, and company-coq.
Updated on September 18, 2022Comments
-
Clément over 1 year
I'm using screenshots as regression tests for GUI software. Before deploying each new version, a series of automatic tasks is run against the old and the new version, screenshots are generated after each command in both cases, and the results are compared. ImageMagick's import command has been working very well fo that.
Recently I added right click menus. Unfortunately,
import -window 'id'
doesn't capture these menus.Which command line tools on Ubuntu can take a screenshot of a window, and all windows on top of it?
That is, which tools can, instead of taking a screenshot of a window corresponding to a window ID, take a screenshot of the entire screen and truncate it to the boundaries a given window?
I haven't been able to get this result in a simple way with any of the tools listed at What is the terminal command to take a screenshot?.
-
Admin about 8 yearscould very well be scripted, with a similar (altered) process like used here: askubuntu.com/questions/578728/…. My question is: would that script do, if I change the selection of the front most window into selection of a specific window? Let me know, can be done.
-
Admin about 8 years@JacobVlijm: No; popup menus (like right click) are implemented as separate screen windows, so a section of a single window won't do.
-
Admin about 8 yearsI believe you misunderstand the linked answer, it does not take a section, but the whole window.
-
Admin about 8 yearsSee updated answer.
-
-
Jacob Vlijm about 8 yearsDoesn't work well on my system, I got "scalped" window images, transparency is gone etc.
-
Clément about 8 yearsThis works, though indeed there are issues with transparency along window edges.
-
Clément about 8 yearsHow does that answer the question?
-
Clément about 8 yearsI'm wary of the
sleep(0.5)
call; there's going to be many screenshots (in the order of 300), so waiting .5 seconds between each isn't very good. -
Jacob Vlijm about 8 years@Clément see updated answer.
-
Clément about 8 yearsSorry for the confusion; I thought mentioning that screenshots were taken after every command in the program would be enough :) Thanks for updating the answer!
-
Clément about 8 yearsJust to clarify: this gets the boundaries of the window, then crops a screenshot of the whole screen, right?
-
Jacob Vlijm about 8 years@Clément the second script, indeed. Will add an explanation after dinner.
-
Clément about 8 yearsThe code is quite readable already; thanks a lot :) +1
-
Jacob Vlijm about 8 years@Clément added a small section. Especially the last part might be useful to consider if you need to shoot a series. With a small addition / editing, easily possible to run a series of hundreds of screenshots automatically in a row.
-
Clément about 8 yearsThanks! There are two very good answers at this point; I'll wait a bit longer before picking one :)
-
erik almost 8 yearsInteresting to know: You get the window id if you do
xwininfo | grep -i 'window id'
and click on the window in question.