You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
zen* codecs — Each implements zencodec::Encoding / zencodec::Decoding on their
own config types. Format-specific methods live on the concrete types, not the trait.
zencodecs — Multi-format dispatch. Provides encoding_jpeg(), encoding_webp() etc.
that return the concrete codec types. Also provides encoding(format) for runtime dispatch
and decode() / probe() convenience functions.
Design Principles
Short default path, full control when needed — one-liner for common cases, builder chain for advanced
Config is reusable, Job is per-operation — Config has no lifetimes (storable), Job borrows (temporary)
.job() bridges the two — type system enforces the lifetime boundary
Pixel type is the terminal method — encode_rgb8(img) provides data AND executes
Trait for the common surface — Encoding / Decoding traits let you abstract over codecs when useful
Concrete types for format-specific features — encoding_jpeg() returns zenjpeg::EncodeConfig with JPEG-specific methods
// Level 1: Format-specific — full access to JPEG featureslet bytes = encoding_jpeg().with_quality(90.0).with_progressive(true)// JPEG-only, not on trait.with_sharp_yuv(true)// JPEG-only.encode_rgb8(img.as_ref())? // from Encoding trait.into_vec();// Level 2: Generic over codecs — only trait methodsfncompress(config:&implEncoding,img:ImgRef<Rgb<u8>>) -> Result<Vec<u8>>{Ok(config.encode_rgb8(img)?.into_vec())}compress(&encoding_jpeg().with_quality(85.0), img.as_ref())?;compress(&encoding_webp().with_quality(80.0), img.as_ref())?;// Level 3: Runtime dispatch — format chosen at runtimelet config = encoding(format).with_quality(80.0);let bytes = config.encode_rgb8(img.as_ref())?.into_vec();
Usage Chains (Shortest to Fullest)
// ═══ DECODE ═══// One-linerlet img = decode(&data)?.into_rgb8();// With limitslet img = decoding().with_limit_pixels(100_000_000).decode(&data)?
.into_rgb8();// Full controllet img = decoding().with_limit_pixels(100_000_000).with_limit_memory(512_000_000).job().with_stop(&stop).decode(&data)?
.into_rgb8();// Format-specific decode configlet img = decoding_jpeg().with_strictness(Strictness::Lenient)// JPEG-only.job().with_stop(&stop).decode(&data)?
.into_rgb8();// Streaming (animation)letmut dec = decoding().job().with_stop(&stop).decoder(&data)?;whileletSome(frame) = dec.next_frame()? {process(frame.into_rgba8(), frame.delay_ms());}// ═══ ENCODE ═══// One-linerlet bytes = encoding_jpeg().encode_rgb8(img.as_ref())?.into_vec();// With qualitylet bytes = encoding_jpeg().with_quality(90.0).encode_rgb8(img.as_ref())?
.into_vec();// Full control (format-specific)let bytes = encoding_jpeg().with_quality(90.0).with_effort(8).with_progressive(true)// JPEG-only.with_sharp_yuv(true)// JPEG-only.job().with_metadata(&decoded.metadata()).with_stop(&stop).encode_rgb8(img.as_ref())?
.into_vec();// Runtime format dispatchlet output = encoding(format).with_quality(80.0).encode_rgb8(img.as_ref())?;println!("format: {:?}, {} bytes", output.format(), output.len());// Auto-select formatlet output = encoding(Auto).with_quality(80.0).encode_rgb8(img.as_ref())?;// Streaming (animation)letmut enc = encoding_gif().with_quality(80).with_dithering(0.5)// GIF-only.job().with_stop(&stop).encoder(width, height)?;for frame in frames {
enc.add_frame_rgba8(frame.as_ref(),100)?;}let bytes = enc.finish()?.into_vec();// ═══ PROBE ═══let info = probe(&data)?;println!("{}x{} {:?}", info.width(), info.height(), info.format());// ═══ ROUNDTRIP ═══let decoded = decode(&data)?;let meta = decoded.metadata();let bytes = encoding_webp().with_quality(80.0).job().with_metadata(&meta).encode_rgb8(decoded.as_rgb8().unwrap())?
.into_vec();// ═══ REUSABLE CONFIG (server pattern) ═══structImageProxy{jpeg_high: zenjpeg::EncodeConfig,// concrete type, has JPEG methodsjpeg_low: zenjpeg::EncodeConfig,webp: zenwebp::EncodeConfig,// concrete type, has WebP methodsdecode: zencodecs::DecodeConfig,// dispatch type}implImageProxy{fnnew() -> Self{Self{jpeg_high:encoding_jpeg().with_quality(90.0).with_effort(8).with_progressive(true),jpeg_low:encoding_jpeg().with_quality(60.0).with_effort(4),webp:encoding_webp().with_quality(80.0),decode:decoding().with_limit_pixels(100_000_000).with_limit_memory(512_000_000),}}fntranscode(&self,data:&[u8],stop:&dynStop) -> Result<Vec<u8>>{let decoded = self.decode.job().with_stop(stop).decode(data)?;let meta = decoded.metadata();Ok(self.jpeg_high.job().with_metadata(&meta).with_stop(stop).encode_rgb8(decoded.as_rgb8().unwrap())?
.into_vec())}// Generic helper — works with any stored configfnencode_with(&self,config:&implEncoding,img:ImgRef<Rgb<u8>>,stop:&dynStop)
-> Result<Vec<u8>>{Ok(config.job().with_stop(stop).encode_rgb8(img)?.into_vec())}}
Why .job() Exists
Config and Job are separate types for a fundamental reason: lifetimes.
EncodeConfig EncodeJob<'a>
(no lifetimes, Clone, storable) (borrows things with lifetime 'a)
┌───────────────────────┐ ┌──────────────────────────┐
│ quality: Option<f32> │ │ config: &'a EncodeConfig │
│ effort: Option<u32> │─────>│ stop: Option<&'a dyn Stop>│
│ lossless: bool │.job()│ metadata: Option<&'a ...> │
│ ... │ │ limit overrides │
└───────────────────────┘ └──────────┬───────────────┘
Storable in structs .encode_rgb8(img)?
Shareable across threads (terminal, consumes Job)
Clone for reuse
Without .job(), you'd need lifetimes on the config, which prevents storing it.
The short path skips .job() entirely — convenience terminals on &EncodeConfig
create a default Job internally.
// In zenjpeg:impl zencodec::Encodingfor zenjpeg::EncodeConfig{typeError = zenjpeg::Error;typeJob<'a> = zenjpeg::EncodeJob<'a>;fnwith_quality(self,q:f32) -> Self{self.with_quality(q)}fnencode_rgb8(&self,img:ImgRef<Rgb<u8>>) -> Result<EncodeOutput,Self::Error>{// delegate to native API, wrap output}// ...}// JPEG-specific methods are NOT on the trait — they're on the concrete type:impl zenjpeg::EncodeConfig{pubfnwith_progressive(self,b:bool) -> Self{ ...}pubfnwith_sharp_yuv(self,b:bool) -> Self{ ...}pubfnwith_chroma(self,c:ChromaSubsampling) -> Self{ ...}pubfnwith_trellis(self,t:TrellisConfig) -> Self{ ...}}
Using the Trait Generically
use zencodec::Encoding;// Works with any codecfncompress_with(config:&implEncoding,img:ImgRef<Rgb<u8>>) -> Vec<u8>{
config.encode_rgb8(img).expect("encode failed").into_vec()}// Works with any codec + cancellationfncompress_with_stop<'a>(config:&'aimplEncoding,img:ImgRef<'_,Rgb<u8>>,stop:&'adynStop,) -> Result<Vec<u8>,Box<dyn core::error::Error>>{Ok(config.job().with_stop(stop).encode_rgb8(img)?.into_vec())}// Call with any codec:compress_with(&encoding_jpeg().with_quality(85.0), img);compress_with(&encoding_webp().with_quality(80.0), img);compress_with(&encoding(Jpeg).with_quality(85.0), img);// dispatch type also implements trait