Store and restore arbitrary files inside 4K PNG images (3840×2160) – the “Quantumbits” format with magic header QBNT.
The original idea for Quantumbits came from classic CRT screen noise and a fascination with hiding data in images. The very first proof‑of‑concept was to read a file bit by bit and map each bit directly to a pixel: black = 0, white = 1, written row by row, pixel by pixel into an image.
Why 4K images? The goal was to pack as much data as possible into a single image. A native 4K resolution is a good fit because it provides a lot of pixels and thus a high raw capacity per frame.
The next question was: “How can I squeeze even more data into the same space?” From this simple black/white representation came the idea of layering: instead of just two states, use grayscale, which immediately made the increased density visible in a single 4K image.
My ADHD brain then pushed this further and connected it to quantum physics: quantum particles in superposition can be in many states at once. Translated to images, this led to using RGB colors instead of grayscale – each pixel effectively carries multiple “states” at the same time. Data density per image increased again in a visibly dramatic way.
From this analogy between bits and quantum states the name “Quantumbits” was born. The very first version of the project was just a proof‑of‑concept to explore and visualize this idea.
This version of Quantumbits was “vibecoded” together with Cursor – it’s experimental rather than carefully designed. The project is not intended for production use. It exists mainly as a playground for ideas, born out of boredom and curiosity, not out of a concrete use case.
There are parts of the code where even I don’t fully remember what happens in detail. You should therefore not use this project as the basis for production systems, safety‑critical applications, or important backups.
Quantumbits does not aim to solve a real‑world problem – it’s primarily an experimental project that shows how to store data in high‑resolution images and how different layering ideas (bits → grayscale → RGB) can visibly increase data density.
quantumbits/
├── main.py # Entry point: encode, decode, info, debug (CLI)
├── requirements.txt
├── README.md
├── LICENSE
├── frames_output/ # Default folder for frames (created if needed)
└── quantumbits/ # Package with all core logic
├── __init__.py
├── common.py # Constants, header, safe_output_filename, limits
├── encoder.py # encode_file (2D/3D, optional gzip, parity/recovery)
├── decoder.py # decode_files, list_sequences (recovery using zfec)
└── debug.py # get_pixel_bytes, hex dump / header debug
All commands are executed via python main.py <command>.
Optionally you can call python -m quantumbits.encoder … or python -m quantumbits.decoder … directly if you only want the low‑level modules.
pip install -r requirements.txt- Python 3.9+
- numpy
- Pillow
- tqdm
- zfec (erasure coding for frame recovery)
- The encoder reads an input file in binary mode (
rb), optionally gzip‑compresses it. - The bytes are written into 4K PNG frames:
- 2D (grayscale): 1 byte per pixel.
- 3D (RGB): 3 bytes per pixel (higher capacity).
- The first frame contains a QBNT header at the end, with:
- number of data frames (
total_frames) - optional number of parity frames (
parity_frames) - compressed/original size
- compression flag (gzip yes/no)
- filename, extension, MIME type.
- number of data frames (
The decoder reads the header, reconstructs the byte stream from the frames (including decompression), and writes the original file back to disk.
- If there are 2 or more data frames, the encoder automatically creates 2 parity frames using
zfec:N = total_frames(data frames)K = 2(parity frames)- In total
N + Kframes are written.
- If there is only one frame (
total_frames == 1), no parity frames are created – losing that one frame would make recovery impossible anyway.
On decode:
- The decoder scans all
qb_frame_XXXX.pngfiles and groups them into sequences. - Each sequence has:
total_frames(data frames)parity_frames(parity frames)- segment length =
total_frames + parity_frames.
- If parity frames are present (
parity_frames > 0):- The decoder needs at least
N = total_framesframes in total (data or parity). - If at most
K = parity_framesframes are missing, they can be reconstructed withzfec. - On successful recovery you’ll see output like:
X frame(s) were missing and could be recovered. - If fewer than
Nframes are available:
Threshold for successful recovery of missing/corrupt frames exceeded – recovery and decoding aborted.
- The decoder needs at least
- Without parity (old files or single‑frame encodes), all data frames must be present.
Important: The system can recover at most K lost frames, independent of the total number of frames – it’s a fixed number, not a percentage.
The default folder for frames is always frames_output, so you usually don’t need to pass it explicitly.
# Encode: file -> frames_output (default)
python main.py encode my_file.pdf
python main.py encode music.mp3 --compress --mode 3d
# Different output folder
python main.py encode document.zip -o other_frames
# Append new frames instead of overwriting
# (multiple files one after another into the same folder)
python main.py encode new_file.pdf --no-overwrite
# Decode: frames_output (default) -> file (name from header)
python main.py decode
# Show metadata only (no decoding)
python main.py info
# Debug: first bytes as hex, optional header dump
python main.py debug
python main.py debug --header
python main.py debug -n 50 -i frames_output
# Decode from another folder or with explicit output file / directory
python main.py decode -i other_frames
python main.py decode -o restored.pdf
python main.py decode -o ./output_dir| Command | Default behaviour (when omitted) |
|---|---|
encode |
Output folder: frames_output |
decode |
Input folder: frames_output, output: current directory, filename from header |
info |
Prints metadata (filename, size, frames, compression, parity) from frames_output |
debug |
Hex‑dump of first bytes (+ optional --header) from frames_output |
Multiple files in one folder:
If you encode multiple files into the same folder one after another (e.g. frames 0–2 = file A, frames 3–4 = file B), the decoder detects this automatically. Each file starts with a frame that contains a QBNT header. On decode, main.py decode will prompt you when multiple sequences are found and ask whether you want to decode specific indices or all of them.
Example output when multiple files are found:
Found QBNT files:
1) photo.zip (123456 bytes, 3 frame(s))
2) music.mp3 (7890123 bytes, 42 frame(s))
3) installer.exe (50123456 bytes, 96 frame(s))
4) Decode all
Selection (number(s) separated by spaces, or 'all'):
Encoder/decoder directly (without main.py):
python -m quantumbits.encoder <input_file> <output_folder> [--compress] [--mode 2d|3d]
python -m quantumbits.decoder <folder> [target_file] [-o output_folder] [--info]
- 2D (grayscale): Single channel, header in the last 1024 pixels of the first frame. The decoder automatically detects grayscale PNGs.
- 3D (RGB): Three channels, header in the last 1024 pixels of the red channel in the first frame. Higher capacity per frame.
Encoder and decoder support both modes; the decoder auto‑detects 2D vs. 3D from the first frame.
The debug mode is mainly for exploration and sanity checks.
python main.py debug
Reads the first frame from the given folder (default:frames_output), detects the mode (2D/3D) and prints the first bytes as a hex dump.python main.py debug --header
Additionally parses and prints the QBNT header of the first frame.python main.py debug -n 50 -i other_folder
Reads the first 50 bytes fromother_folder/qb_frame_0000.png.
Typical output without --header:
Mode: 3D (RGB)
First 100 bytes (hex):
51 42 4e 54 00 02 00 00 00 00 00 00 00 00 3a 98 ...
Typical output with --header:
Mode: 2D (Grayscale)
--- Header (QBNT) ---
total_frames: 96
parity_frames: 2
compressed_size: 50123456
original_size: 73400320
compress_flag: 1
filename: installer.exe
extension: .exe
mime_type: application/vnd.microsoft.portable-executable
First 100 bytes (hex):
51 42 4e 54 00 02 00 00 00 00 00 00 00 03 00 00 ...
This makes it easy to check:
- whether the header was written correctly (frames/parity/sizes/name),
- whether the detected mode (2D/3D) matches your expectations,
- and whether the first bytes look roughly as expected (e.g. for certain file types).
This project is licensed under the MIT License (see LICENSE).
Important notes:
- The software is provided “as is”, without warranty of any kind and without liability for any damage or data loss.
- You may freely use, modify, fork, and integrate the code into other projects as long as the copyright and license notice is preserved.
- Quantumbits uses the library
zfec, which itself is licensed under GPL-2+ and the Transitive Grace Period Public Licence (TGPPL).
If you redistribute a combined work, you must additionally comply withzfec’s license terms (see thezfecproject on PyPI/GitHub).