# Decimen: Transferring Files with Light Using QR Codes and Fountain Codes

Most of the time, sharing a file is a solved problem. Wi-Fi, Bluetooth, AirDrop, and cellular make it feel instant. But strip those away and the problem gets interesting fast. Suppose you need to get a file onto a machine that is deliberately kept off every network, an air-gapped computer, and you do not have a USB stick handy. How do you bridge that gap?

One answer is to send the data as light. [Decimen Optical Transfer](https://github.com/bashalarmistalt/decimen-optical-transfer), a browser-based proof of concept from developer Evan Crawley (who goes by Bash Alarmist), does exactly that. It encodes a file into a rapid stream of animated QR codes, displays them on one screen, and reads them back with another device's camera. **No Wi-Fi, no Bluetooth, no pairing, and nothing to install beyond opening a page.** The idea is not new, and Decimen is upfront about building on earlier work like `divan/txqr` and `sz3/libcimbar`, but the implementation is a clean demonstration of the physics and the clever coding that make screen-to-camera transfer actually work.

This article covers how optical transfer works, the synchronization problem that makes it hard, the fountain codes Decimen uses to solve it, and how to tune the tool for your own hardware.

<iframe class="aspect-video h-auto" width="100%" height="315" src="https://www.youtube.com/embed/MnS0TvxLzaU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## What optical file transfer is

At its core, optical file transfer sends data as light instead of electrical signals or radio waves. The sender turns a file's ones and zeros into visual patterns and displays them on a screen. The receiver points a camera at that screen, reads the patterns, and converts them back into the original bytes.

The usual pattern is a QR code. A single standard QR code only holds a few kilobytes, so to move a real file you split it into many chunks, encode each chunk into its own code, and flash the codes on screen in quick succession, like a high-speed flip-book. The receiver's camera records that stream and decodes each frame to rebuild the file.

This is genuinely useful for air-gapped systems, the kind kept off any untrusted network in high-security settings to reduce the attack surface. Optical transfer moves data across that gap with only a screen and a camera. Worth stating plainly, though: this is a one-way channel, not a secure one. Anyone who can see the screen can capture the same stream, so for anything sensitive you still need encryption, authentication, and physical controls on top. Decimen is a proof of concept, not a hardened security product.

## Meet Decimen Optical Transfer

Decimen is an open-source web app. You open it on a sending device, pick a file, and the screen starts showing a fast-changing, noisy-looking QR code. Open the same app on a receiving device, point its camera at the sender, and it captures frames, decodes them, and reconstructs the file.

![A 3D animation illustrating the sender flashing QR codes which are captured by the receiver's camera and decoded back into a file.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/4355819b-bd4e-4058-36bb-b264836a6000/lg1x =1280x720)

The workflow is simple: one device displays a stream of QR frames, a second device points its camera at the first, and the receiver decodes and reassembles the file. The whole thing runs in the browser with no network stack involved. No Wi-Fi, no Bluetooth, no TCP/IP. The only medium is light traveling from one screen to the other. It works offline after the first load, needs no permission beyond the camera, supports files up to 64 MB, and verifies the result with a SHA-256 hash before handing it over. Simple to describe, but the practical implementation runs straight into the messiness of real hardware.

## The hard part: racing a screen against a camera

Flashing QR codes sounds easy until you remember that screens and cameras are independent pieces of hardware with their own timing. That mismatch is the central problem.

### Why frames get corrupted

Neither device updates instantly. A screen refreshes at some rate, say 60 Hz, and it often updates line by line rather than all at once, while the pixels themselves take time to change color. A camera captures at its own frame rate, say 30 frames per second, and each frame exposes the sensor for a short window rather than freezing time.

![A diagram illustrating how a mismatch between the sender's screen updates and the camera's exposure leads to corrupted, "blended" frames in the decoded output.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/8b3e9b96-4481-48df-38ff-a65fd6911600/public =1280x720)

When the screen switches to a new QR code while the camera is mid-exposure, the captured image is a blur of two codes. That frame is corrupted and cannot be decoded. If the transfer needed every frame in strict sequence, a handful of these blended frames would sink the whole thing. This is the fatal weakness of naive sequential QR transfer: miss one frame and you either wait a full cycle for it to come back or build complex retry logic, which is impossible here because there is no back-channel from camera to screen.

### Fountain codes to the rescue

Decimen's answer is a coding scheme called fountain codes, specifically Luby transform codes, a type of rateless erasure code. Instead of sending chunks in order, it works differently.

The file is split into a fixed number of source blocks. The sender then generates an endless stream of encoded frames, sometimes called droplets. Each frame is not a single block but a mathematical blend of a pseudorandom subset of blocks, combined with XOR, along with metadata describing which blocks went into it. The name is the metaphor: you fill a cup from a fountain by catching any droplets, and it does not matter which ones you catch or in what order.

![An animation showing the source file's chunks at the top. The encoded stream below is generated by creating new frames, each a unique combination of several of the original source chunks.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/385991e3-a407-4ff3-d20f-871a53ee5800/md2x =1280x720)

### Why fountain codes work here

This changes the whole character of the transfer. Redundancy is built in, because every frame carries a piece of many blocks, so no single frame is irreplaceable. If the receiver misses one or catches a blurred one, it just waits for the next clean frame. There is no sequential dependency, so it does not need frame number 47 specifically, only enough distinct frames overall. And it is self-correcting: once the receiver has collected slightly more distinct frames than there are source blocks, roughly 15 percent more, it solves the resulting system of equations and peels the original blocks back out.

That resilience is exactly what an optical channel needs, where dropped and blurred frames are normal operating conditions rather than errors. The sender simply keeps broadcasting until the receiver has enough to finish. Dropped frames cost time, never correctness, and the two devices' frame rates do not even need to match.

## Performance: throughput and trade-offs

Fountain codes make the transfer reliable, but not fast. Throughput is bounded by physics, and understanding those limits is the key to tuning the tool.

### The theoretical ceiling

The maximum data rate is simply how much data each frame carries multiplied by how many frames go out per second. By default, Decimen packs 2,953 bytes into a frame. That number is not arbitrary: it is the maximum byte capacity of a QR Code Version 40, the largest size in the QR standard, which lays out a 177 by 177 grid of black and white modules. The default transmission rate is 60 frames per second.

![A graphic illustrating the calculation of Decimen's theoretical throughput, starting with bytes per frame and frames per second.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/ea2decf6-fe64-481c-e1fe-018ac5474d00/orig =1280x720)

Multiply them out:

```
2,953 bytes/frame x 60 frames/second = 177,180 bytes/second, roughly 177 KB/s
```

That is a ceiling, not a promise. Fountain coding adds some overhead, since a fraction of frames turn out to be redundant, and real optics eat into the rest. In practice, the project reports around 128 KB/s handheld phone-to-phone, climbing to roughly 186 KB/s in ideal conditions with the sender propped up, denser codes, and a 120 fps ProMotion display. Useful context: even at its best, physical USB beats this by a wide margin, so optical transfer is about reaching devices you otherwise could not, not about speed.

### The trade-off triangle

The rate you actually get is a balance between three competing factors.

Frame rate is how fast the sender flashes new codes. Higher is potentially faster, but it raises the risk of blended frames if the camera cannot keep up. Density is how many bytes each code carries. More data per frame means smaller modules that are harder for a camera to resolve, especially at a distance. Resolving power is the camera's ability, given its resolution, focus, and distance, to tell one module from the next. Denser frames demand a sharper, steadier, higher-resolution capture.

![An animation visualizing the three-way trade-off between Frame Rate, Density, and the Resolving Power of the camera.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/d98a9a31-62f9-4607-7835-9a4a2dd98800/lg2x =1280x720)

These pull against each other. Push density up and you may need to move closer or use a better camera. Push frame rate up and you may add enough motion blur that the camera cannot resolve anything at all.

## Tuning Decimen for your hardware

Decimen's defaults are tuned for one case: a close-range, phone-to-phone transfer. Move away from that and performance falls off, but you can usually win most of it back by adjusting the settings.

### Where the defaults shine, and where they don't

The two ends of the spectrum look very different. A laptop-to-phone transfer at default settings, with the phone held at arm's length from the monitor, tends to crawl at only a few KB/s. Three things conspire against it. The laptop's 60 Hz screen refreshes faster than a 30 fps phone camera can reliably sample, so many frames blend and get discarded. The dense Version 40 code looks tiny on a monitor viewed from a distance, so the camera cannot resolve the individual modules. And LCD laptop panels have slower pixel response than phone OLEDs, which leaves faint ghosting from the previous frame.

Phone-to-phone held close is the scenario the defaults were built for, and it climbs to 50 to 60 KB/s and beyond. Here the conditions are ideal: a small, bright OLED screen held close fills the receiver's viewfinder, so every QR module is covered by many camera pixels, and the OLED's near-instant pixel response removes the ghosting entirely.

### Adjusting the settings

If you are stuck in a sub-optimal setup, you do not have to accept the slow rate. Open the sender's transfer settings and work through three adjustments.

![A close-up of the "Transfer Settings" user interface on the sender's screen, showing the dropdowns for TX FPS and Bytes/Frame.](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/499e566b-3039-4684-ec25-5863742f1a00/lg2x =1280x720)

First, lower the frame density. This is the most important change for a longer-distance transfer, because it makes the code easier to read. In the Bytes / Frame dropdown, drop from the default 2,953 to a lower value such as 1,465, which switches to a less dense QR version with larger, more forgiving modules.

Second, lower the transmission frame rate. To fix the mismatch, slow the sender to comfortably below your camera's capture rate. Since most phone cameras capture at 30 fps or higher, a starting point around 24 fps gives the camera a clean, stable image to sample for each frame.

Third, fix the physical conditions. Turn the sender's screen brightness to maximum, position the devices to avoid glare, and hold the receiver as steady as you can to keep focus sharp.

Working through these can take a laptop-to-phone transfer from a few KB/s to the 20 to 25 KB/s range, a large improvement that comes entirely from rebalancing the trade-offs to fit the hardware.

## Final thoughts

Decimen is a neat piece of engineering. It takes a simple premise, moving data with light, and pushes it to a working limit by confronting the hardware head-on. The use of fountain codes is the standout choice, turning an inherently lossy medium into a reliable one by making every dropped frame a matter of time rather than failure.

**Optical transfer is not going to replace Wi-Fi.** Its throughput is low, and it is a proof of concept rather than a secure channel, so sensitive use still needs encryption and physical controls on top. But it fills a real niche for moving data to isolated systems, and it is an excellent way to see the physical constraints that software usually hides, from refresh rates and pixel response to camera optics and exposure. Once you understand how frame rate, density, and resolving power push against each other, you can tune it to work across a surprising range of hardware, using nothing more than a flicker of light.

