Skip to main content

ai_image/
error.rs

1//! Contains detailed error representation.
2//!
3//! See the main [`ImageError`] which contains a variant for each specialized error type. The
4//! subtypes used in each variant are opaque by design. They can be roughly inspected through their
5//! respective `kind` methods which work similar to `no_std_io::io::Error::kind`.
6//!
7//! The error interface makes it possible to inspect the error of an underlying decoder or encoder,
8//! through the `Error::source` method. Note that this is not part of the stable interface and you
9//! may not rely on a particular error value for a particular operation. This means mainly that
10//! `image` does not promise to remain on a particular version of its underlying decoders but if
11//! you ensure to use the same version of the dependency (or at least of the error type) through
12//! external means then you could inspect the error type in slightly more detail.
13//!
14//! [`ImageError`]: enum.ImageError.html
15
16use alloc::boxed::Box;
17use alloc::collections::TryReserveError;
18use alloc::string::String;
19use core::error::Error;
20use core::fmt;
21use no_std_io::io;
22
23use crate::color::ExtendedColorType;
24use crate::{metadata::Cicp, ImageFormat};
25
26/// The generic error type for image operations.
27///
28/// This high level enum allows, by variant matching, a rough separation of concerns between
29/// underlying IO, the caller, format specifications, and the `image` implementation.
30#[derive(Debug)]
31pub enum ImageError {
32    /// An error was encountered while decoding.
33    ///
34    /// This means that the input data did not conform to the specification of some image format,
35    /// or that no format could be determined, or that it did not match format specific
36    /// requirements set by the caller.
37    Decoding(DecodingError),
38
39    /// An error was encountered while encoding.
40    ///
41    /// The input image can not be encoded with the chosen format, for example because the
42    /// specification has no representation for its color space or because a necessary conversion
43    /// is ambiguous. In some cases it might also happen that the dimensions can not be used with
44    /// the format.
45    Encoding(EncodingError),
46
47    /// An error was encountered in input arguments.
48    ///
49    /// This is a catch-all case for strictly internal operations such as scaling, conversions,
50    /// etc. that involve no external format specifications.
51    Parameter(ParameterError),
52
53    /// Completing the operation would have required more resources than allowed.
54    ///
55    /// Errors of this type are limits set by the user or environment, *not* inherent in a specific
56    /// format or operation that was executed.
57    Limits(LimitError),
58
59    /// An operation can not be completed by the chosen abstraction.
60    ///
61    /// This means that it might be possible for the operation to succeed in general but
62    /// * it requires a disabled feature,
63    /// * the implementation does not yet exist, or
64    /// * no abstraction for a lower level could be found.
65    Unsupported(UnsupportedError),
66
67    /// An error occurred while interacting with the environment.
68    IoError(io::Error),
69}
70
71/// The implementation for an operation was not provided.
72///
73/// See the variant [`Unsupported`] for more documentation.
74///
75/// [`Unsupported`]: enum.ImageError.html#variant.Unsupported
76#[derive(Debug)]
77pub struct UnsupportedError {
78    format: ImageFormatHint,
79    kind: UnsupportedErrorKind,
80}
81
82/// Details what feature is not supported.
83#[derive(Clone, Debug, Hash, PartialEq)]
84#[non_exhaustive]
85pub enum UnsupportedErrorKind {
86    /// The required color type can not be handled.
87    Color(ExtendedColorType),
88    /// Dealing with an intricate layout is not implemented for an algorithm.
89    ColorLayout(ExtendedColorType),
90    /// The colors or transfer function of the CICP are not supported.
91    ColorspaceCicp(Cicp),
92    /// An image format is not supported.
93    Format(ImageFormatHint),
94    /// Some feature specified by string.
95    /// This is discouraged and is likely to get deprecated (but not removed).
96    GenericFeature(String),
97}
98
99/// An error was encountered while encoding an image.
100///
101/// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its
102/// documentation for more information.
103///
104/// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding
105#[derive(Debug)]
106pub struct EncodingError {
107    format: ImageFormatHint,
108    underlying: Option<Box<dyn Error + Send + Sync>>,
109}
110
111/// An error was encountered in inputs arguments.
112///
113/// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its
114/// documentation for more information.
115///
116/// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter
117#[derive(Debug)]
118pub struct ParameterError {
119    kind: ParameterErrorKind,
120    underlying: Option<Box<dyn Error + Send + Sync>>,
121}
122
123/// Details how a parameter is malformed.
124#[derive(Clone, Debug, Hash, PartialEq)]
125#[non_exhaustive]
126pub enum ParameterErrorKind {
127    /// The dimensions passed are wrong.
128    DimensionMismatch,
129    /// Repeated an operation for which error that could not be cloned was emitted already.
130    FailedAlready,
131    /// The cicp is required to be RGB-like but had other matrix transforms or narrow range.
132    RgbCicpRequired(Cicp),
133    /// A string describing the parameter.
134    /// This is discouraged and is likely to get deprecated (but not removed).
135    Generic(String),
136    /// The end of the image has been reached.
137    NoMoreData,
138    /// An operation expected a concrete color space but another was found.
139    CicpMismatch {
140        /// The cicp that was expected.
141        expected: Cicp,
142        /// The cicp that was found.
143        found: Cicp,
144    },
145}
146
147/// An error was encountered while decoding an image.
148///
149/// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its
150/// documentation for more information.
151///
152/// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding
153#[derive(Debug)]
154pub struct DecodingError {
155    format: ImageFormatHint,
156    underlying: Option<Box<dyn Error + Send + Sync>>,
157}
158
159/// Completing the operation would have required more resources than allowed.
160///
161/// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its
162/// documentation for more information.
163///
164/// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits
165#[derive(Debug)]
166pub struct LimitError {
167    kind: LimitErrorKind,
168    // do we need an underlying error?
169}
170
171/// Indicates the limit that prevented an operation from completing.
172///
173/// Note that this enumeration is not exhaustive and may in the future be extended to provide more
174/// detailed information or to incorporate other resources types.
175#[derive(Clone, Debug, Hash, PartialEq, Eq)]
176#[non_exhaustive]
177#[allow(missing_copy_implementations)] // Might be non-Copy in the future.
178pub enum LimitErrorKind {
179    /// The resulting image exceed dimension limits in either direction.
180    DimensionError,
181    /// The operation would have performed an allocation larger than allowed.
182    InsufficientMemory,
183    /// The specified strict limits are not supported for this operation
184    Unsupported {
185        /// The given limits
186        limits: crate::Limits,
187        /// The supported strict limits
188        supported: crate::LimitSupport,
189    },
190}
191
192/// A best effort representation for image formats.
193#[derive(Clone, Debug, Hash, PartialEq)]
194#[non_exhaustive]
195pub enum ImageFormatHint {
196    /// The format is known exactly.
197    Exact(ImageFormat),
198
199    /// The format can be identified by a name.
200    Name(String),
201
202    /// A common path extension for the format is known.
203    #[cfg(feature = "std")]
204    PathExtension(std::path::PathBuf),
205
206    /// The format is not known or could not be determined.
207    Unknown,
208}
209
210impl UnsupportedError {
211    /// Create an `UnsupportedError` for an image with details on the unsupported feature.
212    ///
213    /// If the operation was not connected to a particular image format then the hint may be
214    /// `Unknown`.
215    #[must_use]
216    pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self {
217        UnsupportedError { format, kind }
218    }
219
220    /// Returns the corresponding `UnsupportedErrorKind` of the error.
221    #[must_use]
222    pub fn kind(&self) -> UnsupportedErrorKind {
223        self.kind.clone()
224    }
225
226    /// Returns the image format associated with this error.
227    #[must_use]
228    pub fn format_hint(&self) -> ImageFormatHint {
229        self.format.clone()
230    }
231}
232
233impl DecodingError {
234    /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder.
235    pub fn new(format: ImageFormatHint, err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
236        DecodingError {
237            format,
238            underlying: Some(err.into()),
239        }
240    }
241
242    /// Create a `DecodingError` for an image format.
243    ///
244    /// The error will not contain any further information but is very easy to create.
245    #[must_use]
246    pub fn from_format_hint(format: ImageFormatHint) -> Self {
247        DecodingError {
248            format,
249            underlying: None,
250        }
251    }
252
253    /// Returns the image format associated with this error.
254    #[must_use]
255    pub fn format_hint(&self) -> ImageFormatHint {
256        self.format.clone()
257    }
258}
259
260impl EncodingError {
261    /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder.
262    pub fn new(format: ImageFormatHint, err: impl Into<Box<dyn Error + Send + Sync>>) -> Self {
263        EncodingError {
264            format,
265            underlying: Some(err.into()),
266        }
267    }
268
269    /// Create an `EncodingError` for an image format.
270    ///
271    /// The error will not contain any further information but is very easy to create.
272    #[must_use]
273    pub fn from_format_hint(format: ImageFormatHint) -> Self {
274        EncodingError {
275            format,
276            underlying: None,
277        }
278    }
279
280    /// Return the image format associated with this error.
281    #[must_use]
282    pub fn format_hint(&self) -> ImageFormatHint {
283        self.format.clone()
284    }
285}
286
287impl ParameterError {
288    /// Construct a `ParameterError` directly from a corresponding kind.
289    #[must_use]
290    pub fn from_kind(kind: ParameterErrorKind) -> Self {
291        ParameterError {
292            kind,
293            underlying: None,
294        }
295    }
296
297    /// Returns the corresponding `ParameterErrorKind` of the error.
298    #[must_use]
299    pub fn kind(&self) -> ParameterErrorKind {
300        self.kind.clone()
301    }
302}
303
304impl LimitError {
305    /// Construct a generic `LimitError` directly from a corresponding kind.
306    #[must_use]
307    pub fn from_kind(kind: LimitErrorKind) -> Self {
308        LimitError { kind }
309    }
310
311    /// Returns the corresponding `LimitErrorKind` of the error.
312    #[must_use]
313    pub fn kind(&self) -> LimitErrorKind {
314        self.kind.clone()
315    }
316}
317
318impl From<LimitErrorKind> for LimitError {
319    fn from(kind: LimitErrorKind) -> Self {
320        Self { kind }
321    }
322}
323
324impl From<io::Error> for ImageError {
325    fn from(err: io::Error) -> ImageError {
326        ImageError::IoError(err)
327    }
328}
329
330impl From<TryReserveError> for ImageError {
331    fn from(_: TryReserveError) -> ImageError {
332        ImageError::Limits(LimitErrorKind::InsufficientMemory.into())
333    }
334}
335
336impl From<ImageFormat> for ImageFormatHint {
337    fn from(format: ImageFormat) -> Self {
338        ImageFormatHint::Exact(format)
339    }
340}
341
342#[cfg(feature = "std")]
343impl From<&'_ std::path::Path> for ImageFormatHint {
344    fn from(path: &'_ std::path::Path) -> Self {
345        match path.extension() {
346            Some(ext) => ImageFormatHint::PathExtension(ext.into()),
347            None => ImageFormatHint::Unknown,
348        }
349    }
350}
351
352impl From<ImageFormatHint> for UnsupportedError {
353    fn from(hint: ImageFormatHint) -> Self {
354        UnsupportedError {
355            format: hint.clone(),
356            kind: UnsupportedErrorKind::Format(hint),
357        }
358    }
359}
360
361/// Result of an image decoding/encoding process
362pub type ImageResult<T> = Result<T, ImageError>;
363
364impl fmt::Display for ImageError {
365    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
366        match self {
367            ImageError::IoError(err) => err.fmt(fmt),
368            ImageError::Decoding(err) => err.fmt(fmt),
369            ImageError::Encoding(err) => err.fmt(fmt),
370            ImageError::Parameter(err) => err.fmt(fmt),
371            ImageError::Limits(err) => err.fmt(fmt),
372            ImageError::Unsupported(err) => err.fmt(fmt),
373        }
374    }
375}
376
377impl Error for ImageError {
378    fn source(&self) -> Option<&(dyn Error + 'static)> {
379        match self {
380            ImageError::IoError(_err) => None,
381            ImageError::Decoding(err) => err.source(),
382            ImageError::Encoding(err) => err.source(),
383            ImageError::Parameter(err) => err.source(),
384            ImageError::Limits(err) => err.source(),
385            ImageError::Unsupported(err) => err.source(),
386        }
387    }
388}
389
390impl fmt::Display for UnsupportedError {
391    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
392        match &self.kind {
393            UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => {
394                write!(fmt, "The image format could not be determined",)
395            }
396            #[cfg(feature = "std")]
397            UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!(
398                fmt,
399                "The file extension {format} was not recognized as an image format",
400            ),
401            UnsupportedErrorKind::Format(format) => {
402                write!(fmt, "The image format {format} is not supported",)
403            }
404            UnsupportedErrorKind::Color(color) => write!(
405                fmt,
406                "The encoder or decoder for {} does not support the color type `{:?}`",
407                self.format, color,
408            ),
409            UnsupportedErrorKind::ColorLayout(layout) => write!(
410                fmt,
411                "Converting with the texel memory layout {layout:?} is not supported",
412            ),
413            UnsupportedErrorKind::ColorspaceCicp(color) => write!(
414                fmt,
415                "The colorimetric interpretation of a CICP color space is not supported for `{color:?}`",
416            ),
417            UnsupportedErrorKind::GenericFeature(message) => match &self.format {
418                ImageFormatHint::Unknown => write!(
419                    fmt,
420                    "The decoder does not support the format feature {message}",
421                ),
422                other => write!(
423                    fmt,
424                    "The decoder for {other} does not support the format features {message}",
425                ),
426            },
427        }
428    }
429}
430
431impl Error for UnsupportedError {}
432
433impl fmt::Display for ParameterError {
434    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
435        match &self.kind {
436            ParameterErrorKind::DimensionMismatch => write!(
437                fmt,
438                "The Image's dimensions are either too \
439                 small or too large"
440            ),
441            ParameterErrorKind::FailedAlready => write!(
442                fmt,
443                "The end the image stream has been reached due to a previous error"
444            ),
445            ParameterErrorKind::RgbCicpRequired(cicp) => {
446                write!(fmt, "The CICP {cicp:?} can not be used for RGB images",)
447            }
448
449            ParameterErrorKind::Generic(message) => {
450                write!(fmt, "The parameter is malformed: {message}",)
451            }
452            ParameterErrorKind::NoMoreData => write!(fmt, "The end of the image has been reached",),
453            ParameterErrorKind::CicpMismatch { expected, found } => {
454                write!(
455                    fmt,
456                    "The color space {found:?} does not match the expected {expected:?}",
457                )
458            }
459        }?;
460
461        if let Some(underlying) = &self.underlying {
462            write!(fmt, "\n{underlying}")?;
463        }
464
465        Ok(())
466    }
467}
468
469impl Error for ParameterError {
470    fn source(&self) -> Option<&(dyn Error + 'static)> {
471        match &self.underlying {
472            None => None,
473            Some(source) => Some(&**source),
474        }
475    }
476}
477
478impl fmt::Display for EncodingError {
479    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
480        match &self.underlying {
481            Some(underlying) => write!(
482                fmt,
483                "Format error encoding {}:\n{}",
484                self.format, underlying,
485            ),
486            None => write!(fmt, "Format error encoding {}", self.format,),
487        }
488    }
489}
490
491impl Error for EncodingError {
492    fn source(&self) -> Option<&(dyn Error + 'static)> {
493        match &self.underlying {
494            None => None,
495            Some(source) => Some(&**source),
496        }
497    }
498}
499
500impl fmt::Display for DecodingError {
501    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
502        match &self.underlying {
503            None => match self.format {
504                ImageFormatHint::Unknown => write!(fmt, "Format error"),
505                _ => write!(fmt, "Format error decoding {}", self.format),
506            },
507            Some(underlying) => {
508                write!(fmt, "Format error decoding {}: {}", self.format, underlying)
509            }
510        }
511    }
512}
513
514impl Error for DecodingError {
515    fn source(&self) -> Option<&(dyn Error + 'static)> {
516        match &self.underlying {
517            None => None,
518            Some(source) => Some(&**source),
519        }
520    }
521}
522
523impl fmt::Display for LimitError {
524    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
525        match self.kind {
526            LimitErrorKind::InsufficientMemory => write!(fmt, "Memory limit exceeded"),
527            LimitErrorKind::DimensionError => write!(fmt, "Image size exceeds limit"),
528            LimitErrorKind::Unsupported { .. } => {
529                write!(fmt, "The following strict limits are specified but not supported by the opertation: ")?;
530                Ok(())
531            }
532        }
533    }
534}
535
536impl Error for LimitError {}
537
538impl fmt::Display for ImageFormatHint {
539    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
540        match self {
541            ImageFormatHint::Exact(format) => write!(fmt, "{format:?}"),
542            ImageFormatHint::Name(name) => write!(fmt, "`{name}`"),
543            #[cfg(feature = "std")]
544            ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{ext:?}`"),
545            ImageFormatHint::Unknown => write!(fmt, "`Unknown`"),
546        }
547    }
548}
549
550/// Converting [`ExtendedColorType`] to [`ColorType`][`crate::ColorType`] failed.
551///
552/// This type is convertible to [`ImageError`] as [`ImageError::Unsupported`].
553#[derive(Clone)]
554#[allow(missing_copy_implementations)]
555pub struct TryFromExtendedColorError {
556    pub(crate) was: ExtendedColorType,
557}
558
559impl fmt::Debug for TryFromExtendedColorError {
560    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561        write!(f, "{self}")
562    }
563}
564
565impl fmt::Display for TryFromExtendedColorError {
566    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567        write!(
568            f,
569            "The pixel layout {:?} is not supported as a buffer ColorType",
570            self.was
571        )
572    }
573}
574
575impl Error for TryFromExtendedColorError {}
576
577impl From<TryFromExtendedColorError> for ImageError {
578    fn from(err: TryFromExtendedColorError) -> ImageError {
579        ImageError::Unsupported(UnsupportedError::from_format_and_kind(
580            ImageFormatHint::Unknown,
581            UnsupportedErrorKind::Color(err.was),
582        ))
583    }
584}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589    use core::mem::size_of;
590
591    #[allow(dead_code)]
592    // This will fail to compile if the size of this type is large.
593    const ASSERT_SMALLISH: usize = [0][(size_of::<ImageError>() >= 200) as usize];
594
595    #[test]
596    fn test_send_sync_stability() {
597        fn assert_send_sync<T: Send + Sync>() {}
598
599        assert_send_sync::<ImageError>();
600    }
601}