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    /// HDR source requires tone mapping before conversion to an SDR target.
40    ///
41    /// A colorimetric CMS transform from PQ/HLG to an SDR profile will clip
42    /// highlights. Tone map the pixels first, then retry with SDR metadata.
43    HdrTransferRequiresToneMapping,
44}
45
46impl fmt::Display for ConvertError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            Self::NoMatch { source } => {
50                write!(
51                    f,
52                    "no supported format matches source {:?}/{:?}",
53                    source.channel_type(),
54                    source.layout()
55                )
56            }
57            Self::NoPath { from, to } => {
58                write!(
59                    f,
60                    "no conversion path from {:?}/{:?} to {:?}/{:?}",
61                    from.channel_type(),
62                    from.layout(),
63                    to.channel_type(),
64                    to.layout()
65                )
66            }
67            Self::BufferSize { expected, actual } => {
68                write!(
69                    f,
70                    "buffer size mismatch: expected {expected} bytes, got {actual}"
71                )
72            }
73            Self::InvalidWidth(w) => write!(f, "invalid width: {w}"),
74            Self::EmptyFormatList => write!(f, "supported format list is empty"),
75            Self::UnsupportedTransfer { from, to } => {
76                write!(f, "unsupported transfer conversion: {from:?} → {to:?}")
77            }
78            Self::AlphaNotOpaque => write!(f, "alpha channel is not fully opaque"),
79            Self::DepthReductionForbidden => write!(f, "depth reduction forbidden by policy"),
80            Self::AlphaRemovalForbidden => write!(f, "alpha removal forbidden by policy"),
81            Self::RgbToGray => {
82                write!(f, "RGB-to-grayscale requires explicit luma coefficients")
83            }
84            Self::AllocationFailed => write!(f, "buffer allocation failed"),
85            Self::CmsError(msg) => write!(f, "CMS transform failed: {msg}"),
86            Self::HdrTransferRequiresToneMapping => write!(
87                f,
88                "HDR source (PQ/HLG) requires tone mapping before conversion to SDR target"
89            ),
90        }
91    }
92}
93
94#[cfg(feature = "std")]
95impl std::error::Error for ConvertError {}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use alloc::format;
101
102    #[test]
103    fn display_no_match() {
104        let e = ConvertError::NoMatch {
105            source: PixelDescriptor::RGB8_SRGB,
106        };
107        let s = format!("{e}");
108        assert!(s.contains("no supported format"));
109        assert!(s.contains("U8"));
110        assert!(s.contains("Rgb"));
111    }
112
113    #[test]
114    fn display_no_path() {
115        let e = ConvertError::NoPath {
116            from: PixelDescriptor::RGB8_SRGB,
117            to: PixelDescriptor::GRAY8_SRGB,
118        };
119        let s = format!("{e}");
120        assert!(s.contains("no conversion path"));
121    }
122
123    #[test]
124    fn display_buffer_size() {
125        let e = ConvertError::BufferSize {
126            expected: 1024,
127            actual: 512,
128        };
129        let s = format!("{e}");
130        assert!(s.contains("1024"));
131        assert!(s.contains("512"));
132    }
133
134    #[test]
135    fn display_invalid_width() {
136        let e = ConvertError::InvalidWidth(0);
137        assert!(format!("{e}").contains("0"));
138    }
139
140    #[test]
141    fn display_empty_format_list() {
142        let s = format!("{}", ConvertError::EmptyFormatList);
143        assert!(s.contains("empty"));
144    }
145
146    #[test]
147    fn display_unsupported_transfer() {
148        let e = ConvertError::UnsupportedTransfer {
149            from: TransferFunction::Pq,
150            to: TransferFunction::Hlg,
151        };
152        let s = format!("{e}");
153        assert!(s.contains("Pq"));
154        assert!(s.contains("Hlg"));
155    }
156
157    #[test]
158    fn display_alpha_not_opaque() {
159        assert!(format!("{}", ConvertError::AlphaNotOpaque).contains("opaque"));
160    }
161
162    #[test]
163    fn display_depth_reduction_forbidden() {
164        assert!(format!("{}", ConvertError::DepthReductionForbidden).contains("forbidden"));
165    }
166
167    #[test]
168    fn display_alpha_removal_forbidden() {
169        assert!(format!("{}", ConvertError::AlphaRemovalForbidden).contains("forbidden"));
170    }
171
172    #[test]
173    fn display_rgb_to_gray() {
174        assert!(format!("{}", ConvertError::RgbToGray).contains("luma"));
175    }
176
177    #[test]
178    fn display_allocation_failed() {
179        assert!(format!("{}", ConvertError::AllocationFailed).contains("allocation"));
180    }
181
182    #[test]
183    fn display_cms_error() {
184        let e = ConvertError::CmsError(alloc::string::String::from("profile mismatch"));
185        let s = format!("{e}");
186        assert!(s.contains("CMS transform failed"));
187        assert!(s.contains("profile mismatch"));
188    }
189
190    #[test]
191    fn error_eq() {
192        assert_eq!(ConvertError::AlphaNotOpaque, ConvertError::AlphaNotOpaque);
193        assert_ne!(ConvertError::AlphaNotOpaque, ConvertError::RgbToGray);
194    }
195
196    #[test]
197    fn error_debug() {
198        let e = ConvertError::AllocationFailed;
199        let s = format!("{e:?}");
200        assert!(s.contains("AllocationFailed"));
201    }
202
203    #[test]
204    fn error_clone() {
205        let e = ConvertError::BufferSize {
206            expected: 100,
207            actual: 50,
208        };
209        let e2 = e.clone();
210        assert_eq!(e, e2);
211    }
212}