Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 66 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Transfer files via animated QR codes. No network, no Bluetooth, no USB — just
2. **Transfer** — Point a phone camera at the screen, or export a GIF/video and send it any way you like.
3. **Decode** — Open the decoder on the receiving device. Scan with the camera or upload the GIF/video. The file is reassembled.

Each image frame carries **three independent QR codes** — one per RGB colour channel — tripling data density with no increase in image size.

## Quick start

```bash
Expand All @@ -25,19 +27,50 @@ Open `encoder/index.html` or `decoder/index.html` directly in a browser. Camera
## Features

**Encoding:**
- RGB tri-channel encoding: three QR codes per image frame (~3× data density)
- Multi-file and folder support (auto-zipped)
- Gzip compression
- GIF, MP4, and WebM export
- Reed-Solomon erasure coding (+2/+5/+10 parity chunks)
- Adjustable chunk size (100–800 B) and QR error correction level (L/M/Q/H)
- Adjustable QR version and error correction level (L/M/Q/H)

**Decoding:**
- Live camera scan with native `BarcodeDetector` (hardware-accelerated) + jsQR fallback
- Live camera scan with jsQR, including raw binary QR payloads
- GIF and video upload decoding
- Per-channel adaptive thresholding (Otsu) for robust red/blue recovery
- Progressive preview as chunks arrive
- RS recovery of missing chunks
- ZIP archive browsing

## RGB channel encoding

Each image frame is a single full-colour image that encodes three logical QR codes simultaneously — one in the red channel, one in green, one in blue. Every pixel is one of eight pure colours (the corners of the RGB cube: black, white, red, green, blue, cyan, magenta, yellow).

```
Pixel colour = (R_light, G_light, B_light) where each channel is 0 or 255.
```

**Green** carries the primary (authoritative) channel. Green dominates camera sensor sensitivity (Bayer arrays have 2× green photosites), so it is the most faithfully captured channel and the location for the frame metadata. **Red** and **blue** carry additional data.

Logical chunk numbering maps to image frames as:

```
Frame 0: chunks 0 (green), 1 (red), 2 (blue)
Frame 1: chunks 3 (green), 4 (red), 5 (blue)
```

The decoder grid shows one cell per image. Each cell's colour reflects which channels have been successfully decoded, using additive mixing — the first one is red, green, or blue. When there are two colors complete it will be yellow, cyan, or magenta, all three is white.

## Decoding: adaptive channel scanning

For each image frame the decoder:

1. Extracts the **green channel** as grayscale and runs jsQR. On success this also returns the four corner coordinates of the QR code.
2. Uses those coordinates to **crop** the red and blue channel images down to just the QR region before scanning — faster and immune to false positives outside the QR area.
3. Applies **Otsu's thresholding** to the cropped region for red and blue: finds the histogram threshold that maximally separates the dark and light pixel clusters, computes the mean of each cluster, and stretches that range to fill 0–255. This automatically compensates for the lower camera sensitivity in the red and blue channels (the same correction you would do manually with brightness/contrast in an image editor).
4. Falls back to a full-frame scan for red and blue if the green channel fails.

## Project structure

```
Expand All @@ -46,58 +79,61 @@ encoder/index.html Encoder (self-contained)
decoder/index.html Decoder (self-contained)
shared/reedsolomon.js Reed-Solomon library (source)
server.js HTTPS dev server
test/test.js Test suite (43 tests)
test/test.js Test suite
```

Encoder and decoder pages bundle all dependencies locally — no CDN, no build step. They work from `file://`, GitHub Pages, or the dev server.

## Chunk protocol (v2)
## Chunk protocol (v3)

Each QR code contains a JSON chunk:
The encoder emits compact binary QR frames. The decoder still accepts the older JSON v1/v2 frames for compatibility.

```json
{
"v": 2, "i": 0, "n": 17,
"hash": "a1b2c3d4", "name": "file.txt",
"gz": 1, "zip": 0, "rs": 1, "k": 7, "sz": 19721,
"t": "d", "d": "base64data..."
}
Frame layout:

```text
"Q3" magic
flags byte: gzip, zip, Reed-Solomon, parity
frame index varint
total frame count varint
data frame count varint
binary frame body
```

| Field | Description |
|-------|-------------|
| `v` | Protocol version (2) |
| `i` | Chunk index (0-based) |
| `n` | Total chunks (data + parity) |
| `hash`| FNV-1a hash of original file |
| `name`| File name |
| `gz` | 1 if gzip-compressed |
| `zip` | 1 if zip archive |
| `rs` | 1 if Reed-Solomon enabled |
| `k` | Data chunk count (for RS) |
| `sz` | Original file size in bytes |
| `t` | `d` = data, `p` = parity |
| `d` | Base64 payload |
Frame 0's body starts with transfer metadata, then binary payload bytes:

```text
encoded payload size varint
original file size varint
frame 0 data byte count varint
FNV-1a hash uint32
filename length varint
filename UTF-8 bytes
payload bytes
```

Other data frames contain only binary payload bytes. Parity frames contain Reed-Solomon parity bytes. Reed-Solomon protects data frame bodies, including frame 0 metadata, so a missing metadata frame can be recovered from parity.

With RGB encoding the total logical frame count is approximately 3× what it would be for a single-channel encode of the same file. The frame index field in each QR payload is the absolute logical frame number, so the protocol layer is unaware of the channel grouping — the decoder simply collects frames by index as they arrive from whichever channel decoded them.

## Testing

```bash
npm test
```

43 tests covering GF(256) arithmetic, Reed-Solomon encode/decode, recovery scenarios, path traversal protection, GIF parser bounds checking, and the full encode→RS→recover→decompress pipeline.
Tests covering GF(256) arithmetic, Reed-Solomon encode/decode, recovery scenarios, v3 binary framing, path traversal protection, GIF parser bounds checking, and the full encode→RS→recover→decompress pipeline.

## Camera tips

- **Fast scanning**: chunk size 300 + EC level L = larger cells, easier to scan
- **Fewer frames**: chunk size 800 + EC level M = fewer QR codes in the sequence
- **Fast scanning**: lower QR version + EC level L = larger cells, easier to scan
- **Fewer frames**: higher QR version + EC level M = fewer images in the sequence
- **Missed chunks**: add +2 or +5 RS parity to recover from dropped frames
- **Encoder FPS**: start at 3–6 FPS and adjust

## Requirements

- Node.js (for dev server and tests)
- Chrome recommended (BarcodeDetector API support)
- Chrome recommended
- OpenSSL optional (server falls back to JS cert generation)

## License
Expand Down
Loading