zenzop [](https://github.com/imazen/zenzop/actions/workflows/ci.yml) [](https://crates.io/crates/zenzop) [](https://lib.rs/crates/zenzop) [](https://docs.rs/zenzop) [](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) [](#license)

June 28, 2026 · View on GitHub

A faster fork of the Zopfli DEFLATE compressor, written in Rust. Pure Rust, #![forbid(unsafe_code)], no_std + alloc compatible.

Zopfli is a well-known, battle-tested DEFLATE compressor that produces near-optimal output at the cost of speed. zenzop produces byte-identical output 1.2–2x faster through algorithmic improvements: precomputed cost tables, SIMD-accelerated match comparison, arena-based Huffman tree construction, pre-allocated stores, and a skip-hash optimization that eliminates redundant hash-chain walks on cached iterations.

With enhanced mode enabled, zenzop applies optimizations derived from ECT — expanded precode search, multi-strategy Huffman tree selection, and enhanced parser diversification — to produce smaller output than standard Zopfli, trading away byte-for-byte parity with the C reference.

zenzop is compress-only. Like Zopfli itself, it has no decompressor — the output is standard gzip/zlib/raw DEFLATE and decodes with any conforming decoder (e.g. flate2, miniz_oxide, or zenflate's decode side).

Quick start

[dependencies]
zenzop = "0.4.2"
use zenzop::{Options, Format};

let data = b"The quick brown fox jumps over the lazy dog";
let mut compressed = Vec::new();
zenzop::compress(Options::default(), Format::Gzip, &data[..], &mut compressed).unwrap();
assert!(!compressed.is_empty());

compress is the one-call entry point: pick a Format (Gzip, Zlib, or raw Deflate), pass any Read source and Write sink, and zenzop streams the optimized output. For finer control — enhanced mode, iteration count, cancellation — use the Options fields and streaming encoders documented below.

Features

  • Byte-identical output to C Zopfli in the default mode (verified by golden-master tests in CI).
  • Enhanced mode for smaller output than standard Zopfli (Options::enhanced).
  • 1.2–2x faster than Zopfli (input-dependent; larger gains on smaller blocks). See benchmarks/.
  • no_std + alloc — works on embedded and WASM targets.
  • Cooperative cancellation via enough::Stop — cancel long-running compressions cleanly.
  • Streaming encoder APIDeflateEncoder, GzipEncoder, ZlibEncoder.
  • Parallel block compression with the optional parallel (rayon) feature.

Enhanced mode

let mut options = zenzop::Options::default();
options.enhanced = true;

let mut output = Vec::new();
zenzop::compress(options, zenzop::Format::Gzip, &b"Hello, world!"[..], &mut output).unwrap();

Enhanced mode produces smaller DEFLATE output than standard Zopfli with roughly 5% runtime overhead. The output is still valid DEFLATE; it simply no longer matches the C reference byte-for-byte.

Tuning the ratio (iteration count)

Like Zopfli, zenzop trades CPU time for ratio by re-running its forward/backward LZ77 optimization pass multiple times. The knob is Options::iteration_count (default 15) — more iterations means smaller output and more time:

use std::num::NonZeroU64;

// `Options` is `#[non_exhaustive]`, so build from `default()` and set fields.
let mut options = zenzop::Options::default();
options.enhanced = true;
options.iteration_count = NonZeroU64::new(60).unwrap();   // squeeze harder for maximum ratio

let mut output = Vec::new();
zenzop::compress(options, zenzop::Format::Gzip, &b"Hello, world!"[..], &mut output).unwrap();

The effective iteration count is internally clamped to Options::iteration_cap (default DEFAULT_MAX_ITERATIONS = 1000) so that an Options fed from untrusted config can't trigger a compute-DoS; 60 is well under the cap. If you genuinely need more than 1000 iterations, raise the cap with Options::with_iteration_cap.

Options fields

FieldTypeDefaultEffect
iteration_countNonZeroU6415Total LZ77 optimization passes. Higher = smaller output, slower. Raise (e.g. to 60) for maximum ratio.
enhancedboolfalseEnable ECT-derived optimizations (smaller output, ~5% slower, drops byte-for-byte parity with C Zopfli).
iterations_without_improvementNonZeroU64u64::MAXEarly-stop budget: bail after this many consecutive passes with no size improvement. Defaults to "never give up early".
maximum_block_splitsu1615Maximum number of block splits (0 = unlimited).
block_typeBlockTypeBlockType::DynamicDEFLATE block type; Dynamic auto-selects the smallest.
iteration_capNonZeroU641000Internal clamp applied to both iteration fields. Raise via Options::with_iteration_cap.

Options is #[non_exhaustive], so external code must build it from Options::default() and assign the fields it wants to change (as above) — a struct literal won't compile outside this crate.

Output formats

Format selects the container for the compressed stream — all readable by standard decoders:

VariantFormatFeature
Format::Gzipgzip (RFC 1952)gzip (default)
Format::Zlibzlib (RFC 1950)zlib (default)
Format::Deflateraw DEFLATE (RFC 1951)always available

Streaming encoder

use std::io::Write;

let mut encoder = zenzop::DeflateEncoder::new_buffered(
    zenzop::Options::default(),
    Vec::new(),
);
encoder.write_all(b"Hello, world!").unwrap();
let compressed = encoder.into_inner().unwrap().finish().unwrap().into_inner();
assert!(!compressed.is_empty());

With cancellation / timeout

use std::io::{self, Write};

fn compress_cancellable(data: &[u8], stop: impl zenzop::Stop) -> io::Result<Vec<u8>> {
    let mut encoder = zenzop::GzipEncoder::with_stop_buffered(
        zenzop::Options::default(),
        Vec::new(),
        stop,
    )?;
    encoder.write_all(data)?;
    let result = encoder.into_inner()?.finish()?;
    if !result.fully_optimized() {
        eprintln!("compression was cut short by the stop token");
    }
    Ok(result.into_inner())
}

Stop, StopReason, and Unstoppable are re-exported from enough; wire any token (deadline, cancel flag, signal handler) into the with_stop / with_stop_buffered constructors. CompressResult::fully_optimized() tells you whether the encoder finished all iterations or was cut short.

Command-line tool

The bundled zenzop binary compresses each file argument to gzip (<file><file>.gz). It reads two environment variables: ZENZOP_ITERATIONS (sets iteration_count, default 15) and ZENZOP_ENHANCED (any value enables enhanced mode):

ZENZOP_ENHANCED=1 ZENZOP_ITERATIONS=60 zenzop input.bin   # → input.bin.gz

Cargo features

FeatureDefaultDescription
gzipyesGzip format support
zlibyesZlib format support
stdyesStandard library (logging, std::io traits)
parallelnoParallel block compression via rayon

For no_std usage: default-features = false. The crate then falls back to a minimal in-crate Write trait you can implement for your sink.

MSRV

The minimum supported Rust version is 1.89. Bumping this is not considered a breaking change.

Benchmarks

benches/compress.rs measures zenzop against the zopfli crate (the upstream this fork descends from) on three representative inputs — text, JavaScript, and a PNG — all at Format::Gzip with Options::default(). Input bytes are loaded into memory once, outside the timed region, so the measurement is pure compression throughput. Run it yourself:

git clone https://github.com/imazen/zenzop && cd zenzop
cargo bench --bench compress       # build WITHOUT -C target-cpu=native

Full protocol, threading mode, competitor pinning, and how to reproduce a size-vs-time tradeoff curve live in benchmarks/README.md. We don't ship pre-baked numbers — the ratio is workload- and hardware-dependent, so the harness is the source of truth.

Development

cargo build --release          # binary at target/release/zenzop
cargo test                     # unit + property-based tests
./test/run.sh                  # golden master: byte-identical to C Zopfli

License

Apache-2.0. See LICENSE.

Upstream contribution

This is a fork of google/zopfli (Apache-2.0). We'd happily release our improvements under the original Apache-2.0 license if upstream wants to take over maintenance — we'd rather contribute back than maintain a parallel codebase. Open an issue or reach out.

Origin

Forked from zopfli-rs/zopfli, Carol Nichols' well-maintained Rust reimplementation of Google's Zopfli. Enhanced-mode optimizations are derived from ECT (Efficient Compression Tool) by Felix Hanau. Thanks to both projects — zenzop builds directly on their work.

AI-generated code notice

Developed with Claude (Anthropic). Not all code has been manually reviewed; review critical paths before production use.

Image tech I maintain

Codecs ¹zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr
Codec internalszenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize
Compressionzenflate · zenzop · zenzstd
Processingzenresize · zenquant · zenblend · zenfilters · zensally · zentone
Pixels & colorzenpixels · zenpixels-convert · linear-srgb · garb
Pipeline & frameworkzenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract
Metricszensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs
Pickers & MLzenanalyze · zenpredict · zenpicker
ProductsImageflow image engine (.NET · Node · Go) · Imageflow Server · ImageResizer (C#)

¹ pure-Rust, #![forbid(unsafe_code)] codecs, as of 2026

General Rust awesomeness

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

Open source · @imazen · @lilith · lib.rs/~lilith