Pure-Rust JPEG / MJPEG decoder + encoder. No C, no FFI. Baseline and progressive DCT, planar YUV in and out, with real quality and chroma-subsampling control.
This crate is a vendored merge of two upstream pure-Rust projects, carried forward in-tree as one codec:
| Half | Upstream | Licence |
|---|---|---|
decode |
jpeg-decoder 0.3.2 |
MIT OR Apache-2.0 |
encode |
jpeg-encoder 0.7.0 |
(MIT OR Apache-2.0) AND IJG |
See NOTICE.md for attribution and the IJG obligations, and
CHANGES.md for everything changed since vendoring.
This software is based in part on the work of the Independent JPEG Group.
Measured against system FFmpeg 8.1.2, same machine, one pinned core each, CPU time, the two binaries interleaved, paired win-rate with a z-score at N ≥ 31.
| vs FFmpeg | ||
|---|---|---|
| Encode | parity — median 0.96× | matched output size (ours 3.1% smaller), fixed Huffman tables both sides; N=41, z −3.59 |
| Decode | parity — median 1.02× | byte-identical bitstream, both arms discarding output; N=31, z −0.18, inside noise |
Encode is measured on a 40-frame 1920×1080 4:2:0 clip at ~680 KB/frame; decode on a photographic still stream-copied to 300 frames.
Defaults match FFmpeg's MJPEG so the comparison above is like-for-like. Two options trade encode time for smaller files:
| Option | Size | Encode time | Lossless? |
|---|---|---|---|
| default | baseline | baseline | — |
-optimize_huffman 1 |
−7.4% | 2.09× | yes — changes the coding, not the image |
-trellis 1 |
smaller again | +144% | no — RD-optimal coefficient decisions |
Chroma is box-averaged on downsample rather than point-sampled. Point sampling aliases detail above the subsampled Nyquist into the baseband and no bitrate recovers it; averaging is worth −17.12% BD-rate on chroma-detailed content and +2.30 dB on saturated chroma edges, at no cost in speed.
-trellis's BD-rate (−2.51% mean) comes from a synthetic corpus that
mispriced its cost by 46×, so treat the quality figure as unverified on
real material.
- The encoder is gated against the decoder as a round-trip oracle, in CI.
- Every SIMD kernel has a scalar twin and a test asserting they agree bit-exactly; the twin stays in the tree as the fallback.
- NEON kernels are verified on real ARM hardware in CI, not merely cross-compiled.
- A foreign-encoder corpus (libjpeg progressive and baseline, 4:4:4/4:2:2/4:2:0, greyscale, restart intervals, 1×1 to 255×127) runs through both decode paths; with 60 mutations per file that is 117,120 decodes, zero panics.
fuzz/carries cargo-fuzz targets fordecode,decode_planarand a round-trip; a deterministic mutation suite runs on stable in ordinary CI.
use std::io::Cursor;
fn main() -> Result<(), rusty_jpeg::decode::Error> {
let bytes = std::fs::read("in.jpg").expect("read input");
let mut decoder = rusty_jpeg::Decoder::new(Cursor::new(bytes));
let pixels = decoder.decode()?;
let info = decoder.info().expect("populated by decode()");
println!("{}x{}, {:?}", info.width, info.height, info.pixel_format);
Ok(())
}decode_planar() returns the YCbCr planes directly, skipping upsampling and
colour conversion — use it when the consumer wants planar YUV.
use rusty_jpeg::{ColorType, Encoder};
fn main() -> Result<(), rusty_jpeg::EncodingError> {
let pixels = [255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255];
let mut jpeg = Vec::new();
let encoder = Encoder::new(&mut jpeg, 90); // quality 0-100
encoder.encode(&pixels, 2, 2, ColorType::Rgb)?;
std::fs::write("out.jpg", jpeg).expect("write output");
Ok(())
}Chroma subsampling (SamplingFactor), quantization tables
(QuantizationTableType), progressive mode, optimized Huffman tables and
trellis are set on the Encoder before encode. PlanarYcbcrImage accepts
planar YUV input without a round trip through RGB.
| Feature | Default | Effect |
|---|---|---|
std |
yes | Standard library. |
simd |
yes | AVX2/SSE4.1 kernels on x86; NEON on aarch64. |
rayon |
no | Decoder threading. Off because it measured slower at every image size — fork-join costs more than intra-frame parallelism buys. |
platform_independent |
no | Drop arch-specific code, forbid(unsafe_code). |
Decoder::set_single_threaded(true) selects the synchronous worker: ~38% less
CPU than the threaded default, which remains faster in wall-clock on a
multi-core box.
This crate is the standalone JPEG engine of
remade_ffmpeg_rs — a
ground-up, permissively-licensed Rust rebuild of FFmpeg: a drop-in
ffmpeg/ffprobe CLI on pure-Rust codecs, with no copyleft. Also check out our
sister project FFAI — media for
an AI-first world — and the rest of
github.com/remade-with-rust, including
the sibling codec crates
rusty_h264,
rusty_vp9,
rusty_mp3,
rusty_aac,
rusty-opus,
rusty_vorbis, and the
rusty-av1-toolkit forks.
Mata Network builds sovereign, self-hostable infrastructure. Remade With Rust is our open-source home for the permissively-licensed building blocks that work depends on.
(MIT OR Apache-2.0) AND IJG — the IJG clause attaches to the forward-DCT
files inherited from the encoder. See NOTICE.md.