1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{cmp::Ordering, fmt, marker::PhantomData, str}; |
4 | |
5 | use glib::translate::{from_glib, IntoGlib, ToGlibPtr}; |
6 | |
7 | #[doc (alias = "GstVideoFormatInfo" )] |
8 | #[derive (Copy, Clone)] |
9 | pub struct VideoFormatInfo(&'static ffi::GstVideoFormatInfo); |
10 | |
11 | impl 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 | |
374 | unsafe impl Sync for VideoFormatInfo {} |
375 | unsafe impl Send for VideoFormatInfo {} |
376 | |
377 | impl PartialEq for VideoFormatInfo { |
378 | #[inline ] |
379 | fn eq(&self, other: &Self) -> bool { |
380 | self.format() == other.format() |
381 | } |
382 | } |
383 | |
384 | impl Eq for VideoFormatInfo {} |
385 | |
386 | impl PartialOrd for VideoFormatInfo { |
387 | #[inline ] |
388 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
389 | Some(self.cmp(other)) |
390 | } |
391 | } |
392 | |
393 | impl 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 | |
473 | impl 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 | |
510 | impl fmt::Display for VideoFormatInfo { |
511 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
512 | f.write_str(self.name()) |
513 | } |
514 | } |
515 | |
516 | impl 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 | |
526 | impl 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)] |
535 | impl glib::translate::GlibPtrDefault for VideoFormatInfo { |
536 | type GlibType = *mut ffi::GstVideoFormatInfo; |
537 | } |
538 | |
539 | #[doc (hidden)] |
540 | unsafe impl glib::translate::TransparentPtrType for VideoFormatInfo {} |
541 | |
542 | #[doc (hidden)] |
543 | impl<'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)] |
557 | impl 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)] |
565 | impl 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" )] |
576 | pub struct VideoTileInfo(ffi::GstVideoTileInfo); |
577 | |
578 | #[cfg (feature = "v1_22" )] |
579 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_22" )))] |
580 | impl 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" )))] |
593 | impl 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)] |
616 | mod 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 | |