Skip to main content

rawshift_image/formats/
export.rs

1//! Encoder selection and per-implementation configuration.
2//!
3//! [`EncodeOptions`] mirrors [`DecodeOptions`](super::standard::DecodeOptions):
4//! it is an *implementation-keyed* enum — each variant names one output format
5//! *and* one backend, and carries that backend's configuration struct. A format
6//! that gains an alternative encoder simply gains another variant; there is no
7//! generic, implementation-agnostic option set.
8//!
9//! `EncodeOptions` is `#[non_exhaustive]` so the planned C/C++ encoder backends
10//! (libjpeg-turbo, MozJPEG, jpegli, libaom, SVT-AV1) can be added without a
11//! breaking change. Their configuration structs are already defined below — see
12//! [`MozjpegEncodeConfig`] and friends — so the API surface is stable ahead of
13//! the implementations. The libjxl backend ([`LibjxlEncodeConfig`]) is wired up
14//! behind the `jxl-encode-libjxl` feature.
15
16#[cfg(feature = "dng-encode")]
17use crate::formats::dng_export::DngExportConfig;
18
19use super::standard::StandardFormat;
20use crate::core::CodecId;
21
22pub use crate::core::{BitDepth, MetadataEmbedOptions};
23
24/// An output container format produced by an [`EncodeOptions`] variant.
25///
26/// Distinct from [`StandardFormat`] because it additionally includes `Dng`,
27/// which is a RAW format rather than a standard delivery format.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum OutputFormat {
31    /// Portable Network Graphics.
32    Png,
33    /// JPEG.
34    Jpeg,
35    /// WebP.
36    WebP,
37    /// AV1 Image File Format.
38    Avif,
39    /// JPEG XL.
40    Jxl,
41    /// Adobe Digital Negative.
42    Dng,
43}
44
45impl OutputFormat {
46    /// A short human-readable name.
47    pub fn name(self) -> &'static str {
48        match self {
49            OutputFormat::Png => "PNG",
50            OutputFormat::Jpeg => "JPEG",
51            OutputFormat::WebP => "WebP",
52            OutputFormat::Avif => "AVIF",
53            OutputFormat::Jxl => "JPEG XL",
54            OutputFormat::Dng => "DNG",
55        }
56    }
57
58    /// The conventional lowercase file extension (without a leading dot).
59    pub fn extension(self) -> &'static str {
60        match self {
61            OutputFormat::Png => "png",
62            OutputFormat::Jpeg => "jpg",
63            OutputFormat::WebP => "webp",
64            OutputFormat::Avif => "avif",
65            OutputFormat::Jxl => "jxl",
66            OutputFormat::Dng => "dng",
67        }
68    }
69}
70
71/// Encoder-agnostic options shared by every backend.
72///
73/// Embedded as the `common` field of each per-implementation config struct so
74/// that metadata-embedding and output bit-depth are configured uniformly,
75/// independent of the chosen backend.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78pub struct CommonEncodeOptions {
79    /// Which metadata blocks to embed in the output container.
80    pub metadata: MetadataEmbedOptions,
81    /// Requested output bit depth.
82    ///
83    /// Encoders that cannot honour the request return
84    /// [`EncodeError::UnsupportedBitDepth`](crate::error::EncodeError::UnsupportedBitDepth)
85    /// rather than silently degrading.
86    pub bit_depth: BitDepth,
87}
88
89/// WebP encoding mode.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub enum WebPMode {
93    /// VP8L lossless compression.
94    Lossless,
95    /// VP8 lossy compression.
96    Lossy,
97}
98
99/// Chroma subsampling mode for JPEG encoders.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub enum JpegSubsampling {
103    /// 4:2:0 — chroma at quarter resolution. Smallest files; the usual default.
104    #[default]
105    Yuv420,
106    /// 4:2:2 — chroma at half horizontal resolution.
107    Yuv422,
108    /// 4:4:4 — full-resolution chroma. Largest files, best chroma fidelity.
109    Yuv444,
110}
111
112/// Rate-control strategy for AV1-based AVIF encoders.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115pub enum AvifRateControl {
116    /// Constant quality / constant quantizer — quality knob drives file size.
117    #[default]
118    ConstantQuality,
119    /// Constrained quality — quality target with a bitrate ceiling.
120    Constrained,
121}
122
123// ── Currently-implemented backend configs ─────────────────────────────────────
124
125/// Configuration for the `zune-png` PNG encoder.
126#[derive(Debug, Clone, PartialEq, Eq, Default)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct ZunePngEncodeConfig {
129    /// Encoder-agnostic options (metadata embedding, bit depth).
130    ///
131    /// PNG honours `BitDepth::Eight` and `BitDepth::Sixteen`.
132    pub common: CommonEncodeOptions,
133}
134
135/// Configuration for the `jpeg-encoder` (pure-Rust) JPEG encoder.
136#[derive(Debug, Clone, PartialEq, Eq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct JpegEncEncodeConfig {
139    /// Encoder-agnostic options. JPEG output is always 8-bit.
140    pub common: CommonEncodeOptions,
141    /// Quality, `1..=100`. Higher is better quality and larger files (monotonic).
142    /// Default: `90`.
143    pub quality: u8,
144}
145
146impl Default for JpegEncEncodeConfig {
147    fn default() -> Self {
148        Self {
149            common: CommonEncodeOptions::default(),
150            quality: 90,
151        }
152    }
153}
154
155/// Configuration for the `libwebp` WebP encoder.
156#[derive(Debug, Clone, PartialEq)]
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
158pub struct LibwebpEncodeConfig {
159    /// Encoder-agnostic options. WebP output is always 8-bit.
160    pub common: CommonEncodeOptions,
161    /// Lossy or lossless mode.
162    pub mode: WebPMode,
163    /// Quality, `0.0..=100.0`. For lossy this is image quality; for lossless it
164    /// is the compression effort. Higher quality is larger (lossy). Default: `75.0`.
165    pub quality: f32,
166    /// Compression method, `0` (fast) to `6` (slowest, best). Default: `4`.
167    pub method: u32,
168    /// Near-lossless preprocessing, `0..=100` (`100` = off). Lossless mode only.
169    /// Default: `100`.
170    pub near_lossless: u32,
171}
172
173impl LibwebpEncodeConfig {
174    /// Lossy encoding with sensible defaults.
175    pub fn lossy() -> Self {
176        Self {
177            common: CommonEncodeOptions::default(),
178            mode: WebPMode::Lossy,
179            quality: 75.0,
180            method: 4,
181            near_lossless: 100,
182        }
183    }
184
185    /// Lossless encoding with sensible defaults.
186    pub fn lossless() -> Self {
187        Self {
188            mode: WebPMode::Lossless,
189            ..Self::lossy()
190        }
191    }
192}
193
194impl Default for LibwebpEncodeConfig {
195    fn default() -> Self {
196        Self::lossy()
197    }
198}
199
200/// Configuration for the `ravif` (rav1e) AVIF encoder.
201#[derive(Debug, Clone, PartialEq, Eq)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct RavifEncodeConfig {
204    /// Encoder-agnostic options. This backend currently produces 8-bit AVIF.
205    pub common: CommonEncodeOptions,
206    /// Quality, `0..=100`. Higher is better quality and larger files (monotonic).
207    /// Default: `80`.
208    pub quality: u8,
209    /// Encoding speed, `1` (slowest, best) to `10` (fastest). Default: `6`.
210    pub speed: u8,
211}
212
213impl Default for RavifEncodeConfig {
214    fn default() -> Self {
215        Self {
216            common: CommonEncodeOptions::default(),
217            quality: 80,
218            speed: 6,
219        }
220    }
221}
222
223/// Configuration for the `zune-jpegxl` JPEG XL encoder (`JxlSimpleEncoder`).
224#[derive(Debug, Clone, PartialEq)]
225#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
226pub struct ZuneJxlEncodeConfig {
227    /// Encoder-agnostic options. This backend currently produces 8-bit JXL.
228    pub common: CommonEncodeOptions,
229    /// Quality, `0.0..=100.0` (`0.0` requests lossless). Default: `0.0`.
230    pub quality: f32,
231    /// Effort, `1..=9` (higher is slower). `JxlSimpleEncoder` may ignore this.
232    /// Default: `7`.
233    pub effort: u8,
234}
235
236impl Default for ZuneJxlEncodeConfig {
237    fn default() -> Self {
238        Self {
239            common: CommonEncodeOptions::default(),
240            quality: 0.0,
241            effort: 7,
242        }
243    }
244}
245
246/// Modular vs VarDCT mode for the [`libjxl`](LibjxlEncodeConfig) encoder
247/// (`JXL_ENC_FRAME_SETTING_MODULAR`).
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
250pub enum LibjxlModular {
251    /// Let libjxl decide (VarDCT for lossy, modular for lossless). The default.
252    #[default]
253    Auto,
254    /// Force VarDCT — best for photographic, lossy content.
255    VarDct,
256    /// Force modular — best for lossless, non-photographic, or high-bit-depth content.
257    Modular,
258}
259
260/// Internal color transform for the [`libjxl`](LibjxlEncodeConfig) encoder
261/// (`JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM`).
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
263#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
264pub enum LibjxlColorTransform {
265    /// Let libjxl decide (XYB for lossy). The default.
266    #[default]
267    Auto,
268    /// XYB — the JPEG XL perceptual space; best lossy compression.
269    Xyb,
270    /// No transform — encode the RGB channels directly.
271    None,
272    /// YCbCr.
273    YCbCr,
274}
275
276/// Configuration for the **libjxl** JPEG XL encoder (the reference encoder).
277///
278/// Unlike the `zune-jpegxl` default ([`ZuneJxlEncodeConfig`]), libjxl honours
279/// 8- and 16-bit output, mathematically lossless encoding, and the full set of
280/// `JxlEncoderFrameSettingId` toggles. Requires the `jxl-encode-libjxl` feature.
281///
282/// Integer fields default to a `-1` sentinel and `Option` fields to `None`,
283/// meaning "leave at libjxl's own default". This is a plain struct (not
284/// `#[non_exhaustive]`): construct it with a struct literal plus
285/// `..Default::default()`. The two `extra_*_options` vectors are escape hatches
286/// that pass raw frame-setting ids through verbatim, so every libjxl toggle is
287/// reachable even if it has no dedicated field.
288#[derive(Debug, Clone, PartialEq)]
289#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
290pub struct LibjxlEncodeConfig {
291    /// Encoder-agnostic options. libjxl honours `BitDepth::Eight` and `BitDepth::Sixteen`.
292    pub common: CommonEncodeOptions,
293    /// Butteraugli distance: `0.0` is mathematically lossless, `1.0` visually
294    /// lossless, up to `25.0` (smaller files, monotonic). Ignored when `quality`
295    /// is `Some` or `lossless` is `true`.
296    pub distance: f32,
297    /// Optional JPEG-style quality, `0.0..=100.0`. When `Some`, libjxl converts
298    /// it to a distance and it overrides `distance`.
299    pub quality: Option<f32>,
300    /// Force bit-exact lossless encoding (overrides `distance`/`quality`).
301    pub lossless: bool,
302    /// Effort / speed-quality trade-off, `1` (fastest) ..= `10` (slowest, best).
303    pub effort: u8,
304    /// Brotli effort for the metadata/modular streams, `-1` (default) or `0..=11`.
305    pub brotli_effort: i8,
306    /// Decoder-speed tier the stream is optimised for, `0` (default, best) ..= `4`.
307    pub decoding_speed: u8,
308    /// Emit a responsive / progressive stream (sets `RESPONSIVE` + `PROGRESSIVE_DC`).
309    pub progressive: bool,
310    /// Modular vs VarDCT mode.
311    pub modular: LibjxlModular,
312    /// Internal color transform.
313    pub color_transform: LibjxlColorTransform,
314    /// Edge-preserving filter strength, `-1` (default) ..= `3`.
315    pub epf: i8,
316    /// Gaborish deblocking filter: `None` = default, `Some(false)`/`Some(true)` = off/on.
317    pub gaborish: Option<bool>,
318    /// Adaptive noise synthesis: `None` = default, `Some(false)`/`Some(true)` = off/on.
319    pub noise: Option<bool>,
320    /// Dots synthesis: `None` = default, `Some(false)`/`Some(true)` = off/on.
321    pub dots: Option<bool>,
322    /// Patches synthesis: `None` = default, `Some(false)`/`Some(true)` = off/on.
323    pub patches: Option<bool>,
324    /// Photon-noise simulation strength in ISO units (`0.0` = off).
325    pub photon_noise_iso: f32,
326    /// Downsampling factor, `-1` (default), `1`, `2`, `4`, or `8`.
327    pub resampling: i8,
328    /// Force the BMFF container even when a bare codestream would do.
329    pub use_container: bool,
330    /// JPEG XL codestream feature level: `-1` (auto), `5`, or `10`.
331    pub codestream_level: i8,
332    /// Escape hatch: raw `(JxlEncoderFrameSettingId, value)` integer settings
333    /// applied verbatim, for any toggle without a dedicated field above.
334    pub extra_int_options: Vec<(i32, i64)>,
335    /// Escape hatch: raw `(JxlEncoderFrameSettingId, value)` float settings.
336    pub extra_float_options: Vec<(i32, f32)>,
337}
338
339impl Default for LibjxlEncodeConfig {
340    fn default() -> Self {
341        Self {
342            common: CommonEncodeOptions::default(),
343            distance: 1.0,
344            quality: None,
345            lossless: false,
346            effort: 7,
347            brotli_effort: -1,
348            decoding_speed: 0,
349            progressive: false,
350            modular: LibjxlModular::Auto,
351            color_transform: LibjxlColorTransform::Auto,
352            epf: -1,
353            gaborish: None,
354            noise: None,
355            dots: None,
356            patches: None,
357            photon_noise_iso: 0.0,
358            resampling: -1,
359            use_container: false,
360            codestream_level: -1,
361            extra_int_options: Vec::new(),
362            extra_float_options: Vec::new(),
363        }
364    }
365}
366
367// ── Planned backend configs (API surface only — implementations pending) ──────
368//
369// These structs are defined now so the encode API is feature-complete and
370// documented ahead of the C/C++ backend work. They are not yet wired to an
371// `EncodeOptions` variant; each lands together with its backend in a follow-up.
372
373/// Configuration for the planned **libjpeg-turbo** JPEG encoder.
374#[derive(Debug, Clone, PartialEq, Eq)]
375#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
376pub struct LibjpegTurboEncodeConfig {
377    /// Encoder-agnostic options. JPEG output is always 8-bit.
378    pub common: CommonEncodeOptions,
379    /// Quality, `1..=100`. Higher is better quality and larger files (monotonic).
380    pub quality: u8,
381    /// Emit a progressive (multi-scan) JPEG instead of baseline.
382    pub progressive: bool,
383    /// Chroma subsampling mode.
384    pub subsampling: JpegSubsampling,
385}
386
387impl Default for LibjpegTurboEncodeConfig {
388    fn default() -> Self {
389        Self {
390            common: CommonEncodeOptions::default(),
391            quality: 90,
392            progressive: false,
393            subsampling: JpegSubsampling::Yuv420,
394        }
395    }
396}
397
398/// Configuration for the planned **MozJPEG** JPEG encoder.
399#[derive(Debug, Clone, PartialEq, Eq)]
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
401pub struct MozjpegEncodeConfig {
402    /// Encoder-agnostic options. JPEG output is always 8-bit.
403    pub common: CommonEncodeOptions,
404    /// Quality, `1..=100`. Higher is better quality and larger files (monotonic).
405    pub quality: u8,
406    /// Emit a progressive JPEG (MozJPEG enables this by default).
407    pub progressive: bool,
408    /// Enable trellis quantisation — slower, produces smaller files.
409    pub trellis: bool,
410    /// Chroma subsampling mode.
411    pub subsampling: JpegSubsampling,
412}
413
414impl Default for MozjpegEncodeConfig {
415    fn default() -> Self {
416        Self {
417            common: CommonEncodeOptions::default(),
418            quality: 85,
419            progressive: true,
420            trellis: true,
421            subsampling: JpegSubsampling::Yuv420,
422        }
423    }
424}
425
426/// Configuration for the planned **jpegli** JPEG encoder.
427#[derive(Debug, Clone, PartialEq)]
428#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
429pub struct JpegliEncodeConfig {
430    /// Encoder-agnostic options. JPEG output is always 8-bit.
431    pub common: CommonEncodeOptions,
432    /// Butteraugli distance: `0.0` is visually lossless; higher values produce
433    /// smaller files (monotonic). Used when `quality` is `None`.
434    pub distance: f32,
435    /// Optional `1..=100` quality; when `Some`, overrides `distance`.
436    pub quality: Option<u8>,
437    /// Emit a progressive JPEG.
438    pub progressive: bool,
439    /// Encode in the XYB color space (jpegli high-fidelity mode).
440    pub xyb: bool,
441    /// Chroma subsampling mode.
442    pub subsampling: JpegSubsampling,
443}
444
445impl Default for JpegliEncodeConfig {
446    fn default() -> Self {
447        Self {
448            common: CommonEncodeOptions::default(),
449            distance: 1.0,
450            quality: None,
451            progressive: true,
452            xyb: false,
453            subsampling: JpegSubsampling::Yuv420,
454        }
455    }
456}
457
458/// Configuration for the planned **libaom** AVIF encoder.
459#[derive(Debug, Clone, PartialEq, Eq)]
460#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
461pub struct LibaomEncodeConfig {
462    /// Encoder-agnostic options. libaom can produce 8/10/12-bit AVIF.
463    pub common: CommonEncodeOptions,
464    /// Constant-quantiser level, `0..=63`. Lower is better quality and larger
465    /// files (monotonic). Used under [`AvifRateControl::ConstantQuality`].
466    pub cq_level: u8,
467    /// Minimum quantizer, `0..=63`.
468    pub min_quantizer: u8,
469    /// Maximum quantizer, `0..=63`.
470    pub max_quantizer: u8,
471    /// Speed/quality trade-off, `0` (slowest, best) to `8` (fastest).
472    pub cpu_used: u8,
473    /// Rate-control strategy.
474    pub rate_control: AvifRateControl,
475}
476
477impl Default for LibaomEncodeConfig {
478    fn default() -> Self {
479        Self {
480            common: CommonEncodeOptions::default(),
481            cq_level: 30,
482            min_quantizer: 0,
483            max_quantizer: 63,
484            cpu_used: 6,
485            rate_control: AvifRateControl::ConstantQuality,
486        }
487    }
488}
489
490/// Configuration for the planned **SVT-AV1** AVIF encoder.
491#[derive(Debug, Clone, PartialEq, Eq)]
492#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
493pub struct SvtAv1EncodeConfig {
494    /// Encoder-agnostic options. SVT-AV1 can produce 8/10-bit AVIF.
495    pub common: CommonEncodeOptions,
496    /// Constant-Rate-Factor, `0..=63`. Lower is better quality and larger files
497    /// (monotonic).
498    pub crf: u8,
499    /// Encoder preset, `0` (slowest, best) to `13` (fastest).
500    pub preset: u8,
501}
502
503impl Default for SvtAv1EncodeConfig {
504    fn default() -> Self {
505        Self {
506            common: CommonEncodeOptions::default(),
507            crf: 35,
508            preset: 8,
509        }
510    }
511}
512
513// ── EncodeOptions ─────────────────────────────────────────────────────────────
514
515/// Selects the encoder implementation and carries its configuration.
516///
517/// Obtain one with [`EncodeOptions::default_for`] for a format's default
518/// backend, or with a constructor such as [`EncodeOptions::jpeg`], or by naming
519/// a variant directly to pin a specific backend.
520#[derive(Debug, Clone, PartialEq)]
521#[non_exhaustive]
522#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
523pub enum EncodeOptions {
524    /// PNG via `zune-png` (requires `png-encode`).
525    #[cfg(feature = "png-encode")]
526    PngZune(ZunePngEncodeConfig),
527    /// JPEG via the pure-Rust `jpeg-encoder` (requires `jpeg-encode`).
528    #[cfg(feature = "jpeg-encode")]
529    JpegJpegEnc(JpegEncEncodeConfig),
530    /// WebP via `libwebp` (requires `webp-encode`).
531    #[cfg(feature = "webp-encode")]
532    WebpLibwebp(LibwebpEncodeConfig),
533    /// AVIF via `ravif` / rav1e (requires `avif-encode`).
534    #[cfg(feature = "avif-encode")]
535    AvifRavif(RavifEncodeConfig),
536    /// JPEG XL via `zune-jpegxl` (requires `jxl-encode`).
537    #[cfg(feature = "jxl-encode")]
538    JxlZune(ZuneJxlEncodeConfig),
539    /// JPEG XL via `libjxl`, the reference encoder (requires `jxl-encode-libjxl`).
540    #[cfg(feature = "jxl-encode-libjxl")]
541    JxlLibjxl(LibjxlEncodeConfig),
542    /// DNG via the in-repo encoder (requires `dng-encode`).
543    #[cfg(feature = "dng-encode")]
544    Dng(DngExportConfig),
545}
546
547#[cfg(feature = "png-encode")]
548impl Default for EncodeOptions {
549    fn default() -> Self {
550        Self::png()
551    }
552}
553
554impl EncodeOptions {
555    /// PNG with default configuration.
556    #[cfg(feature = "png-encode")]
557    pub fn png() -> Self {
558        Self::PngZune(ZunePngEncodeConfig::default())
559    }
560
561    /// JPEG with default configuration.
562    #[cfg(feature = "jpeg-encode")]
563    pub fn jpeg() -> Self {
564        Self::JpegJpegEnc(JpegEncEncodeConfig::default())
565    }
566
567    /// Lossy WebP with default configuration.
568    #[cfg(feature = "webp-encode")]
569    pub fn webp_lossy() -> Self {
570        Self::WebpLibwebp(LibwebpEncodeConfig::lossy())
571    }
572
573    /// Lossless WebP with default configuration.
574    #[cfg(feature = "webp-encode")]
575    pub fn webp_lossless() -> Self {
576        Self::WebpLibwebp(LibwebpEncodeConfig::lossless())
577    }
578
579    /// AVIF with default configuration.
580    #[cfg(feature = "avif-encode")]
581    pub fn avif() -> Self {
582        Self::AvifRavif(RavifEncodeConfig::default())
583    }
584
585    /// JPEG XL with default configuration.
586    #[cfg(feature = "jxl-encode")]
587    pub fn jxl() -> Self {
588        Self::JxlZune(ZuneJxlEncodeConfig::default())
589    }
590
591    /// JPEG XL via the libjxl reference encoder, with default configuration.
592    #[cfg(feature = "jxl-encode-libjxl")]
593    pub fn jxl_libjxl() -> Self {
594        Self::JxlLibjxl(LibjxlEncodeConfig::default())
595    }
596
597    /// DNG with default configuration.
598    #[cfg(feature = "dng-encode")]
599    pub fn dng() -> Self {
600        Self::Dng(DngExportConfig::default())
601    }
602
603    /// The output format this encoder produces.
604    pub fn format(&self) -> OutputFormat {
605        match self {
606            #[cfg(feature = "png-encode")]
607            EncodeOptions::PngZune(_) => OutputFormat::Png,
608            #[cfg(feature = "jpeg-encode")]
609            EncodeOptions::JpegJpegEnc(_) => OutputFormat::Jpeg,
610            #[cfg(feature = "webp-encode")]
611            EncodeOptions::WebpLibwebp(_) => OutputFormat::WebP,
612            #[cfg(feature = "avif-encode")]
613            EncodeOptions::AvifRavif(_) => OutputFormat::Avif,
614            #[cfg(feature = "jxl-encode")]
615            EncodeOptions::JxlZune(_) => OutputFormat::Jxl,
616            #[cfg(feature = "jxl-encode-libjxl")]
617            EncodeOptions::JxlLibjxl(_) => OutputFormat::Jxl,
618            #[cfg(feature = "dng-encode")]
619            EncodeOptions::Dng(_) => OutputFormat::Dng,
620            // Unreachable: with no encode feature enabled `EncodeOptions` has
621            // no variants and no value of it can be constructed.
622            #[allow(unreachable_patterns)]
623            _ => unreachable!(),
624        }
625    }
626
627    /// The stable identifier of the selected encoder implementation.
628    pub fn codec_id(&self) -> CodecId {
629        match self {
630            #[cfg(feature = "png-encode")]
631            EncodeOptions::PngZune(_) => CodecId::new("png/zune"),
632            #[cfg(feature = "jpeg-encode")]
633            EncodeOptions::JpegJpegEnc(_) => CodecId::new("jpeg/jpeg-encoder"),
634            #[cfg(feature = "webp-encode")]
635            EncodeOptions::WebpLibwebp(_) => CodecId::new("webp/libwebp"),
636            #[cfg(feature = "avif-encode")]
637            EncodeOptions::AvifRavif(_) => CodecId::new("avif/ravif"),
638            #[cfg(feature = "jxl-encode")]
639            EncodeOptions::JxlZune(_) => CodecId::new("jxl/zune"),
640            #[cfg(feature = "jxl-encode-libjxl")]
641            EncodeOptions::JxlLibjxl(_) => CodecId::new("jxl/libjxl"),
642            #[cfg(feature = "dng-encode")]
643            EncodeOptions::Dng(_) => CodecId::new("dng/rawshift"),
644            #[allow(unreachable_patterns)]
645            _ => unreachable!(),
646        }
647    }
648
649    /// The encoder-agnostic options (metadata embedding, bit depth) in effect.
650    ///
651    /// For DNG — which has its own configuration shape — a `CommonEncodeOptions`
652    /// is synthesised from [`DngExportConfig`].
653    pub fn common(&self) -> CommonEncodeOptions {
654        match self {
655            #[cfg(feature = "png-encode")]
656            EncodeOptions::PngZune(c) => c.common,
657            #[cfg(feature = "jpeg-encode")]
658            EncodeOptions::JpegJpegEnc(c) => c.common,
659            #[cfg(feature = "webp-encode")]
660            EncodeOptions::WebpLibwebp(c) => c.common,
661            #[cfg(feature = "avif-encode")]
662            EncodeOptions::AvifRavif(c) => c.common,
663            #[cfg(feature = "jxl-encode")]
664            EncodeOptions::JxlZune(c) => c.common,
665            #[cfg(feature = "jxl-encode-libjxl")]
666            EncodeOptions::JxlLibjxl(c) => c.common,
667            #[cfg(feature = "dng-encode")]
668            EncodeOptions::Dng(c) => CommonEncodeOptions {
669                metadata: MetadataEmbedOptions {
670                    embed_exif: c.embed_exif,
671                    embed_icc: false,
672                    embed_xmp: false,
673                },
674                bit_depth: BitDepth::Sixteen,
675            },
676            #[allow(unreachable_patterns)]
677            _ => unreachable!(),
678        }
679    }
680
681    /// The default encoder backend for `format`, with default configuration.
682    ///
683    /// Returns `None` when no encoder for `format` is compiled in, or when the
684    /// format has no encoder. DNG is absent — it is not a [`StandardFormat`];
685    /// use [`EncodeOptions::dng`].
686    pub fn default_for(format: StandardFormat) -> Option<EncodeOptions> {
687        match format {
688            #[cfg(feature = "png-encode")]
689            StandardFormat::Png => Some(EncodeOptions::png()),
690            #[cfg(feature = "jpeg-encode")]
691            StandardFormat::Jpeg => Some(EncodeOptions::jpeg()),
692            #[cfg(feature = "webp-encode")]
693            StandardFormat::WebP => Some(EncodeOptions::webp_lossy()),
694            #[cfg(feature = "avif-encode")]
695            StandardFormat::Avif => Some(EncodeOptions::avif()),
696            #[cfg(feature = "jxl-encode")]
697            StandardFormat::Jxl => Some(EncodeOptions::jxl()),
698            _ => None,
699        }
700    }
701}
702
703#[cfg(test)]
704mod tests {
705    #[cfg(any_standard_encode)]
706    use super::*;
707
708    #[test]
709    #[cfg(feature = "jpeg-encode")]
710    fn default_for_jpeg_is_jpeg() {
711        let opts = EncodeOptions::default_for(StandardFormat::Jpeg).unwrap();
712        assert_eq!(opts.format(), OutputFormat::Jpeg);
713        assert_eq!(opts.codec_id().id, "jpeg/jpeg-encoder");
714    }
715
716    #[test]
717    #[cfg(feature = "png-encode")]
718    fn default_for_unencodable_format_is_none() {
719        assert!(EncodeOptions::default_for(StandardFormat::Gif).is_none());
720    }
721
722    #[test]
723    #[cfg(feature = "avif-encode")]
724    fn avif_default_quality_and_common() {
725        let opts = EncodeOptions::avif();
726        assert_eq!(opts.format(), OutputFormat::Avif);
727        assert_eq!(opts.common().bit_depth, BitDepth::Sixteen);
728    }
729
730    #[test]
731    fn output_format_names_and_extensions() {
732        assert_eq!(OutputFormat::Jxl.name(), "JPEG XL");
733        assert_eq!(OutputFormat::Avif.extension(), "avif");
734    }
735}