gstreamer_video/
video_format_info.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{cmp::Ordering, fmt, marker::PhantomData, str};
4
5use crate::ffi;
6use glib::translate::{from_glib, IntoGlib, ToGlibPtr};
7
8/// Information for a video format.
9#[doc(alias = "GstVideoFormatInfo")]
10#[derive(Copy, Clone)]
11pub struct VideoFormatInfo(&'static ffi::GstVideoFormatInfo);
12
13impl VideoFormatInfo {
14    #[inline]
15    pub unsafe fn from_ptr(format_info: *const ffi::GstVideoFormatInfo) -> Self {
16        debug_assert!(!format_info.is_null());
17        Self(&*format_info)
18    }
19
20    #[inline]
21    pub fn from_format(format: crate::VideoFormat) -> Self {
22        assert_initialized_main_thread!();
23        unsafe {
24            let info = ffi::gst_video_format_get_info(format.into_glib());
25            debug_assert!(!info.is_null());
26
27            Self(&*info)
28        }
29    }
30
31    #[inline]
32    pub fn format(&self) -> crate::VideoFormat {
33        unsafe { from_glib(self.0.format) }
34    }
35
36    #[inline]
37    pub fn name<'a>(&self) -> &'a glib::GStr {
38        unsafe { glib::GStr::from_ptr(self.0.name) }
39    }
40
41    #[inline]
42    pub fn description<'a>(&self) -> &'a glib::GStr {
43        unsafe { glib::GStr::from_ptr(self.0.description) }
44    }
45
46    #[inline]
47    pub fn flags(&self) -> crate::VideoFormatFlags {
48        unsafe { from_glib(self.0.flags) }
49    }
50
51    #[inline]
52    pub fn bits(&self) -> u32 {
53        self.0.bits
54    }
55
56    #[inline]
57    pub fn n_components(&self) -> u32 {
58        self.0.n_components
59    }
60
61    #[inline]
62    pub fn shift(&self) -> &[u32] {
63        &self.0.shift[0..(self.0.n_components as usize)]
64    }
65
66    #[inline]
67    pub fn depth(&self) -> &[u32] {
68        &self.0.depth[0..(self.0.n_components as usize)]
69    }
70
71    #[inline]
72    pub fn pixel_stride(&self) -> &[i32] {
73        &self.0.pixel_stride[0..(self.0.n_components as usize)]
74    }
75
76    #[inline]
77    pub fn n_planes(&self) -> u32 {
78        self.0.n_planes
79    }
80
81    #[inline]
82    pub fn plane(&self) -> &[u32] {
83        &self.0.plane[0..(self.0.n_components as usize)]
84    }
85
86    #[inline]
87    pub fn poffset(&self) -> &[u32] {
88        &self.0.poffset[0..(self.0.n_components as usize)]
89    }
90
91    #[inline]
92    pub fn w_sub(&self) -> &[u32] {
93        &self.0.w_sub[0..(self.0.n_components as usize)]
94    }
95
96    #[inline]
97    pub fn h_sub(&self) -> &[u32] {
98        &self.0.h_sub[0..(self.0.n_components as usize)]
99    }
100
101    #[inline]
102    pub fn tile_mode(&self) -> crate::VideoTileMode {
103        unsafe { from_glib(self.0.tile_mode) }
104    }
105
106    #[cfg_attr(feature = "v1_22", deprecated = "Since 1.22")]
107    #[inline]
108    pub fn tile_ws(&self) -> u32 {
109        self.0.tile_ws
110    }
111
112    #[cfg_attr(feature = "v1_22", deprecated = "Since 1.22")]
113    #[inline]
114    pub fn tile_hs(&self) -> u32 {
115        self.0.tile_hs
116    }
117
118    #[inline]
119    pub fn unpack_format(&self) -> crate::VideoFormat {
120        unsafe { from_glib(self.0.unpack_format) }
121    }
122
123    #[inline]
124    pub fn pack_lines(&self) -> i32 {
125        self.0.pack_lines
126    }
127
128    #[inline]
129    pub fn has_alpha(&self) -> bool {
130        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_ALPHA != 0
131    }
132
133    #[inline]
134    pub fn has_palette(&self) -> bool {
135        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_PALETTE != 0
136    }
137
138    #[cfg(feature = "v1_22")]
139    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
140    #[inline]
141    pub fn has_subtiles(&self) -> bool {
142        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_SUBTILES != 0
143    }
144
145    #[inline]
146    pub fn is_complex(&self) -> bool {
147        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_COMPLEX != 0
148    }
149
150    #[inline]
151    pub fn is_gray(&self) -> bool {
152        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_GRAY != 0
153    }
154
155    #[inline]
156    pub fn is_le(&self) -> bool {
157        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_LE != 0
158    }
159
160    #[inline]
161    pub fn is_rgb(&self) -> bool {
162        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_RGB != 0
163    }
164
165    #[inline]
166    pub fn is_tiled(&self) -> bool {
167        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_TILED != 0
168    }
169
170    #[inline]
171    pub fn is_yuv(&self) -> bool {
172        self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_YUV != 0
173    }
174
175    #[inline]
176    pub fn scale_width(&self, component: u8, width: u32) -> u32 {
177        (-((-(i64::from(width))) >> self.w_sub()[component as usize])) as u32
178    }
179
180    #[inline]
181    pub fn scale_height(&self, component: u8, height: u32) -> u32 {
182        (-((-(i64::from(height))) >> self.h_sub()[component as usize])) as u32
183    }
184
185    #[allow(clippy::too_many_arguments)]
186    pub fn unpack(
187        &self,
188        flags: crate::VideoPackFlags,
189        dest: &mut [u8],
190        src: &[&[u8]],
191        stride: &[i32],
192        x: i32,
193        y: i32,
194        width: i32,
195    ) {
196        let unpack_format = Self::from_format(self.unpack_format());
197
198        if unpack_format.pixel_stride()[0] == 0 || self.0.unpack_func.is_none() {
199            panic!("No unpack format for {self:?}");
200        }
201
202        if src.len() != self.n_planes() as usize {
203            panic!(
204                "Wrong number of planes provided for format: {} != {}",
205                src.len(),
206                self.n_planes()
207            );
208        }
209
210        if stride.len() != self.n_planes() as usize {
211            panic!(
212                "Wrong number of strides provided for format: {} != {}",
213                stride.len(),
214                self.n_planes()
215            );
216        }
217
218        if dest.len() < unpack_format.pixel_stride()[0] as usize * width as usize {
219            panic!("Too small destination slice");
220        }
221
222        for plane in 0..(self.n_planes()) {
223            if stride[plane as usize]
224                < self.scale_width(plane as u8, width as u32) as i32
225                    * self.pixel_stride()[plane as usize]
226            {
227                panic!("Too small source stride for plane {plane}");
228            }
229
230            let plane_size = y * stride[plane as usize]
231                + self.scale_width(plane as u8, (x + width) as u32) as i32
232                    * self.pixel_stride()[plane as usize];
233
234            if src[plane as usize].len() < plane_size as usize {
235                panic!("Too small source plane size for plane {plane}");
236            }
237        }
238
239        unsafe {
240            use std::ptr;
241
242            let mut src_ptr = [ptr::null(); ffi::GST_VIDEO_MAX_PLANES as usize];
243            for plane in 0..(self.n_planes()) {
244                src_ptr[plane as usize] = src[plane as usize].as_ptr();
245            }
246
247            (self.0.unpack_func.as_ref().unwrap())(
248                self.0,
249                flags.into_glib(),
250                dest.as_mut_ptr() as *mut _,
251                src_ptr.as_ptr() as *const _,
252                stride.as_ptr(),
253                x,
254                y,
255                width,
256            );
257        }
258    }
259
260    #[allow(clippy::too_many_arguments)]
261    pub fn pack(
262        &self,
263        flags: crate::VideoPackFlags,
264        src: &[u8],
265        src_stride: i32,
266        dest: &mut [&mut [u8]],
267        dest_stride: &[i32],
268        chroma_site: crate::VideoChromaSite,
269        y: i32,
270        width: i32,
271    ) {
272        let unpack_format = Self::from_format(self.unpack_format());
273
274        if unpack_format.pixel_stride()[0] == 0 || self.0.unpack_func.is_none() {
275            panic!("No unpack format for {self:?}");
276        }
277
278        if dest.len() != self.n_planes() as usize {
279            panic!(
280                "Wrong number of planes provided for format: {} != {}",
281                dest.len(),
282                self.n_planes()
283            );
284        }
285
286        if dest_stride.len() != self.n_planes() as usize {
287            panic!(
288                "Wrong number of strides provided for format: {} != {}",
289                dest_stride.len(),
290                self.n_planes()
291            );
292        }
293
294        if src.len() < unpack_format.pixel_stride()[0] as usize * width as usize {
295            panic!("Too small source slice");
296        }
297
298        for plane in 0..(self.n_planes()) {
299            if dest_stride[plane as usize]
300                < self.scale_width(plane as u8, width as u32) as i32
301                    * self.pixel_stride()[plane as usize]
302            {
303                panic!("Too small destination stride for plane {plane}");
304            }
305
306            let plane_size = y * dest_stride[plane as usize]
307                + self.scale_width(plane as u8, width as u32) as i32
308                    * self.pixel_stride()[plane as usize];
309
310            if dest[plane as usize].len() < plane_size as usize {
311                panic!("Too small destination plane size for plane {plane}");
312            }
313        }
314
315        unsafe {
316            use std::ptr;
317
318            let mut dest_ptr = [ptr::null_mut(); ffi::GST_VIDEO_MAX_PLANES as usize];
319            for plane in 0..(self.n_planes()) {
320                dest_ptr[plane as usize] = dest[plane as usize].as_mut_ptr();
321            }
322
323            (self.0.pack_func.as_ref().unwrap())(
324                self.0,
325                flags.into_glib(),
326                src.as_ptr() as *mut _,
327                src_stride,
328                dest_ptr.as_mut_ptr() as *mut _,
329                dest_stride.as_ptr(),
330                chroma_site.into_glib(),
331                y,
332                width,
333            );
334        }
335    }
336
337    #[doc(alias = "gst_video_color_range_offsets")]
338    pub fn range_offsets(&self, range: crate::VideoColorRange) -> ([i32; 4], [i32; 4]) {
339        let mut offset = [0i32; 4];
340        let mut scale = [0i32; 4];
341        unsafe {
342            ffi::gst_video_color_range_offsets(
343                range.into_glib(),
344                self.to_glib_none().0,
345                &mut offset,
346                &mut scale,
347            )
348        }
349        (offset, scale)
350    }
351
352    /// Extrapolate `plane` stride from the first stride of an image. This helper is
353    /// useful to support legacy API were only one stride is supported.
354    /// ## `plane`
355    /// a plane number
356    /// ## `stride`
357    /// The fist plane stride
358    ///
359    /// # Returns
360    ///
361    /// The extrapolated stride for `plane`
362    #[cfg(feature = "v1_22")]
363    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
364    #[doc(alias = "gst_video_format_info_extrapolate_stride")]
365    pub fn extrapolate_stride(&self, plane: u32, stride: u32) -> u32 {
366        assert!(plane < self.n_planes());
367
368        unsafe {
369            ffi::gst_video_format_info_extrapolate_stride(
370                self.to_glib_none().0,
371                plane as i32,
372                stride as i32,
373            ) as u32
374        }
375    }
376
377    #[cfg(feature = "v1_22")]
378    #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
379    pub fn tile_info(&self, plane: u32) -> &VideoTileInfo {
380        assert!(plane < self.n_planes());
381
382        unsafe { &*(&self.0.tile_info[plane as usize] as *const _ as *const VideoTileInfo) }
383    }
384
385    /// Fill `components` with the number of all the components packed in plane `p`
386    /// for the format `self`. A value of -1 in `components` indicates that no more
387    /// components are packed in the plane.
388    /// ## `plane`
389    /// a plane number
390    ///
391    /// # Returns
392    ///
393    ///
394    /// ## `components`
395    /// array used to store component numbers
396    #[cfg(feature = "v1_18")]
397    #[cfg_attr(docsrs, doc(cfg(feature = "v1_18")))]
398    #[doc(alias = "gst_video_format_info_component")]
399    pub fn component(&self, plane: u32) -> [i32; ffi::GST_VIDEO_MAX_COMPONENTS as usize] {
400        assert!(plane < self.n_planes());
401
402        let mut comp = [-1i32; ffi::GST_VIDEO_MAX_COMPONENTS as usize];
403        unsafe {
404            ffi::gst_video_format_info_component(self.to_glib_none().0, plane, comp.as_mut_ptr());
405        }
406        comp
407    }
408}
409
410unsafe impl Sync for VideoFormatInfo {}
411unsafe impl Send for VideoFormatInfo {}
412
413impl PartialEq for VideoFormatInfo {
414    #[inline]
415    fn eq(&self, other: &Self) -> bool {
416        self.format() == other.format()
417    }
418}
419
420impl Eq for VideoFormatInfo {}
421
422impl PartialOrd for VideoFormatInfo {
423    #[inline]
424    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
425        Some(self.cmp(other))
426    }
427}
428
429impl Ord for VideoFormatInfo {
430    // See GST_VIDEO_FORMATS_ALL for the sorting algorithm
431    fn cmp(&self, other: &Self) -> Ordering {
432        self.n_components()
433            .cmp(&other.n_components())
434            .reverse()
435            .then_with(|| self.depth().cmp(other.depth()).reverse())
436            .then_with(|| self.w_sub().cmp(other.w_sub()))
437            .then_with(|| self.h_sub().cmp(other.h_sub()))
438            .then_with(|| self.n_planes().cmp(&other.n_planes()).reverse())
439            .then_with(|| {
440                // Format using native endianness is considered smaller
441                let native_endianness = [crate::VideoFormat::Ayuv64, crate::VideoFormat::Argb64];
442                let want_le = cfg!(target_endian = "little");
443
444                match (
445                    self.flags().contains(crate::VideoFormatFlags::LE) == want_le
446                        || native_endianness.contains(&self.format()),
447                    other.flags().contains(crate::VideoFormatFlags::LE) == want_le
448                        || native_endianness.contains(&other.format()),
449                ) {
450                    (true, false) => Ordering::Less,
451                    (false, true) => Ordering::Greater,
452                    _ => Ordering::Equal,
453                }
454            })
455            .then_with(|| {
456                // Prefer non-complex formats
457                match (
458                    self.flags().contains(crate::VideoFormatFlags::COMPLEX),
459                    other.flags().contains(crate::VideoFormatFlags::COMPLEX),
460                ) {
461                    (true, false) => Ordering::Greater,
462                    (false, true) => Ordering::Less,
463                    _ => Ordering::Equal,
464                }
465            })
466            .then_with(|| {
467                // Prefer RGB over YUV
468                if self.flags().contains(crate::VideoFormatFlags::RGB)
469                    && other.flags().contains(crate::VideoFormatFlags::YUV)
470                {
471                    Ordering::Greater
472                } else if self.flags().contains(crate::VideoFormatFlags::YUV)
473                    && other.flags().contains(crate::VideoFormatFlags::RGB)
474                {
475                    Ordering::Less
476                } else {
477                    Ordering::Equal
478                }
479            })
480            .then_with(|| {
481                // Prefer xRGB and permutations over RGB and permutations
482                let xrgb = [
483                    crate::VideoFormat::Xrgb,
484                    crate::VideoFormat::Xbgr,
485                    crate::VideoFormat::Rgbx,
486                    crate::VideoFormat::Bgrx,
487                ];
488                let rgb = [crate::VideoFormat::Rgb, crate::VideoFormat::Bgr];
489
490                if xrgb.contains(&self.format()) && rgb.contains(&other.format()) {
491                    Ordering::Less
492                } else if rgb.contains(&self.format()) && xrgb.contains(&other.format()) {
493                    Ordering::Greater
494                } else {
495                    Ordering::Equal
496                }
497            })
498            .then_with(|| self.pixel_stride().cmp(other.pixel_stride()))
499            .then_with(|| self.poffset().cmp(other.poffset()))
500            .then_with(|| {
501                // tie, sort by name
502                self.name().cmp(other.name())
503            })
504            // and reverse the whole ordering so that "better quality" > "lower quality"
505            .reverse()
506    }
507}
508
509impl fmt::Debug for VideoFormatInfo {
510    #[allow(deprecated)]
511    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
512        let mut fmt = f.debug_struct("VideoFormatInfo");
513
514        fmt.field("format", &self.format())
515            .field("name", &self.name())
516            .field("description", &self.description())
517            .field("flags", &self.flags())
518            .field("bits", &self.bits())
519            .field("n-components", &self.n_components())
520            .field("shift", &self.shift())
521            .field("depth", &self.depth())
522            .field("pixel-stride", &self.pixel_stride())
523            .field("n-planes", &self.n_planes())
524            .field("plane", &self.plane())
525            .field("poffset", &self.poffset())
526            .field("w-sub", &self.w_sub())
527            .field("h-sub", &self.h_sub())
528            .field("unpack-format", &self.unpack_format())
529            .field("pack-lines", &self.pack_lines())
530            .field("tile-mode", &self.tile_mode())
531            .field("tile-ws", &self.tile_ws())
532            .field("tile-hs", &self.tile_hs());
533
534        #[cfg(feature = "v1_22")]
535        {
536            fmt.field(
537                "tile-info",
538                &(0..self.n_planes()).map(|plane| self.tile_info(plane)),
539            );
540        }
541
542        fmt.finish()
543    }
544}
545
546impl fmt::Display for VideoFormatInfo {
547    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
548        f.write_str(self.name())
549    }
550}
551
552impl str::FromStr for crate::VideoFormatInfo {
553    type Err = glib::BoolError;
554
555    fn from_str(s: &str) -> Result<Self, Self::Err> {
556        skip_assert_initialized!();
557        let format = s.parse()?;
558        Ok(Self::from_format(format))
559    }
560}
561
562impl From<crate::VideoFormat> for VideoFormatInfo {
563    #[inline]
564    fn from(f: crate::VideoFormat) -> Self {
565        skip_assert_initialized!();
566        Self::from_format(f)
567    }
568}
569
570#[doc(hidden)]
571impl glib::translate::GlibPtrDefault for VideoFormatInfo {
572    type GlibType = *mut ffi::GstVideoFormatInfo;
573}
574
575#[doc(hidden)]
576unsafe impl glib::translate::TransparentPtrType for VideoFormatInfo {}
577
578#[doc(hidden)]
579impl<'a> glib::translate::ToGlibPtr<'a, *const ffi::GstVideoFormatInfo> for VideoFormatInfo {
580    type Storage = PhantomData<&'a Self>;
581
582    #[inline]
583    fn to_glib_none(&'a self) -> glib::translate::Stash<'a, *const ffi::GstVideoFormatInfo, Self> {
584        glib::translate::Stash(self.0, PhantomData)
585    }
586
587    fn to_glib_full(&self) -> *const ffi::GstVideoFormatInfo {
588        unimplemented!()
589    }
590}
591
592#[doc(hidden)]
593impl glib::translate::FromGlibPtrNone<*mut ffi::GstVideoFormatInfo> for VideoFormatInfo {
594    #[inline]
595    unsafe fn from_glib_none(ptr: *mut ffi::GstVideoFormatInfo) -> Self {
596        Self(&*ptr)
597    }
598}
599
600#[doc(hidden)]
601impl glib::translate::FromGlibPtrNone<*const ffi::GstVideoFormatInfo> for VideoFormatInfo {
602    #[inline]
603    unsafe fn from_glib_none(ptr: *const ffi::GstVideoFormatInfo) -> Self {
604        Self(&*ptr)
605    }
606}
607
608#[cfg(feature = "v1_22")]
609#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
610#[repr(transparent)]
611#[doc(alias = "GstVideoTileInfo")]
612pub struct VideoTileInfo(ffi::GstVideoTileInfo);
613
614#[cfg(feature = "v1_22")]
615#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
616impl fmt::Debug for VideoTileInfo {
617    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
618        f.debug_struct("VideoTileInfo")
619            .field("width", &self.width())
620            .field("height", &self.height())
621            .field("stride", &self.stride())
622            .field("size", &self.size())
623            .finish()
624    }
625}
626
627#[cfg(feature = "v1_22")]
628#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
629impl VideoTileInfo {
630    #[inline]
631    pub fn width(&self) -> u32 {
632        self.0.width
633    }
634
635    #[inline]
636    pub fn height(&self) -> u32 {
637        self.0.height
638    }
639
640    #[inline]
641    pub fn stride(&self) -> u32 {
642        self.0.stride
643    }
644
645    #[inline]
646    pub fn size(&self) -> u32 {
647        self.0.size
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654
655    #[test]
656    fn test_get() {
657        gst::init().unwrap();
658
659        let info = VideoFormatInfo::from_format(crate::VideoFormat::I420);
660        assert_eq!(info.name(), "I420");
661
662        let other_info = "I420".parse().unwrap();
663        assert_eq!(info, other_info);
664
665        assert_eq!(info.scale_width(0, 128), 128);
666        assert_eq!(info.scale_width(1, 128), 64);
667        assert_eq!(info.scale_width(2, 128), 64);
668    }
669
670    #[test]
671    fn test_unpack() {
672        gst::init().unwrap();
673
674        // One line black 320 pixel I420
675        let input = &[&[0; 320][..], &[128; 160][..], &[128; 160][..]];
676        // One line of AYUV
677        let intermediate = &mut [0; 320 * 4][..];
678        // One line of 320 pixel I420
679        let output = &mut [&mut [0; 320][..], &mut [0; 160][..], &mut [0; 160][..]];
680
681        let info = VideoFormatInfo::from_format(crate::VideoFormat::I420);
682        assert_eq!(info.unpack_format(), crate::VideoFormat::Ayuv);
683        info.unpack(
684            crate::VideoPackFlags::empty(),
685            intermediate,
686            input,
687            &[320, 160, 160][..],
688            0,
689            0,
690            320,
691        );
692
693        for pixel in intermediate.chunks_exact(4) {
694            assert_eq!(&[255, 0, 128, 128][..], pixel);
695        }
696
697        info.pack(
698            crate::VideoPackFlags::empty(),
699            &intermediate[..(4 * 320)],
700            4 * 320,
701            output,
702            &[320, 160, 160][..],
703            crate::VideoChromaSite::NONE,
704            0,
705            320,
706        );
707        assert_eq!(input, output);
708    }
709}