A lightweight Windows system-tray application that monitors the clipboard for new screenshots and automatically stretches them to a 16:9 aspect ratio. Runs silently in the background — take a screenshot and paste it already in 16:9.
- Launch the app — a tray icon appears in your system tray
- Take a screenshot (Win+Shift+S, Snipping Tool, Print Screen, etc.)
- The app detects the image on the clipboard
- If it's not already 16:9, it stretches it to 16:9 and puts it back on the clipboard
- Paste (Ctrl+V) anywhere — the image is now 16:9
Images that are already 16:9 are left untouched.
| Technology | Version | Purpose |
|---|---|---|
| Python | 3.12+ | Runtime language |
| Pillow | >=10.0 | Image processing and Lanczos resampling |
| pywin32 | >=306 | Windows clipboard access (CF_DIB format) |
| pystray | >=0.19 | System tray icon and menu |
Platform: Windows only (10/11)
Single-file application (main.py) with a two-thread model:
┌─────────────────────────────┐
│ Main Thread │
│ pystray event loop │
│ (system tray icon) │
│ │
│ ┌─── Tray Icon ───┐ │
│ │ Stretch16by9 │ │
│ │ └─ Quit │ │
│ └─────────────────┘ │
└──────────┬──────────────────┘
│
│ spawns daemon thread
▼
┌─────────────────────────────┐
│ Monitor Thread │
│ │
│ poll clipboard (250ms) │
│ │ │
│ ▼ │
│ new image detected? │
│ │ │
│ ┌──┴──┐ │
│ │yes │ no → sleep │
│ └──┬──┘ │
│ ▼ │
│ already 16:9? │
│ ┌──┴──┐ │
│ │no │ yes → cache hash │
│ └──┬──┘ │
│ ▼ │
│ stretch → write CF_DIB │
│ debounce 600ms │
└─────────────────────────────┘
| Component | Responsibility |
|---|---|
get_clipboard_image() |
Read image from clipboard via PIL.ImageGrab.grabclipboard() |
set_clipboard_image(img) |
Write PIL Image back as CF_DIB (24bpp RGB or 32bpp BGRA) |
image_hash(img) |
MD5 fingerprint of downscaled 64x64 bytes for change detection |
monitor_loop() |
Background thread: poll → detect → stretch → write-back loop |
create_icon_image() |
Generate tray icon programmatically (no external assets) |
on_quit() |
Graceful shutdown handler |
- Main thread runs the pystray event loop (tray icon)
- Daemon thread runs
monitor_loop() - Shared state (
last_hash) protected bythreading.Lock() - Debounce timer (
DEBOUNCE_SECONDS= 0.6s) prevents re-processing own output
- Windows 10 or 11
- Python 3.12+
git clone https://github.com/Ian-bug/ScreenshotStretchTo16by9.git
cd ScreenshotStretchTo16by9
pip install -r requirements.txtpython main.pyThe app starts as a system-tray icon. Right-click the icon and select Quit to stop.
No configuration needed — works out of the box.
ScreenshotStretchTo16by9/
├── main.py # Entry point — entire application
├── requirements.txt # Python dependencies
├── AGENTS.md # Development guidelines & architecture docs
├── LICENSE # MIT License
├── README.md # This file
└── .gitignore # Git ignore rules
- Automatic detection — polls clipboard every 250ms for new screenshots
- Smart stretching — resizes to 16:9 using high-quality Lanczos resampling
- Alpha channel support — preserves transparency (RGBA) from Snipping Tool screenshots via 32bpp DIB
- Change detection — MD5 fingerprinting avoids re-processing unchanged images
- Debounce protection — 600ms cooldown after writing prevents infinite loops
- Zero config — no arguments, settings files, or setup required
- Graceful shutdown — right-click tray icon → Quit; daemon thread exits cleanly
python main.pypip install ruff
ruff check main.py
pip install mypy
mypy main.pyNote: The codebase uses
try/exceptguards around optional Windows-only imports (win32clipboard,pystray). These produce static-analysis false positives that are expected and should not be removed.
No test suite exists yet. When creating tests:
pip install pytest pytest-mock
python -m pytest tests/Tests must mock win32clipboard and ImageGrab since this is a Windows-clipboard tool.
- Indent: 4 spaces (no tabs)
- Line length: prefer under 120 characters
- Naming:
snake_casefor functions/variables,UPPER_SNAKE_CASEfor constants,PascalCasefor classes - No comments unless asked — keep code self-documenting
Standard library first, then third-party, then local. Windows-only imports wrapped in try/except ImportError with fallback to None.
- Never use bare
except:— always catch specific types - Always log caught exceptions (never swallow silently)
- Clipboard operations must have fallback
CloseClipboard()in finally/except paths - Monitor loop body fully wrapped in try/except so the daemon thread never crashes
The most error-prone part of this codebase. Rules:
- BITMAPINFOHEADER:
<IiiHHIIiiII= 11 fields = 40 bytes - Pixel rows are bottom-up (iterate
range(h-1, -1, -1)) - 24bpp rows padded to 4-byte boundaries:
stride = (w * 3 + 3) & ~3 - Always little-endian (
<) - Verify
len(hdr)equalsbiSize(40) after packing
Contributions are welcome! Key guidelines:
- Follow the coding standards outlined above and in AGENTS.md
- Windows-only — do not attempt cross-platform support
- Maintain the threading safety patterns (lock on shared state, monotonic timing, debounce)
- Test clipboard operations carefully — a hung clipboard can lock the entire OS session
- Preserve alpha channel handling for RGBA images
This project is licensed under the MIT License.
Copyright (c) 2026 Ian-bug