# zengif
[](https://github.com/imazen/zengif/actions/workflows/ci.yml)
[](https://crates.io/crates/zengif)
[](https://docs.rs/zengif)
[](https://codecov.io/gh/imazen/zengif)
[](LICENSE-MIT)
A GIF codec built for servers: streaming, memory-safe, and production-ready.
## Getting Started
```bash
cargo add zengif
```
### Decode a GIF
```rust
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
```rust
use zengif::{Encoder, 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(width, height).repeat(Repeat::Infinite);
let mut output = Vec::new();
let mut encoder = Encoder::new(&mut output, config, Limits::default(), Unstoppable)?;
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
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`](https://crates.io/crates/gif) crate, adding these production features:
| 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:
```rust
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:
```rust
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)?;
```
## 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
For the smallest file sizes, enable the `imagequant` feature:
```bash
cargo add zengif --features imagequant
```
```rust
use zengif::{EncoderConfig, Quantizer};
let config = EncoderConfig::new(width, height)
.quantizer(Quantizer::imagequant()); // Best quality, smallest files
```
### Quantizer Options
| `imagequant` | AGPL-3.0* | Best | Medium |
| `quantizr` | MIT | Good | Fast |
| `color_quant` | MIT | Good | Fastest |
*[Commercial license available](https://supso.org/projects/pngquant) for closed-source projects.
**Without any quantizer feature, zengif is MIT/Apache-2.0 licensed.**
## no_std / WASM
For WASM or embedded, disable the default `std` feature:
```toml
zengif = { version = "0.4", 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
On AMD Ryzen 9 5900X:
| Decode (composited) | ~150 MB/s |
| Encode (quantized) | ~40 MB/s |
| Encode (pre-indexed) | ~200 MB/s |
## License
MIT or Apache-2.0, at your option.
The optional `imagequant` feature uses [libimagequant](https://github.com/ImageOptim/libimagequant) (AGPL-3.0). Use `quantizr` or `color_quant` for fully permissive licensing.
---
**100% safe Rust** - `#![forbid(unsafe_code)]`