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: VideoFormat = 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}
688