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