# image-png: a pure Rust PNG codec with no unsafe code and a fast deflate path

> The png crate is a PNG decoder and encoder written in Rust with no unsafe code, fuzzed on OSS-fuzz, and it includes a fast encoding mode built on fdeflate. The interesting part is the trade it offers: memory safety by default, with an opt in zlib-rs feature that gives up that guarantee across your whole dependency tree for speed.

**image-rs/image-png** — PNG decoding and encoding library in pure Rust

- Repository: https://github.com/image-rs/image-png
- Website: https://docs.rs/png
- Stars: 521 · Forks: 178
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/image-rs-image-png

## Pure Rust, and that is the whole pitch

The repository description is a PNG decoding and encoding library in pure Rust, and the readme calls it a PNG decoder and encoder in pure Rust that is fast without being unsafe that also supports APNG.

Pure Rust is the differentiator, and it is worth being specific about what it buys. The readme states the guarantee plainly: no unsafe code, battle-tested, and fuzzed on OSS-fuzz. There is no C library to compile, no system zlib to install, and no cross compilation headache when you are building for an unusual target.

For a language whose ecosystem has spent years making memory safety the default expectation, a codec that contains no unsafe code is a genuinely useful thing to be able to point at. A PNG decoder is exactly the kind of component that ends up parsing untrusted input, so the combination of a pure Rust implementation and continuous fuzzing is a reasonable answer to that.

The crate is published as png on crates.io, with documentation on docs.rs. The manifest gives the version as 0.18.1, edition 2021, with a minimum Rust version of 1.81, and the categories entry of multimedia::images. The licence field is MIT OR Apache-2.0, and the readme gives the full terms: the Apache License, Version 2.0 or the MIT license, at your option.

The contribution terms mirror the licence choice. Any contribution intentionally submitted for inclusion is dual licensed as above without any additional terms or conditions, unless the contributor states otherwise. That is a clean, conventional arrangement for a library that wants both the MIT brevity and the Apache patent grant.

The repository layout is that of a well equipped Rust library: src/ for the code, tests/ and benches/ with several criterion benchmarks, a fuzz/ directory, examples/ with four programs, a png-afl/ directory, a CHANGES.md, a .rustfmt.toml, and a .cargo/ directory.

## Encoding speed comes from fdeflate, not from the PNG format

The performance claims in the readme are specific enough to be checkable and modest enough to be believable.

Performance is typically on par with or better than libpng, which is the reference point for anyone comparing against a C implementation. The more interesting claim is the second one. The crate includes a fast encoding mode powered by fdeflate that is dramatically faster than the fastest mode of libpng while simultaneously providing a better compression ratio.

Getting both speed and compression at once is unusual, because the usual trade is that a faster deflate implementation compresses worse. fdeflate is a Rust deflate implementation specialised for the PNG use case, and it is listed in the manifest as a direct dependency at version 0.3.3, alongside flate2 at 1.0.35 for the general purpose path.

That gives the crate two encoding paths with different characteristics, which is a design choice worth understanding before you pick one. The fast path is the default and the one to use unless you have a reason not to. flate2 is present as the more conservative option and as the backend that other crates in the tree may share.

For decoding, the readme notes that on nightly Rust you can slightly speed up decoding of some images by enabling the unstable feature of the crate. The word slightly is doing real work there, and the feature is named unstable for a reason. The manifest describes the feature as enabling use of nightly features and adds a warning that it sometimes makes performance worse.

So the picture is a crate that is fast by default, has an optional nightly path that might help and might not, and has a second feature flag with a much more serious trade attached.

## The zlib-rs feature changes your whole dependency tree

This is the part of the crate that deserves the most attention, and the manifest is unusually explicit about it.

The feature block reads like a warning notice.

```toml
# Enable use of nightly features. Sometimes makes performance worse.
unstable = []
# Use zlib-rs for faster PNG encoding at the cost of some `unsafe` code.
# WARNING: this changes the flate2 backend for your entire dependency tree!
# While the `png` crate always uses fully memory-safe decoding,
# this enables zlib-rs and introduces some unsafe code to all other crates
# that rely on flate2, including the decoding codepaths.
zlib-rs = ["flate2/zlib-rs"]
```

Read the warning carefully and it says something more interesting than the usual feature documentation. The png crate always uses fully memory-safe decoding. Enabling zlib-rs swaps the flate2 backend for your entire dependency tree, and that introduces unsafe code into every other crate relying on flate2, including the decoding code paths.

So the no unsafe code guarantee holds for the png crate's own code in all configurations. What the feature gives up is the guarantee for everything else in your build that shares flate2, and it does that by changing a shared backend rather than by adding a local option. That is a different kind of decision from adding a dependency with a fast implementation, because you are changing the behaviour of code you did not write and may not have audited.

The benchmarks directory shows how carefully the crate is measured. There are separate benchmark targets for the decoder, for unfilter, for adam7 and for expand_paletted, each with harness set to false because they use criterion rather than the built in test harness, and the unfilter, adam7 and expand_paletted ones carry required-features of benchmarks, so they only build when that feature is enabled.

The example programs are equally telling about what the crate is used for. There is png-generate.rs, which generates images, change-png-info.rs, which edits the metadata of an existing file, pngcheck.rs, which validates files, and corpus-bench.rs, which benchmarks against a corpus. A validator and a metadata editor are the two things you need in a pipeline that ingests PNGs from elsewhere.

## Fuzzing is part of the build, not an afterthought

The repository has a fuzz/ directory and a png-afl/ directory, and the Dockerfile is dedicated to fuzzing rather than to producing an artefact.

```dockerfile
FROM rustlang/rust:nightly
WORKDIR /image-png
COPY . .
RUN cargo install cargo-fuzz
RUN cargo fuzz run decode -- -runs=0
```

The decode target is the one that matters most for a parser, and the -runs=0 flag tells cargo-fuzz to build the fuzz target and run it without fuzzing, which is the check that the target compiles and its corpus loads. The image is based on the nightly toolchain because cargo-fuzz needs it.

The png-afl/ directory points at a second fuzzing approach, AFL rather than libFuzzer, and the Cargo.toml configures a cfg(fuzzing) check, which is how the crate tells a fuzz build apart from a normal one at compile time.

For a codec, fuzzing is not optional polish. A PNG file is a sequence of chunks with a CRC on each one, and the format allows ancillary chunks carrying arbitrary payload data. A decoder that mis-handles a chunk length, a truncated stream or a crafted filter byte is a memory safety bug waiting to happen, and a decoder written in a memory unsafe language has to get every one of those right. Writing it in Rust without unsafe moves the burden from the programmer to the compiler, and fuzzing is how you check that the compiler's guarantees actually hold at the boundary.

The presence of a corpus-bench.rs example alongside the fuzzing setup also suggests the maintainers treat the fuzzer's accumulated corpus as a performance fixture, which is a neat way to keep a realistic test set in the tree.

## Where the png crate is the wrong tool

There are four cases where a different library is the better answer, and they are all cases where the PNG specification has corners that a fast, safe implementation is unlikely to cover.

The first is an obscure but valid PNG that libpng accepts and this crate rejects. The readme does not claim complete coverage, and a codec that is fast and memory safe will tend to reject rather than guess when it meets something it does not understand. If your pipeline ingests PNGs from arbitrary sources, some of which were produced by unusual software, that failure mode matters more than the speed.

The second is a project that already has a C dependency and cares about consistency. If your build already links zlib, adding the png crate alongside libpng gives you two implementations of the same format with different behaviour on edge cases, which is a class of bug that is painful to diagnose.

The third is a project whose policy forbids unsafe code anywhere in the tree, including transitively. The zlib-rs feature is off by default and the crate's own code is always safe, but the feature exists, and an audit that greps for unsafe in the dependency tree will find it in the lock file.

The fourth is a project that needs more than PNG. The crate covers PNG and APNG. It does not do JPEG, WebP, AVIF or the rest, and if your image pipeline handles several formats, the PNG codec is one part of a larger problem rather than a solution to it.

On the alternative, the difference in approach against libpng is the entire toolchain. libpng is a C library you compile, link and configure, with a large API surface, its own error handling conventions, and a zlib dependency you choose. This crate is a Cargo dependency with no system libraries, no build configuration and no FFI boundary, and it gives you a Rust API with the compiler checking the buffer lifetimes for you. The trade is that libpng has decades of accumulated coverage of the specification and a much larger body of third party knowledge about its edge cases, while this crate is younger, smaller and refuses to be unsafe.

On maintenance, the crate version is 0.18.1 and the last push was on 2026-09-23, and the repository publishes no GitHub releases, so the CHANGES.md file is the record of what changed. The crate is hosted under the image-rs organisation, and the documentation is at docs.rs/png.

The one thing to verify before adopting is the compatibility question rather than the performance one. Write a test that decodes the PNGs you actually have, from your actual sources, and see whether the results are right. Speed you can measure later; wrong pixels you cannot.

## Conclusion

png suits a Rust project that needs PNG encoding or decoding without pulling in a C library, particularly one that must keep unsafe code out of its dependency tree, and it does not suit a project that needs the obscure corners of the PNG specification handled by a reference implementation. Before you adopt it, decide about the zlib-rs feature carefully, because the readme warns that it changes the flate2 backend for your entire dependency tree and introduces unsafe code to every crate that relies on flate2, including the decoding code paths, and check the version you pin because the crate is at 0.18.1 and the repository publishes no GitHub releases.

## FAQ

### What is the png crate?

It is a PNG decoding and encoding library written in pure Rust, published on crates.io as png, with support for APNG as well. The manifest gives the version as 0.18.1, edition 2021 and a minimum Rust version of 1.81.

### Does the png crate use unsafe code?

The readme states there is no unsafe code, that the crate is battle-tested, and that it is fuzzed on OSS-fuzz. The zlib-rs feature is the exception in effect, since it changes the flate2 backend for the whole dependency tree and introduces unsafe code into other crates that rely on flate2.

### What does the zlib-rs feature do in the png crate?

It enables zlib-rs for faster PNG encoding at the cost of some unsafe code, and the manifest warns that it changes the flate2 backend for your entire dependency tree, affecting all other crates that rely on flate2 including the decoding code paths.

### What are the benched parts of the png crate?

The manifest declares separate criterion benchmarks for the decoder, for unfilter, for adam7 and for expand_paletted. The last three carry required-features of benchmarks, so they only build when that feature is enabled.

### What licence is the png crate released under?

It is licensed under either the Apache License, Version 2.0 or the MIT license, at your option, and the manifest records it as MIT OR Apache-2.0. Contributions are dual licensed on the same terms unless the contributor states otherwise.

## Sources

- [image-rs/image-png on GitHub](https://github.com/image-rs/image-png)
- [Issues](https://github.com/image-rs/image-png/issues)
- [License: Apache-2.0](https://github.com/image-rs/image-png/blob/master/LICENSE)
- [Project website](https://docs.rs/png)
- [README](https://github.com/image-rs/image-png/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/image-rs-image-png
