"Cost should scale with color diversity, not resolution." A pure-Rust GIF decoder + encoder whose quantizer routes by a color census — exact palette, frequency-weighted k-means, or budget-sampled NeuQuant, chosen per frame. Measured against FFmpeg 8.1.2's
palettegen/paletteusebest path: 1.5–2.5× faster encode at higher PSNR on 8/8 benchmark frames, decode up to 2.5× faster.#![forbid(unsafe_code)].
Encoding video or images end-to-end? You want the facade —
rff / rff-cli
(drop-in ffmpeg-style pipeline). Depend on rusty_gif directly when you are
working with GIF itself — decoding frames, building palettes, or writing GIFs
from raw pixels.
Part of Remade With Rust by Mata Network.
A GIF decoder parses LZW off hostile input; a GIF encoder walks every pixel of
untrusted dimensions. That is exactly where the C implementations grew their
CVEs, so this crate is #![forbid(unsafe_code)] end to end — the LZW layer is
the pure-Rust weezl, and every kernel in
the quantizer is safe integer/float Rust that the compiler auto-vectorizes.
Frame::from_rgb/from_rgba price palette building by the frame's color
census, not its pixel count (real frames measured 2–88× unique-color
redundancy):
| Route | Fires when | What runs |
|---|---|---|
| Exact | ≤256 unique colors | Exact palette — lossless, no quantizer |
| Histogram k-means | ≤65,536 unique | Median-cut init + frequency-weighted k-means over the unique colors (iterations tier 3/2/1 by diversity) — directly minimizes the frequency-weighted squared error that PSNR measures |
| Budget NeuQuant | >65,536 unique | The classic NeuQuant net, trained on a constant 125k-sample budget instead of every pixel |
Every decision is observable and overridable — no silent fallbacks:
| Env var | Effect |
|---|---|
RUSTY_GIF_TRACE=1 |
Print the chosen route + census per frame to stderr |
RUSTY_GIF_Q_POLICY |
Force a policy: auto (default), upstream (pre-fork NeuQuant-on-every-pixel, kept as the byte-identical oracle), kmeans:<iters>, unique:<cap>, budget:<pixels>. Unknown values panic. |
Benchmarked on 8 real-content frames (CIF→1080p, pinned CPU time, interleaved
A/B, 15 rounds) against FFmpeg 8.1.2's high-quality
palettegen/paletteuse dither=none path: 0.40–0.67× its CPU with PSNR
above it on every frame (+0.2 to +4.1 dB), 15/15 paired wins per frame.
| Item | What's in it |
|---|---|
Decoder / DecodeOptions |
High-level streaming decode — frames out as indexed, RGBA, or raw, with MemoryLimit guards |
streaming_decoder |
The low-level push decoder underneath (StreamingDecoder, per-block Decoded events) |
Encoder |
Container writing: global/local palettes, frame delays, Repeat, extensions |
Frame::from_rgb* / from_rgba* |
True-color → indexed conversion through the content-routed quantizer above |
neuquant |
The vendored NeuQuant net (formerly the color_quant crate), public for direct palette work |
| Feature | Default | Effect |
|---|---|---|
color_quant |
✔ | The quantizer behind Frame::from_rgb*/from_rgba* (vendored — no external dependency) |
std |
✔ | Std I/O plumbing (Read/Write encode/decode) |
raii_no_panic |
✔ | Encoder drop never panics on write failure |
cargo add rusty_gif
// Encode: quantize an RGB frame to a single-frame GIF.
let (width, height) = (4u16, 4u16);
let rgb = vec![0u8; width as usize * height as usize * 3];
let frame = rusty_gif::Frame::from_rgb(width, height, &rgb);
let mut out = Vec::new();
{
let mut encoder = rusty_gif::Encoder::new(&mut out, width, height, &[]).unwrap();
encoder.write_frame(&frame).unwrap();
}
// Decode it back.
let mut options = rusty_gif::DecodeOptions::new();
options.set_color_output(rusty_gif::ColorOutput::RGBA);
let mut decoder = options.read_info(std::io::Cursor::new(out)).unwrap();
let first = decoder.read_next_frame().unwrap().unwrap();
assert_eq!((first.width, first.height), (width, height));| Crate | Role |
|---|---|
rff / rff-cli |
the ffmpeg-style pipeline — most users want this |
rff-codec-gif |
the pipeline's GIF codec adapter |
rff-format-gif |
the pipeline's GIF container layer |
rusty_gif |
← you are here — the standalone GIF codec |
rusty_gif is a performance fork of
image-rs/image-gif with
color_quant's NeuQuant vendored
in-tree — see NOTICE.md for attribution and
UPSTREAM-CHANGES.md for every change against upstream,
including the measurement gates each one shipped under.
Remade With Rust is an initiative by Mata Network to rebuild essential C and C++ tools in Rust — for the memory safety, the predictable performance, and the freedom of a permissive license. Each project is a reimplementation, not a fork: same wire protocols and file formats, new code you can actually depend on. No copyleft. No surprises.
| Project | What it is |
|---|---|
| 🎬 remade_ffmpeg_rs | Our FFmpeg alternative. Drop-in ffmpeg and ffprobe binaries — demux → decode → filter → encode → mux, rebuilt as composable Rust crates with zero GPL/LGPL. Apache-2.0. rusty_gif is its GIF codec. |
| 🧠 FFAI | Our sister project: media for AI. "The AI media toolkit, remade with rust." Embedded ASR + TTS (Mercury), OCR (Carmenta) and vision-language captioning (Argus) behind an ffmpeg-style, swap-by-name architecture — no Python, no CUDA. MIT OR Apache-2.0. |
| 🌐 Mata Network | The home page. "Stop sacrificing your privacy for convenience." Sovereign, self-hostable privacy infrastructure — wallet & identity, password manager, contact manager, and a browser extension that stops information leaking as you browse. Remade With Rust is its open-source arm. |
→ All projects: github.com/Remade-With-Rust
MIT OR Apache-2.0, matching upstream — see
LICENSE-MIT / LICENSE-APACHE.
src/neuquant.rs additionally retains the original Piston Developers and
Anthony Dekker NeuQuant notices verbatim, as its MIT license requires.