BIC — image codec for flat graphics
BIC is a Rust prototype of a lossless and near-lossless RGBA image codec for images with large solid areas: screenshots, user interfaces, diagrams, charts, icons, logos and pixel art. It combines palettes, simple prediction and run-length coding with Zstandard. It ships as a library, a command-line tool and a small native viewer.
Where BIC helps, and where it does not
| Content | Advantage | Why |
|---|---|---|
| Screenshots, UI, diagrams, charts, icons, logos, pixel art | Large | Long runs of identical pixels and few colors suit its palette and run-length coding. On the synthetic corpus, files are about 24 % smaller than oxipng (geometric mean) and about 70 % smaller than a default PNG. |
| Photographs already in JPEG (the usual case) | None | A lossless format cannot beat a lossy one. BIC detects this and stores the JPEG unchanged, +32 bytes. |
| Photographs stored losslessly | Small, unproven | About 16 % smaller than oxipng on 14 test photos, but not compared with strong photographic codecs such as cwebp -lossless or JPEG XL, which are likely as good or better. |
If most of your images are photographs, BIC is not the right format. Use JPEG, WebP, AVIF or JPEG XL. Measurements are in BENCHMARKS.md.
Status: research prototype. The
BIC2format is not a stable interchange standard and may change between versions. Do not use it for archival storage.
Contents
- Where BIC helps, and where it does not
- Features
- Installation
- Command-line usage
- Library usage
- How it works
- Safety and decoder limits
- Benchmarking
- Limitations
- Roadmap
- Development
- License
Features
- Lossless by default: decoding returns exactly the original pixels.
- Near-lossless mode:
--max-error Nguarantees every decoded sample is withinNof the original (JPEG-LS-style quantization). - Per-image choice of encoding: a palette for images with up to 256 colors, an optional subtract-green color transform, and prediction that is either fixed (none or up) or chosen per row (none, left, up, average, Paeth, MED). The encoder tries every valid combination and keeps the smallest.
- Size first, fast decoding second: encoding is deliberately exhaustive and slow. Lossless decoding uses vectorizable row loops.
- Planar coding: channels are coded separately; constant channels cost two bytes.
- Adaptive block coding: each plane is split into blocks of 256, 1024 or 4096 samples. Each block is stored RAW or with run-length encoding using 4-, 8-, 12- or 16-bit counters. The split with the fewest total bytes is chosen by dynamic programming.
- Zstandard wrapping of the whole plane stream, with a configurable level.
- Never larger than the original: when encoding a PNG, JPEG, WebP or HEIF
file, BIC keeps the file unchanged inside the container if that is smaller
than its own encoding. Lossy JPEGs are usually kept this way, at +32 bytes.
--transcodeturns this off. - PNG fallback: optionally stores a PNG of the pixels whenever it is smaller than BIC's own encoding.
- Wide input support: PNG, JPEG, WebP, BMP, GIF, TIFF, HEIC and HEIF. EXIF orientation is applied, so phone photos come out upright.
- ICC color profiles are kept: read from the source image, stored in the
.bicfile and written back when decoding to PNG. - Bounded decoding: memory and pixel limits are checked before anything is allocated.
- Desktop viewer and converter:
bic-viewopens.bicfiles and every supported input format. File > Open (Ctrl+O) opens another image, and File > Save as BIC (Ctrl+S) saves the current one as.bicin the background, losslessly and with its ICC profile, keeping the original file inside when that is smaller. It runs without a console window, opens on double-click through a Windows file association, shows transparency over a checkerboard, and reports errors in a dialog. Images are scaled to the window with smooth resampling (Lanczos3 and Catmull-Rom), keeping proportions and never above 100 %.
Installation
Prebuilt programs
Releases have bic and bic-view
for Windows (x64), Linux (x64) and macOS (Apple Silicon and Intel). Unpack the
archive and keep both programs in the same folder.
The programs are not code-signed. On Windows, SmartScreen may ask for confirmation the first time. On macOS, allow them in System Settings > Privacy & Security, or run:
On Linux, the viewer needs a desktop session (X11 or Wayland) and, for its file dialogs, the XDG desktop portal that most desktops provide.
From source
You need a recent stable Rust toolchain.
Or build from a clone:
This builds two programs in target/release: bic, the command-line tool,
and bic-view, the viewer (.exe on Windows).
Command-line usage
| Command | Description |
|---|---|
bic encode <input> <output.bic> |
Encode an image to BIC |
bic decode <input.bic> <output> |
Decode to a format chosen by the output extension (PNG keeps the ICC profile) |
bic view <file> |
Open a .bic or any supported image in the viewer (Esc closes it) |
bic-view [file] |
The same viewer without a console window; without a file it asks for one |
bic verify <original> <file.bic> [--max-error N] |
Check a BIC file against its original: exact, or within N |
bic compare <paths>... [--max-error N] [--fast-decode] [--csv out.csv] |
Benchmark BIC against PNG, oxipng, WebP and QOI on files or folders; reports PSNR when N > 0 |
bic install-association |
Open .bic files with bic-view for the current Windows user |
Encode options
| Option | Default | Effect |
|---|---|---|
--max-error <N> |
0 |
Largest allowed difference per sample. 0 is lossless. The encoder also tries lossless coding and keeps it when it is smaller. |
--png-fallback |
off | Store a PNG of the pixels instead when it is smaller (lossless encodes only). |
--transcode |
off | Always use BIC's own encoding, even when the original PNG/JPEG/WebP/HEIF file is smaller. |
--fast-decode |
off | Skip per-row prediction. On photographs: about 1 % larger, about 35 % faster to decode. No effect on flat graphics. |
--zstd-level <N> |
19 |
Zstandard compression level (1–22). Higher levels shrink files; decoding speed barely changes. |
Examples
# Lossless encode and exact check
# Near-lossless: no sample differs by more than 2
# Faster to open, slightly larger
# A JPEG is usually kept as is inside the .bic (+32 bytes)
# Decode back to PNG
Windows file association
After building, keep bic and bic-view in the same folder and run once:
This writes per-user keys under HKCU\Software\Classes, so double-clicking a
.bic file opens it in bic-view. No administrator rights are needed, and
nothing changes until you run the command. If bic-view is missing, the
association falls back to bic view, which also opens a console window.
Library usage
Add the crate from Git:
[]
= { = "https://github.com/proteo5/bic", = false }
default-features = false leaves out the cli feature (command-line tool,
viewer, file dialogs and the BMP, GIF, TIFF and QOI inputs). The codec then
depends on anyhow, zstd, image (PNG, JPEG, WebP) and heic-rs. The
decoders are needed because a BIC file may embed an original file.
To encode a file rather than raw pixels (keeping the original when it is
smaller), use encode_file. read_source decodes PNG, JPEG, WebP and HEIF
with orientation applied.
use ;
Input and output are 8-bit RGBA in row-major order (width × height × 4
bytes). Set EncodeOptions::max_error for near-lossless encoding. To store
or read an ICC profile, use encode_rgba_with_metadata and
decode_with_metadata.
How it works
RGBA image
├─ ≤ 256 colors (lossless) → palette + one index plane
└─ otherwise → G, R, B, A planes (R and B relative to G)
└─ per plane: constant → 2 bytes, or
├─ prediction (none, up, or a rule per row)
├─ residual quantization (exact when max_error = 0)
└─ adaptive blocks: RAW | RLE-4 | RLE-8 | RLE-12 | RLE-16
└─ Zstandard
└─ BIC2 container (32-byte header)
The encoder builds every candidate encoding in parallel and keeps the smallest.
The full wire format is specified in FORMAT.md.
Safety and decoder limits
Decoding untrusted files is guarded by DecodeLimits:
- Declared dimensions are checked against
max_pixelsbefore allocation. - The decoder's peak memory (about
6 × pixelsplus the decompressed stream) must fit withinmax_alloc. - Zstandard output is capped at the length declared in the header, so payloads that inflate beyond it are rejected.
- Embedded PNG payloads are decoded with the header dimensions as hard limits.
- Malformed streams, such as out-of-range palette indices or quantizer symbols, return errors instead of panicking.
The decoder is fuzzed in CI (see Fuzzing), but it is still prototype code. Do not treat it as a security boundary.
Benchmarking
# Deterministic synthetic corpus of flat graphics plus two controls
# Benchmark any mix of files and folders
compare encodes each image losslessly and verifies the exact roundtrip. It
then prints a per-image table and a summary against PNG (image-rs), oxipng,
WebP lossless and QOI, using both total-size and geometric-mean ratios. If any
image fails, the command exits with an error.
Results are recorded in BENCHMARKS.md. With BIC2 at the default settings, files are on average (geometric mean) about 24 % smaller than oxipng on the synthetic flat-graphics corpus. On photographs the gain is small, and the comparison does not include strong photographic codecs (see Where BIC helps). CI reruns the synthetic benchmark on every push.
Limitations
- 8-bit RGBA only. 16-bit images are reduced to 8 bits on load.
- One color transform (subtract green).
- ICC profiles are stored but not applied: the viewer shows raw RGB values.
- Decoding to formats other than PNG drops the ICC profile, and JPEG output drops transparency.
- Other EXIF metadata (camera, GPS, dates) is not kept.
- No animation or progressive decoding.
- Encoding is slow by design (about a minute for a 20-megapixel photo).
- Rows that use the left, average, Paeth or MED rules decode sequentially,
about half the speed of
noneanduprows.--fast-decodeavoids them. - Near-lossless decoding uses a per-sample loop and is slower than lossless.
- The format version may change without a migration path.
Roadmap
- Spatial prediction and color decorrelation, chosen by measurement (BIC2)
- Palette mode for images with up to 256 colors
- Near-lossless mode with a guaranteed per-sample error bound
- Keep or drop the RLE stage: measured, kept (plain Zstandard was larger)
- Faster lossless decoding (vectorizable row loops)
- Per-row predictor choice (left, average, Paeth, MED)
- Image corpus and regression benchmarks against PNG, oxipng, WebP lossless and QOI
- Decoder fuzzing (
cargo-fuzz) and property-based roundtrip tests - Viewer without a console window, with frame-rate limiting
- EXIF orientation and ICC profile handling
- Open any supported image in the viewer and save it as BIC
- Color-managed display in the viewer
Development
PROPTEST_CASES=5000
Fuzzing
The fuzz/ crate has three cargo-fuzz
targets:
| Target | Input |
|---|---|
decode |
Arbitrary bytes as a whole BIC file |
decode_plain |
Arbitrary bytes as the plane stream, inside a valid container |
roundtrip |
Arbitrary images and encoder options; checks exactness or the lossy error bound |
On Windows (MSVC), the AddressSanitizer runtime must be on PATH, for example
VC\Tools\MSVC\<version>\bin\Hostx64\x64 from your Visual Studio
installation. CI fuzzes each target for two minutes on every push.
Issues and pull requests are welcome. Please include tests for codec changes and, for compression changes, before/after sizes on representative images.
License
Licensed under the Apache License, Version 2.0.
Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion in this work shall be licensed under the Apache License, Version 2.0, without any additional terms or conditions.