Skip to main content

zencodec/
capabilities.rs

1//! Codec capability descriptors.
2//!
3//! Encoders return [`EncodeCapabilities`] and decoders return
4//! [`DecodeCapabilities`] describing what they support. This lets callers
5//! discover behavior before calling methods that might be no-ops or expensive.
6//!
7//! [`UnsupportedOperation`] provides a standard enum for codecs to report
8//! which operations they don't support. Use [`CodecErrorExt`](crate::CodecErrorExt)
9//! to find these in any error's source chain.
10
11use core::fmt;
12
13/// Identifies an operation that a codec does not support.
14///
15/// Codecs include this in their error types (e.g. as a variant payload)
16/// so callers can generically detect "this codec doesn't support this
17/// operation" without downcasting. See [`CodecErrorExt`](crate::CodecErrorExt).
18///
19/// # Example
20///
21/// ```
22/// use zencodec::UnsupportedOperation;
23///
24/// let op = UnsupportedOperation::DecodeInto;
25/// assert_eq!(format!("{op}"), "unsupported operation: decode_into");
26/// ```
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28#[non_exhaustive]
29pub enum UnsupportedOperation {
30    /// `Encoder::push_rows()` + `finish()` (row-level encode).
31    RowLevelEncode,
32    /// `Encoder::encode_from()` (pull-from-source encode).
33    PullEncode,
34    /// All `AnimationFrameEncoder` methods (animation encoding).
35    AnimationEncode,
36    /// `Decoder::decode_into()` (decode into caller buffer).
37    DecodeInto,
38    /// `Decoder::decode_rows()` (row-level decode).
39    RowLevelDecode,
40    /// All `AnimationFrameDecoder` methods (animation decoding).
41    AnimationDecode,
42    /// A specific pixel format is not supported.
43    PixelFormat,
44    /// All `MultiPageDecoder` methods (multi-image decode).
45    MultiImageDecode,
46}
47
48impl UnsupportedOperation {
49    /// Short name for the operation (suitable for error messages).
50    pub const fn name(self) -> &'static str {
51        match self {
52            Self::RowLevelEncode => "row_level_encode",
53            Self::PullEncode => "pull_encode",
54            Self::AnimationEncode => "animation_encode",
55            Self::DecodeInto => "decode_into",
56            Self::RowLevelDecode => "row_level_decode",
57            Self::AnimationDecode => "animation_decode",
58            Self::PixelFormat => "pixel_format",
59            Self::MultiImageDecode => "multi_image_decode",
60        }
61    }
62}
63
64impl fmt::Display for UnsupportedOperation {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "unsupported operation: {}", self.name())
67    }
68}
69
70impl core::error::Error for UnsupportedOperation {}
71
72// ===========================================================================
73// EncodeCapabilities
74// ===========================================================================
75
76/// Describes what an encoder supports.
77///
78/// Returned by [`EncoderConfig::capabilities()`](crate::encode::EncoderConfig::capabilities)
79/// as a `&'static` reference. Uses getter methods so fields can be added
80/// without breaking changes.
81///
82/// # Example
83///
84/// ```
85/// use zencodec::encode::EncodeCapabilities;
86///
87/// static CAPS: EncodeCapabilities = EncodeCapabilities::new()
88///     .with_icc(true)
89///     .with_exif(true)
90///     .with_xmp(true)
91///     .with_stop(true)
92///     .with_native_gray(true);
93///
94/// assert!(CAPS.icc());
95/// assert!(CAPS.native_gray());
96/// ```
97#[derive(Clone, PartialEq)]
98#[non_exhaustive]
99pub struct EncodeCapabilities {
100    // Metadata embedding
101    icc: bool,
102    exif: bool,
103    xmp: bool,
104    cicp: bool,
105    // Operation support
106    stop: bool,
107    animation: bool,
108    push_rows: bool,
109    encode_from: bool,
110    // Format capabilities
111    lossy: bool,
112    lossless: bool,
113    hdr: bool,
114    gain_map: bool,
115    native_gray: bool,
116    native_16bit: bool,
117    native_f32: bool,
118    native_alpha: bool,
119    // Limit enforcement
120    enforces_max_pixels: bool,
121    enforces_max_memory: bool,
122    // Tuning ranges
123    effort_range: Option<[i32; 2]>,
124    quality_range: Option<[f32; 2]>,
125    // Threading
126    threads_supported_range: (u16, u16),
127}
128
129impl Default for EncodeCapabilities {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl EncodeCapabilities {
136    /// Empty capabilities (everything disabled, single-threaded).
137    pub const EMPTY: Self = Self::new();
138
139    /// Create capabilities with everything disabled.
140    pub const fn new() -> Self {
141        Self {
142            icc: false,
143            exif: false,
144            xmp: false,
145            cicp: false,
146            stop: false,
147            animation: false,
148            push_rows: false,
149            encode_from: false,
150            lossy: false,
151            lossless: false,
152            hdr: false,
153            gain_map: false,
154            native_gray: false,
155            native_16bit: false,
156            native_f32: false,
157            native_alpha: false,
158            enforces_max_pixels: false,
159            enforces_max_memory: false,
160            effort_range: None,
161            quality_range: None,
162            threads_supported_range: (1, 1),
163        }
164    }
165
166    // --- Getters ---
167
168    /// Whether the encoder embeds ICC color profiles from `with_metadata`.
169    pub const fn icc(&self) -> bool {
170        self.icc
171    }
172    /// Whether the encoder embeds EXIF data from `with_metadata`.
173    pub const fn exif(&self) -> bool {
174        self.exif
175    }
176    /// Whether the encoder embeds XMP data from `with_metadata`.
177    pub const fn xmp(&self) -> bool {
178        self.xmp
179    }
180    /// Whether the encoder embeds CICP color description from `with_metadata`.
181    pub const fn cicp(&self) -> bool {
182        self.cicp
183    }
184    /// Whether `with_stop` on encode jobs is respected (not a no-op).
185    pub const fn stop(&self) -> bool {
186        self.stop
187    }
188    /// Whether the codec supports encoding animation (multiple frames).
189    pub const fn animation(&self) -> bool {
190        self.animation
191    }
192    /// Whether `push_rows()` / `finish()` work (row-level encode).
193    pub const fn push_rows(&self) -> bool {
194        self.push_rows
195    }
196    /// Whether `encode_from()` works (pull-from-source encode).
197    pub const fn encode_from(&self) -> bool {
198        self.encode_from
199    }
200    /// Whether the codec supports lossy encoding.
201    pub const fn lossy(&self) -> bool {
202        self.lossy
203    }
204    /// Whether the codec supports mathematically lossless encoding.
205    pub const fn lossless(&self) -> bool {
206        self.lossless
207    }
208    /// Whether the codec supports HDR content.
209    pub const fn hdr(&self) -> bool {
210        self.hdr
211    }
212    /// Whether the codec supports gain map (HDR/SDR) embedding.
213    pub const fn gain_map(&self) -> bool {
214        self.gain_map
215    }
216    /// Whether the codec supports grayscale natively.
217    pub const fn native_gray(&self) -> bool {
218        self.native_gray
219    }
220    /// Whether the codec supports 16-bit per channel natively.
221    pub const fn native_16bit(&self) -> bool {
222        self.native_16bit
223    }
224    /// Whether the codec handles f32 pixel data natively.
225    pub const fn native_f32(&self) -> bool {
226        self.native_f32
227    }
228    /// Whether the codec handles alpha channel natively.
229    pub const fn native_alpha(&self) -> bool {
230        self.native_alpha
231    }
232    /// Whether the codec enforces `max_pixels` limits.
233    pub const fn enforces_max_pixels(&self) -> bool {
234        self.enforces_max_pixels
235    }
236    /// Whether the codec enforces `max_memory_bytes` limits.
237    pub const fn enforces_max_memory(&self) -> bool {
238        self.enforces_max_memory
239    }
240
241    /// Meaningful effort range `[min, max]`.
242    ///
243    /// `None` means the codec has no effort tuning.
244    pub const fn effort_range(&self) -> Option<[i32; 2]> {
245        self.effort_range
246    }
247
248    /// Meaningful quality range `[min, max]` on the calibrated 0.0–100.0 scale.
249    ///
250    /// `None` means the codec is lossless-only.
251    pub const fn quality_range(&self) -> Option<[f32; 2]> {
252        self.quality_range
253    }
254
255    /// Supported thread count range `(min, max)`.
256    ///
257    /// `(1, 1)` means single-threaded only.
258    /// `(1, 16)` means the encoder can use 1 to 16 threads.
259    pub const fn threads_supported_range(&self) -> (u16, u16) {
260        self.threads_supported_range
261    }
262
263    /// Check whether this encoder supports a given operation.
264    ///
265    /// Returns `true` if the capability flag corresponding to `op` is set.
266    /// Returns `false` for decode-only operations or [`PixelFormat`](UnsupportedOperation::PixelFormat)
267    /// (which depends on the specific format, not a static flag).
268    ///
269    /// # Example
270    ///
271    /// ```
272    /// use zencodec::UnsupportedOperation;
273    /// use zencodec::encode::EncodeCapabilities;
274    ///
275    /// static CAPS: EncodeCapabilities = EncodeCapabilities::new()
276    ///     .with_animation(true)
277    ///     .with_push_rows(true);
278    ///
279    /// assert!(CAPS.supports(UnsupportedOperation::AnimationEncode));
280    /// assert!(CAPS.supports(UnsupportedOperation::RowLevelEncode));
281    /// assert!(!CAPS.supports(UnsupportedOperation::PullEncode));
282    /// assert!(!CAPS.supports(UnsupportedOperation::DecodeInto));
283    /// ```
284    pub const fn supports(&self, op: UnsupportedOperation) -> bool {
285        match op {
286            UnsupportedOperation::RowLevelEncode => self.push_rows,
287            UnsupportedOperation::PullEncode => self.encode_from,
288            UnsupportedOperation::AnimationEncode => self.animation,
289            UnsupportedOperation::DecodeInto
290            | UnsupportedOperation::RowLevelDecode
291            | UnsupportedOperation::AnimationDecode
292            | UnsupportedOperation::MultiImageDecode
293            | UnsupportedOperation::PixelFormat => false,
294        }
295    }
296
297    // --- Const builder methods ---
298
299    /// Set whether the encoder embeds ICC color profiles.
300    pub const fn with_icc(mut self, v: bool) -> Self {
301        self.icc = v;
302        self
303    }
304    /// Set whether the encoder embeds EXIF data.
305    pub const fn with_exif(mut self, v: bool) -> Self {
306        self.exif = v;
307        self
308    }
309    /// Set whether the encoder embeds XMP data.
310    pub const fn with_xmp(mut self, v: bool) -> Self {
311        self.xmp = v;
312        self
313    }
314    /// Set whether the encoder embeds CICP color description.
315    pub const fn with_cicp(mut self, v: bool) -> Self {
316        self.cicp = v;
317        self
318    }
319    /// Set whether cooperative cancellation via [`Stop`](enough::Stop) is supported.
320    pub const fn with_stop(mut self, v: bool) -> Self {
321        self.stop = v;
322        self
323    }
324    /// Set whether animation encoding is supported.
325    pub const fn with_animation(mut self, v: bool) -> Self {
326        self.animation = v;
327        self
328    }
329    /// Set whether row-level (`push_rows`/`finish`) encoding is supported.
330    pub const fn with_push_rows(mut self, v: bool) -> Self {
331        self.push_rows = v;
332        self
333    }
334    /// Set whether pull-from-source (`encode_from`) encoding is supported.
335    pub const fn with_encode_from(mut self, v: bool) -> Self {
336        self.encode_from = v;
337        self
338    }
339    /// Set whether lossy encoding is supported.
340    pub const fn with_lossy(mut self, v: bool) -> Self {
341        self.lossy = v;
342        self
343    }
344    /// Set whether lossless encoding is supported.
345    pub const fn with_lossless(mut self, v: bool) -> Self {
346        self.lossless = v;
347        self
348    }
349    /// Set whether HDR content is supported.
350    pub const fn with_hdr(mut self, v: bool) -> Self {
351        self.hdr = v;
352        self
353    }
354    /// Set whether gain map (HDR/SDR) embedding is supported.
355    pub const fn with_gain_map(mut self, v: bool) -> Self {
356        self.gain_map = v;
357        self
358    }
359    /// Set whether native grayscale encoding is supported.
360    pub const fn with_native_gray(mut self, v: bool) -> Self {
361        self.native_gray = v;
362        self
363    }
364    /// Set whether native 16-bit per channel encoding is supported.
365    pub const fn with_native_16bit(mut self, v: bool) -> Self {
366        self.native_16bit = v;
367        self
368    }
369    /// Set whether native f32 pixel data is supported.
370    pub const fn with_native_f32(mut self, v: bool) -> Self {
371        self.native_f32 = v;
372        self
373    }
374    /// Set whether native alpha channel is supported.
375    pub const fn with_native_alpha(mut self, v: bool) -> Self {
376        self.native_alpha = v;
377        self
378    }
379    /// Set whether the encoder enforces `max_pixels` limits.
380    pub const fn with_enforces_max_pixels(mut self, v: bool) -> Self {
381        self.enforces_max_pixels = v;
382        self
383    }
384    /// Set whether the encoder enforces `max_memory_bytes` limits.
385    pub const fn with_enforces_max_memory(mut self, v: bool) -> Self {
386        self.enforces_max_memory = v;
387        self
388    }
389
390    /// Set the meaningful effort range `[min, max]`.
391    pub const fn with_effort_range(mut self, min: i32, max: i32) -> Self {
392        assert!(min <= max, "effort range: min must be <= max");
393        self.effort_range = Some([min, max]);
394        self
395    }
396
397    /// Set the meaningful quality range `[min, max]`.
398    pub const fn with_quality_range(mut self, min: f32, max: f32) -> Self {
399        assert!(min <= max, "quality range: min must be <= max");
400        self.quality_range = Some([min, max]);
401        self
402    }
403
404    /// Set supported thread count range.
405    pub const fn with_threads_supported_range(mut self, min: u16, max: u16) -> Self {
406        assert!(min >= 1, "threads range: min must be >= 1");
407        assert!(min <= max, "threads range: min must be <= max");
408        self.threads_supported_range = (min, max);
409        self
410    }
411}
412
413impl fmt::Debug for EncodeCapabilities {
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415        let mut s = f.debug_struct("EncodeCapabilities");
416        s.field("icc", &self.icc)
417            .field("exif", &self.exif)
418            .field("xmp", &self.xmp)
419            .field("cicp", &self.cicp)
420            .field("stop", &self.stop)
421            .field("animation", &self.animation)
422            .field("lossy", &self.lossy)
423            .field("lossless", &self.lossless)
424            .field("hdr", &self.hdr)
425            .field("gain_map", &self.gain_map)
426            .field("native_gray", &self.native_gray)
427            .field("native_16bit", &self.native_16bit)
428            .field("native_f32", &self.native_f32)
429            .field("native_alpha", &self.native_alpha)
430            .field("push_rows", &self.push_rows)
431            .field("encode_from", &self.encode_from)
432            .field("enforces_max_pixels", &self.enforces_max_pixels)
433            .field("enforces_max_memory", &self.enforces_max_memory)
434            .field("threads_supported_range", &self.threads_supported_range);
435        if let Some(range) = &self.effort_range {
436            s.field("effort_range", range);
437        }
438        if let Some(range) = &self.quality_range {
439            s.field("quality_range", range);
440        }
441        s.finish()
442    }
443}
444
445// ===========================================================================
446// DecodeCapabilities
447// ===========================================================================
448
449/// Describes what a decoder supports.
450///
451/// Returned by [`DecoderConfig::capabilities()`](crate::decode::DecoderConfig::capabilities)
452/// as a `&'static` reference. Uses getter methods so fields can be added
453/// without breaking changes.
454///
455/// # Example
456///
457/// ```
458/// use zencodec::decode::DecodeCapabilities;
459///
460/// static CAPS: DecodeCapabilities = DecodeCapabilities::new()
461///     .with_icc(true)
462///     .with_exif(true)
463///     .with_stop(true)
464///     .with_cheap_probe(true);
465///
466/// assert!(CAPS.icc());
467/// assert!(CAPS.cheap_probe());
468/// ```
469#[derive(Clone, PartialEq, Eq)]
470#[non_exhaustive]
471pub struct DecodeCapabilities {
472    // Metadata extraction
473    icc: bool,
474    exif: bool,
475    xmp: bool,
476    cicp: bool,
477    // Operation support
478    stop: bool,
479    animation: bool,
480    multi_image: bool,
481    cheap_probe: bool,
482    decode_into: bool,
483    streaming: bool,
484    // Format capabilities
485    hdr: bool,
486    gain_map: bool,
487    native_gray: bool,
488    native_16bit: bool,
489    native_f32: bool,
490    native_alpha: bool,
491    // Limit enforcement
492    enforces_max_pixels: bool,
493    enforces_max_memory: bool,
494    enforces_max_input_bytes: bool,
495    // Threading
496    threads_supported_range: (u16, u16),
497}
498
499impl Default for DecodeCapabilities {
500    fn default() -> Self {
501        Self::new()
502    }
503}
504
505impl DecodeCapabilities {
506    /// Empty capabilities (everything disabled, single-threaded).
507    pub const EMPTY: Self = Self::new();
508
509    /// Create capabilities with everything disabled.
510    pub const fn new() -> Self {
511        Self {
512            icc: false,
513            exif: false,
514            xmp: false,
515            cicp: false,
516            stop: false,
517            animation: false,
518            multi_image: false,
519            cheap_probe: false,
520            decode_into: false,
521            streaming: false,
522            hdr: false,
523            gain_map: false,
524            native_gray: false,
525            native_16bit: false,
526            native_f32: false,
527            native_alpha: false,
528            enforces_max_pixels: false,
529            enforces_max_memory: false,
530            enforces_max_input_bytes: false,
531            threads_supported_range: (1, 1),
532        }
533    }
534
535    // --- Getters ---
536
537    /// Whether the decoder extracts ICC color profiles into `ImageInfo`.
538    pub const fn icc(&self) -> bool {
539        self.icc
540    }
541    /// Whether the decoder extracts EXIF data into `ImageInfo`.
542    pub const fn exif(&self) -> bool {
543        self.exif
544    }
545    /// Whether the decoder extracts XMP data into `ImageInfo`.
546    pub const fn xmp(&self) -> bool {
547        self.xmp
548    }
549    /// Whether the decoder extracts CICP color description into `ImageInfo`.
550    pub const fn cicp(&self) -> bool {
551        self.cicp
552    }
553    /// Whether `with_stop` on decode jobs is respected (not a no-op).
554    pub const fn stop(&self) -> bool {
555        self.stop
556    }
557    /// Whether the codec supports decoding animation (multiple frames).
558    pub const fn animation(&self) -> bool {
559        self.animation
560    }
561    /// Whether this decoder supports multi-image containers (TIFF, HEIF, ICO).
562    ///
563    /// True for codecs with independently-addressable images.
564    /// False for single-image and animation-only codecs.
565    pub const fn multi_image(&self) -> bool {
566        self.multi_image
567    }
568    /// Whether `probe()` is cheap (header parse only, not a full decode).
569    pub const fn cheap_probe(&self) -> bool {
570        self.cheap_probe
571    }
572    /// Whether `decode_into()` is implemented.
573    pub const fn decode_into(&self) -> bool {
574        self.decode_into
575    }
576    /// Whether `StreamingDecode` / `streaming_decoder()` is implemented.
577    pub const fn streaming(&self) -> bool {
578        self.streaming
579    }
580    /// Whether the codec supports HDR content.
581    pub const fn hdr(&self) -> bool {
582        self.hdr
583    }
584    /// Whether the codec supports gain map (HDR/SDR) extraction.
585    pub const fn gain_map(&self) -> bool {
586        self.gain_map
587    }
588    /// Whether the codec supports grayscale natively.
589    pub const fn native_gray(&self) -> bool {
590        self.native_gray
591    }
592    /// Whether the codec supports 16-bit per channel natively.
593    pub const fn native_16bit(&self) -> bool {
594        self.native_16bit
595    }
596    /// Whether the codec handles f32 pixel data natively.
597    pub const fn native_f32(&self) -> bool {
598        self.native_f32
599    }
600    /// Whether the codec handles alpha channel natively.
601    pub const fn native_alpha(&self) -> bool {
602        self.native_alpha
603    }
604    /// Whether the codec enforces `max_pixels` limits.
605    pub const fn enforces_max_pixels(&self) -> bool {
606        self.enforces_max_pixels
607    }
608    /// Whether the codec enforces `max_memory_bytes` limits.
609    pub const fn enforces_max_memory(&self) -> bool {
610        self.enforces_max_memory
611    }
612    /// Whether the codec enforces `max_input_bytes` limits.
613    pub const fn enforces_max_input_bytes(&self) -> bool {
614        self.enforces_max_input_bytes
615    }
616
617    /// Supported thread count range `(min, max)`.
618    ///
619    /// `(1, 1)` means single-threaded only.
620    /// `(1, 8)` means the decoder can use 1 to 8 threads.
621    pub const fn threads_supported_range(&self) -> (u16, u16) {
622        self.threads_supported_range
623    }
624
625    /// Check whether this decoder supports a given operation.
626    ///
627    /// Returns `true` if the capability flag corresponding to `op` is set.
628    /// Returns `false` for encode-only operations or [`PixelFormat`](UnsupportedOperation::PixelFormat)
629    /// (which depends on the specific format, not a static flag).
630    ///
631    /// # Example
632    ///
633    /// ```
634    /// use zencodec::UnsupportedOperation;
635    /// use zencodec::decode::DecodeCapabilities;
636    ///
637    /// static CAPS: DecodeCapabilities = DecodeCapabilities::new()
638    ///     .with_animation(true)
639    ///     .with_decode_into(true);
640    ///
641    /// assert!(CAPS.supports(UnsupportedOperation::AnimationDecode));
642    /// assert!(CAPS.supports(UnsupportedOperation::DecodeInto));
643    /// assert!(!CAPS.supports(UnsupportedOperation::RowLevelDecode));
644    /// assert!(!CAPS.supports(UnsupportedOperation::RowLevelEncode));
645    /// ```
646    pub const fn supports(&self, op: UnsupportedOperation) -> bool {
647        match op {
648            UnsupportedOperation::DecodeInto => self.decode_into,
649            UnsupportedOperation::RowLevelDecode => self.streaming,
650            UnsupportedOperation::AnimationDecode => self.animation,
651            UnsupportedOperation::MultiImageDecode => self.multi_image,
652            UnsupportedOperation::RowLevelEncode
653            | UnsupportedOperation::PullEncode
654            | UnsupportedOperation::AnimationEncode
655            | UnsupportedOperation::PixelFormat => false,
656        }
657    }
658
659    // --- Const builder methods ---
660
661    /// Set whether the decoder extracts ICC color profiles.
662    pub const fn with_icc(mut self, v: bool) -> Self {
663        self.icc = v;
664        self
665    }
666    /// Set whether the decoder extracts EXIF data.
667    pub const fn with_exif(mut self, v: bool) -> Self {
668        self.exif = v;
669        self
670    }
671    /// Set whether the decoder extracts XMP data.
672    pub const fn with_xmp(mut self, v: bool) -> Self {
673        self.xmp = v;
674        self
675    }
676    /// Set whether the decoder extracts CICP color description.
677    pub const fn with_cicp(mut self, v: bool) -> Self {
678        self.cicp = v;
679        self
680    }
681    /// Set whether cooperative cancellation via [`Stop`](enough::Stop) is supported.
682    pub const fn with_stop(mut self, v: bool) -> Self {
683        self.stop = v;
684        self
685    }
686    /// Set whether animation decoding is supported.
687    pub const fn with_animation(mut self, v: bool) -> Self {
688        self.animation = v;
689        self
690    }
691    /// Set whether multi-image container decoding is supported.
692    pub const fn with_multi_image(mut self, v: bool) -> Self {
693        self.multi_image = v;
694        self
695    }
696    /// Set whether `probe()` is cheap (header parse only).
697    pub const fn with_cheap_probe(mut self, v: bool) -> Self {
698        self.cheap_probe = v;
699        self
700    }
701    /// Set whether `decode_into()` is implemented.
702    pub const fn with_decode_into(mut self, v: bool) -> Self {
703        self.decode_into = v;
704        self
705    }
706    /// Set whether `StreamingDecode` / `streaming_decoder()` is supported.
707    pub const fn with_streaming(mut self, v: bool) -> Self {
708        self.streaming = v;
709        self
710    }
711    /// Set whether HDR content is supported.
712    pub const fn with_hdr(mut self, v: bool) -> Self {
713        self.hdr = v;
714        self
715    }
716    /// Set whether gain map (HDR/SDR) extraction is supported.
717    pub const fn with_gain_map(mut self, v: bool) -> Self {
718        self.gain_map = v;
719        self
720    }
721    /// Set whether native grayscale decoding is supported.
722    pub const fn with_native_gray(mut self, v: bool) -> Self {
723        self.native_gray = v;
724        self
725    }
726    /// Set whether native 16-bit per channel decoding is supported.
727    pub const fn with_native_16bit(mut self, v: bool) -> Self {
728        self.native_16bit = v;
729        self
730    }
731    /// Set whether native f32 pixel data is supported.
732    pub const fn with_native_f32(mut self, v: bool) -> Self {
733        self.native_f32 = v;
734        self
735    }
736    /// Set whether native alpha channel is supported.
737    pub const fn with_native_alpha(mut self, v: bool) -> Self {
738        self.native_alpha = v;
739        self
740    }
741    /// Set whether the decoder enforces `max_pixels` limits.
742    pub const fn with_enforces_max_pixels(mut self, v: bool) -> Self {
743        self.enforces_max_pixels = v;
744        self
745    }
746    /// Set whether the decoder enforces `max_memory_bytes` limits.
747    pub const fn with_enforces_max_memory(mut self, v: bool) -> Self {
748        self.enforces_max_memory = v;
749        self
750    }
751    /// Set whether the decoder enforces `max_input_bytes` limits.
752    pub const fn with_enforces_max_input_bytes(mut self, v: bool) -> Self {
753        self.enforces_max_input_bytes = v;
754        self
755    }
756
757    /// Set supported thread count range.
758    pub const fn with_threads_supported_range(mut self, min: u16, max: u16) -> Self {
759        assert!(min >= 1, "threads range: min must be >= 1");
760        assert!(min <= max, "threads range: min must be <= max");
761        self.threads_supported_range = (min, max);
762        self
763    }
764}
765
766impl fmt::Debug for DecodeCapabilities {
767    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
768        let mut s = f.debug_struct("DecodeCapabilities");
769        s.field("icc", &self.icc)
770            .field("exif", &self.exif)
771            .field("xmp", &self.xmp)
772            .field("cicp", &self.cicp)
773            .field("stop", &self.stop)
774            .field("animation", &self.animation)
775            .field("multi_image", &self.multi_image)
776            .field("cheap_probe", &self.cheap_probe)
777            .field("decode_into", &self.decode_into)
778            .field("streaming", &self.streaming)
779            .field("hdr", &self.hdr)
780            .field("gain_map", &self.gain_map)
781            .field("native_gray", &self.native_gray)
782            .field("native_16bit", &self.native_16bit)
783            .field("native_f32", &self.native_f32)
784            .field("native_alpha", &self.native_alpha)
785            .field("enforces_max_pixels", &self.enforces_max_pixels)
786            .field("enforces_max_memory", &self.enforces_max_memory)
787            .field("enforces_max_input_bytes", &self.enforces_max_input_bytes)
788            .field("threads_supported_range", &self.threads_supported_range);
789        s.finish()
790    }
791}
792
793#[cfg(test)]
794mod tests {
795    use super::*;
796
797    #[test]
798    fn encode_default_all_false() {
799        let caps = EncodeCapabilities::new();
800        assert!(!caps.icc());
801        assert!(!caps.exif());
802        assert!(!caps.xmp());
803        assert!(!caps.cicp());
804        assert!(!caps.stop());
805        assert!(!caps.animation());
806        assert!(!caps.push_rows());
807        assert!(!caps.encode_from());
808        assert!(!caps.lossy());
809        assert!(!caps.lossless());
810        assert!(!caps.hdr());
811        assert!(!caps.native_gray());
812        assert!(!caps.native_16bit());
813        assert!(!caps.native_f32());
814        assert!(!caps.native_alpha());
815        assert!(!caps.enforces_max_pixels());
816        assert!(!caps.enforces_max_memory());
817        assert!(caps.effort_range().is_none());
818        assert!(caps.quality_range().is_none());
819        assert_eq!(caps.threads_supported_range(), (1, 1));
820    }
821
822    #[test]
823    fn decode_default_all_false() {
824        let caps = DecodeCapabilities::new();
825        assert!(!caps.icc());
826        assert!(!caps.exif());
827        assert!(!caps.xmp());
828        assert!(!caps.cicp());
829        assert!(!caps.stop());
830        assert!(!caps.animation());
831        assert!(!caps.cheap_probe());
832        assert!(!caps.decode_into());
833        assert!(!caps.streaming());
834        assert!(!caps.hdr());
835        assert!(!caps.native_gray());
836        assert!(!caps.native_16bit());
837        assert!(!caps.native_f32());
838        assert!(!caps.native_alpha());
839        assert!(!caps.enforces_max_pixels());
840        assert!(!caps.enforces_max_memory());
841        assert!(!caps.enforces_max_input_bytes());
842        assert_eq!(caps.threads_supported_range(), (1, 1));
843    }
844
845    #[test]
846    fn encode_builder() {
847        let caps = EncodeCapabilities::new()
848            .with_icc(true)
849            .with_stop(true)
850            .with_native_gray(true)
851            .with_animation(true)
852            .with_native_16bit(true)
853            .with_hdr(true)
854            .with_threads_supported_range(1, 8);
855        assert!(caps.icc());
856        assert!(!caps.exif());
857        assert!(caps.stop());
858        assert!(caps.native_gray());
859        assert!(caps.animation());
860        assert!(caps.native_16bit());
861        assert!(!caps.lossless());
862        assert!(caps.hdr());
863        assert_eq!(caps.threads_supported_range(), (1, 8));
864    }
865
866    #[test]
867    fn decode_builder() {
868        let caps = DecodeCapabilities::new()
869            .with_icc(true)
870            .with_cheap_probe(true)
871            .with_stop(true)
872            .with_animation(true)
873            .with_enforces_max_input_bytes(true)
874            .with_threads_supported_range(1, 4);
875        assert!(caps.icc());
876        assert!(caps.cheap_probe());
877        assert!(caps.stop());
878        assert!(caps.animation());
879        assert!(caps.enforces_max_input_bytes());
880        assert_eq!(caps.threads_supported_range(), (1, 4));
881    }
882
883    #[test]
884    fn encode_static_construction() {
885        static CAPS: EncodeCapabilities = EncodeCapabilities::new()
886            .with_icc(true)
887            .with_exif(true)
888            .with_xmp(true)
889            .with_stop(true)
890            .with_animation(true)
891            .with_lossless(true)
892            .with_cicp(true)
893            .with_effort_range(0, 100)
894            .with_quality_range(0.0, 100.0)
895            .with_threads_supported_range(1, 16);
896        assert!(CAPS.icc());
897        assert!(CAPS.stop());
898        assert!(!CAPS.native_gray());
899        assert!(CAPS.animation());
900        assert!(CAPS.lossless());
901        assert!(CAPS.cicp());
902        assert_eq!(CAPS.effort_range(), Some([0, 100]));
903        assert_eq!(CAPS.quality_range(), Some([0.0, 100.0]));
904        assert_eq!(CAPS.threads_supported_range(), (1, 16));
905    }
906
907    #[test]
908    fn decode_static_construction() {
909        static CAPS: DecodeCapabilities = DecodeCapabilities::new()
910            .with_icc(true)
911            .with_cheap_probe(true)
912            .with_stop(true)
913            .with_animation(true)
914            .with_enforces_max_pixels(true)
915            .with_enforces_max_input_bytes(true);
916        assert!(CAPS.icc());
917        assert!(CAPS.cheap_probe());
918        assert!(CAPS.enforces_max_pixels());
919        assert!(!CAPS.enforces_max_memory());
920        assert!(CAPS.enforces_max_input_bytes());
921    }
922
923    #[test]
924    fn encode_effort_quality_ranges() {
925        let caps = EncodeCapabilities::new()
926            .with_effort_range(0, 10)
927            .with_quality_range(0.0, 100.0);
928        assert_eq!(caps.effort_range(), Some([0i32, 10]));
929        assert_eq!(caps.quality_range(), Some([0.0, 100.0]));
930    }
931
932    #[test]
933    fn encode_supports() {
934        let caps = EncodeCapabilities::new()
935            .with_push_rows(true)
936            .with_encode_from(true)
937            .with_animation(true);
938        assert!(caps.supports(UnsupportedOperation::RowLevelEncode));
939        assert!(caps.supports(UnsupportedOperation::PullEncode));
940        assert!(caps.supports(UnsupportedOperation::AnimationEncode));
941        assert!(!caps.supports(UnsupportedOperation::DecodeInto));
942        assert!(!caps.supports(UnsupportedOperation::RowLevelDecode));
943        assert!(!caps.supports(UnsupportedOperation::AnimationDecode));
944        assert!(!caps.supports(UnsupportedOperation::PixelFormat));
945    }
946
947    #[test]
948    fn decode_supports() {
949        let caps = DecodeCapabilities::new()
950            .with_decode_into(true)
951            .with_streaming(true)
952            .with_animation(true);
953        assert!(caps.supports(UnsupportedOperation::DecodeInto));
954        assert!(caps.supports(UnsupportedOperation::RowLevelDecode));
955        assert!(caps.supports(UnsupportedOperation::AnimationDecode));
956        assert!(!caps.supports(UnsupportedOperation::RowLevelEncode));
957        assert!(!caps.supports(UnsupportedOperation::PullEncode));
958        assert!(!caps.supports(UnsupportedOperation::AnimationEncode));
959        assert!(!caps.supports(UnsupportedOperation::PixelFormat));
960    }
961
962    #[test]
963    fn supports_empty_all_false() {
964        let enc = EncodeCapabilities::new();
965        let dec = DecodeCapabilities::new();
966        for op in [
967            UnsupportedOperation::RowLevelEncode,
968            UnsupportedOperation::PullEncode,
969            UnsupportedOperation::AnimationEncode,
970            UnsupportedOperation::DecodeInto,
971            UnsupportedOperation::RowLevelDecode,
972            UnsupportedOperation::AnimationDecode,
973            UnsupportedOperation::PixelFormat,
974        ] {
975            assert!(
976                !enc.supports(op),
977                "EncodeCapabilities::EMPTY.supports({op:?})"
978            );
979            assert!(
980                !dec.supports(op),
981                "DecodeCapabilities::EMPTY.supports({op:?})"
982            );
983        }
984    }
985
986    #[test]
987    fn unsupported_operation_display() {
988        assert_eq!(
989            alloc::format!("{}", UnsupportedOperation::RowLevelEncode),
990            "unsupported operation: row_level_encode"
991        );
992        assert_eq!(
993            alloc::format!("{}", UnsupportedOperation::DecodeInto),
994            "unsupported operation: decode_into"
995        );
996        assert_eq!(
997            alloc::format!("{}", UnsupportedOperation::PixelFormat),
998            "unsupported operation: pixel_format"
999        );
1000    }
1001
1002    #[test]
1003    fn unsupported_operation_name() {
1004        assert_eq!(UnsupportedOperation::PullEncode.name(), "pull_encode");
1005        assert_eq!(
1006            UnsupportedOperation::AnimationEncode.name(),
1007            "animation_encode"
1008        );
1009        assert_eq!(
1010            UnsupportedOperation::RowLevelDecode.name(),
1011            "row_level_decode"
1012        );
1013        assert_eq!(
1014            UnsupportedOperation::AnimationDecode.name(),
1015            "animation_decode"
1016        );
1017    }
1018
1019    #[test]
1020    fn unsupported_operation_is_error() {
1021        let op = UnsupportedOperation::DecodeInto;
1022        let err: &dyn core::error::Error = &op;
1023        assert!(err.source().is_none());
1024    }
1025
1026    #[test]
1027    fn unsupported_operation_eq_hash() {
1028        assert_eq!(
1029            UnsupportedOperation::DecodeInto,
1030            UnsupportedOperation::DecodeInto
1031        );
1032        assert_ne!(
1033            UnsupportedOperation::DecodeInto,
1034            UnsupportedOperation::PullEncode
1035        );
1036    }
1037}