pixelfmt/
lib.rs

1// Copyright (C) 2024 Infinite Athlete, Inc. <av-eng@infiniteathlete.ai>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Pixel format conversions.
5
6#[doc(hidden)] // `pub` only for benchmarks.
7pub mod uyvy_to_i420;
8
9pub mod frame;
10
11pub use uyvy_to_i420::convert as convert_uyvy_to_i420;
12
13/// Re-export of the `arrayvec` version used by this crate.
14///
15/// [`arrayvec::ArrayVec`] is exposed in e.g. [`crate::frame::Frame::planes`], and callers may
16/// wish to use matching types.
17pub use arrayvec;
18
19/// The maximum number of image planes defined by any supported [`PixelFormat`].
20pub const MAX_PLANES: usize = 3;
21
22/// Error type for pixel format conversions.
23#[derive(Clone, Debug)]
24pub struct ConversionError(&'static str);
25
26impl std::fmt::Display for ConversionError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        self.0.fmt(f)
29    }
30}
31
32impl std::error::Error for ConversionError {}
33
34/// Pixel format: layout of pixels in memory, defining the number/meaning
35/// of planes including the size of each sample in bits.
36///
37/// YUV color ranges (e.g. full/JPEG vs limited/MPEG) are not defined here.
38#[derive(Copy, Clone, Debug, PartialEq, Eq)]
39pub enum PixelFormat {
40    /// [UYVY](https://fourcc.org/pixel-format/yuv-uyvy/).
41    ///
42    /// Matches ffmpeg's `AV_PIX_FMT_UYVY422`: "packed YUV 4:2:2, 16bpp, Cb Y0 Cr Y1".
43    ///
44    /// For odd-width images, the width is rounded up to the next multiple of 2,
45    /// with the final `Y` as a don't-care byte, and the final chroma values not
46    /// subsampled.
47    UYVY422,
48
49    /// [I420](https://fourcc.org/pixel-format/yuv-i420/).
50    ///
51    /// Matches ffmpeg's `AV_PIX_FMT_YUV420P`: "planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)".
52    ///
53    /// For odd-width and odd-height images, the final pixel is not subsampled.
54    I420,
55
56    /// BGRA.
57    ///
58    /// Matches ffmpeg's `AV_PIX_FMT_BGRA`: "packed BGRA 8:8:8:8, 32bpp, BGRABGRA...".
59    BGRA,
60}
61
62/// Dimensions of a particular image plane.
63#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
64pub struct PlaneDims {
65    /// The stride for a row, in bytes. This may include extra padding.
66    pub stride: usize,
67
68    /// The number of rows. This often matches the image height, but some
69    /// chroma planes may be subsampled.
70    pub rows: usize,
71}
72
73impl PixelFormat {
74    /// Returns the number of planes for this format.
75    #[inline]
76    pub fn num_planes(self) -> usize {
77        match self {
78            PixelFormat::UYVY422 => 1,
79            PixelFormat::I420 => 3,
80            PixelFormat::BGRA => 1,
81        }
82    }
83
84    /// Returns the plane dimensions at minimum stride (no extra bytes for padding).
85    pub fn min_plane_dims(self, width: usize, height: usize) -> impl Iterator<Item = PlaneDims> {
86        let mut sizes = arrayvec::ArrayVec::<PlaneDims, MAX_PLANES>::new();
87        match self {
88            PixelFormat::UYVY422 => {
89                sizes.push(PlaneDims {
90                    // Round to next multiple of 2, then double.
91                    stride: width
92                        .checked_add(width & 1)
93                        .and_then(|w| w.checked_shl(1))
94                        .expect("stride should not overflow"),
95                    rows: height,
96                });
97            }
98            PixelFormat::I420 => {
99                sizes.push(PlaneDims {
100                    // Y plane.
101                    stride: width,
102                    rows: height,
103                });
104                // U/V planes.
105                let chroma_plane_size = PlaneDims {
106                    // Overflow-safe divide by two that rounds up.
107                    stride: (width >> 1) + (width & 1),
108                    rows: (height >> 1) + (height & 1),
109                };
110                sizes.push(chroma_plane_size);
111                sizes.push(chroma_plane_size);
112            }
113            PixelFormat::BGRA => {
114                sizes.push(PlaneDims {
115                    stride: width.checked_shl(2).expect("stride should not overflow"),
116                    rows: height,
117                });
118            }
119        }
120        debug_assert_eq!(sizes.len(), self.num_planes());
121        sizes.into_iter()
122    }
123
124    /// Returns human-readable names of the planes for this format.
125    pub fn plane_names(self) -> &'static [&'static str] {
126        match self {
127            PixelFormat::UYVY422 => &["YUYV"],
128            PixelFormat::I420 => &["Y", "U", "V"],
129            PixelFormat::BGRA => &["BGRA"],
130        }
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    #[test]
137    fn odd_sizes() {
138        assert_eq!(
139            super::PixelFormat::UYVY422
140                .min_plane_dims(1, 1)
141                .collect::<Vec<_>>(),
142            vec![super::PlaneDims { stride: 4, rows: 1 }]
143        );
144        assert_eq!(
145            super::PixelFormat::UYVY422
146                .min_plane_dims(2, 2)
147                .collect::<Vec<_>>(),
148            vec![super::PlaneDims { stride: 4, rows: 2 }]
149        );
150        assert_eq!(
151            super::PixelFormat::UYVY422
152                .min_plane_dims(3, 3)
153                .collect::<Vec<_>>(),
154            vec![super::PlaneDims { stride: 8, rows: 3 }]
155        );
156        assert_eq!(
157            super::PixelFormat::I420
158                .min_plane_dims(1, 1)
159                .collect::<Vec<_>>(),
160            vec![
161                super::PlaneDims { stride: 1, rows: 1 },
162                super::PlaneDims { stride: 1, rows: 1 },
163                super::PlaneDims { stride: 1, rows: 1 }
164            ]
165        );
166        assert_eq!(
167            super::PixelFormat::I420
168                .min_plane_dims(2, 2)
169                .collect::<Vec<_>>(),
170            vec![
171                super::PlaneDims { stride: 2, rows: 2 },
172                super::PlaneDims { stride: 1, rows: 1 },
173                super::PlaneDims { stride: 1, rows: 1 }
174            ]
175        );
176        assert_eq!(
177            super::PixelFormat::I420
178                .min_plane_dims(3, 3)
179                .collect::<Vec<_>>(),
180            vec![
181                super::PlaneDims { stride: 3, rows: 3 },
182                super::PlaneDims { stride: 2, rows: 2 },
183                super::PlaneDims { stride: 2, rows: 2 }
184            ]
185        );
186    }
187}