English | 中文
Automatically find the seams in a stitched long image (vertically/horizontally combined photos, photo collages, long screenshots) and split it back into the individual pictures — losslessly, without re-encoding or resizing.
It detects the cut lines for you, lets you review and tweak them by mouse, and saves each piece at the exact original format, resolution and dimensions.
Keywords: split long image · unstitch photos · photo collage splitter · split stitched / combined photos · lossless JPEG crop · split long image · unstitch photos
Grab long-image-splitter.exe from the Releases page
and double-click it — no Python needed (~30 MB, Windows 64-bit). Lossless JPEG
splitting works out of the box (jpegtran is bundled). The first launch lets you
pick the interface language (English / 简体中文); you can switch any time later.
Prefer running from source, or on macOS / Linux? See Install below.
pip install -r requirements.txtRequires Python 3.8+ with Tkinter (bundled with the standard CPython installer).
The single-file executable is built with PyInstaller from the included spec. Build in a clean pip venv (not a conda base env, which would balloon the binary to ~250 MB by pulling in MKL):
python -m venv .venv-build
.\.venv-build\Scripts\python.exe -m pip install -r requirements-build.txt
.\.venv-build\Scripts\python.exe -m PyInstaller --noconfirm --clean long-image-splitter.spec
# -> dist\long-image-splitter.exe (~30 MB)The spec bundles vendor/jpegtran and the Tcl/Tk runtime automatically.
python gui.pyThe interface ships in English and Simplified Chinese. You are asked once on first launch (defaulting to your system language) and can switch live anytime from the Language picker at the top of Area A; the choice is remembered.
-
Left (Area A) — parameters & save settings
- Three presets
strict / medium (optimum) / loose, plus sliders for fine tuning. "↻ Recompute current" re-runs detection on the current image (tuning otherwise only affects the next image). - Save settings: filename template (default
{stem}_{index:02d}→name_01,name_02…; placeholders{stem}{index}{label}); output location (subfolder / same folder / custom). - After a successful save: do nothing / delete the original / move it to
a
_processed_long_imagessubfolder (automatically skipped during batch runs so nothing is processed twice). - Force lossless (JPEG): when on, cut lines snap to the lossless MCU boundary for zero quality loss; when off, cuts land on the exact pixel you placed (with a tiny re-encode cost). PNG / lossless WebP cut losslessly anywhere, so this option doesn't affect them.
- Three presets
-
Center / right (Area B) — review, all by mouse
- Drag a line to move it; click empty space to add one; click the
×at a line's head to delete it (toggle horizontal / vertical for new lines up top). - Click a line to select it (highlight), then nudge it with the arrow
keys; hold to repeat;
Escto deselect. - Each region has a ✓/× checkbox in its top-left corner — all are saved by default; click to mark a region as skip (excluded from output).
- "Accept & Save" (bottom-right) writes every enabled piece.
- Drag a line to move it; click empty space to add one; click the
-
Open Folder — recurse through all images and send only the detected long images to review (non-long images are skipped). Open File(s) — multi-select; all chosen files go to review whether or not they look like a long image.
-
The status bar shows how many long images remain in the queue (with scan progress until the scan finishes).
-
While you review one image the next few are decoded in the background, so the next image appears with no latency after you accept. Area B resets when a folder is finished.
python split.py <IMAGE_OR_DIR> [options]
# Preview detected cut rows without writing files
python split.py stitched.jpg --preview
# Split one image (output to a stitched_split/ subfolder)
python split.py stitched.jpg
# Choose an output directory
python split.py stitched.jpg --output-dir D:\output
# Batch-process a whole folder (recursively)
python split.py "D:\Photos"
# Require larger minimum piece height (fewer micro-cuts)
python split.py stitched.jpg --min-height 400
# Demand strictly-lossless JPEG; error out if jpegtran is missing
python split.py stitched.jpg --lossless-only| Option | Default | Meaning |
|---|---|---|
--output-dir PATH |
<stem>_split/ next to source |
Output directory |
--min-height PX |
120 (plus a "≥5% of height" rule) | Absolute floor for piece height |
--preview |
— | Print cut rows only, write nothing |
--lossless-only |
— | Error instead of falling back when jpegtran is absent |
--jpegtran PATH |
auto-detected | Point at a specific jpegtran binary |
Never resizes — the pieces tile the source exactly: a 400×2000 image cut twice gives three pieces whose heights sum to 2000, all 400 wide; horizontal cuts likewise preserve width.
| Format | How it's cut | Quality |
|---|---|---|
| PNG | direct crop | bit-exact lossless |
| JPEG piece with all edges on the MCU grid | jpegtran lossless crop | no re-encode, zero loss |
| JPEG piece with an off-grid edge | Pillow, original quant tables kept | tiny re-encode loss, exact size |
| WebP (lossless) | direct crop | bit-exact lossless |
| WebP (lossy) | Pillow re-encode | re-encode, with a warning |
JPEG can only be cut losslessly on the MCU grid (a multiple of 8 or 16 px). This tool is WYSIWYG by default — a cut lands on the exact pixel you placed it on, never silently shifted. Losslessness is therefore opportunistic, per piece:
- Force-lossless OFF (default): each cut is exact. At save time each piece
is judged on its own — pieces whose edges all land on the MCU grid are cropped
losslessly with jpegtran; only the pieces touching an off-grid cut take the
(tiny) re-encode. E.g. cuts
[1504, 2001](first aligned, second not): the first piece is lossless, the other two are re-encoded. - Force-lossless ON: lines snap to the MCU boundary as you drag/add them (visible in the UI, still WYSIWYG); every piece is zero-loss and the image's right/bottom edge pixels are preserved, so width/height never shrink.
A Windows jpegtran ships with the app and is used automatically for JPEG saves
— no setup required. If the bundled binary is missing it falls back to a
quant-table-preserving Pillow re-encode (tiny loss, exact size).
To use a different binary:
python split.py stitched.jpg --jpegtran "C:\path\to\jpegtran.exe"Non-Windows: install jpegtran (it ships with libjpeg-turbo) via your package manager and the app will find it on
PATH.
A stitch seam is a digitally composited edge with three signatures that camera-captured edges (horizons, table edges, window frames…) lack. All three must hold for a row/column to be called a seam:
- Coverage — the brightness step spans most of the image's width.
- Sharpness — the transition completes within two adjacent rows; real optical edges are blurred across more rows.
- Straightness — every stepping column does so on the same row.
A separate pass handles low-contrast seams (e.g. white-on-white) and solid separator bars.
Accuracy on the test set: long images that contain a straight boundary are detected 89/89 (100%), 92/100 overall; the misses are rounded-corner decorative templates that have no straight boundary at all. False positives on non-long images are ~5%, mostly chat screenshots with embedded images whose edges are physically indistinguishable from stitch seams.
Tip: preview with --preview, then adjust --min-height if needed.
The bundled jpegtran comes from libjpeg-turbo
(see vendor/jpegtran/LICENSE.md).