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    /// PNG data was recognised but the `png` feature is disabled in this build.
39    PngNotEnabled,
40    /// JPEG chroma subsampling configuration where a chroma component is
41    /// sampled at a higher rate than luma along some axis (rejected so the
42    /// downstream upsampler does not divide by zero).
43    JpegChromaSubsampling,
44}
45
46impl fmt::Display for UnsupportedFeature {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            Self::ProgressiveJpeg => write!(f, "progressive JPEG (SOF2)"),
50            Self::ExtendedSequentialJpeg => {
51                write!(f, "extended sequential JPEG (SOF1)")
52            }
53            Self::ArithmeticCodedJpeg => write!(
54                f,
55                "arithmetic-coded JPEG (SOF9/SOF10/SOF11/SOF13/SOF14/SOF15)"
56            ),
57            Self::LosslessJpeg => write!(f, "lossless JPEG (SOF3)"),
58            Self::HierarchicalJpeg => write!(f, "hierarchical JPEG (SOF5/SOF6/SOF7)"),
59            Self::JpegPrecision { bits } => {
60                write!(f, "JPEG sample precision {bits} (only 8-bit is supported)")
61            }
62            Self::JpegComponentCount { components } => {
63                write!(
64                    f,
65                    "JPEG component count {components} (only 1 or 3 are supported)"
66                )
67            }
68            Self::PngNotEnabled => write!(
69                f,
70                "PNG decoding is not enabled in this build (enable the `png` feature)"
71            ),
72            Self::JpegChromaSubsampling => {
73                write!(f, "JPEG chroma subsampling that exceeds luma sampling rate")
74            }
75        }
76    }
77}
78
79/// Errors that can occur during image decoding.
80#[derive(Debug)]
81pub enum CodecError {
82    /// Image dimensions exceed the tensor's capacity.
83    InsufficientCapacity {
84        /// (width, height) of the decoded image.
85        image: (usize, usize),
86        /// (width, height) capacity of the destination tensor.
87        tensor: (usize, usize),
88    },
89    /// The tensor's element type is not supported for image decoding.
90    UnsupportedDtype(edgefirst_tensor::DType),
91    /// The requested pixel format is not supported for this image type.
92    UnsupportedFormat(edgefirst_tensor::PixelFormat),
93    /// The image data is well-formed but uses a feature this decoder does
94    /// not implement. Callers can pattern-match on the carried
95    /// [`UnsupportedFeature`] to react programmatically (e.g. transcode
96    /// before retry, surface a precise message, or skip gracefully).
97    Unsupported(UnsupportedFeature),
98    /// The image data is corrupted, truncated, or not a recognized format.
99    InvalidData(String),
100    /// I/O error reading image data.
101    Io(std::io::Error),
102    /// An error from the tensor subsystem.
103    Tensor(edgefirst_tensor::Error),
104}
105
106impl fmt::Display for CodecError {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::InsufficientCapacity { image, tensor } => write!(
110                f,
111                "image dimensions {}×{} exceed tensor capacity {}×{}",
112                image.0, image.1, tensor.0, tensor.1,
113            ),
114            Self::UnsupportedDtype(dt) => {
115                write!(f, "unsupported tensor dtype {dt:?} for image decode")
116            }
117            Self::UnsupportedFormat(pf) => {
118                write!(f, "unsupported pixel format {pf:?} for image decode")
119            }
120            Self::Unsupported(feat) => write!(f, "unsupported image feature: {feat}"),
121            Self::InvalidData(msg) => write!(f, "invalid image data: {msg}"),
122            Self::Io(e) => write!(f, "I/O error: {e}"),
123            Self::Tensor(e) => write!(f, "tensor error: {e}"),
124        }
125    }
126}
127
128impl std::error::Error for CodecError {
129    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
130        match self {
131            Self::Io(e) => Some(e),
132            Self::Tensor(e) => Some(e),
133            _ => None,
134        }
135    }
136}
137
138impl From<std::io::Error> for CodecError {
139    fn from(e: std::io::Error) -> Self {
140        Self::Io(e)
141    }
142}
143
144impl From<edgefirst_tensor::Error> for CodecError {
145    fn from(e: edgefirst_tensor::Error) -> Self {
146        Self::Tensor(e)
147    }
148}
149
150#[cfg(feature = "png")]
151impl From<zune_png::error::PngDecodeErrors> for CodecError {
152    fn from(e: zune_png::error::PngDecodeErrors) -> Self {
153        Self::InvalidData(format!("PNG: {e}"))
154    }
155}