ScreenshotNeo

BlogHow-to

How to Join Two Website Screenshots Together

Learn how to join two website screenshots vertically or horizontally, remove overlap, preserve readable text, and automate full-page captures.

By the ScreenshotNeo team1 October 20266 min read

To join two website screenshots, make their widths identical, crop repeated content from the top of the second image, then place the images in reading order. Stack them vertically when the second capture continues the first page; place them side by side when comparing two states or pages. Export PNG at native size when text must remain sharp.

Choose the right workflow

Situation Best method
Two saved images that continue one page Crop the overlap and vertically stitch them
Two states or pages to compare Align widths and join horizontally with a small separator
You need the whole live page Use a full-page scrolling capture, then inspect sticky elements and lazy-loaded content

A basic joiner appends pixels; it usually does not detect matching content. ImageCombiner documents vertical, horizontal, and grid layouts with spacing and background controls (documentation). For a live page, Snagit supports vertical and horizontal scrolling capture (Snagit website capture). Urlbox documents both scroll-and-stitch and native full-page modes, with waiting and lazy-load controls (Urlbox screenshots).

Prepare the screenshots

  1. Capture both images at the same browser or viewport width and device scale.
  2. Remove browser chrome, toolbars, and unrelated margins.
  3. Check the intended reading order before editing; filenames may not describe sequence.
  4. Find the first row in the second image that is new content. Include enough context to verify the seam, but remove repeated headers, fixed toolbars, and other duplicated bands.

Join screenshots manually

Vertical continuation

  1. Crop the repeated strip from the top of the later screenshot.
  2. Place the first image at x=0, y=0 and the cropped second image directly below it with no gap.
  3. Inspect the seam at 100% zoom. If a line, shadow, or sticky header repeats, crop a few more pixels and check again.

Horizontal comparison

  1. Crop both images to the same height and align their top edges.
  2. Place the second image to the right of the first.
  3. Add a narrow neutral separator only when the two images represent separate states; omit it for a continuous page.

Keep placement at native size. Resizing can soften small interface text. PNG is usually the safest export for documentation; JPEG or WebP can reduce file size when slight compression is acceptable. These practices are also recommended by ImagePilot and StitchPic (ImagePilot guide, StitchPic guide).

Automate the join with Python

The script below joins two files vertically or horizontally. For a vertical join, set --overlap to the number of pixels repeated at the top of the second image. It validates widths and preserves the source pixel dimensions.

from PIL import Image
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('first')
parser.add_argument('second')
parser.add_argument('output')
parser.add_argument('--direction', choices=['vertical', 'horizontal'], default='vertical')
parser.add_argument('--overlap', type=int, default=0)
parser.add_argument('--gap', type=int, default=0)
args = parser.parse_args()

first = Image.open(args.first).convert('RGBA')
second = Image.open(args.second).convert('RGBA')
if args.direction == 'vertical':
    if first.width != second.width:
        raise SystemExit(f'Width mismatch: {first.width} vs {second.width}; crop or resize first.')
    if not 0 <= args.overlap <= second.height:
        raise SystemExit('overlap must be between 0 and the second image height')
    second = second.crop((0, args.overlap, second.width, second.height))
    canvas = Image.new('RGBA', (first.width, first.height + args.gap + second.height), (255, 255, 255, 0))
    canvas.paste(first, (0, 0))
    canvas.paste(second, (0, first.height + args.gap))
else:
    if first.height != second.height:
        raise SystemExit(f'Height mismatch: {first.height} vs {second.height}; crop or resize first.')
    canvas = Image.new('RGBA', (first.width + args.gap + second.width, first.height), (255, 255, 255, 255))
    canvas.paste(first, (0, 0))
    canvas.paste(second, (first.width + args.gap, 0))

canvas.save(args.output, format='PNG')

Install Pillow with python -m pip install pillow. Example commands:

python join_screenshots.py top.png bottom.png joined.png --direction vertical --overlap 96
python join_screenshots.py before.png after.png comparison.png --direction horizontal --gap 12

If the files have different widths, crop them to a shared content width before running the script. Avoid arbitrary stretching because it changes text proportions.

Command-line alternatives

ImageMagick can append images after you have removed the overlap:

magick top.png bottom-cropped.png -append joined.png
magick before.png after.png +append comparison.png

Use -append for vertical output and +append for horizontal output. Add a controlled background or border only when the images are a comparison.

Capturing a live page before joining

A full-page browser capture can replace manual assembly, but behavior varies with dynamic content, embedded frames, sticky elements, and lazy-loaded images. Scroll-and-stitch implementations may wait between scrolls, detect fixed elements, and trigger lazy loading; native browser capture is faster but is not reliable on every site. Treat the first result as something to inspect, especially on pages with iframes or changing content. A Reddit discussion describes a scrolling iframe case where a suggested Firefox whole-page capture did not work for that user (discussion).

Or skip the browser setup

ScreenshotNeo captures a live URL through one GET request, including full-page shots with lazy images loaded. It accepts 63 options such as viewport and device presets, retina scale, element selectors, dark mode, custom CSS and JavaScript, click actions, wait conditions, blocked resources, headers, cookies, user agents, timezone, geolocation, resizing, caching, signed links, asynchronous webhooks, bulk capture, PDFs, HTML/CSS rendering, and usage reporting. Its clean-shot processing accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled.

cURL:

curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
open('shot.webp', 'wb').write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for parameter names and response headers. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; inspect X-Page-Verdict and X-Billed to see the result. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

ScreenshotNeo is the first service to try when you need an API workflow because it removes consent clutter before capture, bills only clean shots, and has a $5 paid plan for 3,000 shots. The Free plan includes 1,000 shots per month without a card. Create a free ScreenshotNeo account.

Troubleshooting

Symptom Cause Fix
A repeated band appears at the seam The later image still contains overlap or a fixed header Increase the crop on the later image; inspect at 100%
Edges do not line up Different viewport widths, device scale, or crop boxes Recapture at one width or crop both images to the same pixel width
Text is fuzzy JPEG compression or unnecessary resizing Export PNG and place at native size
Sticky navigation repeats The capture tool included a fixed element on every scroll Hide it, crop repeated copies, or use a capture mode with sticky-element handling
Lazy images are missing The page was captured before images loaded Wait for a selector, delay, or network idle; scroll before capture and verify the result
An iframe is blank Cross-origin or dynamically rendered content Capture the frame separately when permitted, or use the page’s own export
Python reports a size mismatch The source images have different dimensions Crop to a shared width or height; do not stretch without a deliberate reason

Performance, reliability, and cost

  • For local joins, memory use is roughly the output pixel count multiplied by the number of channels; very tall pages may require a 64-bit process and sufficient RAM.
  • Keep the source width stable. Recapturing is often faster than repairing subpixel alignment later.
  • Use PNG for text-heavy documentation and WebP or JPEG for lighter previews.
  • For repeatable live captures, wait on a meaningful selector or network idle instead of relying only on a fixed delay.
  • Cache stable source captures and join locally when you need many variants. With ScreenshotNeo, choose a cache TTL and monitor X-Page-Verdict and X-Billed; cache hits are not billed.
  • Review privacy requirements before uploading sensitive screenshots to a hosted service; local editors keep processing on your machine, while hosted capture sends the target page to a remote browser.

FAQ

Should I overlap the images when joining?

Only when the second capture contains repeated pixels. Crop the repeated strip first; do not blend two copies of the same header.

Is vertical or horizontal joining better?

Vertical is for one continuous page. Horizontal is for comparing separate pages or states.

Can I join screenshots with different widths?

Yes, after cropping them to a shared width. Resizing is possible but can make text softer or distort layout proportions.

Why did a full-page capture miss content?

Dynamic pages may need scrolling, a wait condition, or lazy-load handling. Embedded frames and sticky elements can also behave differently across capture engines.

What format should I publish?

Use PNG when interface text must stay crisp; use WebP or JPEG when file size matters more than lossless detail.