Skip to main content

ff_format/
pixel.rs

1//! Pixel format definitions for video processing.
2//!
3//! This module provides the [`PixelFormat`] enum which represents various
4//! pixel formats used in video processing. It supports both packed (RGB/BGRA)
5//! and planar (YUV) formats commonly used in video editing.
6//!
7//! # Examples
8//!
9//! ```
10//! use ff_format::PixelFormat;
11//!
12//! let format = PixelFormat::Yuv420p;
13//! assert!(format.is_planar());
14//! assert!(!format.is_packed());
15//! assert_eq!(format.num_planes(), 3);
16//!
17//! let rgba = PixelFormat::Rgba;
18//! assert!(rgba.has_alpha());
19//! assert_eq!(rgba.bits_per_pixel(), Some(32));
20//! ```
21
22use std::fmt;
23
24/// Pixel format for video frames.
25///
26/// This enum represents various pixel formats used in video processing.
27/// It is designed to cover the most common formats used in video editing
28/// while remaining extensible via the `Other` variant.
29///
30/// # Format Categories
31///
32/// - **Packed RGB**: Data stored contiguously (Rgb24, Rgba, Bgr24, Bgra)
33/// - **Planar YUV**: Separate planes for Y, U, V components (Yuv420p, Yuv422p, Yuv444p)
34/// - **Semi-planar**: Y plane + interleaved UV (Nv12, Nv21)
35/// - **High bit depth**: 10-bit formats for HDR content (Yuv420p10le, P010le)
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37#[non_exhaustive]
38pub enum PixelFormat {
39    // Packed RGB
40    /// 24-bit RGB (8:8:8) - 3 bytes per pixel
41    Rgb24,
42    /// 32-bit RGBA (8:8:8:8) - 4 bytes per pixel with alpha
43    Rgba,
44    /// 24-bit BGR (8:8:8) - 3 bytes per pixel, reversed channel order
45    Bgr24,
46    /// 32-bit BGRA (8:8:8:8) - 4 bytes per pixel with alpha, reversed channel order
47    Bgra,
48
49    // Planar YUV
50    /// YUV 4:2:0 planar - most common video format (H.264, etc.)
51    Yuv420p,
52    /// YUV 4:2:2 planar - higher chroma resolution
53    Yuv422p,
54    /// YUV 4:4:4 planar - full chroma resolution
55    Yuv444p,
56
57    // Semi-planar (NV12/NV21)
58    /// Y plane + interleaved UV - common in hardware decoders
59    Nv12,
60    /// Y plane + interleaved VU - Android camera format
61    Nv21,
62
63    // High bit depth
64    /// 10-bit YUV 4:2:0 planar - HDR content
65    Yuv420p10le,
66    /// 10-bit semi-planar NV12 - HDR hardware decoding
67    P010le,
68
69    // Grayscale
70    /// 8-bit grayscale
71    Gray8,
72
73    // Extensibility
74    /// Unknown or unsupported format with `FFmpeg`'s `AVPixelFormat` value
75    Other(u32),
76}
77
78impl PixelFormat {
79    /// Returns the format name as a human-readable string.
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// use ff_format::PixelFormat;
85    ///
86    /// assert_eq!(PixelFormat::Yuv420p.name(), "yuv420p");
87    /// assert_eq!(PixelFormat::Rgba.name(), "rgba");
88    /// ```
89    #[must_use]
90    pub const fn name(&self) -> &'static str {
91        match self {
92            Self::Rgb24 => "rgb24",
93            Self::Rgba => "rgba",
94            Self::Bgr24 => "bgr24",
95            Self::Bgra => "bgra",
96            Self::Yuv420p => "yuv420p",
97            Self::Yuv422p => "yuv422p",
98            Self::Yuv444p => "yuv444p",
99            Self::Nv12 => "nv12",
100            Self::Nv21 => "nv21",
101            Self::Yuv420p10le => "yuv420p10le",
102            Self::P010le => "p010le",
103            Self::Gray8 => "gray8",
104            Self::Other(_) => "unknown",
105        }
106    }
107
108    /// Returns the number of planes for this format.
109    ///
110    /// - Packed formats (RGB, RGBA, etc.) have 1 plane
111    /// - Planar YUV formats have 3 planes (Y, U, V)
112    /// - Semi-planar formats (NV12, NV21) have 2 planes (Y, UV)
113    /// - Grayscale has 1 plane
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use ff_format::PixelFormat;
119    ///
120    /// assert_eq!(PixelFormat::Rgba.num_planes(), 1);
121    /// assert_eq!(PixelFormat::Yuv420p.num_planes(), 3);
122    /// assert_eq!(PixelFormat::Nv12.num_planes(), 2);
123    /// ```
124    #[must_use]
125    pub const fn num_planes(&self) -> usize {
126        match self {
127            // Planar YUV - Y, U, V planes
128            Self::Yuv420p | Self::Yuv422p | Self::Yuv444p | Self::Yuv420p10le => 3,
129            // Semi-planar - Y plane + interleaved UV plane
130            Self::Nv12 | Self::Nv21 | Self::P010le => 2,
131            // Packed formats and unknown - single plane
132            Self::Rgb24 | Self::Rgba | Self::Bgr24 | Self::Bgra | Self::Gray8 | Self::Other(_) => 1,
133        }
134    }
135
136    /// Alias for [`num_planes`](Self::num_planes) for API compatibility.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use ff_format::PixelFormat;
142    ///
143    /// assert_eq!(PixelFormat::Yuv420p.plane_count(), 3);
144    /// ```
145    #[must_use]
146    #[inline]
147    pub const fn plane_count(&self) -> usize {
148        self.num_planes()
149    }
150
151    /// Returns `true` if this is a packed format (single plane with interleaved components).
152    ///
153    /// Packed formats store all color components contiguously in memory,
154    /// making them suitable for direct rendering but less efficient for
155    /// video compression.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use ff_format::PixelFormat;
161    ///
162    /// assert!(PixelFormat::Rgba.is_packed());
163    /// assert!(!PixelFormat::Yuv420p.is_packed());
164    /// ```
165    #[must_use]
166    pub const fn is_packed(&self) -> bool {
167        matches!(
168            self,
169            Self::Rgb24 | Self::Rgba | Self::Bgr24 | Self::Bgra | Self::Gray8
170        )
171    }
172
173    /// Returns `true` if this is a planar format (separate planes for each component).
174    ///
175    /// Planar formats store each color component in a separate memory region,
176    /// which is more efficient for video codecs and some GPU operations.
177    ///
178    /// Note: Semi-planar formats (NV12, NV21, P010le) are considered planar
179    /// as they have multiple planes, even though UV is interleaved.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use ff_format::PixelFormat;
185    ///
186    /// assert!(PixelFormat::Yuv420p.is_planar());
187    /// assert!(PixelFormat::Nv12.is_planar());  // Semi-planar is also planar
188    /// assert!(!PixelFormat::Rgba.is_planar());
189    /// ```
190    #[must_use]
191    pub const fn is_planar(&self) -> bool {
192        !self.is_packed()
193    }
194
195    /// Returns `true` if this format has an alpha (transparency) channel.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use ff_format::PixelFormat;
201    ///
202    /// assert!(PixelFormat::Rgba.has_alpha());
203    /// assert!(PixelFormat::Bgra.has_alpha());
204    /// assert!(!PixelFormat::Rgb24.has_alpha());
205    /// assert!(!PixelFormat::Yuv420p.has_alpha());
206    /// ```
207    #[must_use]
208    pub const fn has_alpha(&self) -> bool {
209        matches!(self, Self::Rgba | Self::Bgra)
210    }
211
212    /// Returns `true` if this is an RGB-based format.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use ff_format::PixelFormat;
218    ///
219    /// assert!(PixelFormat::Rgb24.is_rgb());
220    /// assert!(PixelFormat::Rgba.is_rgb());
221    /// assert!(PixelFormat::Bgra.is_rgb());  // BGR is still RGB family
222    /// assert!(!PixelFormat::Yuv420p.is_rgb());
223    /// ```
224    #[must_use]
225    pub const fn is_rgb(&self) -> bool {
226        matches!(self, Self::Rgb24 | Self::Rgba | Self::Bgr24 | Self::Bgra)
227    }
228
229    /// Returns `true` if this is a YUV-based format.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use ff_format::PixelFormat;
235    ///
236    /// assert!(PixelFormat::Yuv420p.is_yuv());
237    /// assert!(PixelFormat::Nv12.is_yuv());
238    /// assert!(!PixelFormat::Rgba.is_yuv());
239    /// ```
240    #[must_use]
241    pub const fn is_yuv(&self) -> bool {
242        matches!(
243            self,
244            Self::Yuv420p
245                | Self::Yuv422p
246                | Self::Yuv444p
247                | Self::Nv12
248                | Self::Nv21
249                | Self::Yuv420p10le
250                | Self::P010le
251        )
252    }
253
254    /// Returns the bits per pixel for packed formats.
255    ///
256    /// For planar formats, this returns `None` because the concept of
257    /// "bits per pixel" doesn't apply directly - use [`bytes_per_pixel`](Self::bytes_per_pixel)
258    /// to get the average bytes per pixel instead.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use ff_format::PixelFormat;
264    ///
265    /// assert_eq!(PixelFormat::Rgb24.bits_per_pixel(), Some(24));
266    /// assert_eq!(PixelFormat::Rgba.bits_per_pixel(), Some(32));
267    /// assert_eq!(PixelFormat::Yuv420p.bits_per_pixel(), None);
268    /// ```
269    #[must_use]
270    pub const fn bits_per_pixel(&self) -> Option<usize> {
271        match self {
272            Self::Rgb24 | Self::Bgr24 => Some(24),
273            Self::Rgba | Self::Bgra => Some(32),
274            Self::Gray8 => Some(8),
275            // Planar formats don't have a simple bits-per-pixel value
276            _ => None,
277        }
278    }
279
280    /// Returns the average bytes per pixel.
281    ///
282    /// For packed formats, this is exact. For planar YUV formats, this
283    /// returns the average considering subsampling:
284    /// - YUV 4:2:0: 1.5 bytes/pixel (12 bits)
285    /// - YUV 4:2:2: 2 bytes/pixel (16 bits)
286    /// - YUV 4:4:4: 3 bytes/pixel (24 bits)
287    ///
288    /// Note: For formats with non-integer bytes per pixel (like `Yuv420p`),
289    /// this rounds up to the nearest byte.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use ff_format::PixelFormat;
295    ///
296    /// assert_eq!(PixelFormat::Rgba.bytes_per_pixel(), 4);
297    /// assert_eq!(PixelFormat::Rgb24.bytes_per_pixel(), 3);
298    /// assert_eq!(PixelFormat::Yuv420p.bytes_per_pixel(), 2);  // Actually 1.5, rounded up
299    /// assert_eq!(PixelFormat::Yuv444p.bytes_per_pixel(), 3);
300    /// ```
301    #[must_use]
302    pub const fn bytes_per_pixel(&self) -> usize {
303        match self {
304            // Grayscale - 1 byte per pixel
305            Self::Gray8 => 1,
306
307            // YUV 4:2:0 (8-bit and 10-bit) and YUV 4:2:2 - average ~2 bytes per pixel
308            Self::Yuv420p
309            | Self::Nv12
310            | Self::Nv21
311            | Self::Yuv420p10le
312            | Self::P010le
313            | Self::Yuv422p => 2,
314
315            // RGB24/BGR24 and YUV 4:4:4 - 3 bytes per pixel
316            Self::Rgb24 | Self::Bgr24 | Self::Yuv444p => 3,
317
318            // RGBA/BGRA and unknown formats - 4 bytes per pixel
319            Self::Rgba | Self::Bgra | Self::Other(_) => 4,
320        }
321    }
322
323    /// Returns `true` if this is a high bit depth format (> 8 bits per component).
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use ff_format::PixelFormat;
329    ///
330    /// assert!(PixelFormat::Yuv420p10le.is_high_bit_depth());
331    /// assert!(PixelFormat::P010le.is_high_bit_depth());
332    /// assert!(!PixelFormat::Yuv420p.is_high_bit_depth());
333    /// ```
334    #[must_use]
335    pub const fn is_high_bit_depth(&self) -> bool {
336        matches!(self, Self::Yuv420p10le | Self::P010le)
337    }
338
339    /// Returns the bit depth per component.
340    ///
341    /// Most formats use 8 bits per component, while high bit depth
342    /// formats use 10 bits.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use ff_format::PixelFormat;
348    ///
349    /// assert_eq!(PixelFormat::Rgba.bit_depth(), 8);
350    /// assert_eq!(PixelFormat::Yuv420p10le.bit_depth(), 10);
351    /// ```
352    #[must_use]
353    pub const fn bit_depth(&self) -> usize {
354        match self {
355            Self::Yuv420p10le | Self::P010le => 10,
356            // All other formats including unknown are 8-bit
357            _ => 8,
358        }
359    }
360}
361
362impl fmt::Display for PixelFormat {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        write!(f, "{}", self.name())
365    }
366}
367
368impl Default for PixelFormat {
369    /// Returns the default pixel format.
370    ///
371    /// The default is [`PixelFormat::Yuv420p`] as it's the most common
372    /// format used in video encoding.
373    fn default() -> Self {
374        Self::Yuv420p
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_format_names() {
384        assert_eq!(PixelFormat::Rgb24.name(), "rgb24");
385        assert_eq!(PixelFormat::Rgba.name(), "rgba");
386        assert_eq!(PixelFormat::Bgr24.name(), "bgr24");
387        assert_eq!(PixelFormat::Bgra.name(), "bgra");
388        assert_eq!(PixelFormat::Yuv420p.name(), "yuv420p");
389        assert_eq!(PixelFormat::Yuv422p.name(), "yuv422p");
390        assert_eq!(PixelFormat::Yuv444p.name(), "yuv444p");
391        assert_eq!(PixelFormat::Nv12.name(), "nv12");
392        assert_eq!(PixelFormat::Nv21.name(), "nv21");
393        assert_eq!(PixelFormat::Yuv420p10le.name(), "yuv420p10le");
394        assert_eq!(PixelFormat::P010le.name(), "p010le");
395        assert_eq!(PixelFormat::Gray8.name(), "gray8");
396        assert_eq!(PixelFormat::Other(999).name(), "unknown");
397    }
398
399    #[test]
400    fn test_plane_count() {
401        // Packed formats - 1 plane
402        assert_eq!(PixelFormat::Rgb24.num_planes(), 1);
403        assert_eq!(PixelFormat::Rgba.num_planes(), 1);
404        assert_eq!(PixelFormat::Bgr24.num_planes(), 1);
405        assert_eq!(PixelFormat::Bgra.num_planes(), 1);
406        assert_eq!(PixelFormat::Gray8.num_planes(), 1);
407
408        // Planar YUV - 3 planes
409        assert_eq!(PixelFormat::Yuv420p.num_planes(), 3);
410        assert_eq!(PixelFormat::Yuv422p.num_planes(), 3);
411        assert_eq!(PixelFormat::Yuv444p.num_planes(), 3);
412        assert_eq!(PixelFormat::Yuv420p10le.num_planes(), 3);
413
414        // Semi-planar - 2 planes
415        assert_eq!(PixelFormat::Nv12.num_planes(), 2);
416        assert_eq!(PixelFormat::Nv21.num_planes(), 2);
417        assert_eq!(PixelFormat::P010le.num_planes(), 2);
418
419        // plane_count is alias for num_planes
420        assert_eq!(PixelFormat::Yuv420p.plane_count(), 3);
421    }
422
423    #[test]
424    fn test_packed_vs_planar() {
425        // Packed formats
426        assert!(PixelFormat::Rgb24.is_packed());
427        assert!(PixelFormat::Rgba.is_packed());
428        assert!(PixelFormat::Bgr24.is_packed());
429        assert!(PixelFormat::Bgra.is_packed());
430        assert!(PixelFormat::Gray8.is_packed());
431        assert!(!PixelFormat::Rgb24.is_planar());
432
433        // Planar formats
434        assert!(PixelFormat::Yuv420p.is_planar());
435        assert!(PixelFormat::Yuv422p.is_planar());
436        assert!(PixelFormat::Yuv444p.is_planar());
437        assert!(PixelFormat::Nv12.is_planar());
438        assert!(PixelFormat::Nv21.is_planar());
439        assert!(!PixelFormat::Yuv420p.is_packed());
440    }
441
442    #[test]
443    fn test_has_alpha() {
444        assert!(PixelFormat::Rgba.has_alpha());
445        assert!(PixelFormat::Bgra.has_alpha());
446        assert!(!PixelFormat::Rgb24.has_alpha());
447        assert!(!PixelFormat::Bgr24.has_alpha());
448        assert!(!PixelFormat::Yuv420p.has_alpha());
449        assert!(!PixelFormat::Gray8.has_alpha());
450    }
451
452    #[test]
453    fn test_is_rgb() {
454        assert!(PixelFormat::Rgb24.is_rgb());
455        assert!(PixelFormat::Rgba.is_rgb());
456        assert!(PixelFormat::Bgr24.is_rgb());
457        assert!(PixelFormat::Bgra.is_rgb());
458        assert!(!PixelFormat::Yuv420p.is_rgb());
459        assert!(!PixelFormat::Nv12.is_rgb());
460        assert!(!PixelFormat::Gray8.is_rgb());
461    }
462
463    #[test]
464    fn test_is_yuv() {
465        assert!(PixelFormat::Yuv420p.is_yuv());
466        assert!(PixelFormat::Yuv422p.is_yuv());
467        assert!(PixelFormat::Yuv444p.is_yuv());
468        assert!(PixelFormat::Nv12.is_yuv());
469        assert!(PixelFormat::Nv21.is_yuv());
470        assert!(PixelFormat::Yuv420p10le.is_yuv());
471        assert!(PixelFormat::P010le.is_yuv());
472        assert!(!PixelFormat::Rgb24.is_yuv());
473        assert!(!PixelFormat::Rgba.is_yuv());
474        assert!(!PixelFormat::Gray8.is_yuv());
475    }
476
477    #[test]
478    fn test_bits_per_pixel() {
479        // Packed formats have defined bits per pixel
480        assert_eq!(PixelFormat::Rgb24.bits_per_pixel(), Some(24));
481        assert_eq!(PixelFormat::Bgr24.bits_per_pixel(), Some(24));
482        assert_eq!(PixelFormat::Rgba.bits_per_pixel(), Some(32));
483        assert_eq!(PixelFormat::Bgra.bits_per_pixel(), Some(32));
484        assert_eq!(PixelFormat::Gray8.bits_per_pixel(), Some(8));
485
486        // Planar formats don't have simple bits per pixel
487        assert_eq!(PixelFormat::Yuv420p.bits_per_pixel(), None);
488        assert_eq!(PixelFormat::Nv12.bits_per_pixel(), None);
489    }
490
491    #[test]
492    fn test_bytes_per_pixel() {
493        // Packed formats
494        assert_eq!(PixelFormat::Rgb24.bytes_per_pixel(), 3);
495        assert_eq!(PixelFormat::Bgr24.bytes_per_pixel(), 3);
496        assert_eq!(PixelFormat::Rgba.bytes_per_pixel(), 4);
497        assert_eq!(PixelFormat::Bgra.bytes_per_pixel(), 4);
498        assert_eq!(PixelFormat::Gray8.bytes_per_pixel(), 1);
499
500        // YUV 4:2:0 - 1.5 bytes average, rounded to 2
501        assert_eq!(PixelFormat::Yuv420p.bytes_per_pixel(), 2);
502        assert_eq!(PixelFormat::Nv12.bytes_per_pixel(), 2);
503        assert_eq!(PixelFormat::Nv21.bytes_per_pixel(), 2);
504
505        // YUV 4:2:2 - 2 bytes
506        assert_eq!(PixelFormat::Yuv422p.bytes_per_pixel(), 2);
507
508        // YUV 4:4:4 - 3 bytes
509        assert_eq!(PixelFormat::Yuv444p.bytes_per_pixel(), 3);
510
511        // High bit depth
512        assert_eq!(PixelFormat::Yuv420p10le.bytes_per_pixel(), 2);
513        assert_eq!(PixelFormat::P010le.bytes_per_pixel(), 2);
514    }
515
516    #[test]
517    fn test_high_bit_depth() {
518        assert!(PixelFormat::Yuv420p10le.is_high_bit_depth());
519        assert!(PixelFormat::P010le.is_high_bit_depth());
520        assert!(!PixelFormat::Yuv420p.is_high_bit_depth());
521        assert!(!PixelFormat::Rgba.is_high_bit_depth());
522    }
523
524    #[test]
525    fn test_bit_depth() {
526        assert_eq!(PixelFormat::Rgba.bit_depth(), 8);
527        assert_eq!(PixelFormat::Yuv420p.bit_depth(), 8);
528        assert_eq!(PixelFormat::Yuv420p10le.bit_depth(), 10);
529        assert_eq!(PixelFormat::P010le.bit_depth(), 10);
530    }
531
532    #[test]
533    fn test_display() {
534        assert_eq!(format!("{}", PixelFormat::Yuv420p), "yuv420p");
535        assert_eq!(format!("{}", PixelFormat::Rgba), "rgba");
536        assert_eq!(format!("{}", PixelFormat::Other(123)), "unknown");
537    }
538
539    #[test]
540    fn test_default() {
541        assert_eq!(PixelFormat::default(), PixelFormat::Yuv420p);
542    }
543
544    #[test]
545    fn test_debug() {
546        assert_eq!(format!("{:?}", PixelFormat::Rgba), "Rgba");
547        assert_eq!(format!("{:?}", PixelFormat::Yuv420p), "Yuv420p");
548        assert_eq!(format!("{:?}", PixelFormat::Other(42)), "Other(42)");
549    }
550
551    #[test]
552    fn test_equality_and_hash() {
553        use std::collections::HashSet;
554
555        assert_eq!(PixelFormat::Rgba, PixelFormat::Rgba);
556        assert_ne!(PixelFormat::Rgba, PixelFormat::Bgra);
557        assert_eq!(PixelFormat::Other(1), PixelFormat::Other(1));
558        assert_ne!(PixelFormat::Other(1), PixelFormat::Other(2));
559
560        // Test Hash implementation
561        let mut set = HashSet::new();
562        set.insert(PixelFormat::Rgba);
563        set.insert(PixelFormat::Yuv420p);
564        assert!(set.contains(&PixelFormat::Rgba));
565        assert!(!set.contains(&PixelFormat::Bgra));
566    }
567
568    #[test]
569    fn test_copy() {
570        let format = PixelFormat::Yuv420p;
571        let copied = format;
572        // Both original and copy are still usable (Copy semantics)
573        assert_eq!(format, copied);
574        assert_eq!(format.name(), copied.name());
575    }
576}