ezk_image/
pixel_format.rs

1use crate::{InvalidNumberOfPlanesError, StrictApi as _, plane_decs::*, planes::read_planes};
2
3/// Supported pixel formats
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[non_exhaustive]
6pub enum PixelFormat {
7    /// Y, U and V planes, 4:2:0 sub sampling, 8 bits per sample
8    #[cfg(feature = "I420")]
9    I420,
10
11    /// Y, U and V planes, 4:2:2 sub sampling, 8 bits per sample
12    #[cfg(feature = "I422")]
13    I422,
14
15    /// Y, U and V planes, 4:4:4 sub sampling, 8 bits per sample
16    #[cfg(feature = "I444")]
17    I444,
18
19    /// Y, U, and V planes, 4:2:0 sub sampling, 10 bits per sample
20    #[cfg(feature = "I010")]
21    I010,
22
23    /// Y, U, and V planes, 4:2:0 sub sampling, 12 bits per sample
24    #[cfg(feature = "I012")]
25    I012,
26
27    /// Y, U, and V planes, 4:2:2 sub sampling, 10 bits per sample
28    #[cfg(feature = "I210")]
29    I210,
30
31    /// Y, U, and V planes, 4:2:2 sub sampling, 10 bits per sample
32    #[cfg(feature = "I212")]
33    I212,
34
35    /// Y, U, and V planes, 4:4:4 sub sampling, 10 bits per sample
36    #[cfg(feature = "I410")]
37    I410,
38
39    /// Y, U, and V planes, 4:4:4 sub sampling, 12 bits per sample
40    #[cfg(feature = "I412")]
41    I412,
42
43    /// Y and interleaved UV planes, 4:2:0 sub sampling, 8 bits per sample
44    #[cfg(feature = "NV12")]
45    NV12,
46
47    /// Y and interleaved UV planes, 4:2:0 sub sampling, 10 bits per sample
48    #[cfg(feature = "P010")]
49    P010,
50
51    /// Y and interleaved UV planes, 4:2:0 sub sampling, 12 bits per sample
52    #[cfg(feature = "P012")]
53    P012,
54
55    /// Single YUYV, 4:2:2 sub sampling
56    #[cfg(feature = "YUYV")]
57    YUYV,
58
59    /// Single RGBA interleaved plane
60    #[cfg(feature = "RGBA")]
61    RGBA,
62
63    /// Single BGRA interleaved plane
64    #[cfg(feature = "BGRA")]
65    BGRA,
66
67    /// Single ARGB interleaved plane
68    #[cfg(feature = "ARGB")]
69    ARGB,
70
71    /// Single ABGR interleaved plane
72    #[cfg(feature = "ABGR")]
73    ABGR,
74
75    /// Single RGB interleaved plane
76    #[cfg(feature = "RGB")]
77    RGB,
78
79    /// Single BGR interleaved plane
80    #[cfg(feature = "BGR")]
81    BGR,
82}
83
84impl PixelFormat {
85    /// Calculate the required buffer size given the [`PixelFormat`] self and image dimensions (in pixel width, height).
86    ///
87    /// The size is the amount of primitives (u8, u16) so when allocating size this must be accounted for.
88    #[deny(clippy::arithmetic_side_effects)]
89    pub fn buffer_size(self, width: usize, height: usize) -> usize {
90        fn buffer_size(planes: &[PlaneDesc], width: usize, height: usize) -> usize {
91            let mut size = 0;
92
93            for plane in planes {
94                let w = plane.width_op.op(width);
95                let h = plane.height_op.op(height);
96
97                size = size.strict_add_(w.strict_mul_(h).strict_mul_(plane.bytes_per_primitive));
98            }
99
100            size
101        }
102
103        buffer_size(self.plane_desc(), width, height)
104    }
105
106    /// Calculate the strides of an image in a packed buffer
107    #[deny(clippy::arithmetic_side_effects)]
108    pub fn packed_strides(self, width: usize) -> Vec<usize> {
109        fn packed_strides(planes: &[PlaneDesc], width: usize) -> Vec<usize> {
110            planes
111                .iter()
112                .map(|desc| desc.packed_stride(width))
113                .collect()
114        }
115
116        packed_strides(self.plane_desc(), width)
117    }
118
119    /// Check if the given planes+strides are valid for dimensions
120    #[deny(clippy::arithmetic_side_effects)]
121    pub fn bounds_check<'a>(
122        self,
123        planes: impl Iterator<Item = (&'a [u8], usize)>,
124        width: usize,
125        height: usize,
126    ) -> Result<(), BoundsCheckError> {
127        use PixelFormat::*;
128
129        fn bounds_check<const N: usize>(
130            planes: [PlaneDesc; N],
131            got: [(&[u8], usize); N],
132            width: usize,
133            height: usize,
134        ) -> Result<(), BoundsCheckError> {
135            for (i, (plane, (slice, stride))) in planes.into_iter().zip(got).enumerate() {
136                // Ensure stride is not smaller than the width would allow
137                let min_stride = plane.packed_stride(width);
138
139                if min_stride > stride {
140                    return Err(BoundsCheckError::InvalidStride {
141                        plane: i,
142                        minimum: min_stride,
143                        got: stride,
144                    });
145                }
146
147                // Ensure slice is large enough
148                let min_len = stride.strict_mul_(plane.height_op.op(height));
149
150                if min_len > slice.len() {
151                    return Err(BoundsCheckError::InvalidPlaneSize {
152                        plane: i,
153                        minimum: min_len,
154                        got: slice.len(),
155                    });
156                }
157            }
158
159            Ok(())
160        }
161
162        match self {
163            #[cfg(feature = "I420")]
164            I420 => bounds_check(I420_PLANES, read_planes(planes)?, width, height),
165            #[cfg(feature = "I422")]
166            I422 => bounds_check(I422_PLANES, read_planes(planes)?, width, height),
167            #[cfg(feature = "I444")]
168            I444 => bounds_check(I444_PLANES, read_planes(planes)?, width, height),
169            #[cfg(feature = "I010")]
170            I010 => bounds_check(I01X_PLANES, read_planes(planes)?, width, height),
171            #[cfg(feature = "I012")]
172            I012 => bounds_check(I01X_PLANES, read_planes(planes)?, width, height),
173            #[cfg(feature = "I210")]
174            I210 => bounds_check(I21X_PLANES, read_planes(planes)?, width, height),
175            #[cfg(feature = "I212")]
176            I212 => bounds_check(I21X_PLANES, read_planes(planes)?, width, height),
177            #[cfg(feature = "I410")]
178            I410 => bounds_check(I41X_PLANES, read_planes(planes)?, width, height),
179            #[cfg(feature = "I412")]
180            I412 => bounds_check(I41X_PLANES, read_planes(planes)?, width, height),
181            #[cfg(feature = "NV12")]
182            NV12 => bounds_check(NV12_PLANES, read_planes(planes)?, width, height),
183            #[cfg(feature = "P010")]
184            P010 => bounds_check(P01X_PLANES, read_planes(planes)?, width, height),
185            #[cfg(feature = "P012")]
186            P012 => bounds_check(P01X_PLANES, read_planes(planes)?, width, height),
187            #[cfg(feature = "YUYV")]
188            YUYV => bounds_check(YUYV_PLANES, read_planes(planes)?, width, height),
189            #[cfg(feature = "RGBA")]
190            RGBA => bounds_check(RGBA_PLANES, read_planes(planes)?, width, height),
191            #[cfg(feature = "BGRA")]
192            BGRA => bounds_check(RGBA_PLANES, read_planes(planes)?, width, height),
193            #[cfg(feature = "ARGB")]
194            ARGB => bounds_check(RGBA_PLANES, read_planes(planes)?, width, height),
195            #[cfg(feature = "ABGR")]
196            ABGR => bounds_check(RGBA_PLANES, read_planes(planes)?, width, height),
197            #[cfg(feature = "RGB")]
198            RGB => bounds_check(RGB_PLANES, read_planes(planes)?, width, height),
199            #[cfg(feature = "BGR")]
200            BGR => bounds_check(RGB_PLANES, read_planes(planes)?, width, height),
201        }
202    }
203
204    pub fn bits_per_component(&self) -> usize {
205        match self {
206            #[cfg(feature = "I420")]
207            PixelFormat::I420 => 8,
208            #[cfg(feature = "I422")]
209            PixelFormat::I422 => 8,
210            #[cfg(feature = "I444")]
211            PixelFormat::I444 => 8,
212            #[cfg(feature = "I010")]
213            PixelFormat::I010 => 10,
214            #[cfg(feature = "I012")]
215            PixelFormat::I012 => 12,
216            #[cfg(feature = "I210")]
217            PixelFormat::I210 => 10,
218            #[cfg(feature = "I212")]
219            PixelFormat::I212 => 12,
220            #[cfg(feature = "I410")]
221            PixelFormat::I410 => 10,
222            #[cfg(feature = "I412")]
223            PixelFormat::I412 => 12,
224            #[cfg(feature = "NV12")]
225            PixelFormat::NV12 => 8,
226            #[cfg(feature = "P010")]
227            PixelFormat::P010 => 10,
228            #[cfg(feature = "P012")]
229            PixelFormat::P012 => 12,
230            #[cfg(feature = "YUYV")]
231            PixelFormat::YUYV => 8,
232            #[cfg(feature = "RGBA")]
233            PixelFormat::RGBA => 8,
234            #[cfg(feature = "BGRA")]
235            PixelFormat::BGRA => 8,
236            #[cfg(feature = "ARGB")]
237            PixelFormat::ARGB => 8,
238            #[cfg(feature = "ABGR")]
239            PixelFormat::ABGR => 8,
240            #[cfg(feature = "RGB")]
241            PixelFormat::RGB => 8,
242            #[cfg(feature = "BGR")]
243            PixelFormat::BGR => 8,
244        }
245    }
246
247    pub(crate) fn plane_desc(&self) -> &'static [PlaneDesc] {
248        use PixelFormat::*;
249
250        match self {
251            #[cfg(feature = "I420")]
252            I420 => &I420_PLANES,
253            #[cfg(feature = "I422")]
254            I422 => &I422_PLANES,
255            #[cfg(feature = "I444")]
256            I444 => &I444_PLANES,
257            #[cfg(feature = "I010")]
258            I010 => &I01X_PLANES,
259            #[cfg(feature = "I012")]
260            I012 => &I01X_PLANES,
261            #[cfg(feature = "I210")]
262            I210 => &I21X_PLANES,
263            #[cfg(feature = "I212")]
264            I212 => &I21X_PLANES,
265            #[cfg(feature = "I410")]
266            I410 => &I41X_PLANES,
267            #[cfg(feature = "I412")]
268            I412 => &I41X_PLANES,
269            #[cfg(feature = "NV12")]
270            NV12 => &NV12_PLANES,
271            #[cfg(feature = "P010")]
272            P010 => &P01X_PLANES,
273            #[cfg(feature = "P012")]
274            P012 => &P01X_PLANES,
275            #[cfg(feature = "YUYV")]
276            YUYV => &YUYV_PLANES,
277            #[cfg(feature = "RGBA")]
278            RGBA => &RGBA_PLANES,
279            #[cfg(feature = "BGRA")]
280            BGRA => &RGBA_PLANES,
281            #[cfg(feature = "ARGB")]
282            ARGB => &RGBA_PLANES,
283            #[cfg(feature = "ABGR")]
284            ABGR => &RGBA_PLANES,
285            #[cfg(feature = "RGB")]
286            RGB => &RGB_PLANES,
287            #[cfg(feature = "BGR")]
288            BGR => &RGB_PLANES,
289        }
290    }
291
292    pub fn variants() -> impl IntoIterator<Item = Self> {
293        use PixelFormat::*;
294
295        [
296            #[cfg(feature = "I420")]
297            I420,
298            #[cfg(feature = "I422")]
299            I422,
300            #[cfg(feature = "I444")]
301            I444,
302            #[cfg(feature = "I010")]
303            I010,
304            #[cfg(feature = "I012")]
305            I012,
306            #[cfg(feature = "I210")]
307            I210,
308            #[cfg(feature = "I212")]
309            I212,
310            #[cfg(feature = "I410")]
311            I410,
312            #[cfg(feature = "I412")]
313            I412,
314            #[cfg(feature = "NV12")]
315            NV12,
316            #[cfg(feature = "P010")]
317            P010,
318            #[cfg(feature = "P012")]
319            P012,
320            #[cfg(feature = "YUYV")]
321            YUYV,
322            #[cfg(feature = "RGBA")]
323            RGBA,
324            #[cfg(feature = "BGRA")]
325            BGRA,
326            #[cfg(feature = "ARGB")]
327            ARGB,
328            #[cfg(feature = "ABGR")]
329            ABGR,
330            #[cfg(feature = "RGB")]
331            RGB,
332            #[cfg(feature = "BGR")]
333            BGR,
334        ]
335    }
336}
337
338#[derive(Debug, thiserror::Error)]
339pub enum BoundsCheckError {
340    #[error(transparent)]
341    InvalidNumberOfPlanes(#[from] InvalidNumberOfPlanesError),
342
343    #[error("invalid stride at plane {plane}, expected it to be at least {minimum}, but got {got}")]
344    InvalidStride {
345        plane: usize,
346        minimum: usize,
347        got: usize,
348    },
349
350    #[error(
351        "invalid plane size at plane {plane}, expected it to be at least {minimum}, but got {got}"
352    )]
353    InvalidPlaneSize {
354        plane: usize,
355        minimum: usize,
356        got: usize,
357    },
358}