1use std::{cmp::Ordering, fmt, marker::PhantomData, str};
4
5use crate::ffi;
6use glib::translate::{IntoGlib, ToGlibPtr, from_glib};
7
8#[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 #[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 #[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 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 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 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 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 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 self.name().cmp(other.name())
505 })
506 .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 let input = &[&[0; 320][..], &[128; 160][..], &[128; 160][..]];
678 let intermediate = &mut [0; 320 * 4][..];
680 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}