Skip to main content

zenpixels_convert/
error.rs

1//! Error types for pixel format conversion.
2
3use crate::{PixelDescriptor, TransferFunction};
4use core::fmt;
5
6/// Errors that can occur during pixel format negotiation or conversion.
7#[derive(Debug, Clone, PartialEq)]
8pub enum ConvertError {
9    /// No supported format could be found for the source descriptor.
10    NoMatch { source: PixelDescriptor },
11    /// No conversion path exists between the two formats.
12    NoPath {
13        from: PixelDescriptor,
14        to: PixelDescriptor,
15    },
16    /// Source and destination buffer sizes don't match the expected dimensions.
17    BufferSize { expected: usize, actual: usize },
18    /// Width is zero or would overflow stride calculations.
19    InvalidWidth(u32),
20    /// The supported format list was empty.
21    EmptyFormatList,
22    /// Conversion between these transfer functions is not yet supported.
23    UnsupportedTransfer {
24        from: TransferFunction,
25        to: TransferFunction,
26    },
27    /// Alpha channel is not fully opaque and [`AlphaPolicy::DiscardIfOpaque`](crate::AlphaPolicy::DiscardIfOpaque) was set.
28    AlphaNotOpaque,
29    /// Depth reduction was requested but [`DepthPolicy::Forbid`](crate::DepthPolicy::Forbid) was set.
30    DepthReductionForbidden,
31    /// Alpha removal was requested but [`AlphaPolicy::Forbid`](crate::AlphaPolicy::Forbid) was set.
32    AlphaRemovalForbidden,
33    /// RGB-to-grayscale conversion requires explicit luma coefficients.
34    RgbToGray,
35    /// Buffer allocation failed.
36    AllocationFailed,
37    /// CMS transform could not be built (invalid ICC profile, unsupported color space, etc.).
38    CmsError(alloc::string::String),
39}
40
41impl fmt::Display for ConvertError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::NoMatch { source } => {
45                write!(
46                    f,
47                    "no supported format matches source {:?}/{:?}",
48                    source.channel_type(),
49                    source.layout()
50                )
51            }
52            Self::NoPath { from, to } => {
53                write!(
54                    f,
55                    "no conversion path from {:?}/{:?} to {:?}/{:?}",
56                    from.channel_type(),
57                    from.layout(),
58                    to.channel_type(),
59                    to.layout()
60                )
61            }
62            Self::BufferSize { expected, actual } => {
63                write!(
64                    f,
65                    "buffer size mismatch: expected {expected} bytes, got {actual}"
66                )
67            }
68            Self::InvalidWidth(w) => write!(f, "invalid width: {w}"),
69            Self::EmptyFormatList => write!(f, "supported format list is empty"),
70            Self::UnsupportedTransfer { from, to } => {
71                write!(f, "unsupported transfer conversion: {from:?} → {to:?}")
72            }
73            Self::AlphaNotOpaque => write!(f, "alpha channel is not fully opaque"),
74            Self::DepthReductionForbidden => write!(f, "depth reduction forbidden by policy"),
75            Self::AlphaRemovalForbidden => write!(f, "alpha removal forbidden by policy"),
76            Self::RgbToGray => {
77                write!(f, "RGB-to-grayscale requires explicit luma coefficients")
78            }
79            Self::AllocationFailed => write!(f, "buffer allocation failed"),
80            Self::CmsError(msg) => write!(f, "CMS transform failed: {msg}"),
81        }
82    }
83}
84
85#[cfg(feature = "std")]
86impl std::error::Error for ConvertError {}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use alloc::format;
92
93    #[test]
94    fn display_no_match() {
95        let e = ConvertError::NoMatch {
96            source: PixelDescriptor::RGB8_SRGB,
97        };
98        let s = format!("{e}");
99        assert!(s.contains("no supported format"));
100        assert!(s.contains("U8"));
101        assert!(s.contains("Rgb"));
102    }
103
104    #[test]
105    fn display_no_path() {
106        let e = ConvertError::NoPath {
107            from: PixelDescriptor::RGB8_SRGB,
108            to: PixelDescriptor::GRAY8_SRGB,
109        };
110        let s = format!("{e}");
111        assert!(s.contains("no conversion path"));
112    }
113
114    #[test]
115    fn display_buffer_size() {
116        let e = ConvertError::BufferSize {
117            expected: 1024,
118            actual: 512,
119        };
120        let s = format!("{e}");
121        assert!(s.contains("1024"));
122        assert!(s.contains("512"));
123    }
124
125    #[test]
126    fn display_invalid_width() {
127        let e = ConvertError::InvalidWidth(0);
128        assert!(format!("{e}").contains("0"));
129    }
130
131    #[test]
132    fn display_empty_format_list() {
133        let s = format!("{}", ConvertError::EmptyFormatList);
134        assert!(s.contains("empty"));
135    }
136
137    #[test]
138    fn display_unsupported_transfer() {
139        let e = ConvertError::UnsupportedTransfer {
140            from: TransferFunction::Pq,
141            to: TransferFunction::Hlg,
142        };
143        let s = format!("{e}");
144        assert!(s.contains("Pq"));
145        assert!(s.contains("Hlg"));
146    }
147
148    #[test]
149    fn display_alpha_not_opaque() {
150        assert!(format!("{}", ConvertError::AlphaNotOpaque).contains("opaque"));
151    }
152
153    #[test]
154    fn display_depth_reduction_forbidden() {
155        assert!(format!("{}", ConvertError::DepthReductionForbidden).contains("forbidden"));
156    }
157
158    #[test]
159    fn display_alpha_removal_forbidden() {
160        assert!(format!("{}", ConvertError::AlphaRemovalForbidden).contains("forbidden"));
161    }
162
163    #[test]
164    fn display_rgb_to_gray() {
165        assert!(format!("{}", ConvertError::RgbToGray).contains("luma"));
166    }
167
168    #[test]
169    fn display_allocation_failed() {
170        assert!(format!("{}", ConvertError::AllocationFailed).contains("allocation"));
171    }
172
173    #[test]
174    fn display_cms_error() {
175        let e = ConvertError::CmsError(alloc::string::String::from("profile mismatch"));
176        let s = format!("{e}");
177        assert!(s.contains("CMS transform failed"));
178        assert!(s.contains("profile mismatch"));
179    }
180
181    #[test]
182    fn error_eq() {
183        assert_eq!(ConvertError::AlphaNotOpaque, ConvertError::AlphaNotOpaque);
184        assert_ne!(ConvertError::AlphaNotOpaque, ConvertError::RgbToGray);
185    }
186
187    #[test]
188    fn error_debug() {
189        let e = ConvertError::AllocationFailed;
190        let s = format!("{e:?}");
191        assert!(s.contains("AllocationFailed"));
192    }
193
194    #[test]
195    fn error_clone() {
196        let e = ConvertError::BufferSize {
197            expected: 100,
198            actual: 50,
199        };
200        let e2 = e.clone();
201        assert_eq!(e, e2);
202    }
203}