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