Skip to content

Repository files navigation

long-image-splitter

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

Download (Windows, no install)

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.

Install

pip install -r requirements.txt

Requires Python 3.8+ with Tkinter (bundled with the standard CPython installer).

Build the Windows .exe

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.

GUI (recommended)

python gui.py

The 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_images subfolder (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.
  • 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; Esc to 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.
  • 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.

Command line

python split.py <IMAGE_OR_DIR> [options]

Examples

# 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

Options

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

Saving (splitting) strategy

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.

About jpegtran (bundled)

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.

How detection works

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:

  1. Coverage — the brightness step spans most of the image's width.
  2. Sharpness — the transition completes within two adjacent rows; real optical edges are blurred across more rows.
  3. 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.

License

The bundled jpegtran comes from libjpeg-turbo (see vendor/jpegtran/LICENSE.md).

About

Automatically detect stitch seams to split long images, photo collages & long screenshots into individual pictures (lossless JPEG) | 自动检测拼接缝,把长图、拼图、长截图无损拆分为单张照片

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages