jig

QR Data Transfer

Runs entirely in this tab — nothing is uploaded

Loading…


Move text or a file between two devices with nothing but a screen and a camera. No network, no pairing, no server.

What this tool does

One device shows an animated stream of QR codes; the other reads it with a camera. That’s the whole channel. No network, no pairing, no account, no server — the two devices never talk to anything except each other’s optics.

That makes it useful in exactly the situations where everything else fails: an air-gapped machine, a locked-down corporate laptop, a phone with no signal, a VM with no shared clipboard, or any moment where you’d rather not hand a private key to a “quick file transfer” website.

Both halves run entirely in your browser. The sender never uploads the file; the receiver’s camera feed never leaves the device. There is no server to send it to.

Why it survives missed frames

A camera pointed at a screen misses frames constantly — refresh timing, autofocus, glare, a hand that moved. The naive fix is to loop the sequence and wait for the gaps to fill, which means one missed frame can cost you a whole cycle.

This tool uses RaptorQ, a fountain code standardised in RFC 6330. Instead of numbered chunks that must all arrive, the sender emits a pool of packets where any sufficiently large subset reconstructs the original. Order doesn’t matter. Which specific packets you caught doesn’t matter. You can start scanning halfway through and still finish.

Measured on this implementation, with 20 KB of incompressible data at v20 density:

Repair packets Packets emitted Loss it survives
20% 29 17%
50% (default) 36 33%
100% 48 50%
200% 72 67%

The decoding overhead is zero in every case: receiving exactly as many packets as the source needs is enough. There is no “collect 5% extra” tax, which is what separates RaptorQ from the simpler LT codes most QR transfer tools use.

Because the sender loops its packet pool, a lower repair setting doesn’t make a transfer impossible — only slower. Packets you missed come around again.

Speed, honestly

Throughput is bytes per frame × frames per second, minus whatever your camera drops.

Density Bytes per frame Nominal at 15 fps
v10 240 B 3.5 KB/s
v15 488 B 7.1 KB/s
v20 (default) 824 B 12.1 KB/s
v25 1,240 B 18.2 KB/s
v30 1,696 B 24.8 KB/s

Those are ceilings before any loss, and the higher densities only reach them with a good camera and a bright, steady screen. Real transfers run below this.

Decoding is not the constraint. Measured on this implementation, ZXing reads one v20 frame from a 1280×720 image in 3.1 ms — over 300 frames per second, roughly a hundred times faster than the display can show them. Everything you lose is lost between the screen and the sensor, not in the decoder.

That is why the speed options are all divisors of 60. Frames advance on display refresh boundaries, so each one is shown for a whole number of refreshes. A rate like 23 fps would make some frames last two refreshes and others three, and the camera is far more likely to catch one mid-redraw — a torn code decodes as nothing at all.

There is no size limit — but do the arithmetic before starting. The tool shows you the estimate up front: a 12 MB file is a genuinely long sit. This is a tool for keys, config snippets, certificates, recovery phrases, and notes; it will move a photo, but you won’t enjoy it.

When a transfer stalls

Two knobs stay live while sending, and they are the only two that matter:

Speed — lower it. If the receiving camera can’t keep up, frames go by unread and the fragment grid stops filling. 5 fps with everything read beats 30 fps with two thirds missed.

Density — lower it. Denser codes carry more per frame but need more pixels on the sensor. If the fragment grid is barely moving, drop to v15 or v10 and move the camera closer.

Changing Speed takes effect immediately. Changing Density safely rebuilds the packet stream at the selected QR size, just as the mobile app changes presets; point the receiver at the new stream and continue scanning. This avoids ever forcing an already-large packet into a smaller QR code.

Use fullscreen on the sending device. A bigger QR is a more readable QR, and it costs nothing.

Reading the fragment grid

The receiver shows one cell per packet, lit as it arrives. This is deliberately not a percentage bar: the pattern tells you why a transfer is struggling. Cells filling steadily means it’s working and you just need to wait. Cells barely lighting means the camera isn’t resolving the code — get closer, add light, or lower the density.

Frames that fail their checksum are counted separately and discarded. Seeing a nonzero discard count is normal; the camera reads plenty of garbage between good frames.

What gets transferred

Text goes across as text. Files carry their name and MIME type alongside the bytes, so the receiving side can offer a correctly named download.

Everything is compressed first when that helps (Gzip by default; Brotli and no compression are available in Settings), which is why a repetitive text file often transfers far faster than its size suggests. Incompressible data — archives, images, encrypted blobs — transfers at the raw rate.

Integrity is checked at two levels: an OptiDrop QF4 revision 2 CRC32 plus stream fingerprint on every packet, and a full checksum on the reassembled result. A corrupted or stale-stream frame is dropped rather than trusted.

Browser support

The sender works anywhere with a canvas. The receiver needs camera access, which browsers only grant over HTTPS.

On iOS, only Safari and browsers using its engine can open the camera. If the camera button does nothing in a third-party iOS browser, that’s the platform, not this page.

Related