Scrolling screenshots that follow you, in any direction

In most tools "scrolling capture" means one thing: the program scrolls down for you until it hits the bottom. kuaika inverts that. You scroll — with the wheel, with a scrollbar, up, down, sideways, back over ground you already covered — and it watches, placing every settled frame at the right position on a growing canvas. Chat histories, spreadsheets wider than the screen, and design mockups several screens tall all come out as one image.

Last updated 2026-09-09

Why most scrolling capture only goes down

The classic implementation is a loop: inject a scroll event, grab a frame, inject another scroll event. The program owns the direction and you just watch it happen. That design carries two limitations that are impossible to work around from the outside.

The first is that it is vertical only. A table, a timeline or a Gantt chart that runs off the right edge of the screen simply cannot be captured, because there is no code path that scrolls sideways. The second is that the order is fixed: if you realise halfway through that you needed a bit more context above, you start over.

kuaika moves the initiative to the other side. You scroll; the program observes and stitches. The control bar still has the four auto-scroll toggles (down, up, left, right) and they stop by themselves when they reach an edge — but that is a convenience, not the only route through the feature.

The consequence is that stitching and scroll direction are fully decoupled. Whichever way you scroll, that is the way the canvas grows; and anything you scroll back over lands in the position it already occupies rather than being appended twice.

How two-dimensional stitching actually works

The hard part of stitching is not blending two images together. It is answering the question "how far did the second frame move relative to the first" reliably enough that a hundred frames do not drift. The matcher that answers it now lives entirely in native C and takes 50 to 150 milliseconds per step. It was rewritten because of one specific report: "scrolling down gives me a scrambled stitch, but scrolling up the same page comes out fine." Everything below came out of chasing that down.

  • Still anchors: matching only happens at the instant the content settles, when the frame-to-frame difference is near zero. Tracking a smooth scrolling animation frame by frame looks more continuous but is an error accumulator — the longer you scroll, the further it drifts.
  • Dense pixels decide, descriptors only nominate. The old approach picked the peak by voting with a five-point cross descriptor around sparse feature points. On templated, repetitive content — lists, tables, chat logs — a peak that is off by exactly one row scores just as well on those descriptors as the true one, and scrolling down is precisely where you meet that content. Descriptors now only propose candidates; the winner is chosen by comparing content pixels directly.
  • Two metrics score every candidate, and they catch different lies. Differences are counted only on high-energy rows of the anchor and only at pixels the edge mask calls content. The clean-row ratio handles sticky headers: a frozen header makes even the "nothing moved at all" offset accumulate a handful of perfectly matching rows, but it reaches 32% where the true offset reaches 50%. The mean handles template twins: an off-by-one-row offset can tie on clean rows, yet a few glyphs always differ, and the mean notices.
  • The stride was measured, not guessed. Scrolling happens in whole pixels and the metric is a ratio, so the aligned peak is only one sample wide — one pixel off and the score falls to 0.7. So each axis gets its own stride-1 sweep (pure vertical and pure horizontal cover almost everything real), with a stride-4 two-dimensional sweep behind them for diagonal scrolling.
  • Candidate suppression has to replace, not just reject. An off-by-one neighbour that arrives first scores badly but still beats whatever is last on the shortlist, so it takes the slot and locks the precise peak out. That is exactly how a one-pixel-wrong displacement once beat the correct one.
  • Refinement changes domain as it narrows: find the basin on the blurred image, which tolerates one or two pixels of misalignment, then return to the original pixels to settle the last one. The score is recomputed at each level rather than carried down, so a tie in the blurred domain cannot decide the final answer.

None of that is verified by eye. test/long-stitch.test.cjs renders a long page off-screen as ground truth, crops viewport frames at known offsets and feeds them to the matcher in pairs. Ten assertions pin down all four directions, steps from 55 to 220 pixels, a sticky header, the ambiguity contract on periodic content, rejection of both non-overlapping and unrelated frames, and a single match staying under 300 milliseconds.

Chat logs, wide tables and long pages

  • Chat history: draw the selection around the conversation area, enter scrolling capture, then wheel upwards through the backlog. Overshot? Scroll back down a little — the repeated stretch lands back in the same place instead of being stitched twice.
  • Wide tables: drag the horizontal scrollbar, or hold Shift and use the wheel. Horizontal stitching is the same code path as vertical; there is no mode to switch.
  • Long web pages: the down arrow on the control bar auto-scrolls and stops at the bottom on its own. If you want a horizontal stretch in the middle, switch direction whenever you like.
  • Finishing: nothing is composed until you press Done. The result goes to the clipboard and can also be saved as a PNG. Until then you can always keep adding.

When stitching does not work, we say so

Three situations are genuinely outside what this approach can do, and it is more useful to name them than to pretend otherwise.

The first is strongly periodic content: table rules, evenly spaced list items, regular stripes. At an offset that is a whole multiple of the period, every single pixel lines up and the result is still off by an entire row — the two readings are physically indistinguishable, not merely hard to tell apart. The matcher reports ambiguity there and the status line says it could not connect. It will not guess, and the threshold is deliberately tight: scrolling back a little and trying again beats an image that looks right and is quietly wrong.

The second is content that changes while you scroll: an auto-playing video, a live-updating dashboard, a page whose lazy-loaded images pop in late. The matcher assumes the pixels it saw a moment ago are still there somewhere; when they are not, it says so on the status line instead of quietly stitching garbage.

The third is a scroll step so large that the overlap between two settled frames falls below what the search radius can find. Scroll back a little and it reconnects. The status line tells you the current canvas size continuously, so you can see it growing and notice immediately if it stops.

Keys and entry points

Key / entry pointWhat it does
Ctrl+Shift+AStart a region capture; the scrolling button lives on the toolbar that appears
Toolbar: scrolling captureEnter scrolling capture with the current selection as the viewport
Wheel / scrollbar / touchpadScroll it yourself, any direction, any order
Arrow buttonsAuto-scroll in one of four directions; press again to stop, and it stops at an edge on its own
DoneCompose and export: clipboard, plus optional save as PNG
CancelDiscard the canvas and leave without writing anything

Frequently asked

Can it capture horizontally, not just down the page?
Yes. Horizontal and vertical use the same matcher and the same canvas; there is no separate mode. Wide tables and timelines are the main reason the feature was built this way.
What happens if I scroll back over something I already captured?
It lands back in the position it already occupies. The canvas is addressed in two dimensions, so a region you revisit is overwritten in place rather than appended.
Does it work in any application, or only in a browser?
Any application. Capture happens at the screen level through native GDI, so it does not depend on a browser extension or on the app exposing a document model.
Is there a limit on how large the stitched image can get?
Yes: the working canvas is capped at 80 million pixels. When you reach it the status line says so and asks you to press Done — it will not silently stop stitching or start dropping frames.
Why did scrolling down stitch badly while scrolling up was fine?
That was the old matcher picking its peak by descriptor vote. On templated, repetitive content a candidate that is off by exactly one row scores as well as the correct one on those descriptors, and scrolling down is where you meet that content most often. It was fixed in v0.0.31: the matcher was rewritten in native C, peak selection went back to comparing content pixels directly, and descriptors now only nominate candidates.

Read next