1use 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 #[cfg(feature = "v1_30")]
152 #[cfg_attr(docsrs, doc(cfg(feature = "v1_30")))]
153 #[inline]
154 pub fn is_float(&self) -> bool {
155 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_FLOAT != 0
156 }
157
158 #[inline]
159 pub fn is_gray(&self) -> bool {
160 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_GRAY != 0
161 }
162
163 #[inline]
164 pub fn is_le(&self) -> bool {
165 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_LE != 0
166 }
167
168 #[inline]
169 pub fn is_rgb(&self) -> bool {
170 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_RGB != 0
171 }
172
173 #[inline]
174 pub fn is_tiled(&self) -> bool {
175 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_TILED != 0
176 }
177
178 #[inline]
179 pub fn is_yuv(&self) -> bool {
180 self.0.flags & ffi::GST_VIDEO_FORMAT_FLAG_YUV != 0
181 }
182
183 #[inline]
184 pub fn scale_width(&self, component: u8, width: u32) -> u32 {
185 (-((-(i64::from(width))) >> self.w_sub()[component as usize])) as u32
186 }
187
188 #[inline]
189 pub fn scale_height(&self, component: u8, height: u32) -> u32 {
190 (-((-(i64::from(height))) >> self.h_sub()[component as usize])) as u32
191 }
192
193 #[allow(clippy::too_many_arguments)]
194 pub fn unpack(
195 &self,
196 flags: crate::VideoPackFlags,
197 dest: &mut [u8],
198 src: &[&[u8]],
199 stride: &[i32],
200 x: i32,
201 y: i32,
202 width: i32,
203 ) {
204 let unpack_format = Self::from_format(self.unpack_format());
205
206 if unpack_format.pixel_stride()[0] == 0 || self.0.unpack_func.is_none() {
207 panic!("No unpack format for {self:?}");
208 }
209
210 if src.len() != self.n_planes() as usize {
211 panic!(
212 "Wrong number of planes provided for format: {} != {}",
213 src.len(),
214 self.n_planes()
215 );
216 }
217
218 if stride.len() != self.n_planes() as usize {
219 panic!(
220 "Wrong number of strides provided for format: {} != {}",
221 stride.len(),
222 self.n_planes()
223 );
224 }
225
226 if dest.len() < unpack_format.pixel_stride()[0] as usize * width as usize {
227 panic!("Too small destination slice");
228 }
229
230 for plane in 0..(self.n_planes()) {
231 if stride[plane as usize]
232 < self.scale_width(plane as u8, width as u32) as i32
233 * self.pixel_stride()[plane as usize]
234 {
235 panic!("Too small source stride for plane {plane}");
236 }
237
238 let plane_size = y * stride[plane as usize]
239 + self.scale_width(plane as u8, (x + width) as u32) as i32
240 * self.pixel_stride()[plane as usize];
241
242 if src[plane as usize].len() < plane_size as usize {
243 panic!("Too small source plane size for plane {plane}");
244 }
245 }
246
247 unsafe {
248 use std::ptr;
249
250 let mut src_ptr = [ptr::null(); ffi::GST_VIDEO_MAX_PLANES as usize];
251 for plane in 0..(self.n_planes()) {
252 src_ptr[plane as usize] = src[plane as usize].as_ptr();
253 }
254
255 (self.0.unpack_func.as_ref().unwrap())(
256 self.0,
257 flags.into_glib(),
258 dest.as_mut_ptr() as *mut _,
259 src_ptr.as_ptr() as *const _,
260 stride.as_ptr(),
261 x,
262 y,
263 width,
264 );
265 }
266 }
267
268 #[allow(clippy::too_many_arguments)]
269 pub fn pack(
270 &self,
271 flags: crate::VideoPackFlags,
272 src: &[u8],
273 src_stride: i32,
274 dest: &mut [&mut [u8]],
275 dest_stride: &[i32],
276 chroma_site: crate::VideoChromaSite,
277 y: i32,
278 width: i32,
279 ) {
280 let unpack_format = Self::from_format(self.unpack_format());
281
282 if unpack_format.pixel_stride()[0] == 0 || self.0.unpack_func.is_none() {
283 panic!("No unpack format for {self:?}");
284 }
285
286 if dest.len() != self.n_planes() as usize {
287 panic!(
288 "Wrong number of planes provided for format: {} != {}",
289 dest.len(),
290 self.n_planes()
291 );
292 }
293
294 if dest_stride.len() != self.n_planes() as usize {
295 panic!(
296 "Wrong number of strides provided for format: {} != {}",
297 dest_stride.len(),
298 self.n_planes()
299 );
300 }
301
302 if src.len() < unpack_format.pixel_stride()[0] as usize * width as usize {
303 panic!("Too small source slice");
304 }
305
306 for plane in 0..(self.n_planes()) {
307 if 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 panic!("Too small destination stride for plane {plane}");
312 }
313
314 let plane_size = y * dest_stride[plane as usize]
315 + self.scale_width(plane as u8, width as u32) as i32
316 * self.pixel_stride()[plane as usize];
317
318 if dest[plane as usize].len() < plane_size as usize {
319 panic!("Too small destination plane size for plane {plane}");
320 }
321 }
322
323 unsafe {
324 use std::ptr;
325
326 let mut dest_ptr = [ptr::null_mut(); ffi::GST_VIDEO_MAX_PLANES as usize];
327 for plane in 0..(self.n_planes()) {
328 dest_ptr[plane as usize] = dest[plane as usize].as_mut_ptr();
329 }
330
331 (self.0.pack_func.as_ref().unwrap())(
332 self.0,
333 flags.into_glib(),
334 src.as_ptr() as *mut _,
335 src_stride,
336 dest_ptr.as_mut_ptr() as *mut _,
337 dest_stride.as_ptr(),
338 chroma_site.into_glib(),
339 y,
340 width,
341 );
342 }
343 }
344
345 #[doc(alias = "gst_video_color_range_offsets")]
346 pub fn range_offsets(&self, range: crate::VideoColorRange) -> ([i32; 4], [i32; 4]) {
347 let mut offset = [0i32; 4];
348 let mut scale = [0i32; 4];
349 unsafe {
350 ffi::gst_video_color_range_offsets(
351 range.into_glib(),
352 self.to_glib_none().0,
353 &mut offset,
354 &mut scale,
355 )
356 }
357 (offset, scale)
358 }
359
360 #[cfg(feature = "v1_30")]
361 #[cfg_attr(docsrs, doc(cfg(feature = "v1_30")))]
362 #[doc(alias = "gst_video_color_range_offsets_full")]
363 pub fn range_offsets_full(
364 &self,
365 range: crate::VideoColorRange,
366 ) -> ([f64; 4], [f64; 4], [f64; 4]) {
367 let mut offset = [0f64; 4];
368 let mut scale = [0f64; 4];
369 let mut fullscale = [0f64; 4];
370
371 unsafe {
372 ffi::gst_video_color_range_offsets_full(
373 range.into_glib(),
374 self.to_glib_none().0,
375 &mut offset,
376 &mut scale,
377 &mut fullscale,
378 )
379 }
380
381 (offset, scale, fullscale)
382 }
383
384 #[cfg(feature = "v1_22")]
385 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
386 #[doc(alias = "gst_video_format_info_extrapolate_stride")]
387 pub fn extrapolate_stride(&self, plane: u32, stride: u32) -> u32 {
388 assert!(plane < self.n_planes());
389
390 unsafe {
391 ffi::gst_video_format_info_extrapolate_stride(
392 self.to_glib_none().0,
393 plane as i32,
394 stride as i32,
395 ) as u32
396 }
397 }
398
399 #[cfg(feature = "v1_22")]
400 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
401 pub fn tile_info(&self, plane: u32) -> &VideoTileInfo {
402 assert!(plane < self.n_planes());
403
404 unsafe { &*(&self.0.tile_info[plane as usize] as *const _ as *const VideoTileInfo) }
405 }
406
407 #[cfg(feature = "v1_18")]
408 #[cfg_attr(docsrs, doc(cfg(feature = "v1_18")))]
409 #[doc(alias = "gst_video_format_info_component")]
410 pub fn component(&self, plane: u32) -> [i32; ffi::GST_VIDEO_MAX_COMPONENTS as usize] {
411 assert!(plane < self.n_planes());
412
413 let mut comp = [-1i32; ffi::GST_VIDEO_MAX_COMPONENTS as usize];
414 unsafe {
415 ffi::gst_video_format_info_component(self.to_glib_none().0, plane, &mut comp);
416 }
417 comp
418 }
419}
420
421unsafe impl Sync for VideoFormatInfo {}
422unsafe impl Send for VideoFormatInfo {}
423
424impl PartialEq for VideoFormatInfo {
425 #[inline]
426 fn eq(&self, other: &Self) -> bool {
427 self.format() == other.format()
428 }
429}
430
431impl Eq for VideoFormatInfo {}
432
433impl PartialOrd for VideoFormatInfo {
434 #[inline]
435 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
436 Some(self.cmp(other))
437 }
438}
439
440const VIDEO_FORMAT_FLAG_FLOAT: ffi::GstVideoFormatFlags = 1 << 10;
445
446fn effective_depth(info: &VideoFormatInfo) -> [u32; 4] {
449 if info.0.flags & VIDEO_FORMAT_FLAG_FLOAT != 0 {
452 return info.0.depth.map(|d| match d {
453 16 => 11,
454 32 => 24,
455 d => d,
456 });
457 }
458
459 info.0.depth
460}
461
462impl Ord for VideoFormatInfo {
463 fn cmp(&self, other: &Self) -> Ordering {
465 self.n_components()
466 .cmp(&other.n_components())
467 .reverse()
468 .then_with(|| effective_depth(self).cmp(&effective_depth(other)).reverse())
469 .then_with(|| self.w_sub().cmp(other.w_sub()))
470 .then_with(|| self.h_sub().cmp(other.h_sub()))
471 .then_with(|| self.n_planes().cmp(&other.n_planes()).reverse())
472 .then_with(|| {
473 let native_endianness = [crate::VideoFormat::Ayuv64, crate::VideoFormat::Argb64];
475 let want_le = cfg!(target_endian = "little");
476
477 match (
478 self.flags().contains(crate::VideoFormatFlags::LE) == want_le
479 || native_endianness.contains(&self.format()),
480 other.flags().contains(crate::VideoFormatFlags::LE) == want_le
481 || native_endianness.contains(&other.format()),
482 ) {
483 (true, false) => Ordering::Less,
484 (false, true) => Ordering::Greater,
485 _ => Ordering::Equal,
486 }
487 })
488 .then_with(|| {
489 match (
491 self.flags().contains(crate::VideoFormatFlags::COMPLEX),
492 other.flags().contains(crate::VideoFormatFlags::COMPLEX),
493 ) {
494 (true, false) => Ordering::Greater,
495 (false, true) => Ordering::Less,
496 _ => Ordering::Equal,
497 }
498 })
499 .then_with(|| {
500 if self.flags().contains(crate::VideoFormatFlags::RGB)
502 && other.flags().contains(crate::VideoFormatFlags::YUV)
503 {
504 Ordering::Greater
505 } else if self.flags().contains(crate::VideoFormatFlags::YUV)
506 && other.flags().contains(crate::VideoFormatFlags::RGB)
507 {
508 Ordering::Less
509 } else {
510 Ordering::Equal
511 }
512 })
513 .then_with(|| {
514 let xrgb = [
516 crate::VideoFormat::Xrgb,
517 crate::VideoFormat::Xbgr,
518 crate::VideoFormat::Rgbx,
519 crate::VideoFormat::Bgrx,
520 ];
521 let rgb = [crate::VideoFormat::Rgb, crate::VideoFormat::Bgr];
522
523 if xrgb.contains(&self.format()) && rgb.contains(&other.format()) {
524 Ordering::Less
525 } else if rgb.contains(&self.format()) && xrgb.contains(&other.format()) {
526 Ordering::Greater
527 } else {
528 Ordering::Equal
529 }
530 })
531 .then_with(|| self.pixel_stride().cmp(other.pixel_stride()))
532 .then_with(|| self.poffset().cmp(other.poffset()))
533 .then_with(|| {
534 self.name().cmp(other.name())
536 })
537 .reverse()
539 }
540}
541
542impl fmt::Debug for VideoFormatInfo {
543 #[allow(deprecated)]
544 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
545 let mut fmt = f.debug_struct("VideoFormatInfo");
546
547 fmt.field("format", &self.format())
548 .field("name", &self.name())
549 .field("description", &self.description())
550 .field("flags", &self.flags())
551 .field("bits", &self.bits())
552 .field("n-components", &self.n_components())
553 .field("shift", &self.shift())
554 .field("depth", &self.depth())
555 .field("pixel-stride", &self.pixel_stride())
556 .field("n-planes", &self.n_planes())
557 .field("plane", &self.plane())
558 .field("poffset", &self.poffset())
559 .field("w-sub", &self.w_sub())
560 .field("h-sub", &self.h_sub())
561 .field("unpack-format", &self.unpack_format())
562 .field("pack-lines", &self.pack_lines())
563 .field("tile-mode", &self.tile_mode())
564 .field("tile-ws", &self.tile_ws())
565 .field("tile-hs", &self.tile_hs());
566
567 #[cfg(feature = "v1_22")]
568 {
569 fmt.field(
570 "tile-info",
571 &(0..self.n_planes()).map(|plane| self.tile_info(plane)),
572 );
573 }
574
575 fmt.finish()
576 }
577}
578
579impl fmt::Display for VideoFormatInfo {
580 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
581 f.write_str(self.name())
582 }
583}
584
585impl str::FromStr for crate::VideoFormatInfo {
586 type Err = glib::BoolError;
587
588 fn from_str(s: &str) -> Result<Self, Self::Err> {
589 skip_assert_initialized!();
590 let format = s.parse()?;
591 Ok(Self::from_format(format))
592 }
593}
594
595impl From<crate::VideoFormat> for VideoFormatInfo {
596 #[inline]
597 fn from(f: crate::VideoFormat) -> Self {
598 skip_assert_initialized!();
599 Self::from_format(f)
600 }
601}
602
603#[doc(hidden)]
604impl glib::translate::GlibPtrDefault for VideoFormatInfo {
605 type GlibType = *mut ffi::GstVideoFormatInfo;
606}
607
608#[doc(hidden)]
609unsafe impl glib::translate::TransparentPtrType for VideoFormatInfo {}
610
611#[doc(hidden)]
612impl<'a> glib::translate::ToGlibPtr<'a, *const ffi::GstVideoFormatInfo> for VideoFormatInfo {
613 type Storage = PhantomData<&'a Self>;
614
615 #[inline]
616 fn to_glib_none(&'a self) -> glib::translate::Stash<'a, *const ffi::GstVideoFormatInfo, Self> {
617 glib::translate::Stash(self.0, PhantomData)
618 }
619
620 fn to_glib_full(&self) -> *const ffi::GstVideoFormatInfo {
621 unimplemented!()
622 }
623}
624
625#[doc(hidden)]
626impl glib::translate::FromGlibPtrNone<*mut ffi::GstVideoFormatInfo> for VideoFormatInfo {
627 #[inline]
628 unsafe fn from_glib_none(ptr: *mut ffi::GstVideoFormatInfo) -> Self {
629 unsafe { Self(&*ptr) }
630 }
631}
632
633#[doc(hidden)]
634impl glib::translate::FromGlibPtrNone<*const ffi::GstVideoFormatInfo> for VideoFormatInfo {
635 #[inline]
636 unsafe fn from_glib_none(ptr: *const ffi::GstVideoFormatInfo) -> Self {
637 unsafe { Self(&*ptr) }
638 }
639}
640
641#[cfg(feature = "v1_22")]
642#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
643#[repr(transparent)]
644#[doc(alias = "GstVideoTileInfo")]
645pub struct VideoTileInfo(ffi::GstVideoTileInfo);
646
647#[cfg(feature = "v1_22")]
648#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
649impl fmt::Debug for VideoTileInfo {
650 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
651 f.debug_struct("VideoTileInfo")
652 .field("width", &self.width())
653 .field("height", &self.height())
654 .field("stride", &self.stride())
655 .field("size", &self.size())
656 .finish()
657 }
658}
659
660#[cfg(feature = "v1_22")]
661#[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
662impl VideoTileInfo {
663 #[inline]
664 pub fn width(&self) -> u32 {
665 self.0.width
666 }
667
668 #[inline]
669 pub fn height(&self) -> u32 {
670 self.0.height
671 }
672
673 #[inline]
674 pub fn stride(&self) -> u32 {
675 self.0.stride
676 }
677
678 #[inline]
679 pub fn size(&self) -> u32 {
680 self.0.size
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[test]
689 fn test_get() {
690 gst::init().unwrap();
691
692 let info = VideoFormatInfo::from_format(crate::VideoFormat::I420);
693 assert_eq!(info.name(), "I420");
694
695 let other_info = "I420".parse().unwrap();
696 assert_eq!(info, other_info);
697
698 assert_eq!(info.scale_width(0, 128), 128);
699 assert_eq!(info.scale_width(1, 128), 64);
700 assert_eq!(info.scale_width(2, 128), 64);
701 }
702
703 #[test]
704 fn test_unpack() {
705 gst::init().unwrap();
706
707 let input = &[&[0; 320][..], &[128; 160][..], &[128; 160][..]];
709 let intermediate = &mut [0; 320 * 4][..];
711 let output = &mut [&mut [0; 320][..], &mut [0; 160][..], &mut [0; 160][..]];
713
714 let info = VideoFormatInfo::from_format(crate::VideoFormat::I420);
715 assert_eq!(info.unpack_format(), crate::VideoFormat::Ayuv);
716 info.unpack(
717 crate::VideoPackFlags::empty(),
718 intermediate,
719 input,
720 &[320, 160, 160][..],
721 0,
722 0,
723 320,
724 );
725
726 for pixel in intermediate.as_chunks::<4>().0 {
727 assert_eq!(&[255, 0, 128, 128][..], pixel);
728 }
729
730 info.pack(
731 crate::VideoPackFlags::empty(),
732 &intermediate[..(4 * 320)],
733 4 * 320,
734 output,
735 &[320, 160, 160][..],
736 crate::VideoChromaSite::NONE,
737 0,
738 320,
739 );
740 assert_eq!(input, output);
741 }
742}