raphlinus/nibnib

Media & Design

A 4bpp image format with a tiny deterministic decoder, aimed at microcontrollers that render images at line speed.

123
5
Rust
Apache-2.0
3 days old

Overview

nibnib is an image format built around decompression speed, not compression ratio. It grew out of an experiment on the RP2350 microcontroller, where roughly 6 cycles per pixel must cover a pixel fetch, palette lookup and store into a line buffer. The format keeps a palette at its core, but each palette entry stores a delta rather than an absolute color, which improves soft regions compared with plain palette images.

Decoding is deterministic and symmetric-free: the last decoded pixel is duplicated into a 32-bit value, a byte is read, the matching 32-bit palette entry is added, and the result yields two 16bpp RGB 565 pixels. That is two pixels per input byte, hence 4bpp. Encoding is the slow side: it is a search over palettes and codewords, with Floyd-Steinberg error diffusion distributing quantization error to later pixels.

The intended users are people building tight display pipelines on small MCUs or other fixed-function renderers, not people who need a general-purpose photo format.

Quick start

The repository does not contain a ready-to-run CLI or a documented end-to-end encode/decode command. To work with the format, start from the Rust source in the repository and the decoder inner loop described in the README:

// first two pixels copied from src[0..4], initializing pix
for x in 1..(width / 2) {
    let inp = src[x + 3];
    pix = ((pix >> 16) * 0x10001).wrapping_add(pal[inp as usize]);
    out[x * 2] = pix & 0xffff;
    out[x * 2 + 1] = pix >> 16;
}

The input stream layout is fixed: 2 bytes width, 2 bytes height, 256 32-bit palette entries, then rows. Each row starts with two RGB 565 pixels followed by (width - 2) / 2 palette indices, with the row stride rounded up to a multiple of 4 bytes. For a hardware demonstration, the on-the-fly RP2350 decoder is in the image demo of the vic-2350 project, and the author says the Adafruit Fruit Jam board can stream 640x480 video from SD card at 60fps or show a static image from flash.

Constraints

  • The format carries no metadata beyond dimensions and the palette; it is explicitly not intended for archival use.
  • The current encoder is orders of magnitude slower than decoding and is not adaptive: the palette is a fixed cube plus random Gaussian values rather than being derived from the input image.
  • Encoding artifacts trail to the right of sharp edges because the search is greedy; the README describes right-to-left or boustrophedon encoding as a possible future improvement.
  • The decoder is tightly coupled to 16bpp RGB 565 output, so integration is expected to happen close to the display path.
  • Licensed under Apache-2.0 or MIT, at your option.

HotRepo's take

The format arrived with a clear niche and a public discussion rather than a polished release. On 2026-11-02 the repository was recorded in the Rust new-projects list with 14 stars gained over a roughly three-hour window, reaching 114 stars and 5 forks. The associated Hacker News thread from 2026-10-30 drew 149 points and 24 comments.

The discussion is useful for calibration: one commenter questioned emphasizing decompression speed without JPEG comparisons, while another framed the target as platforms that cannot decode JPEG in real time and cited roughly 6 opcodes per pixel. In the same thread the author pointed to the Fruit Jam demo and noted that the RP2350 decoder is written in symbolasm. Practical evaluation should therefore center on the decoder's fit for a fixed display pipeline, since the encoder's speed and image quality are both acknowledged as early-stage.

Project source and documentation

Written by AI from the project's README, updated . The project's own docs are the source of truth.

Hacker News

Nibnib: Image compression with extreme decompression speed
▲ 149 · 24 comments · October 30, 2026