j-stitch
Scrolling screenshot stitching for Python, implemented in Rust.
Finds the vertical overlap between two consecutive screenshots by hashing each pixel row and running a longest-common-substring match over the hash sequences, then splices the images. Optionally detects whether the second image belongs above or below the first.
The distribution is j-stitch; the module imports as longstitch.
import longstitch
result = longstitch.stitch(png_bytes_1, png_bytes_2)
if result is None:
print("the two images do not overlap")
else:
open("stitched.png", "wb").write(result.png)
stitch() returns None when no overlap is found -- that is a legitimate
outcome, not a failure. Decode and encode failures raise longstitch.StitchError.
Incremental sessions
For a long capture, StitchSession keeps the stitched image inside Rust and
takes raw BGRA frames, so each frame costs the same no matter how long the
result has grown. It matches frames the way stitch() does, but looks for each
frame near the previous one, so scrolling back is tracked instead of trimming
the result, and the result can grow at either end.
session = longstitch.StitchSession(width, height)
for bgra, heading in frames: # bytes, width * height * 4; "down", "up" or None
step = session.push(bgra, heading=heading)
if step is None:
continue # no overlap; the session is unchanged
# step.top is the row of the result where this frame starts
image = session.export() # BGRA bytes, width x session.height
A frame that lands inside what has already been stitched (the user scrolled
back) leaves the result unchanged and only moves step.top. A frame that runs
past the end extends it: the result was cut to step.keep rows, then frame
rows from step.skip on were appended. A frame that runs past the start
extends the top: step.head_cut rows were removed from the top, then the first
step.head_rows frame rows were put in front. So a capture can start in the
middle of a page and grow both ways. Rows already stitched are only replaced
where the result's edge disagrees with the frame (an old fixed header or
footer); a small difference inside the overlap, such as a hover highlight, is
left out.
heading is the direction scrolled before this frame, "down" meaning towards
higher result rows. It decides between places that match equally well; with
None the session follows the direction of the last movement, exposed as
session.last_move. If nothing
credible matches near the previous frame, the whole result is searched, so a
jump such as Home/End is still located.
Rows are first compared pixel for pixel (ignoring at least a tenth of the width on the right, where a scrollbar may sit), so list entries that differ in a few characters are told apart; only if that finds nothing are rows compared by average colour, which tolerates slight rendering differences. A place counts when it is backed by rows that occur once in both the frame and the result; failing that, the match has to cover most of what would overlap and either contain a couple of such rows or be long (entries that repeat exactly, a large blank area). A frame that merely shares a few rows with the result (near-identical entries, blank lines between paragraphs, its fixed header or footer) is reported as no overlap instead of being forced in; scroll back until the view overlaps what has been stitched and it joins again.
session.crop_top() drops everything above the latest frame and
session.crop_bottom() everything below it; both return the number of rows
removed, and later frames keep growing the result as before. Scroll back to
where the capture should start or end, then crop.
Windows x86_64 or ARM64, CPython 3.11+ (abi3). Part of jietuba. MIT licensed.
Metadata
Release files for j-stitch 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| j_stitch-0.5.1-cp311-abi3-win_arm64.whl | CPython 3.11 | abi3 | Windows ARM64 | Details for j_stitch-0.5.1-cp311-abi3-win_arm64.whl |
| j_stitch-0.5.1-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details for j_stitch-0.5.1-cp311-abi3-win_amd64.whl |
Total release size: 1.7 MB
Release files / j_stitch-0.5.1-cp311-abi3-win_arm64.whl
| Download URL | j_stitch-0.5.1-cp311-abi3-win_arm64.whl |
|---|---|
| Size | 848.1 kB |
| Tags | CPython 3.11 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
6a4832224f423e76d9e3b5331a07ee488c65eefc3dcb9b8fdbc6a296a5390745
|
|
BLAKE2b-256 checksum How to use checksums |
1ba4e5a338e3c7ada7b823a8269a3e9e9776fc71c938111a4643767e8d45267f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / j_stitch-0.5.1-cp311-abi3-win_amd64.whl
| Download URL | j_stitch-0.5.1-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 892.9 kB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3c6802d57777a5fafc97cdfd032cc229640b1eae20714d60f2d07873315b9e56
|
|
BLAKE2b-256 checksum How to use checksums |
bb4ed5d53f145c523aa853d0babe7c5dc1aeadae847b594b70295b8bc6f1a242
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for j-stitch 0.5.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.