DEV Community

Cover image for Why capturing a long screenshot on Android is harder than it looks
Codnet Software Company
Codnet Software Company

Posted on AI-assisted

Why capturing a long screenshot on Android is harder than it looks

Every phone can screenshot what is on the screen. None of them, out of the box, can screenshot what is below it — the rest of the chat, the rest of the article, the rest of the settings page you are trying to send to someone. You take five overlapping screenshots and apologise for the seams.

I spent a while building an app that does the whole scroll in one image — LongShot — and most of that time went not into the feature people ask for but into the failure modes nobody sees. Here is what actually makes this hard, and how the app handles each part. No framework does this for you; you assemble it out of parts that were built for other jobs.

There is no "capture the scroll" API

The first instinct is to look for a system call that returns a tall bitmap. There isn't one. What Android gives a normal app is MediaProjection — the same API a screen recorder uses. You get frames of what is currently visible, at the moment it is visible, and nothing else.

So a scrolling screenshot is not a capture. It is a reconstruction: record the screen while the user scrolls, then stitch the frames back into the single tall image they would have formed if the screen were long enough to hold them.

The deliberate cost of using MediaProjection instead of an AccessibilityService — which can read and drive other apps — is that the app cannot scroll for you. You scroll; it records. That trade is the whole reason the app can honestly say it never reads the content of your other apps: it has no mechanism to.

The stitch is the entire problem

Say the user scrolls through a chat. You have thirty frames, each 1080×2340, overlapping by some unknown amount because a human thumb does not move at a constant speed. Stitching them means, for every new frame, answering one question: how far down has the content moved since the last frame I kept?

Get it right and the frames weld into one seamless column. Get it wrong by four pixels and you either duplicate a band of content or drop one, and the result has a visible tear or a line of text that appears twice.

The naive approach — template-match the whole frame against the previous one — falls apart the moment anything on screen moves independently of the scroll: a playing video, an animated sticker, a "typing…" indicator. Those regions match at the wrong offset and drag the whole estimate with them. The capture that looks hardest (a feed full of autoplaying video) is exactly the one a whole-frame match handles worst.

What works is consensus over rigid slices: cut each frame into horizontal strips, ask each strip independently how far it moved, and trust the offset the majority of strips agree on rather than any single region. A video playing in the middle of the screen becomes one dissenting slice outvoted by the static ones around it. The text keeps its place; the video's private motion is ignored.

Memory is the quiet killer

A full capture of a long chat is routinely 1080×18660 pixels. At 4 bytes a pixel that is ~80 MB for one bitmap — and while you are building it you are also holding the frame buffer, the display copy, and whatever the editor needs. Decode one thing carelessly and a 2 GB device is out of memory before it finishes.

Two things keep it alive:

  • The finished image is never held whole. It is streamed to a PNG on disk row by row, so an arbitrarily long capture saves at full resolution without ever existing as a single in-memory bitmap.
  • Anything that decodes a saved capture back — for a thumbnail, for text recognition — does it a region at a time with subsampling, so the cost is bounded by the tile, not by how tall the capture happens to be.

I learned the byte-vs-pixel distinction the hard way: a memory ceiling written in pixels while the actual cost is bytes will pass every test on short captures and OOM on the long ones users actually take.

The on-device extras, and one honest limit

Once the pixels are on the device and staying there, a few things follow for free — because "no network" is not a slogan here, it is enforced: the app declares no INTERNET permission at all, and both INTERNET and ACCESS_NETWORK_STATE are stripped from the merged manifest, including the copies the bundled ML Kit recogniser would otherwise pull in. The operating system refuses any socket, so nothing recognised from your screenshot can leave the device even if a library tried. You do not have to trust me on that — run aapt dump permissions on the APK.

On top of that:

  • Copy text lifts the words out of a capture with on-device recognition, in reading order — banded into lines, sorted within each band, paragraph breaks inferred from the gaps.
  • Hide personal data finds card numbers (Luhn-verified), IBANs (mod-97-verified), emails and phone numbers, and paints them out — genuinely out, not blurred, so the covered pixels are gone from the exported file rather than merely scrambled.
  • Text, in 13 typefaces — 7 Arabic and 6 Latin — with free rotation and resizing, for annotating over the shot.
  • Crop, PDF/PNG export, share.

The honest limit: the text recognition is Latin script only. ML Kit ships no Arabic model — the artifact literally does not exist — so Arabic and other non-Latin scripts are not read. The app says so on screen rather than returning a confident empty result. If you build on ML Kit and expected Arabic, that is the thing to know going in.

If you are building something similar

  • Don't look for a scroll-capture API; there isn't one. Reconstruct from MediaProjection frames.
  • Estimate scroll offset by consensus across rigid slices, not whole-frame matching, or independently-moving content will wreck the alignment.
  • Budget memory in bytes, not pixels, and never hold the finished image whole.
  • If your privacy story is "no network," enforce it by removing the permission, not by promising — and check the merged manifest, because your dependencies vote on it too.

LongShot is free on Google Play, no ads, no accounts, no analytics: https://play.google.com/store/apps/details?id=com.codnet.longshot

Happy to go deeper on any part of the stitch in the comments — the slice-consensus stage especially has more corners than fit here.

Top comments (0)