zenwebp [](https://github.com/imazen/zenwebp/actions/workflows/ci.yml) [](https://crates.io/crates/zenwebp) [](https://lib.rs/crates/zenwebp) [](https://docs.rs/zenwebp) [](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) [](#license)
July 2, 2026 · View on GitHub
zenwebp is a pure-Rust WebP codec — lossy (VP8) and lossless (VP8L) encode and
decode, plus alpha, animation, and ICC/EXIF/XMP metadata. No C dependencies,
#![forbid(unsafe_code)], and no_std-compatible (just alloc). It is
SIMD-accelerated on x86 (SSE2/SSE4.1/AVX2), ARM64 (NEON), and WASM (SIMD128) via
runtime CPU detection, and tracks libwebp on both features and performance.
Quick start
[dependencies]
zenwebp = "0.4.5"
use zenwebp::{EncodeRequest, LossyConfig, PixelLayout};
// Decode any WebP to packed RGBA8 (width * height * 4 bytes) in one call:
let (rgba, width, height) = zenwebp::oneshot::decode_rgba(webp_bytes)?;
// Encode 8-bit pixels to lossy WebP at quality 85 (RGB shown; RGBA/BGRA/L8/… also accepted):
let rgb: &[u8] = /* width * height * 3 bytes */;
let webp = EncodeRequest::lossy(&LossyConfig::new().with_quality(85.0), rgb, PixelLayout::Rgb8, width, height)
.encode()?;
New to zenwebp? The API guide demonstrates 100% of the public API with runnable examples.
Decoding
// One-shot decode — always 4-channel RGBA8 (alpha = 255 for opaque images):
let (pixels, width, height) = zenwebp::oneshot::decode_rgba(webp_bytes)?;
The oneshot module also has decode_rgb, decode_bgra, decode_argb,
decode_yuv420, decode_rgb565, decode_rgba4444, premultiplied variants, and
*_into versions that decode into a caller-provided buffer.
For control over headers, dithering, upsampling, and limits, use WebPDecoder
for two-phase decoding (inspect headers before allocating):
use zenwebp::WebPDecoder;
let webp_bytes: &[u8] = /* your WebP data */;
let mut decoder = WebPDecoder::build(webp_bytes)?;
let info = decoder.info();
println!("{}x{}, alpha={}, orientation={:?}",
info.width, info.height, info.has_alpha, info.orientation);
let mut output = vec![0u8; decoder.output_buffer_size().unwrap()];
decoder.read_image(&mut output)?;
read_image output format. read_image writes the image's native format:
packed RGBA8 (4 bytes/pixel, R,G,B,A) when info.has_alpha, otherwise packed
RGB8 (3 bytes/pixel, R,G,B). It does not take a format parameter — the channel
count follows the bitstream. The buffer must be exactly output_buffer_size()
bytes (width * height * {3 or 4}); any other length returns
DecodeError::ImageTooLarge. Branch on info.has_alpha before interpreting the
bytes. If you always want 4-channel output regardless of the source, use
oneshot::decode_rgba / DecodeRequest::decode_rgba instead (alpha is set to 255
for opaque images).
Encoding
Build a reusable LossyConfig or LosslessConfig and drive it with
EncodeRequest. Lossy encode accepts any interleaved or planar PixelLayout:
Rgb8, Rgba8, Bgr8, Bgra8, Argb8, L8, La8, and Yuv420 — pass the
variant that matches your buffer (e.g. PixelLayout::Rgba8 for 4-channel RGBA, no
pre-conversion needed). Alpha-bearing layouts encode an alpha plane; the rest are
opaque. (See the feature comparison
for the full input-format matrix.)
use zenwebp::{LossyConfig, EncodeRequest, PixelLayout};
let rgb_pixels: &[u8] = /* your RGB data */;
let (width, height) = (800, 600);
let config = LossyConfig::new()
.with_quality(85.0)
.with_method(4); // 0 = fast, 6 = best
let webp = EncodeRequest::lossy(&config, rgb_pixels, PixelLayout::Rgb8, width, height)
.encode()?;
Lossless takes the same shape:
use zenwebp::{LosslessConfig, EncodeRequest, PixelLayout};
let rgba_pixels: &[u8] = /* your RGBA data */;
let (width, height) = (800, 600);
let config = LosslessConfig::new()
.with_quality(90.0)
.with_method(6);
let webp = EncodeRequest::lossless(&config, rgba_pixels, PixelLayout::Rgba8, width, height)
.encode()?;
ICC/EXIF/XMP metadata can be embedded during encode with
EncodeRequest::with_icc_profile / with_metadata, or added to, read from, and
removed from already-encoded bytes via the zenwebp::metadata module (no pixel
re-encode). Animation is built with mux::AnimationEncoder.
Transcode (decode → re-encode)
use zenwebp::{LossyConfig, EncodeRequest, PixelLayout};
let (rgba, w, h) = zenwebp::oneshot::decode_rgba(input_webp)?;
let cfg = LossyConfig::new().with_quality(75.0);
// Arg order: lossy(config, pixels, LAYOUT, width, height) — the pixel layout
// comes BEFORE the dimensions. `decode_rgba` always yields `PixelLayout::Rgba8`.
let out = EncodeRequest::lossy(&cfg, &rgba, PixelLayout::Rgba8, w, h).encode()?;
Untrusted input: error handling & limits
Decode/encode return Result<_, whereat::At<E>> (At<DecodeError> /
At<EncodeError>). The At<…> wrapper records a build-time source location for
your logs; get the underlying error with err.error() (borrow) or
err.into_inner() (owned), then match the variant:
use zenwebp::DecodeError;
match zenwebp::oneshot::decode_rgba(webp_bytes) {
Ok((pixels, w, h)) => { /* `pixels` is packed RGBA8: w*h*4 bytes, R,G,B,A */ }
Err(e) => {
// `e.location()` is the whereat capture site (file:line) — log it for triage:
if let Some(loc) = e.location() {
eprintln!("decode failed at {}:{}", loc.file(), loc.line());
}
match e.error() {
DecodeError::Cancelled(_) => eprintln!("cancelled / timed out"), // 499
// pixel/dimension caps surface here; the message says which limit:
DecodeError::InvalidParameter(msg) => eprintln!("rejected: {msg}"), // 400/413
DecodeError::MemoryLimitExceeded => eprintln!("too large"), // 413
other => eprintln!("decode failed: {other:?}"), // 400/500
}
}
}
The one-shot helpers decode with DecodeConfig::default(), which already
enforces a server-safe Limits (≤120 MP, 16384×16384, 1 GB memory). To tighten
or relax the caps, build a DecodeConfig with your own Limits and drive the
decode explicitly with DecodeRequest:
use zenwebp::{DecodeConfig, DecodeRequest, Limits};
// `Limits` is `#[non_exhaustive]`; spread `..Limits::default()` to keep the other
// server-safe caps, then override the fields you care about. Fields are
// `Option<…>`; `None` means "no cap on this dimension".
let config = DecodeConfig::default().limits(Limits {
max_total_pixels: Some(40_000_000), // 40 MP hard cap
max_memory: Some(256 * 1024 * 1024), // 256 MB during decode
max_width: Some(8192),
max_height: Some(8192),
..Limits::default()
});
let (rgba, w, h) = DecodeRequest::new(&config, webp_bytes).decode_rgba()?;
DecodeConfig also has builder shortcuts for the common caps —
DecodeConfig::default().max_dimensions(8192, 8192).max_memory(256 << 20) — and
Limits::none() removes every cap (only for fully trusted input).
The two-phase WebPDecoder carries the same knobs — set them before read_image:
use zenwebp::{WebPDecoder, Limits};
let webp_bytes: &[u8] = /* untrusted WebP data */;
let mut decoder = WebPDecoder::build(webp_bytes)?;
// Reject anything bigger than your budget *before* allocating the output buffer.
decoder.set_limits(Limits {
max_total_pixels: Some(40_000_000), // 40 MP
max_memory: Some(256 * 1024 * 1024), // 256 MB
..Limits::default() // keeps the other server-safe caps
});
let mut output = vec![0u8; decoder.output_buffer_size().unwrap()];
decoder.read_image(&mut output)?; // errs if a limit is exceeded
Untrusted input: cancellation (no thread killing)
Both decode and encode accept a cooperative enough::Stop token, so a
long-running operation on a hostile input can be aborted from another thread
without kill-ing it (which would leak the work-in-progress allocations). The
token types are re-exported at the crate root (zenwebp::Stop / StopReason /
Unstoppable), so you don't need to add enough to your own Cargo.toml. Wrap
your own cancel flag — an AtomicBool, a deadline, a request-aborted handle — in
a tiny impl Stop:
use core::sync::atomic::{AtomicBool, Ordering};
use zenwebp::{Stop, StopReason};
use zenwebp::{DecodeConfig, DecodeRequest, EncodeRequest, LossyConfig, PixelLayout};
struct CancelFlag<'a>(&'a AtomicBool);
impl Stop for CancelFlag<'_> {
fn check(&self) -> Result<(), StopReason> {
if self.0.load(Ordering::Relaxed) {
Err(StopReason::Cancelled) // also StopReason::TimedOut for deadlines
} else {
Ok(())
}
}
}
let cancelled = AtomicBool::new(false);
let stop = CancelFlag(&cancelled);
// (another thread / a timeout sets `cancelled` to true to abort)
// Cancellable decode: pass the token via `.stop(...)`.
let config = DecodeConfig::default();
let decoded = DecodeRequest::new(&config, webp_bytes).stop(&stop).decode_rgba();
// Cancellable encode: pass the token via `.with_stop(...)`.
let cfg = LossyConfig::new().with_quality(75.0);
let encoded = EncodeRequest::lossy(&cfg, &rgb_pixels, PixelLayout::Rgb8, w, h)
.with_stop(&stop)
.encode();
A cancelled operation returns DecodeError::Cancelled(StopReason) /
EncodeError::Cancelled(StopReason). The token is checked periodically inside the
hot loops, so cancellation latency is bounded by a chunk of work, not the whole
image. The default (Unstoppable, used when you don't call .stop(...)) is
zero-cost. For a ready-made, thread-safe cancel/deadline token see
almost_enough::Stopper.
Features
- Pure Rust — no C dependencies, builds anywhere Rust does
#![forbid(unsafe_code)]— memory safety guaranteed; SIMD via safe abstractionsno_stdcompatible — works with justalloc, no standard library needed- SIMD accelerated — SSE2/SSE4.1/AVX2 on x86, NEON on ARM64, SIMD128 on WASM
- Full format support — lossy, lossless, alpha, animation (encode + decode), ICC/EXIF/XMP metadata, EXIF orientation parsing, mux/demux, chroma dithering
- Metadata module —
zenwebp::metadataextracts/embeds/removes ICC, EXIF, and XMP in encoded WebP bytes without decoding pixels - Server-safe — memory/pixel limits and cooperative cancellation for untrusted input
- zencodec integration — always-available unified codec traits (no feature flag), streaming decode, color/metadata policy, and resource estimation
Safe SIMD
We achieve both safety and performance through safe abstractions over CPU intrinsics:
archmageandmagetypes— token-gated safe intrinsics with runtime CPU detectionsafe_unaligned_simd— safe unaligned load/store
These abstractions may not be perfect, but we trust them over hand-rolled unsafe code.
Decoder
Supports all WebP features: lossy and lossless compression, alpha channel, animation, and the extended format with ICC/EXIF/XMP chunks. Output formats: RGB, RGBA, BGR, BGRA, ARGB, YUV 4:2:0, RGB565, RGBA4444, and premultiplied RGBA/BGRA/ARGB. Chroma dithering matches libwebp pixel-for-pixel.
Encoder
Supports lossy and lossless encoding with configurable quality (0–100) and a speed/quality tradeoff (method 0–6). Lossy adds content-aware presets, target file size / target PSNR search, SNS, trellis quantization, and near-lossless; lossless adds the full predictor / cross-color / palette / meta-Huffman transform pipeline.
use zenwebp::{LossyConfig, LosslessConfig, EncodeRequest, PixelLayout};
// Lossless encoding
let config = LosslessConfig::new().with_quality(100.0);
let webp = EncodeRequest::lossless(&config, pixels, PixelLayout::Rgba8, w, h).encode()?;
// Fast lossy encoding (larger files)
let config = LossyConfig::new().with_quality(75.0).with_method(0);
let webp = EncodeRequest::lossy(&config, pixels, PixelLayout::Rgb8, w, h).encode()?;
// High quality lossy (slower, smaller files)
let config = LossyConfig::new().with_quality(75.0).with_method(6);
let webp = EncodeRequest::lossy(&config, pixels, PixelLayout::Rgb8, w, h).encode()?;
Feature comparison with libwebp
Decoder
| Feature | zenwebp | libwebp |
|---|---|---|
| Lossy (VP8) | :white_check_mark: | :white_check_mark: |
| Lossless (VP8L) | :white_check_mark: | :white_check_mark: |
| Alpha channel | :white_check_mark: | :white_check_mark: |
| Animation decode + encode (ANIM/ANMF) | :white_check_mark: | :white_check_mark: |
| Extended format (VP8X) | :white_check_mark: | :white_check_mark: |
| ICC/EXIF/XMP metadata | :white_check_mark: | :white_check_mark: |
| Metadata without pixel decode | :white_check_mark: | :white_check_mark: |
| Output: RGB, RGBA | :white_check_mark: | :white_check_mark: |
| Output: BGR, BGRA | :white_check_mark: | :white_check_mark: |
| Output: ARGB | :white_check_mark: | :white_check_mark: |
| Output: YUV 4:2:0 | :white_check_mark: | :white_check_mark: |
| Output: RGB565, RGBA4444 | :white_check_mark: | :white_check_mark: |
| Premultiplied alpha output (RGBA, BGRA, ARGB) | :white_check_mark: | :white_check_mark: |
| Fancy chroma upsampling | :white_check_mark: | :white_check_mark: |
| Bilinear chroma upsampling | :white_check_mark: | :white_check_mark: |
| Nearest-neighbor upsampling | :white_check_mark: | :white_check_mark: |
| Incremental decode (partial bytes in, rows out) | :x: | :white_check_mark: |
| Crop during decode | :x: | :white_check_mark: |
| Scale during decode | :x: | :white_check_mark: |
| 2-thread decode pipeline (reconstruct + filter overlap) | :x: | :white_check_mark: width >= 512 |
| Chroma dithering (hides banding at high Q) | :white_check_mark: default off | :white_check_mark: default off |
| Memory limits | :white_check_mark: | :x: |
Encoder (Lossy VP8)
| Feature | zenwebp | libwebp |
|---|---|---|
| Quality (0-100) | :white_check_mark: | :white_check_mark: |
| Method (0-6) speed/quality | :white_check_mark: | :white_check_mark: |
| Presets (Photo, Drawing, etc.) | :white_check_mark: 6 | :white_check_mark: 6 |
| Auto preset (content-aware selection) | :white_check_mark: | :x: |
| Target file size (secant method) | :white_check_mark: | :x: |
| Target file size (multi-pass) | :x: | :white_check_mark: |
| Target PSNR | :white_check_mark: | :white_check_mark: |
| SNS (spatial noise shaping) | :white_check_mark: | :white_check_mark: |
| Filter strength/sharpness | :white_check_mark: | :white_check_mark: |
| Autofilter | :white_check_mark: | :white_check_mark: |
| Segments (1-4) | :white_check_mark: | :white_check_mark: |
| Token partitions | 1 | 1-8 |
| Intra16 modes (DC/V/H/TM) | :white_check_mark: | :white_check_mark: |
| Intra4 modes (10 modes) | :white_check_mark: | :white_check_mark: |
| Trellis quantization (m5-6) | :white_check_mark: | :white_check_mark: |
| Alpha channel (lossless + lossy quant) | :white_check_mark: | :white_check_mark: |
| Sharp YUV conversion | :white_check_mark: | :white_check_mark: |
| Multi-pass encoding | :white_check_mark: | :white_check_mark: |
| Near-lossless | :white_check_mark: | :white_check_mark: |
| Encoding statistics | :white_check_mark: | :white_check_mark: |
| Progress callback | :white_check_mark: | :white_check_mark: |
| Cancellation without thread killing (key for untrusted input) | :white_check_mark: | :x: |
| Alpha encoded on 2nd thread | :x: | :white_check_mark: |
Encoder (Lossless VP8L)
| Feature | zenwebp | libwebp |
|---|---|---|
| Predictor transform (14 modes) | :white_check_mark: | :white_check_mark: |
| Cross-color transform | :white_check_mark: | :white_check_mark: |
| Subtract green transform | :white_check_mark: | :white_check_mark: |
| Color indexing (palette) | :white_check_mark: | :white_check_mark: |
| Palette sorting strategies | :white_check_mark: 2 | :white_check_mark: |
| Pixel bundling (2/4/8 per pixel) | :white_check_mark: | :white_check_mark: |
| Color cache (auto-sized) | :white_check_mark: | :white_check_mark: |
| LZ77 (standard + RLE + box) | :white_check_mark: | :white_check_mark: |
| TraceBackwards DP (Zopfli-style) | :white_check_mark: | :white_check_mark: |
| Meta-Huffman (spatial codes) | :white_check_mark: | :white_check_mark: |
| Multi-config testing (m5-6) | :white_check_mark: | :white_check_mark: |
| Near-lossless (pixel + residual) | :white_check_mark: | :white_check_mark: |
| AnalyzeEntropy (5 modes) | :white_check_mark: | :white_check_mark: |
Encoder input formats
| Format | zenwebp | libwebp |
|---|---|---|
| RGB, RGBA | :white_check_mark: | :white_check_mark: |
| BGR, BGRA | :white_check_mark: | :white_check_mark: |
| ARGB | :white_check_mark: | :white_check_mark: |
| YUV 4:2:0 | :white_check_mark: | :white_check_mark: |
| L8 (grayscale) | :white_check_mark: | :x: requires conversion |
| LA8 (grayscale + alpha) | :white_check_mark: | :x: requires conversion |
| Streaming input (push_rows) | :white_check_mark: | :x: |
Container / metadata
| Feature | zenwebp | libwebp |
|---|---|---|
| RIFF container read/write | :white_check_mark: | :white_check_mark: |
| VP8X extended format | :white_check_mark: | :white_check_mark: |
| ICC/EXIF/XMP read | :white_check_mark: | :white_check_mark: |
| ICC/EXIF/XMP write | :white_check_mark: | :white_check_mark: via libwebpmux |
| Animation encode | :white_check_mark: | :white_check_mark: |
| Mux API (assemble chunks) | :white_check_mark: | :white_check_mark: via libwebpmux |
| Demux API (frame iteration) | :white_check_mark: | :white_check_mark: via libwebpdemux |
Platform / build
| Feature | zenwebp | libwebp |
|---|---|---|
| Language | Pure Rust | C |
| Memory safety | :white_check_mark: #![forbid(unsafe_code)] | :x: manual C memory management |
| no_std + alloc | :white_check_mark: | :x: |
| WASM | :white_check_mark: | :white_check_mark: via Emscripten |
| WASM SIMD128 acceleration | :white_check_mark: | :white_check_mark: via SIMDe |
| SSE2 / SSE4.1 | :white_check_mark: | :white_check_mark: |
| AVX2 | :white_check_mark: | :x: |
| NEON (ARM64) | :white_check_mark: | :white_check_mark: |
| MIPS DSP | :x: | :white_check_mark: |
| Runtime CPU detection | :white_check_mark: | :white_check_mark: |
Performance
Benchmarks compare against libwebp (C reference) with runtime SIMD dispatch (no
-C target-cpu=native — that is what ships to users). Full methodology, harnesses,
competitor pinning, and result files are under
benchmarks/ and
docs/PERFORMANCE.md.
Lossy decoder
Bit-exact with libwebp — 0 pixel diffs on 12,825 scraped WebP files and 218
conformance files. The v2 streaming decoder (src/decoder/vp8v2/) is the default
path, byte-identical to v1 on all conformance + roundtrip tests.
Tested across 14 images (CLIC2025 photos, screenshots, CID22):
| Content | zenwebp vs libwebp (C) |
|---|---|
| Photos (CLIC2025, 2K) | 1.09–1.12x |
| Screenshots (1K–4K) | 1.06–1.14x |
| Small photos (512–576px) | 1.10–1.15x |
Streaming via zencodec's `StreamingDecode$ \text{trait} \text{yields} 16-\text{row} \text{RGB} \text{strips} — \text{the} \text{full} \text{decoded} \text{image} \text{never} \text{needs} \text{to} \text{exist} \text{in} \text{memory}:
| \text{Decode} \text{mode} | \text{Peak} \text{memory} (2940 \times 1912) |
|---|---|
| $StreamingDecode::next_batch()` | 1.5 MB |
decode_rgb() (full frame) | 35 MB |
libwebp WebPDecodeRGB | 34 MB |
Lossless decoder
| Content | zenwebp vs libwebp (C) |
|---|---|
| Photos (512px) | at parity or faster |
| Screenshots (2K) | 1.23–1.31x |
Lossy encoder
| Method | Speed vs libwebp | Size vs libwebp |
|---|---|---|
| m4 (default) | 1.35x | 1.01x |
| m5 | 1.34x | 1.0002x |
| m6 (best) | 1.32x | 1.002x |
File sizes within 0.02% of libwebp at method 5.
Lossless encoder
| Method | Speed vs libwebp | Size vs libwebp |
|---|---|---|
| m2–m4 | 1.03x (near parity) | 1.00–1.01x |
| m6 | 2.6x faster | 1.01x |
24/24 pixel-exact lossless roundtrips verified. At the same quality setting, zenwebp produces files within 1–5% of libwebp's size with comparable visual quality.
no_std support
[dependencies]
zenwebp = { version = "0.4.5", default-features = false }
Both encoder and decoder work without std. The decoder takes &[u8] slices and the
encoder writes to Vec<u8>. Only EncodeRequest::encode_to (writing to a
std::io::Write) requires the std feature.
Credits
zenwebp started as a fork of image-webp,
the pure-Rust WebP crate from the image-rs project.
The original decoder and lossless encoder formed the foundation on which zenwebp was
built, and the lossless decoder still shares the most DNA with image-webp. We're
grateful to the image-rs maintainers for their well-structured, battle-tested
codebase — if you don't need lossy encoding, animation, or SIMD, their crate is a
smaller, simpler dependency.
From that foundation, zenwebp added a ground-up lossy encoder with full RD optimization, a redesigned streaming decoder, SIMD acceleration via archmage / magetypes (and safe_unaligned_simd), animation encoding, and extensive optimization across all pipelines to reach libwebp feature and performance parity.
- libwebp (Google) — reference implementation. Our lossy encoder follows libwebp's algorithms for RD optimization, trellis quantization, and mode selection. The WebP format itself is Google's creation.
- Claude (Anthropic) — AI-assisted development.
Contributions are welcome — please open an issue or pull request. Code review is recommended for production use.
License
Dual-licensed: AGPL-3.0 or commercial.
I've maintained and developed open-source image server software — and the 40+ library ecosystem it depends on — full-time since 2011. Fifteen years of continual maintenance, backwards compatibility, support, and the (very rare) security patch. That kind of stability requires sustainable funding, and dual-licensing is how we make it work without venture capital or rug-pulls. Support sustainable and secure software; swap patch tuesday for patch leap-year.
Your options:
- Startup license — $1 if your company has under $1M revenue and fewer than 5 employees. Get a key →
- Commercial subscription — Governed by the Imazen Site-wide Subscription License v1.1 or later. Apache 2.0-like terms, no source-sharing requirement. Sliding scale by company size. Pricing & 60-day free trial →
- AGPL v3 — Free and open. Share your source if you distribute.
See LICENSE-COMMERCIAL for details.
Upstream code from image-rs/image-webp is licensed under MIT OR Apache-2.0. Our additions and improvements are dual-licensed (AGPL-3.0 or commercial) as above. Versions 0.1.x–0.3.x of zenwebp were MIT OR Apache-2.0.
Image tech I maintain
| Codecs ¹ | zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr |
| Codec internals | zenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize |
| Compression | zenflate · zenzop · zenzstd |
| Processing | zenresize · zenquant · zenblend · zenfilters · zensally · zentone |
| Pixels & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline & framework | zenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract |
| Metrics | zensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs |
| Pickers & ML | zenanalyze · zenpredict · zenpicker |
| Products | Imageflow 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