Skip to main content

edgefirst_image/
error.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4pub type Result<T, E = Error> = std::result::Result<T, E>;
5
6#[derive(Debug)]
7pub enum Error {
8    Io(std::io::Error),
9    NotFound(String),
10    Library(libloading::Error),
11    JpegEncoding(jpeg_encoder::EncodingError),
12    #[cfg(feature = "codec")]
13    Codec(edgefirst_codec::CodecError),
14    ResizeImageBuffer(fast_image_resize::ImageBufferError),
15    Resize(fast_image_resize::ResizeError),
16    Yuv(yuv::YuvError),
17    #[cfg(target_os = "linux")]
18    G2D(g2d_sys::Error),
19    Tensor(edgefirst_tensor::Error),
20    NotImplemented(String),
21    NotSupported(String),
22    InvalidShape(String),
23    /// Two tensor arguments that must be distinct reference the same
24    /// underlying buffer (e.g. `dst` and `background` in a draw call).
25    AliasedBuffers(String),
26    #[cfg(feature = "opengl")]
27    EGL(edgefirst_egl::Error),
28    GLVersion(String),
29    #[cfg(feature = "opengl")]
30    EGLLoad(edgefirst_egl::LoadError<libloading::Error>),
31    #[cfg(target_os = "linux")]
32    #[cfg(feature = "opengl")]
33    GbmInvalidFd(gbm::InvalidFdError),
34    #[cfg(target_os = "linux")]
35    #[cfg(feature = "opengl")]
36    GbmFrontBuffer(gbm::FrontBufferError),
37    OpenGl(String),
38    Internal(String),
39    CropInvalid(String),
40    /// Convert options that cannot apply to the destination (normalization
41    /// coefficients, quantization).
42    InvalidOptions(String),
43    ForcedBackendUnavailable(String),
44    /// A GPU backend declined a convert and the CPU fallback was refused
45    /// because a tensor's declared CPU access does not cover it.
46    CpuFallbackDenied {
47        /// Why each GPU backend declined, as `backend: reason` entries.
48        declined: String,
49        /// The CPU backend's refusal, an
50        /// [`edgefirst_tensor::Error::CpuAccessDenied`].
51        denied: edgefirst_tensor::Error,
52    },
53    NoConverter,
54    NotAnImage,
55    UnsupportedFormat(String),
56}
57
58impl From<std::io::Error> for Error {
59    fn from(err: std::io::Error) -> Self {
60        Error::Io(err)
61    }
62}
63
64impl From<libloading::Error> for Error {
65    fn from(err: libloading::Error) -> Self {
66        Error::Library(err)
67    }
68}
69
70impl From<jpeg_encoder::EncodingError> for Error {
71    fn from(err: jpeg_encoder::EncodingError) -> Self {
72        Error::JpegEncoding(err)
73    }
74}
75
76#[cfg(feature = "codec")]
77impl From<edgefirst_codec::CodecError> for Error {
78    fn from(err: edgefirst_codec::CodecError) -> Self {
79        Error::Codec(err)
80    }
81}
82
83impl From<fast_image_resize::ImageBufferError> for Error {
84    fn from(err: fast_image_resize::ImageBufferError) -> Self {
85        Error::ResizeImageBuffer(err)
86    }
87}
88
89impl From<fast_image_resize::ResizeError> for Error {
90    fn from(err: fast_image_resize::ResizeError) -> Self {
91        Error::Resize(err)
92    }
93}
94
95impl From<yuv::YuvError> for Error {
96    fn from(err: yuv::YuvError) -> Self {
97        Error::Yuv(err)
98    }
99}
100
101#[cfg(target_os = "linux")]
102impl From<g2d_sys::Error> for Error {
103    fn from(err: g2d_sys::Error) -> Self {
104        Error::G2D(err)
105    }
106}
107
108impl From<edgefirst_tensor::Error> for Error {
109    fn from(err: edgefirst_tensor::Error) -> Self {
110        Error::Tensor(err)
111    }
112}
113
114#[cfg(feature = "opengl")]
115impl From<edgefirst_egl::Error> for Error {
116    fn from(err: edgefirst_egl::Error) -> Self {
117        Error::EGL(err)
118    }
119}
120
121#[cfg(feature = "opengl")]
122impl From<edgefirst_egl::LoadError<libloading::Error>> for Error {
123    fn from(err: edgefirst_egl::LoadError<libloading::Error>) -> Self {
124        Error::EGLLoad(err)
125    }
126}
127
128#[cfg(target_os = "linux")]
129#[cfg(feature = "opengl")]
130impl From<gbm::InvalidFdError> for Error {
131    fn from(err: gbm::InvalidFdError) -> Self {
132        Error::GbmInvalidFd(err)
133    }
134}
135
136#[cfg(target_os = "linux")]
137#[cfg(feature = "opengl")]
138impl From<gbm::FrontBufferError> for Error {
139    fn from(err: gbm::FrontBufferError) -> Self {
140        Error::GbmFrontBuffer(err)
141    }
142}
143
144#[cfg(target_os = "linux")]
145#[cfg(feature = "opengl")]
146impl From<edgefirst_gl::Error> for Error {
147    fn from(err: edgefirst_gl::Error) -> Self {
148        Error::OpenGl(err.to_string())
149    }
150}
151
152impl From<ndarray::ShapeError> for Error {
153    fn from(err: ndarray::ShapeError) -> Self {
154        Error::Internal(format!("{err}"))
155    }
156}
157
158impl From<fast_image_resize::CropBoxError> for Error {
159    fn from(err: fast_image_resize::CropBoxError) -> Self {
160        Error::Internal(format!("{err}"))
161    }
162}
163
164impl Error {
165    /// The [`CpuAccessDenied`](edgefirst_tensor::Error::CpuAccessDenied)
166    /// refusal this error carries, if any: directly, inside a
167    /// [`CpuFallbackDenied`](Error::CpuFallbackDenied), or from a codec.
168    pub fn cpu_access_denied(&self) -> Option<&edgefirst_tensor::Error> {
169        match self {
170            Error::Tensor(e) => e.cpu_access_denied(),
171            Error::CpuFallbackDenied { denied, .. } => Some(denied),
172            #[cfg(feature = "codec")]
173            Error::Codec(e) => e.cpu_access_denied(),
174            _ => None,
175        }
176    }
177}
178
179impl std::fmt::Display for Error {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        match self {
182            Error::Tensor(e) => write!(f, "Tensor({e})"),
183            Error::CpuFallbackDenied { declined, denied } => write!(
184                f,
185                "the GPU path declined this convert ({declined}) and the CPU fallback \
186                 was refused: {denied}"
187            ),
188            _ => write!(f, "{self:?}"),
189        }
190    }
191}
192
193impl std::error::Error for Error {}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_image_error_display() {
201        let e = Error::Io(std::io::Error::new(
202            std::io::ErrorKind::NotFound,
203            "file missing",
204        ));
205        let msg = e.to_string();
206        assert!(!msg.is_empty());
207        assert!(
208            msg.contains("Io") && msg.contains("file missing"),
209            "unexpected Io message: {msg}"
210        );
211
212        let e = Error::NotFound("texture.png".to_string());
213        let msg = e.to_string();
214        assert!(!msg.is_empty());
215        assert!(
216            msg.contains("NotFound") && msg.contains("texture.png"),
217            "unexpected NotFound message: {msg}"
218        );
219
220        let e = Error::Tensor(edgefirst_tensor::Error::InvalidSize(42));
221        let msg = e.to_string();
222        assert!(!msg.is_empty());
223        assert!(
224            msg.contains("Tensor") && msg.contains("InvalidSize"),
225            "unexpected Tensor message: {msg}"
226        );
227
228        let e = Error::NotImplemented("webp".to_string());
229        let msg = e.to_string();
230        assert!(!msg.is_empty());
231        assert!(
232            msg.contains("NotImplemented") && msg.contains("webp"),
233            "unexpected NotImplemented message: {msg}"
234        );
235
236        let e = Error::NotSupported("bmp format".to_string());
237        let msg = e.to_string();
238        assert!(!msg.is_empty());
239        assert!(
240            msg.contains("NotSupported") && msg.contains("bmp format"),
241            "unexpected NotSupported message: {msg}"
242        );
243
244        let e = Error::InvalidShape("wrong dims".to_string());
245        let msg = e.to_string();
246        assert!(!msg.is_empty());
247        assert!(
248            msg.contains("InvalidShape") && msg.contains("wrong dims"),
249            "unexpected InvalidShape message: {msg}"
250        );
251
252        let e = Error::GLVersion("4.5 required".to_string());
253        let msg = e.to_string();
254        assert!(!msg.is_empty());
255        assert!(
256            msg.contains("GLVersion") && msg.contains("4.5 required"),
257            "unexpected GLVersion message: {msg}"
258        );
259
260        let e = Error::OpenGl("shader compile failed".to_string());
261        let msg = e.to_string();
262        assert!(!msg.is_empty());
263        assert!(
264            msg.contains("OpenGl") && msg.contains("shader compile failed"),
265            "unexpected OpenGl message: {msg}"
266        );
267
268        let e = Error::Internal("unexpected state".to_string());
269        let msg = e.to_string();
270        assert!(!msg.is_empty());
271        assert!(
272            msg.contains("Internal") && msg.contains("unexpected state"),
273            "unexpected Internal message: {msg}"
274        );
275
276        let e = Error::CropInvalid("out of bounds".to_string());
277        let msg = e.to_string();
278        assert!(!msg.is_empty());
279        assert!(
280            msg.contains("CropInvalid") && msg.contains("out of bounds"),
281            "unexpected CropInvalid message: {msg}"
282        );
283
284        let e = Error::ForcedBackendUnavailable("g2d".to_string());
285        let msg = e.to_string();
286        assert!(!msg.is_empty());
287        assert!(
288            msg.contains("ForcedBackendUnavailable") && msg.contains("g2d"),
289            "unexpected ForcedBackendUnavailable message: {msg}"
290        );
291
292        let e = Error::NoConverter;
293        let msg = e.to_string();
294        assert!(!msg.is_empty());
295        assert!(
296            msg.contains("NoConverter"),
297            "unexpected NoConverter message: {msg}"
298        );
299
300        let e = Error::NotAnImage;
301        let msg = e.to_string();
302        assert!(!msg.is_empty());
303        assert!(
304            msg.contains("NotAnImage"),
305            "unexpected NotAnImage message: {msg}"
306        );
307
308        let e = Error::UnsupportedFormat("tiff".to_string());
309        let msg = e.to_string();
310        assert!(!msg.is_empty());
311        assert!(
312            msg.contains("UnsupportedFormat") && msg.contains("tiff"),
313            "unexpected UnsupportedFormat message: {msg}"
314        );
315    }
316}