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