Skip to main content

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