#gif #image-codec #gif-animation #animation #image #image-server

no-std zengif

Server-side GIF codec with zero-trust design, memory bounds, streaming, and full animation transparency support

11 releases (6 breaking)

Uses new Rust 2024

0.7.3 Apr 17, 2026
0.7.0 Mar 29, 2026

#1887 in Images

Download history 275/week @ 2026-04-15 2/week @ 2026-04-22 3/week @ 2026-04-29 83/week @ 2026-05-06 96/week @ 2026-06-17 41/week @ 2026-06-24 58/week @ 2026-07-15 4/week @ 2026-07-22 6/week @ 2026-07-29

68 downloads per month
Used in 2 crates

MIT/Apache and AGPL-3.0-only…

450KB
9K SLoC

zengif CI crates.io lib.rs docs.rs codecov MSRV license

A GIF codec built for servers: streaming, memory-bounded, and thoroughly tested.

Licensing note: The default features include zenquant (AGPL-3.0-or-later). A plain cargo add zengif pulls in AGPL-licensed code. For MIT/Apache-2.0-only licensing, use default-features = false and select a permissive quantizer (e.g., quantette, quantizr, or color_quant). See Quantizer Options.

Getting Started

cargo add zengif

Decode a GIF

use zengif::{Decoder, Limits, Unstoppable};
use std::fs::File;
use std::io::BufReader;

fn main() -> zengif::Result<()> {
    let file = File::open("animation.gif")?;
    let reader = BufReader::new(file);

    let mut decoder = Decoder::new(reader, Limits::default(), &Unstoppable)?;

    println!("{}x{}, {} frames",
        decoder.metadata().width,
        decoder.metadata().height,
        decoder.metadata().frame_count_hint.unwrap_or(0));

    while let Some(frame) = decoder.next_frame()? {
        // frame.pixels: Vec<Rgba> - fully composited with transparency
        // frame.delay: u16 - delay in centiseconds (100ths of a second)
        println!("Frame {}: {}ms delay", frame.index, frame.delay as u32 * 10);
    }

    Ok(())
}

Encode a GIF

use zengif::{EncodeRequest, EncoderConfig, FrameInput, Limits, Repeat, Rgba, Unstoppable};

fn main() -> zengif::Result<()> {
    let width = 100;
    let height = 100;

    // Create 3 frames of solid colors
    let red: Vec<Rgba> = (0..width*height).map(|_| Rgba::rgb(255, 0, 0)).collect();
    let green: Vec<Rgba> = (0..width*height).map(|_| Rgba::rgb(0, 255, 0)).collect();
    let blue: Vec<Rgba> = (0..width*height).map(|_| Rgba::rgb(0, 0, 255)).collect();

    let config = EncoderConfig::new().repeat(Repeat::Infinite);
    let limits = Limits::default();

    let mut encoder = EncodeRequest::new(&config, width, height)
        .limits(&limits)
        .stop(&Unstoppable)
        .build()?;

    encoder.add_frame(FrameInput::new(width, height, 50, red))?;   // 500ms
    encoder.add_frame(FrameInput::new(width, height, 50, green))?; // 500ms
    encoder.add_frame(FrameInput::new(width, height, 50, blue))?;  // 500ms

    let output = encoder.finish()?;

    std::fs::write("output.gif", &output)?;
    Ok(())
}

Why zengif?

If you're building a server that handles untrusted GIF uploads, you need:

  • Memory limits - Reject oversized images before allocating
  • Cancellation - Stop processing if the request is cancelled
  • Error context - Know exactly where parsing failed
  • Correct compositing - Handle all disposal methods and transparency

zengif builds on the excellent gif crate, adding these production features:

Feature gif crate zengif
Streaming decode
Memory limits
Frame compositing ❌ (use gif-dispose) ✅ built-in
Cooperative cancellation
Error tracing (file:line)
High-quality encoding ✅ (optional)

Memory Protection

Protect your server from malicious inputs:

use zengif::Limits;

let limits = Limits::default()
    .max_dimensions(4096, 4096)       // Reject huge canvases
    .max_frame_count(1000)            // Limit animation length
    .max_memory(256 * 1024 * 1024);   // 256 MB peak memory

The decoder will return an error before allocating if limits would be exceeded.

Cancellation

For web servers, you often need to stop processing if the client disconnects:

// `almost-enough` provides a thread-safe Stopper (add it separately: cargo add almost-enough)
use almost_enough::Stopper;
use zengif::{Decoder, Limits};

let stop = Stopper::new();
let stop_for_handler = stop.clone();

// In your request handler, if client disconnects:
stop_for_handler.cancel();

// The decoder will return GifError::Cancelled at the next check point
let mut decoder = Decoder::new(reader, Limits::default(), &stop)?;

Any type implementing enough::Stop works here. zengif re-exports Unstoppable for cases where cancellation isn't needed.

Error Diagnostics

When something goes wrong, you get the full story:

Error: InvalidFrameBounds { frame_left: 0, frame_top: 0, frame_width: 5000,
                            frame_height: 5000, canvas_width: 100, canvas_height: 100 }
   at src/decode/frame.rs:142:9
      ╰─ validating frame 3
   at src/decode/mod.rs:89:5
      ╰─ in decode_frame

High-Quality Encoding

With default features, zenquant is enabled and selected automatically:

use zengif::{EncoderConfig, Quantizer};

let config = EncoderConfig::new()
    .quantizer(Quantizer::auto());  // Picks best available (zenquant by default)

To use a specific quantizer, enable its feature and select it explicitly:

cargo add zengif --no-default-features --features std,imagequant
let config = EncoderConfig::new()
    .quantizer(Quantizer::imagequant());

Quantizer Options

Auto-selection priority (top to bottom):

Feature License Quality Speed Notes
zenquant (default) AGPL-3.0 Best perceptual Medium Butteraugli/SSIMULACRA2 metrics
quantette MIT/Apache-2.0 Very good Fast Oklab k-means
imagequant GPL-3.0* Good, smallest files Medium Compressible dithering patterns
quantizr MIT Good Fast
color_quant MIT Acceptable Fastest Good for high-throughput

*imagequant is GPL-3.0-or-later. Commercial license available from upstream.

Default features include AGPL code. cargo add zengif enables zenquant, which is AGPL-3.0-or-later. For permissive-only licensing, disable default features and pick a quantizer:

zengif = { version = "0.7", default-features = false, features = ["std", "quantette"] }

Without any quantizer feature, zengif is MIT/Apache-2.0 but encoding requires pre-indexed frames.

no_std / WASM

For WASM or embedded, disable the default std feature:

zengif = { version = "0.7", default-features = false }

You get core types (Rgba, Limits, GifError, etc.) but not the codec. Useful when you need to share types between WASM and native code.

Performance

Approximate throughput on AMD Ryzen 9 5900X (single-threaded, not independently verified -- run benches/codec.rs to reproduce):

Operation Throughput
Decode (composited) ~150 MB/s
Encode (quantized) ~40 MB/s
Encode (pre-indexed) ~200 MB/s

Image tech I maintain

State of the art codecs* zenjpeg · zenpng · zenwebp · zengif · zenavif (rav1d-safe · zenrav1e · zenavif-parse · zenavif-serialize) · zenjxl (jxl-encoder · zenjxl-decoder) · zentiff · zenbitmaps · heic · zenraw · zenpdf · ultrahdr · mozjpeg-rs · webpx
Compression zenflate · zenzop
Processing zenresize · zenfilters · zenquant · zenblend
Metrics zensim · fast-ssim2 · butteraugli · resamplescope-rs · codec-eval · codec-corpus
Pixel types & color zenpixels · zenpixels-convert · linear-srgb · garb
Pipeline zenpipe · zencodec · zencodecs · zenlayout · zennode
ImageResizer ImageResizer (C#) — 24M+ NuGet downloads across all packages
Imageflow Image optimization engine (Rust) — .NET · node · go — 9M+ NuGet downloads across all packages
Imageflow Server The fast, safe image server (Rust+C#) — 552K+ NuGet downloads, deployed by Fortune 500s and major brands

* as of 2026

General Rust awesomeness

archmage · magetypes · enough · whereat · zenbench · cargo-copter

And other projects · GitHub @imazen · GitHub @lilith · lib.rs/~lilith · NuGet (over 30 million downloads / 87 packages)

AI-Generated Code Notice

Developed with Claude (Anthropic). Not all code manually reviewed. Review critical paths before production use.

License

zengif itself is MIT or Apache-2.0, at your option.

Default features pull in AGPL code. The zenquant quantizer (enabled by default) is AGPL-3.0-or-later. The imagequant quantizer is GPL-3.0-or-later (commercial license available from upstream). For fully permissive licensing, disable defaults and use quantette, quantizr, or color_quant.


100% safe Rust - #![forbid(unsafe_code)]

Dependencies

~9MB
~191K SLoC