Documentation
BIC documentation
Everything you need to use BIC: the packages, their API, the build plugins, the command line and the format. Examples are real code from the released packages.
Overview
BIC is a lossless image format. It stores charts, logos, icons, diagrams and pixel art in fewer bytes than optimized PNG, and it keeps photographs as their original JPEG (adding only 32 bytes). Three things make up the project:
@proteo5/bic-runtime: a decoder for browsers and Node.js. Zero dependencies exceptfzstd; 5.7 KiB gzip.@proteo5/bic-build: a build step for Vite and webpack that converts the images that benefit, and a commandbic-build.bic: the command-line tool and the viewer, for Windows, Linux and macOS.
The file format is described in FORMAT.md.
Install
Install the packages you need. The decoder is enough to read .bic files; the build step is for projects that convert images.
npm install @proteo5/bic-runtime
npm install --save-dev @proteo5/bic-buildThe build step needs the bic program. On install it downloads the binary for your platform from the release with the same version number, checks it against the release checksums, and keeps it in vendor/.
| Environment variable | Effect |
|---|---|
BIC_SKIP_DOWNLOAD=1 | Do not download the binary (offline installs). |
BIC_BIN=/path/to/bic | Use this binary instead of the downloaded one. |
Without either, bic from the PATH is used. Binaries for each release are on the releases page.
Decoder API
Import from @proteo5/bic-runtime. The functions work in browsers and in Node.js.
decodeBic(input, options?)
Decodes a BIC file given as a Uint8Array or an ArrayBuffer. It returns one of two results:
{ kind: 'pixels', width, height, rgba, icc }: BIC's own coding.rgbais aUint8Arrayof 8-bit RGBA pixels, row by row.{ kind: 'file', mime, bytes, width, height, icc }: a PNG or an original JPEG/WebP stored in the file. Showbytesas they are.
icc is the ICC color profile when the file has one, otherwise null. Options: maxPixels (default 100 million) refuses larger images before any memory is used.
import { decodeBic, BicError } from '@proteo5/bic-runtime';
const bytes = new Uint8Array(await (await fetch('chart.bic')).arrayBuffer());
try {
const image = decodeBic(bytes);
if (image.kind === 'pixels') {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext('2d').putImageData(
new ImageData(new Uint8ClampedArray(image.rgba), image.width, image.height), 0, 0);
document.body.append(canvas);
} else {
const url = URL.createObjectURL(new Blob([image.bytes], { type: image.mime }));
document.body.insertAdjacentHTML('beforeend', `<img src="${url}" alt="">`);
}
} catch (error) {
if (error instanceof BicError) console.error('Not a valid .bic file:', error.message);
else throw error;
}Other exports
parseContainer(input, options?): the header fields and payload of a file, without decoding the pixels.sniffImageType(bytes):image/png,image/jpeg,image/webporimage/heif, from the signature;nullif unknown.BicError: thrown for malformed or corrupted input. Other exceptions are not expected.
Performance: a 1600-pixel chart decodes in about 20 to 40 ms in JavaScript. The decoder is tested against the Rust decoder on 53 fixtures, and on random corruption.
Vite plugin
The plugin converts the images that pass the policy during vite build, and serves them through a Service Worker under their original URLs. Your HTML, CSS and JavaScript do not change.
// vite.config.js
import { bicVite } from '@proteo5/bic-build/vite';
export default {
plugins: [bicVite({ policy: { minSaving: 0.1, maxDistinctColors: 4096 } })],
};Options: policy (see the conversion rule below), and bic for the path of the binary. The plugin runs in vite build only.
The build writes these files next to your assets:
bic-sw.js: the Service Worker, with the list of converted images built in.bic-loader.js: registers the worker. It is added to each HTML page before</head>.bic-report.json: the decision and the reason for every image.
Requirements and limits
- The base path must be absolute (
/,/app/). With a relative base nothing is converted. - Service Workers need HTTPS or localhost.
- The first visit downloads the PNG as usual. Later visits show the converted image, and the decoded image is cached, so each .bic is downloaded once per device.
- Images inlined as data URLs and files copied from
public/are not converted. - A failed conversion keeps the original file and prints a warning; the build does not fail.
webpack plugin
The same behavior for webpack 5, in production builds. It needs an absolute output.publicPath.
// webpack.config.mjs
import { BicWebpackPlugin } from '@proteo5/bic-build/webpack';
export default {
mode: 'production',
output: { publicPath: '/' },
plugins: [new BicWebpackPlugin({ policy: { minSaving: 0.1 } })],
};Options: policy, bic, and enableInDevelopment: true to convert in development builds too. The loader is added to HTML pages from html-webpack-plugin or another plugin that writes HTML.
Conversion rule
The build step converts an image only when all of these hold:
- The image is not a JPEG. JPEG is lossy, and BIC keeps it as it is.
- It has at most
maxDistinctColorscolors (default 4096). More colors means a photograph-like image. - The BIC file is at least
minSavingsmaller than the best lossless format: PNG, oxipng or WebP lossless (default0.1, that is 10%).
The same rule is available as a function, for your own tools:
import { decide, DEFAULT_POLICY } from '@proteo5/bic-build';
// measurement: the JSON of `bic inspect --json <image>`
const decision = decide(measurement, { minSaving: 0.2 });
// decision.use, decision.reason, decision.saving, decision.bestCommand line
The bic program encodes, decodes, verifies and measures images. The build step uses it for you; these commands are for scripts and manual work.
| Command | What it does |
|---|---|
bic encode <input> <out.bic> | Encode an image. Options: --max-error N (near-lossless), --png-fallback, --fast-decode, --transcode, --zstd-level N. |
bic decode <in.bic> <out> | Decode to PNG, JPEG, WebP or BMP, chosen by the extension. |
bic verify <original> <in.bic> | Check that the file decodes to the original pixels. --max-error N accepts bounded differences. |
bic inspect <image> [--json] | Sizes of PNG, oxipng, WebP lossless and BIC for one image. |
bic compare <paths...> [--csv out.csv] | Benchmark BIC against the other lossless formats, on files or folders. |
bic view <file> | Open a .bic or any supported image in the viewer. |
bic install-association | Open .bic files with the viewer for the current Windows user. |
bic-build <input> <output> | Convert a folder with the rule above. Options: --min-saving 10, --max-colors 4096, --bic path. |
# Lossless, verified
bic encode logo.png logo.bic
bic verify logo.png logo.bic
# Near-lossless: no sample differs by more than 2
bic encode photo.png photo.bic --max-error 2
# Convert a folder, writing bic-manifest.json next to the files
npx bic-build src/images dist/bicFile format
A BIC2 file has a 32-byte header and a payload. The payload is either BIC's own coding (planes, palettes or color transforms, predictors and run-length blocks, compressed with Zstandard), a PNG, or the original JPEG, WebP or HEIF file. The complete specification, with the decoder limits, is in FORMAT.md.
The format is experimental. It may change between versions, and the decoders in JavaScript and Rust are kept identical by the tests.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| The build prints that the base path is not absolute | Set base in Vite or output.publicPath in webpack to an absolute path such as /. |
| No image is converted | Look at bic-report.json: each image has a reason. JPEGs are never converted, and images with more colors than the limit are kept. |
| Images are not served as BIC on the first visit | That is expected: the worker is installed on the first visit. Reload once; later visits use it. |
| The page works locally but not on a server | Service Workers need HTTPS. GitHub Pages, Netlify and similar hosts provide it. |
bic is older than the bic-build package | The binary has no inspect command. Install a current release, or set BIC_BIN. |
| Install prints that the binary could not be downloaded | Check the network, or set BIC_BIN. The package still works with a binary from the PATH. |
| Unsupported browser | Tested in Chrome. Firefox and Safari are not tested yet. |
Resources
- Source code and issues: github.com/proteo5/bic
- Releases and binaries: github.com/proteo5/bic/releases
- Runtime on npm: @proteo5/bic-runtime
- Build step on npm: @proteo5/bic-build
- Measurements and methodology: BENCHMARKS.md
- Changes: CHANGELOG.md
- License: Apache-2.0