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}