Skip to main content

edgefirst_codec/
error.rs

1// SPDX-FileCopyrightText: Copyright 2026 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4//! Error types for the codec crate.
5
6use std::fmt;
7
8/// A specific image-format feature that the codec does not implement.
9///
10/// Carried by [`CodecError::Unsupported`]. Use this to distinguish "the byte
11/// string is well-formed but uses a feature we don't decode" from "the byte
12/// string is corrupt" ([`CodecError::InvalidData`]).
13#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum UnsupportedFeature {
16    /// JPEG SOF2 (progressive baseline).
17    ProgressiveJpeg,
18    /// JPEG SOF1 (extended sequential, Huffman coded).
19    ExtendedSequentialJpeg,
20    /// JPEG SOF9/SOF10/SOF11/SOF13/SOF14/SOF15 (arithmetic coding, in all
21    /// sequential/progressive/lossless/hierarchical flavours).
22    ArithmeticCodedJpeg,
23    /// JPEG SOF3 (lossless predictive).
24    LosslessJpeg,
25    /// JPEG SOF5/SOF6/SOF7 (hierarchical).
26    HierarchicalJpeg,
27    /// JPEG sample precision other than 8 bits per channel.
28    JpegPrecision {
29        /// The unsupported precision reported in the SOF marker.
30        bits: u8,
31    },
32    /// JPEG with a component count we do not handle (anything other than 1
33    /// for greyscale or 3 for YCbCr).
34    JpegComponentCount {
35        /// The unsupported component count reported in the SOF marker.
36        components: u8,
37    },
38    /// JPEG chroma subsampling configuration where a chroma component is
39    /// sampled at a higher rate than luma along some axis (rejected so the
40    /// downstream upsampler does not divide by zero).
41    JpegChromaSubsampling,
42}
43
44impl fmt::Display for UnsupportedFeature {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            Self::ProgressiveJpeg => write!(f, "progressive JPEG (SOF2)"),
48            Self::ExtendedSequentialJpeg => {
49                write!(f, "extended sequential JPEG (SOF1)")
50            }
51            Self::ArithmeticCodedJpeg => write!(
52                f,
53                "arithmetic-coded JPEG (SOF9/SOF10/SOF11/SOF13/SOF14/SOF15)"
54            ),
55            Self::LosslessJpeg => write!(f, "lossless JPEG (SOF3)"),
56            Self::HierarchicalJpeg => write!(f, "hierarchical JPEG (SOF5/SOF6/SOF7)"),
57            Self::JpegPrecision { bits } => {
58                write!(f, "JPEG sample precision {bits} (only 8-bit is supported)")
59            }
60            Self::JpegComponentCount { components } => {
61                write!(
62                    f,
63                    "JPEG component count {components} (only 1 or 3 are supported)"
64                )
65            }
66            Self::JpegChromaSubsampling => {
67                write!(f, "JPEG chroma subsampling that exceeds luma sampling rate")
68            }
69        }
70    }
71}
72
73/// Errors that can occur during image decoding.
74#[derive(Debug)]
75pub enum CodecError {
76    /// Image dimensions exceed the tensor's capacity.
77    InsufficientCapacity {
78        /// (width, height) of the decoded image.
79        image: (usize, usize),
80        /// (width, height) capacity of the destination tensor.
81        tensor: (usize, usize),
82    },
83    /// The tensor's element type is not supported for image decoding.
84    UnsupportedDtype(edgefirst_tensor::DType),
85    /// The requested pixel format is not supported for this image type.
86    UnsupportedFormat(edgefirst_tensor::PixelFormat),
87    /// The image data is well-formed but uses a feature this decoder does
88    /// not implement. Callers can pattern-match on the carried
89    /// [`UnsupportedFeature`] to react programmatically (e.g. transcode
90    /// before retry, surface a precise message, or skip gracefully).
91    Unsupported(UnsupportedFeature),
92    /// The image data is corrupted, truncated, or not a recognized format.
93    InvalidData(String),
94    /// I/O error reading image data.
95    Io(std::io::Error),
96    /// An error from the tensor subsystem.
97    Tensor(edgefirst_tensor::Error),
98}
99
100impl fmt::Display for CodecError {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        match self {
103            Self::InsufficientCapacity { image, tensor } => write!(
104                f,
105                "image dimensions {}×{} exceed tensor capacity {}×{}",
106                image.0, image.1, tensor.0, tensor.1,
107            ),
108            Self::UnsupportedDtype(dt) => {
109                write!(f, "unsupported tensor dtype {dt:?} for image decode")
110            }
111            Self::UnsupportedFormat(pf) => {
112                write!(f, "unsupported pixel format {pf:?} for image decode")
113            }
114            Self::Unsupported(feat) => write!(f, "unsupported image feature: {feat}"),
115            Self::InvalidData(msg) => write!(f, "invalid image data: {msg}"),
116            Self::Io(e) => write!(f, "I/O error: {e}"),
117            Self::Tensor(e) => write!(f, "tensor error: {e}"),
118        }
119    }
120}
121
122impl std::error::Error for CodecError {
123    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124        match self {
125            Self::Io(e) => Some(e),
126            Self::Tensor(e) => Some(e),
127            _ => None,
128        }
129    }
130}
131
132impl From<std::io::Error> for CodecError {
133    fn from(e: std::io::Error) -> Self {
134        Self::Io(e)
135    }
136}
137
138impl From<edgefirst_tensor::Error> for CodecError {
139    fn from(e: edgefirst_tensor::Error) -> Self {
140        Self::Tensor(e)
141    }
142}
143
144impl From<zune_png::error::PngDecodeErrors> for CodecError {
145    fn from(e: zune_png::error::PngDecodeErrors) -> Self {
146        Self::InvalidData(format!("PNG: {e}"))
147    }
148}