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}