BlogScreenshots on your device
How to Take a Screenshot and Display It in Tkinter
Capture a screen or region with Python, convert it to a Tkinter image, and display it reliably in a Label or Canvas.
Use PyAutoGUI to capture pixels, convert the returned Pillow image with ImageTk.PhotoImage, and keep a reference to that PhotoImage. Tkinter widgets do not retain the Python reference themselves, so losing it makes the image disappear.
Install the required packages
Install PyAutoGUI and Pillow in the environment that runs your Tkinter program:
python -m pip install pyautogui pillow
PyAutoGUI screenshot support on Linux may also require the scrot utility. Install it with your distribution’s package manager and grant the desktop permissions required by your session. See the PyAutoGUI screenshot documentation.
A minimal full-screen example
This complete program captures the screen once and displays it in a Tkinter Label:
import tkinter as tk
import pyautogui
from PIL import ImageTk
root = tk.Tk()
root.title("Screenshot")
shot = pyautogui.screenshot()
photo = ImageTk.PhotoImage(shot, master=root)
label = tk.Label(root, image=photo)
label.image = photo # Keep a live reference.
label.pack()
root.mainloop()
pyautogui.screenshot() returns a Pillow Image. ImageTk.PhotoImage bridges that Pillow object to Tk’s image system. Passing master=root associates the image with the correct Tk interpreter.
Capture only a rectangular region
Pass (left, top, width, height) to region when the entire desktop is unnecessary:
shot = pyautogui.screenshot(region=(100, 100, 800, 600))
Coordinates are screen pixels, measured from the top-left of the desktop. Region capture reduces the number of pixels held in memory and is usually easier to fit in a window.
Display a screenshot safely in a Label
A Label is the simplest widget for one image. If you replace the image later, update the widget and its retained reference together:
import tkinter as tk
import pyautogui
from PIL import ImageTk
class ScreenshotWindow:
def __init__(self, root):
self.root = root
self.root.title("Live screenshot")
self.label = tk.Label(root)
self.label.pack()
self.refresh()
def refresh(self):
shot = pyautogui.screenshot(region=(0, 0, 800, 600))
self.photo = ImageTk.PhotoImage(shot, master=self.root)
self.label.configure(image=self.photo)
self.label.image = self.photo
self.root.after(1000, self.refresh)
root = tk.Tk()
app = ScreenshotWindow(root)
root.mainloop()
Use after to schedule the next capture. A loop containing repeated screenshots and time.sleep would block Tkinter’s event loop and make the window stop responding.
Resize large screenshots before displaying them
A screen can be much larger than the available window. Resize a copy with Pillow, then convert the resized image:
import tkinter as tk
import pyautogui
from PIL import ImageTk
root = tk.Tk()
root.title("Scaled screenshot")
shot = pyautogui.screenshot()
preview = shot.copy()
preview.thumbnail((1000, 700)) # Preserves aspect ratio.
photo = ImageTk.PhotoImage(preview, master=root)
label = tk.Label(root, image=photo)
label.image = photo
label.pack()
root.mainloop()
Keep the original if you need full-resolution saving or later processing. Use the smaller image only for the preview.
Use a Canvas for scrolling, positioning, or overlays
A Canvas supports image placement, drawing, and scrollbars:
import tkinter as tk
import pyautogui
from PIL import ImageTk
root = tk.Tk()
root.title("Canvas screenshot")
shot = pyautogui.screenshot()
photo = ImageTk.PhotoImage(shot, master=root)
canvas = tk.Canvas(root, width=1000, height=700, scrollregion=(0, 0, shot.width, shot.height))
canvas.pack(fill="both", expand=True)
canvas.create_image(0, 0, image=photo, anchor="nw")
canvas.image = photo # Retain the reference.
root.mainloop()
For a production viewer, add horizontal and vertical scrollbars and set their commands to canvas.xview and canvas.yview. Keep the image anchored at "nw" so its coordinates match the scroll region.
Save the capture as well as displaying it
PyAutoGUI can save directly, or Pillow can save the returned image:
shot = pyautogui.screenshot()
shot.save("screen.png")
shot.save("screen.webp", format="WEBP")
Tk’s native PhotoImage supports GIF, PGM/PPM and, with Tk 8.6 or newer, PNG. Pillow’s ImageTk.PhotoImage lets you display formats such as BMP, JPEG, TIFF and WebP after Pillow opens or creates them.
Alternative capture with Pillow ImageGrab
PIL.ImageGrab.grab() provides a programmatic interface to system viewport pixels or clipboard bitmap buffers and returns a Pillow image. The display code is unchanged:
import tkinter as tk
from PIL import ImageGrab, ImageTk
root = tk.Tk()
shot = ImageGrab.grab()
photo = ImageTk.PhotoImage(shot, master=root)
label = tk.Label(root, image=photo)
label.image = photo
label.pack()
root.mainloop()
Choose PyAutoGUI when you already use its cross-platform automation API. Choose ImageGrab when the Pillow capture path fits your platform and application.
Full-screen versus region capture
| Choice | Use it when | Trade-off |
|---|---|---|
| Full screen | You need every monitor pixel or a complete desktop snapshot. | More memory and a larger image to render. |
| Region | You know the rectangle containing the application or control. | Coordinates must be correct for the current display layout. |
One-shot versus periodic refresh
- One-shot: capture before creating the widgets, then call
mainloop(). - Periodic: schedule refreshes with
root.after(milliseconds, callback); replace the retained PhotoImage each time. - Slow captures: perform capture work away from the UI thread only when your platform and design permit it, then marshal the result back to Tk’s main thread. Tkinter widget operations should stay on the main thread.
Common errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
ModuleNotFoundError: pyautogui or PIL |
Packages are missing from the active interpreter. | Run python -m pip install pyautogui pillow using the same Python executable that launches the app. |
| Linux capture fails | PyAutoGUI cannot find the required screenshot utility or desktop permission. | Install scrot, run inside a graphical session, and check desktop permission settings. |
| Window opens but image is blank | The PhotoImage object was garbage-collected. | Assign it to label.image, canvas.image, or an instance attribute such as self.photo. |
TclError: image ... doesn't exist |
The Tk image belongs to a destroyed interpreter or its reference was lost. | Create the PhotoImage with the live root as master and retain it until the widget is destroyed. |
| UI freezes during refresh | Screenshot work or a sleep call blocks Tk’s event loop. | Use after for scheduling; avoid a blocking loop in the callback. |
| Only part of a multi-monitor desktop appears | Desktop coordinate spaces and OS capture permissions differ. | Test each monitor’s coordinates, use an explicit region, and verify OS screen-recording permissions. |
| Image is too large for the window | The capture dimensions exceed the display area. | Resize with Pillow’s thumbnail for the preview or display it in a scrollable Canvas. |
Performance, reliability, and memory notes
- Capture only the region you need to reduce pixel copying and conversion work.
- Resize previews before constructing
PhotoImage; retain the full-size Pillow image only when required. - Do not create a new root window for every refresh. Create one Tk root and update its widgets.
- Keep exactly one current PhotoImage reference per displayed image; discard old references after replacement so memory can be reclaimed.
- Handle capture exceptions inside a periodic callback and schedule the next attempt, so a transient desktop error does not terminate the UI.
- Stop scheduled callbacks when the window closes if your application owns other resources.
Or skip the browser setup
If your input is a web URL rather than the local desktop, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one request. The API can remove cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for all capture options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);
Every plan includes the features. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Why does a Tkinter image disappear after the function returns?
Tk does not retain the Python reference to the PhotoImage. Store it on the widget or an object attribute.
Can I show a JPEG or WebP directly in a Label?
Load or create it with Pillow and convert it through ImageTk.PhotoImage; native Tk support is narrower.
Should I use Label or Canvas?
Use Label for one straightforward image. Use Canvas for scrolling, precise positioning, drawings, or overlays.
How do I capture a browser page instead of my desktop?
Use a browser automation workflow for a local, interactive page, or call ScreenshotNeo with the page URL when you need a rendered web capture without managing a browser.


