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 #[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 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 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 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 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 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 self.name().cmp(other.name())
483 })
484 .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 let input = &[&[0; 320][..], &[128; 160][..], &[128; 160][..]];
656 let intermediate = &mut [0; 320 * 4][..];
658 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}