1use crate::internals::*;
30use crate::numerics::qrshr;
31use crate::yuv_error::check_rgba_destination;
32use crate::yuv_support::*;
33use crate::{YuvBiPlanarImage, YuvError};
34#[cfg(feature = "rayon")]
35use rayon::iter::{IndexedParallelIterator, ParallelIterator};
36#[cfg(feature = "rayon")]
37use rayon::prelude::{ParallelSlice, ParallelSliceMut};
38
39type TRowHandler = Option<
40 unsafe fn(
41 range: &YuvChromaRange,
42 transform: &CbCrInverseTransform<i32>,
43 y_plane: &[u8],
44 uv_plane: &[u8],
45 rgba: &mut [u8],
46 start_cx: usize,
47 start_ux: usize,
48 width: usize,
49 ) -> ProcessedOffset,
50>;
51
52type TRowHandler420 = Option<
53 unsafe fn(
54 range: &YuvChromaRange,
55 transform: &CbCrInverseTransform<i32>,
56 y_plane0: &[u8],
57 y_plane1: &[u8],
58 uv_plane: &[u8],
59 rgba0: &mut [u8],
60 rgba1: &mut [u8],
61 start_cx: usize,
62 start_ux: usize,
63 width: usize,
64 ) -> ProcessedOffset,
65>;
66
67struct NVRowHandler<
68 const UV_ORDER: u8,
69 const DESTINATION_CHANNELS: u8,
70 const YUV_CHROMA_SAMPLING: u8,
71 const PRECISION: i32,
72> {
73 handler: TRowHandler,
74}
75
76#[cfg(feature = "fast_mode")]
77struct NVRowHandlerFast<
78 const UV_ORDER: u8,
79 const DESTINATION_CHANNELS: u8,
80 const YUV_CHROMA_SAMPLING: u8,
81 const PRECISION: i32,
82> {
83 handler: TRowHandler,
84}
85
86#[cfg(feature = "professional_mode")]
87struct NVRowHandlerProfessional<
88 const UV_ORDER: u8,
89 const DESTINATION_CHANNELS: u8,
90 const YUV_CHROMA_SAMPLING: u8,
91 const PRECISION: i32,
92> {
93 handler: TRowHandler,
94}
95
96impl<
97 const UV_ORDER: u8,
98 const DESTINATION_CHANNELS: u8,
99 const YUV_CHROMA_SAMPLING: u8,
100 const PRECISION: i32,
101 > Default for NVRowHandler<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
102{
103 fn default() -> Self {
104 if PRECISION != 13 {
105 return NVRowHandler { handler: None };
106 }
107 assert_eq!(PRECISION, 13);
108 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
109 {
110 #[cfg(feature = "rdm")]
111 {
112 use crate::neon::neon_yuv_nv_to_rgba_row_rdm;
113 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
114 if is_rdm_available {
115 return NVRowHandler {
116 handler: Some(
117 neon_yuv_nv_to_rgba_row_rdm::<
118 UV_ORDER,
119 DESTINATION_CHANNELS,
120 YUV_CHROMA_SAMPLING,
121 >,
122 ),
123 };
124 }
125 }
126 use crate::neon::neon_yuv_nv_to_rgba_row;
127
128 NVRowHandler {
129 handler: Some(
130 neon_yuv_nv_to_rgba_row::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING>,
131 ),
132 }
133 }
134 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
135 {
136 #[cfg(feature = "nightly_avx512")]
137 {
138 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
139 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
140 use crate::avx512bw::{avx512_yuv_nv_to_rgba, avx512_yuv_nv_to_rgba422};
141 let subsampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
142 if use_avx512 {
143 return if subsampling == YuvChromaSubsampling::Yuv422
144 || subsampling == YuvChromaSubsampling::Yuv420
145 {
146 assert!(
147 subsampling == YuvChromaSubsampling::Yuv422
148 || subsampling == YuvChromaSubsampling::Yuv420
149 );
150 NVRowHandler {
151 handler: Some(if use_vbmi {
152 avx512_yuv_nv_to_rgba422::<UV_ORDER, DESTINATION_CHANNELS, true>
153 } else {
154 avx512_yuv_nv_to_rgba422::<UV_ORDER, DESTINATION_CHANNELS, false>
155 }),
156 }
157 } else {
158 NVRowHandler {
159 handler: Some(if use_vbmi {
160 avx512_yuv_nv_to_rgba::<
161 UV_ORDER,
162 DESTINATION_CHANNELS,
163 YUV_CHROMA_SAMPLING,
164 true,
165 >
166 } else {
167 avx512_yuv_nv_to_rgba::<
168 UV_ORDER,
169 DESTINATION_CHANNELS,
170 YUV_CHROMA_SAMPLING,
171 false,
172 >
173 }),
174 }
175 };
176 }
177 }
178
179 #[cfg(feature = "avx")]
180 {
181 let use_avx2 = std::arch::is_x86_feature_detected!("avx2");
182 let subsampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
183 if use_avx2 {
184 use crate::avx2::{avx2_yuv_nv_to_rgba_row, avx2_yuv_nv_to_rgba_row422};
185 return NVRowHandler {
186 handler: Some(
187 if subsampling == YuvChromaSubsampling::Yuv420
188 || subsampling == YuvChromaSubsampling::Yuv422
189 {
190 avx2_yuv_nv_to_rgba_row422::<UV_ORDER, DESTINATION_CHANNELS>
191 } else {
192 avx2_yuv_nv_to_rgba_row::<
193 UV_ORDER,
194 DESTINATION_CHANNELS,
195 YUV_CHROMA_SAMPLING,
196 >
197 },
198 ),
199 };
200 }
201 }
202
203 #[cfg(feature = "sse")]
204 {
205 let subsampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
206 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
207 if use_sse {
208 use crate::sse::{sse_yuv_nv_to_rgba, sse_yuv_nv_to_rgba422};
209 return NVRowHandler {
210 handler: Some(
211 if subsampling == YuvChromaSubsampling::Yuv420
212 || subsampling == YuvChromaSubsampling::Yuv422
213 {
214 sse_yuv_nv_to_rgba422::<UV_ORDER, DESTINATION_CHANNELS>
215 } else {
216 sse_yuv_nv_to_rgba::<
217 UV_ORDER,
218 DESTINATION_CHANNELS,
219 YUV_CHROMA_SAMPLING,
220 >
221 },
222 ),
223 };
224 }
225 }
226 }
227 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
228 {
229 use crate::wasm32::wasm_yuv_nv_to_rgba_row;
230 return NVRowHandler {
231 handler: Some(
232 wasm_yuv_nv_to_rgba_row::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING>,
233 ),
234 };
235 }
236 #[cfg(not(any(
237 all(target_arch = "aarch64", target_feature = "neon"),
238 all(target_arch = "wasm32", target_feature = "simd128")
239 )))]
240 NVRowHandler { handler: None }
241 }
242}
243
244#[cfg(feature = "fast_mode")]
245impl<
246 const UV_ORDER: u8,
247 const DESTINATION_CHANNELS: u8,
248 const YUV_CHROMA_SAMPLING: u8,
249 const PRECISION: i32,
250 > Default for NVRowHandlerFast<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
251{
252 fn default() -> Self {
253 if PRECISION == 6 {
254 assert_eq!(PRECISION, 6);
255 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
256 {
257 use crate::neon::neon_yuv_nv_to_rgba_fast_row;
258 return NVRowHandlerFast {
259 handler: Some(
260 neon_yuv_nv_to_rgba_fast_row::<
261 UV_ORDER,
262 DESTINATION_CHANNELS,
263 YUV_CHROMA_SAMPLING,
264 >,
265 ),
266 };
267 }
268
269 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
270 {
271 #[cfg(feature = "avx")]
272 {
273 let use_avx = std::arch::is_x86_feature_detected!("avx2");
274 if use_avx {
275 use crate::avx2::avx_yuv_nv_to_rgba_fast;
276 return NVRowHandlerFast {
277 handler: Some(
278 avx_yuv_nv_to_rgba_fast::<
279 UV_ORDER,
280 DESTINATION_CHANNELS,
281 YUV_CHROMA_SAMPLING,
282 >,
283 ),
284 };
285 }
286 }
287
288 #[cfg(feature = "sse")]
289 {
290 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
291 if use_sse {
292 use crate::sse::sse_yuv_nv_to_rgba_fast;
293 return NVRowHandlerFast {
294 handler: Some(
295 sse_yuv_nv_to_rgba_fast::<
296 UV_ORDER,
297 DESTINATION_CHANNELS,
298 YUV_CHROMA_SAMPLING,
299 >,
300 ),
301 };
302 }
303 }
304 }
305 }
306
307 NVRowHandlerFast { handler: None }
308 }
309}
310
311#[cfg(feature = "professional_mode")]
312impl<
313 const UV_ORDER: u8,
314 const DESTINATION_CHANNELS: u8,
315 const YUV_CHROMA_SAMPLING: u8,
316 const PRECISION: i32,
317 > Default
318 for NVRowHandlerProfessional<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
319{
320 fn default() -> Self {
321 if PRECISION == 14 {
322 assert_eq!(PRECISION, 14);
323 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
324 {
325 use crate::neon::neon_yuv_nv_to_rgba_row_prof;
326 return NVRowHandlerProfessional {
327 handler: Some(
328 neon_yuv_nv_to_rgba_row_prof::<
329 UV_ORDER,
330 DESTINATION_CHANNELS,
331 YUV_CHROMA_SAMPLING,
332 >,
333 ),
334 };
335 }
336 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
337 {
338 #[cfg(feature = "avx")]
339 {
340 let use_avx = std::arch::is_x86_feature_detected!("avx2");
341 if use_avx {
342 use crate::avx2::avx2_yuv_nv_to_rgba_row_prof;
343 return NVRowHandlerProfessional {
344 handler: Some(
345 avx2_yuv_nv_to_rgba_row_prof::<
346 UV_ORDER,
347 DESTINATION_CHANNELS,
348 YUV_CHROMA_SAMPLING,
349 >,
350 ),
351 };
352 }
353 }
354 #[cfg(feature = "sse")]
355 {
356 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
357 if use_sse {
358 use crate::sse::sse_yuv_nv_to_rgba_row_prof;
359 return NVRowHandlerProfessional {
360 handler: Some(
361 sse_yuv_nv_to_rgba_row_prof::<
362 UV_ORDER,
363 DESTINATION_CHANNELS,
364 YUV_CHROMA_SAMPLING,
365 >,
366 ),
367 };
368 }
369 }
370 }
371 }
372
373 NVRowHandlerProfessional { handler: None }
374 }
375}
376
377macro_rules! impl_row_biplanar_inversion_handler {
378 ($struct_name:ident) => {
379 impl<
380 const UV_ORDER: u8,
381 const DESTINATION_CHANNELS: u8,
382 const YUV_CHROMA_SAMPLING: u8,
383 const PRECISION: i32,
384 > RowBiPlanarInversionHandler<u8, i32>
385 for $struct_name<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
386 {
387 fn handle_row(
388 &self,
389 y_plane: &[u8],
390 uv_plane: &[u8],
391 rgba: &mut [u8],
392 width: u32,
393 chroma: YuvChromaRange,
394 transform: &CbCrInverseTransform<i32>,
395 ) -> ProcessedOffset {
396 if let Some(handler) = self.handler {
397 unsafe {
398 return handler(
399 &chroma,
400 transform,
401 y_plane,
402 uv_plane,
403 rgba,
404 0,
405 0,
406 width as usize,
407 );
408 }
409 }
410 ProcessedOffset { ux: 0, cx: 0 }
411 }
412 }
413 };
414}
415
416impl_row_biplanar_inversion_handler!(NVRowHandler);
417#[cfg(feature = "fast_mode")]
418impl_row_biplanar_inversion_handler!(NVRowHandlerFast);
419#[cfg(feature = "professional_mode")]
420impl_row_biplanar_inversion_handler!(NVRowHandlerProfessional);
421
422struct NVRow420Handler<
423 const UV_ORDER: u8,
424 const DESTINATION_CHANNELS: u8,
425 const YUV_CHROMA_SAMPLING: u8,
426 const PRECISION: i32,
427> {
428 handler: TRowHandler420,
429}
430
431#[cfg(feature = "fast_mode")]
432struct NVRow420HandlerFast<
433 const UV_ORDER: u8,
434 const DESTINATION_CHANNELS: u8,
435 const YUV_CHROMA_SAMPLING: u8,
436 const PRECISION: i32,
437> {
438 handler: TRowHandler420,
439}
440
441#[cfg(feature = "professional_mode")]
442struct NVRow420HandlerProfessional<
443 const UV_ORDER: u8,
444 const DESTINATION_CHANNELS: u8,
445 const YUV_CHROMA_SAMPLING: u8,
446 const PRECISION: i32,
447> {
448 handler: TRowHandler420,
449}
450
451macro_rules! impl_row_biplanar_inversion_420_handler {
452 ($struct_name:ident, $handler_trait:ident) => {
453 impl<
454 const UV_ORDER: u8,
455 const DESTINATION_CHANNELS: u8,
456 const YUV_CHROMA_SAMPLING: u8,
457 const PRECISION: i32,
458 > $handler_trait<u8, i32>
459 for $struct_name<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
460 {
461 fn handle_row(
462 &self,
463 y_plane0: &[u8],
464 y_plane1: &[u8],
465 uv_plane: &[u8],
466 rgba0: &mut [u8],
467 rgba1: &mut [u8],
468 width: u32,
469 chroma: YuvChromaRange,
470 transform: &CbCrInverseTransform<i32>,
471 ) -> ProcessedOffset {
472 if let Some(handler) = self.handler {
473 unsafe {
474 return handler(
475 &chroma,
476 transform,
477 y_plane0,
478 y_plane1,
479 uv_plane,
480 rgba0,
481 rgba1,
482 0,
483 0,
484 width as usize,
485 );
486 }
487 }
488 ProcessedOffset { cx: 0, ux: 0 }
489 }
490 }
491 };
492}
493
494impl_row_biplanar_inversion_420_handler!(NVRow420Handler, RowBiPlanarInversion420Handler);
495#[cfg(feature = "fast_mode")]
496impl_row_biplanar_inversion_420_handler!(NVRow420HandlerFast, RowBiPlanarInversion420Handler);
497#[cfg(feature = "professional_mode")]
498impl_row_biplanar_inversion_420_handler!(
499 NVRow420HandlerProfessional,
500 RowBiPlanarInversion420Handler
501);
502
503impl<
504 const UV_ORDER: u8,
505 const DESTINATION_CHANNELS: u8,
506 const YUV_CHROMA_SAMPLING: u8,
507 const PRECISION: i32,
508 > Default for NVRow420Handler<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
509{
510 fn default() -> Self {
511 let sampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
512 if sampling != YuvChromaSubsampling::Yuv420 {
513 return NVRow420Handler { handler: None };
514 }
515 assert_eq!(sampling, YuvChromaSubsampling::Yuv420);
516 if PRECISION != 13 {
517 return NVRow420Handler { handler: None };
518 }
519 assert_eq!(PRECISION, 13);
520 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
521 {
522 #[cfg(feature = "rdm")]
523 {
524 use crate::neon::neon_yuv_nv_to_rgba_row_rdm420;
525 let is_rdm_available = std::arch::is_aarch64_feature_detected!("rdm");
526 if is_rdm_available {
527 return NVRow420Handler {
528 handler: Some(
529 neon_yuv_nv_to_rgba_row_rdm420::<UV_ORDER, DESTINATION_CHANNELS>,
530 ),
531 };
532 }
533 }
534
535 use crate::neon::neon_yuv_nv_to_rgba_row420;
536 NVRow420Handler {
537 handler: Some(neon_yuv_nv_to_rgba_row420::<UV_ORDER, DESTINATION_CHANNELS>),
538 }
539 }
540 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
541 {
542 #[cfg(feature = "nightly_avx512")]
543 {
544 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
545 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
546 if use_avx512 {
547 use crate::avx512bw::avx512_yuv_nv_to_rgba420;
548 return NVRow420Handler {
549 handler: Some(if use_vbmi {
550 avx512_yuv_nv_to_rgba420::<UV_ORDER, DESTINATION_CHANNELS, true>
551 } else {
552 avx512_yuv_nv_to_rgba420::<UV_ORDER, DESTINATION_CHANNELS, false>
553 }),
554 };
555 }
556 }
557
558 #[cfg(feature = "avx")]
559 {
560 let use_avx2 = std::arch::is_x86_feature_detected!("avx2");
561 if use_avx2 {
562 use crate::avx2::avx2_yuv_nv_to_rgba_row420;
563 return NVRow420Handler {
564 handler: Some(avx2_yuv_nv_to_rgba_row420::<UV_ORDER, DESTINATION_CHANNELS>),
565 };
566 }
567 }
568
569 #[cfg(feature = "sse")]
570 {
571 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
572 if use_sse {
573 use crate::sse::sse_yuv_nv_to_rgba420;
574 return NVRow420Handler {
575 handler: Some(sse_yuv_nv_to_rgba420::<UV_ORDER, DESTINATION_CHANNELS>),
576 };
577 }
578 }
579 }
580 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
581 {
582 use crate::wasm32::wasm_yuv_nv_to_rgba_row420;
583 return NVRow420Handler {
584 handler: Some(
585 wasm_yuv_nv_to_rgba_row420::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING>,
586 ),
587 };
588 }
589 #[cfg(not(any(
590 all(target_arch = "aarch64", target_feature = "neon"),
591 all(target_arch = "wasm32", target_feature = "simd128")
592 )))]
593 NVRow420Handler { handler: None }
594 }
595}
596
597#[cfg(feature = "fast_mode")]
598impl<
599 const UV_ORDER: u8,
600 const DESTINATION_CHANNELS: u8,
601 const YUV_CHROMA_SAMPLING: u8,
602 const PRECISION: i32,
603 > Default
604 for NVRow420HandlerFast<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
605{
606 fn default() -> Self {
607 let sampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
608 if sampling != YuvChromaSubsampling::Yuv420 {
609 return NVRow420HandlerFast { handler: None };
610 }
611 assert_eq!(sampling, YuvChromaSubsampling::Yuv420);
612 if PRECISION == 6 {
613 assert_eq!(PRECISION, 6);
614 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
615 {
616 use crate::neon::neon_yuv_nv_to_rgba_fast_row420;
617 return NVRow420HandlerFast {
618 handler: Some(
619 neon_yuv_nv_to_rgba_fast_row420::<UV_ORDER, DESTINATION_CHANNELS>,
620 ),
621 };
622 }
623 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
624 {
625 #[cfg(feature = "nightly_avx512")]
626 {
627 let use_avx512 = std::arch::is_x86_feature_detected!("avx512bw");
628 let use_vbmi = std::arch::is_x86_feature_detected!("avx512vbmi");
629 if use_avx512 {
630 use crate::avx512bw::avx512_yuv_nv_to_rgba_fast420;
631 return NVRow420HandlerFast {
632 handler: Some(if use_vbmi {
633 avx512_yuv_nv_to_rgba_fast420::<UV_ORDER, DESTINATION_CHANNELS, true>
634 } else {
635 avx512_yuv_nv_to_rgba_fast420::<UV_ORDER, DESTINATION_CHANNELS, false>
636 }),
637 };
638 }
639 }
640
641 #[cfg(feature = "avx")]
642 {
643 let use_avx = std::arch::is_x86_feature_detected!("avx2");
644 if use_avx {
645 use crate::avx2::avx_yuv_nv_to_rgba_fast420;
646 return NVRow420HandlerFast {
647 handler: Some(
648 avx_yuv_nv_to_rgba_fast420::<UV_ORDER, DESTINATION_CHANNELS>,
649 ),
650 };
651 }
652 }
653
654 #[cfg(feature = "sse")]
655 {
656 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
657 if use_sse {
658 use crate::sse::sse_yuv_nv_to_rgba_fast420;
659 return NVRow420HandlerFast {
660 handler: Some(
661 sse_yuv_nv_to_rgba_fast420::<UV_ORDER, DESTINATION_CHANNELS>,
662 ),
663 };
664 }
665 }
666 }
667 }
668
669 NVRow420HandlerFast { handler: None }
670 }
671}
672
673#[cfg(feature = "professional_mode")]
674impl<
675 const UV_ORDER: u8,
676 const DESTINATION_CHANNELS: u8,
677 const YUV_CHROMA_SAMPLING: u8,
678 const PRECISION: i32,
679 > Default
680 for NVRow420HandlerProfessional<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, PRECISION>
681{
682 fn default() -> Self {
683 let sampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
684 if sampling != YuvChromaSubsampling::Yuv420 {
685 return NVRow420HandlerProfessional { handler: None };
686 }
687 assert_eq!(sampling, YuvChromaSubsampling::Yuv420);
688 if PRECISION == 14 {
689 assert_eq!(PRECISION, 14);
690 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
691 {
692 use crate::neon::neon_yuv_nv_to_rgba_row420_prof;
693 return NVRow420HandlerProfessional {
694 handler: Some(
695 neon_yuv_nv_to_rgba_row420_prof::<UV_ORDER, DESTINATION_CHANNELS>,
696 ),
697 };
698 }
699 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
700 {
701 #[cfg(feature = "avx")]
702 {
703 let use_avx = std::arch::is_x86_feature_detected!("avx2");
704 if use_avx {
705 use crate::avx2::avx2_yuv_nv_to_rgba_row420_prof;
706 return NVRow420HandlerProfessional {
707 handler: Some(
708 avx2_yuv_nv_to_rgba_row420_prof::<UV_ORDER, DESTINATION_CHANNELS>,
709 ),
710 };
711 }
712 }
713 #[cfg(feature = "sse")]
714 {
715 let use_sse = std::arch::is_x86_feature_detected!("sse4.1");
716 if use_sse {
717 use crate::sse::sse_yuv_nv_to_rgba_row420_prof;
718 return NVRow420HandlerProfessional {
719 handler: Some(
720 sse_yuv_nv_to_rgba_row420_prof::<UV_ORDER, DESTINATION_CHANNELS>,
721 ),
722 };
723 }
724 }
725 }
726 }
727
728 NVRow420HandlerProfessional { handler: None }
729 }
730}
731
732fn yuv_nv12_to_rgbx_impl<
733 const UV_ORDER: u8,
734 const DESTINATION_CHANNELS: u8,
735 const YUV_CHROMA_SAMPLING: u8,
736 const PRECISION: i32,
737>(
738 image: &YuvBiPlanarImage<u8>,
739 bgra: &mut [u8],
740 bgra_stride: u32,
741 range: YuvRange,
742 matrix: YuvStandardMatrix,
743 row_handler: impl RowBiPlanarInversionHandler<u8, i32> + Sync + Send,
744 row_handler420: impl RowBiPlanarInversion420Handler<u8, i32> + Sync + Send,
745) -> Result<(), YuvError> {
746 let order: YuvNVOrder = UV_ORDER.into();
747 let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
748 let chroma_subsampling: YuvChromaSubsampling = YUV_CHROMA_SAMPLING.into();
749
750 image.check_constraints(chroma_subsampling)?;
751 check_rgba_destination(
752 bgra,
753 bgra_stride,
754 image.width,
755 image.height,
756 dst_chans.get_channels_count(),
757 )?;
758
759 let chroma_range = get_yuv_range(8, range);
760 let channels = dst_chans.get_channels_count();
761 let kr_kb = matrix.get_kr_kb();
762
763 let inverse_transform =
764 search_inverse_transform(PRECISION, 8, range, matrix, chroma_range, kr_kb);
765 let cr_coef = inverse_transform.cr_coef;
766 let cb_coef = inverse_transform.cb_coef;
767 let y_coef = inverse_transform.y_coef;
768 let g_coef_1 = inverse_transform.g_coeff_1;
769 let g_coef_2 = inverse_transform.g_coeff_2;
770
771 let bias_y = chroma_range.bias_y as i32;
772 let bias_uv = chroma_range.bias_uv as i32;
773
774 let width = image.width;
775
776 let process_double_chroma_row =
777 |y_src0: &[u8], y_src1: &[u8], uv_src: &[u8], rgba0: &mut [u8], rgba1: &mut [u8]| {
778 let processed = row_handler420.handle_row(
779 y_src0,
780 y_src1,
781 uv_src,
782 rgba0,
783 rgba1,
784 width,
785 chroma_range,
786 &inverse_transform,
787 );
788 if processed.cx != image.width as usize {
789 for ((((rgba0, rgba1), y_src0), y_src1), uv_src) in rgba0
790 .chunks_exact_mut(channels * 2)
791 .zip(rgba1.chunks_exact_mut(channels * 2))
792 .zip(y_src0.chunks_exact(2))
793 .zip(y_src1.chunks_exact(2))
794 .zip(uv_src.chunks_exact(2))
795 .skip(processed.cx / 2)
796 {
797 let y_vl00 = y_src0[0] as i32;
798 let cb_value = (uv_src[order.get_u_position()] as i32) - bias_uv;
799 let cr_value = (uv_src[order.get_v_position()] as i32) - bias_uv;
800
801 let y_value00: i32 = (y_vl00 - bias_y) * y_coef;
802
803 let g_built_coeff = -g_coef_1 * cr_value - g_coef_2 * cb_value;
804
805 let r00 = qrshr::<PRECISION, 8>(y_value00 + cr_coef * cr_value);
806 let b00 = qrshr::<PRECISION, 8>(y_value00 + cb_coef * cb_value);
807 let g00 = qrshr::<PRECISION, 8>(y_value00 + g_built_coeff);
808
809 let rgba00 = &mut rgba0[0..channels];
810
811 rgba00[dst_chans.get_b_channel_offset()] = b00 as u8;
812 rgba00[dst_chans.get_g_channel_offset()] = g00 as u8;
813 rgba00[dst_chans.get_r_channel_offset()] = r00 as u8;
814
815 if dst_chans.has_alpha() {
816 rgba00[dst_chans.get_a_channel_offset()] = 255;
817 }
818
819 let y_vl01 = y_src0[1] as i32;
820
821 let y_value01: i32 = (y_vl01 - bias_y) * y_coef;
822
823 let r01 = qrshr::<PRECISION, 8>(y_value01 + cr_coef * cr_value);
824 let b01 = qrshr::<PRECISION, 8>(y_value01 + cb_coef * cb_value);
825 let g01 = qrshr::<PRECISION, 8>(y_value01 + g_built_coeff);
826
827 let rgba01 = &mut rgba0[channels..channels * 2];
828
829 rgba01[dst_chans.get_b_channel_offset()] = b01 as u8;
830 rgba01[dst_chans.get_g_channel_offset()] = g01 as u8;
831 rgba01[dst_chans.get_r_channel_offset()] = r01 as u8;
832
833 if dst_chans.has_alpha() {
834 rgba01[dst_chans.get_a_channel_offset()] = 255;
835 }
836
837 let y_vl10 = y_src1[0] as i32;
838
839 let y_value00: i32 = (y_vl10 - bias_y) * y_coef;
840
841 let r10 = qrshr::<PRECISION, 8>(y_value00 + cr_coef * cr_value);
842 let b10 = qrshr::<PRECISION, 8>(y_value00 + cb_coef * cb_value);
843 let g10 = qrshr::<PRECISION, 8>(y_value00 + g_built_coeff);
844
845 let rgba10 = &mut rgba1[0..channels];
846
847 rgba10[dst_chans.get_b_channel_offset()] = b10 as u8;
848 rgba10[dst_chans.get_g_channel_offset()] = g10 as u8;
849 rgba10[dst_chans.get_r_channel_offset()] = r10 as u8;
850
851 if dst_chans.has_alpha() {
852 rgba10[dst_chans.get_a_channel_offset()] = 255;
853 }
854
855 let y_vl11 = y_src1[1] as i32;
856
857 let y_value11: i32 = (y_vl11 - bias_y) * y_coef;
858
859 let r11 = qrshr::<PRECISION, 8>(y_value11 + cr_coef * cr_value);
860 let b11 = qrshr::<PRECISION, 8>(y_value11 + cb_coef * cb_value);
861 let g11 = qrshr::<PRECISION, 8>(y_value11 + g_built_coeff);
862
863 let rgba11 = &mut rgba1[channels..channels * 2];
864
865 rgba11[dst_chans.get_b_channel_offset()] = b11 as u8;
866 rgba11[dst_chans.get_g_channel_offset()] = g11 as u8;
867 rgba11[dst_chans.get_r_channel_offset()] = r11 as u8;
868
869 if dst_chans.has_alpha() {
870 rgba11[dst_chans.get_a_channel_offset()] = 255;
871 }
872 }
873
874 if width & 1 != 0 {
875 let rgba0 = rgba0.chunks_exact_mut(channels * 2).into_remainder();
876 let rgba1 = rgba1.chunks_exact_mut(channels * 2).into_remainder();
877 let rgba0 = &mut rgba0[0..channels];
878 let rgba1 = &mut rgba1[0..channels];
879 let uv_src = uv_src.chunks_exact(2).last().unwrap();
880 let y_src0 = y_src0.chunks_exact(2).remainder();
881 let y_src1 = y_src1.chunks_exact(2).remainder();
882
883 let y_vl0 = y_src0[0] as i32;
884 let y_value0: i32 = (y_vl0 - bias_y) * y_coef;
885 let cb_value = (uv_src[order.get_u_position()] as i32) - bias_uv;
886 let cr_value = (uv_src[order.get_v_position()] as i32) - bias_uv;
887
888 let g_built_coeff = -g_coef_1 * cr_value - g_coef_2 * cb_value;
889
890 let r0 = qrshr::<PRECISION, 8>(y_value0 + cr_coef * cr_value);
891 let b0 = qrshr::<PRECISION, 8>(y_value0 + cb_coef * cb_value);
892 let g0 = qrshr::<PRECISION, 8>(y_value0 + g_built_coeff);
893
894 rgba0[dst_chans.get_b_channel_offset()] = b0 as u8;
895 rgba0[dst_chans.get_g_channel_offset()] = g0 as u8;
896 rgba0[dst_chans.get_r_channel_offset()] = r0 as u8;
897
898 if dst_chans.has_alpha() {
899 rgba0[dst_chans.get_a_channel_offset()] = 255;
900 }
901
902 let y_vl1 = y_src1[0] as i32;
903 let y_value1: i32 = (y_vl1 - bias_y) * y_coef;
904
905 let r1 = qrshr::<PRECISION, 8>(y_value1 + cr_coef * cr_value);
906 let b1 = qrshr::<PRECISION, 8>(y_value1 + cb_coef * cb_value);
907 let g1 = qrshr::<PRECISION, 8>(y_value1 + g_built_coeff);
908
909 rgba1[dst_chans.get_b_channel_offset()] = b1 as u8;
910 rgba1[dst_chans.get_g_channel_offset()] = g1 as u8;
911 rgba1[dst_chans.get_r_channel_offset()] = r1 as u8;
912
913 if dst_chans.has_alpha() {
914 rgba1[dst_chans.get_a_channel_offset()] = 255;
915 }
916 }
917 }
918 };
919
920 let process_halved_chroma_row = |y_src: &[u8], uv_src: &[u8], rgba: &mut [u8]| {
921 let processed =
922 row_handler.handle_row(y_src, uv_src, rgba, width, chroma_range, &inverse_transform);
923
924 if processed.cx != image.width as usize {
925 for ((rgba, y_src), uv_src) in rgba
926 .chunks_exact_mut(channels * 2)
927 .zip(y_src.chunks_exact(2))
928 .zip(uv_src.chunks_exact(2))
929 .skip(processed.cx / 2)
930 {
931 let y_vl0 = y_src[0] as i32;
932 let cb_value = (uv_src[order.get_u_position()] as i32) - bias_uv;
933 let cr_value = (uv_src[order.get_v_position()] as i32) - bias_uv;
934
935 let y_value0: i32 = (y_vl0 - bias_y) * y_coef;
936
937 let r0 = qrshr::<PRECISION, 8>(y_value0 + cr_coef * cr_value);
938 let b0 = qrshr::<PRECISION, 8>(y_value0 + cb_coef * cb_value);
939 let g0 =
940 qrshr::<PRECISION, 8>(y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value);
941
942 rgba[dst_chans.get_b_channel_offset()] = b0 as u8;
943 rgba[dst_chans.get_g_channel_offset()] = g0 as u8;
944 rgba[dst_chans.get_r_channel_offset()] = r0 as u8;
945
946 if dst_chans.has_alpha() {
947 rgba[dst_chans.get_a_channel_offset()] = 255;
948 }
949
950 let y_vl1 = y_src[1] as i32;
951
952 let y_value1: i32 = (y_vl1 - bias_y) * y_coef;
953
954 let r1 = qrshr::<PRECISION, 8>(y_value1 + cr_coef * cr_value);
955 let b1 = qrshr::<PRECISION, 8>(y_value1 + cb_coef * cb_value);
956 let g1 =
957 qrshr::<PRECISION, 8>(y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value);
958
959 let rgba0 = &mut rgba[channels..channels * 2];
960
961 rgba0[dst_chans.get_b_channel_offset()] = b1 as u8;
962 rgba0[dst_chans.get_g_channel_offset()] = g1 as u8;
963 rgba0[dst_chans.get_r_channel_offset()] = r1 as u8;
964
965 if dst_chans.has_alpha() {
966 rgba0[dst_chans.get_a_channel_offset()] = 255;
967 }
968 }
969
970 if width & 1 != 0 {
971 let rgba = rgba.chunks_exact_mut(channels * 2).into_remainder();
972 let rgba = &mut rgba[0..channels];
973 let uv_src = uv_src.chunks_exact(2).last().unwrap();
974 let y_src = y_src.chunks_exact(2).remainder();
975
976 let y_vl0 = y_src[0] as i32;
977 let y_value0: i32 = (y_vl0 - bias_y) * y_coef;
978 let cb_value = (uv_src[order.get_u_position()] as i32) - bias_uv;
979 let cr_value = (uv_src[order.get_v_position()] as i32) - bias_uv;
980
981 let r0 = qrshr::<PRECISION, 8>(y_value0 + cr_coef * cr_value);
982 let b0 = qrshr::<PRECISION, 8>(y_value0 + cb_coef * cb_value);
983 let g0 =
984 qrshr::<PRECISION, 8>(y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value);
985
986 rgba[dst_chans.get_b_channel_offset()] = b0 as u8;
987 rgba[dst_chans.get_g_channel_offset()] = g0 as u8;
988 rgba[dst_chans.get_r_channel_offset()] = r0 as u8;
989
990 if dst_chans.has_alpha() {
991 rgba[dst_chans.get_a_channel_offset()] = 255;
992 }
993 }
994 }
995 };
996
997 let y_stride = image.y_stride;
998 let uv_stride = image.uv_stride;
999 let y_plane = image.y_plane;
1000 let uv_plane = image.uv_plane;
1001
1002 if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
1003 let iter;
1004 #[cfg(feature = "rayon")]
1005 {
1006 iter = y_plane
1007 .par_chunks_exact(y_stride as usize)
1008 .zip(uv_plane.par_chunks_exact(uv_stride as usize))
1009 .zip(bgra.par_chunks_exact_mut(bgra_stride as usize));
1010 }
1011 #[cfg(not(feature = "rayon"))]
1012 {
1013 iter = y_plane
1014 .chunks_exact(y_stride as usize)
1015 .zip(uv_plane.chunks_exact(uv_stride as usize))
1016 .zip(bgra.chunks_exact_mut(bgra_stride as usize));
1017 }
1018 iter.for_each(|((y_src, uv_src), rgba)| {
1019 let y_src = &y_src[0..image.width as usize];
1020 let processed = row_handler.handle_row(
1021 y_src,
1022 uv_src,
1023 rgba,
1024 width,
1025 chroma_range,
1026 &inverse_transform,
1027 );
1028
1029 for ((rgba, &y_src), uv_src) in rgba
1030 .chunks_exact_mut(channels)
1031 .zip(y_src.iter())
1032 .zip(uv_src.chunks_exact(2))
1033 .skip(processed.cx)
1034 {
1035 let y_vl = y_src as i32;
1036 let mut cb_value = uv_src[order.get_u_position()] as i32;
1037 let mut cr_value = uv_src[order.get_v_position()] as i32;
1038
1039 let y_value: i32 = (y_vl - bias_y) * y_coef;
1040
1041 cb_value -= bias_uv;
1042 cr_value -= bias_uv;
1043
1044 let r = qrshr::<PRECISION, 8>(y_value + cr_coef * cr_value);
1045 let b = qrshr::<PRECISION, 8>(y_value + cb_coef * cb_value);
1046 let g = qrshr::<PRECISION, 8>(y_value - g_coef_1 * cr_value - g_coef_2 * cb_value);
1047
1048 rgba[dst_chans.get_b_channel_offset()] = b as u8;
1049 rgba[dst_chans.get_g_channel_offset()] = g as u8;
1050 rgba[dst_chans.get_r_channel_offset()] = r as u8;
1051
1052 if dst_chans.has_alpha() {
1053 rgba[dst_chans.get_a_channel_offset()] = 255;
1054 }
1055 }
1056 });
1057 } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
1058 let iter;
1059 #[cfg(feature = "rayon")]
1060 {
1061 iter = y_plane
1062 .par_chunks_exact(y_stride as usize)
1063 .zip(uv_plane.par_chunks_exact(uv_stride as usize))
1064 .zip(bgra.par_chunks_exact_mut(bgra_stride as usize));
1065 }
1066 #[cfg(not(feature = "rayon"))]
1067 {
1068 iter = y_plane
1069 .chunks_exact(y_stride as usize)
1070 .zip(uv_plane.chunks_exact(uv_stride as usize))
1071 .zip(bgra.chunks_exact_mut(bgra_stride as usize));
1072 }
1073 iter.for_each(|((y_src, uv_src), rgba)| {
1074 process_halved_chroma_row(
1075 &y_src[0..image.width as usize],
1076 &uv_src[0..(image.width as usize).div_ceil(2) * 2],
1077 &mut rgba[0..image.width as usize * channels],
1078 );
1079 });
1080 } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
1081 let iter;
1082 #[cfg(feature = "rayon")]
1083 {
1084 iter = y_plane
1085 .par_chunks_exact(y_stride as usize * 2)
1086 .zip(uv_plane.par_chunks_exact(uv_stride as usize))
1087 .zip(bgra.par_chunks_exact_mut(bgra_stride as usize * 2));
1088 }
1089 #[cfg(not(feature = "rayon"))]
1090 {
1091 iter = y_plane
1092 .chunks_exact(y_stride as usize * 2)
1093 .zip(uv_plane.chunks_exact(uv_stride as usize))
1094 .zip(bgra.chunks_exact_mut(bgra_stride as usize * 2));
1095 }
1096 iter.for_each(|((y_src, uv_src), rgba)| {
1097 let (y_src0, y_src1) = y_src.split_at(y_stride as usize);
1098 let (rgba0, rgba1) = rgba.split_at_mut(bgra_stride as usize);
1099 process_double_chroma_row(
1100 &y_src0[0..image.width as usize],
1101 &y_src1[0..image.width as usize],
1102 &uv_src[0..(image.width as usize).div_ceil(2) * 2],
1103 &mut rgba0[0..image.width as usize * channels],
1104 &mut rgba1[0..image.width as usize * channels],
1105 );
1106 });
1107 if image.height & 1 != 0 {
1108 let y_src = y_plane.chunks_exact(y_stride as usize * 2).remainder();
1109 let uv_src = uv_plane.chunks_exact(uv_stride as usize).last().unwrap();
1110 let rgba = bgra
1111 .chunks_exact_mut(bgra_stride as usize * 2)
1112 .into_remainder();
1113 process_halved_chroma_row(
1114 &y_src[0..image.width as usize],
1115 &uv_src[0..(image.width as usize).div_ceil(2) * 2],
1116 &mut rgba[0..image.width as usize * channels],
1117 );
1118 }
1119 } else {
1120 unreachable!();
1121 }
1122
1123 Ok(())
1124}
1125
1126fn yuv_nv12_to_rgbx<
1127 const UV_ORDER: u8,
1128 const DESTINATION_CHANNELS: u8,
1129 const YUV_CHROMA_SAMPLING: u8,
1130>(
1131 image: &YuvBiPlanarImage<u8>,
1132 bgra: &mut [u8],
1133 bgra_stride: u32,
1134 range: YuvRange,
1135 matrix: YuvStandardMatrix,
1136 _mode: YuvConversionMode,
1137) -> Result<(), YuvError> {
1138 match _mode {
1139 #[cfg(feature = "fast_mode")]
1140 YuvConversionMode::Fast => {
1141 yuv_nv12_to_rgbx_impl::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 6>(
1142 image,
1143 bgra,
1144 bgra_stride,
1145 range,
1146 matrix,
1147 NVRowHandlerFast::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 6>::default(
1148 ),
1149 NVRow420HandlerFast::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 6>::default(
1150 ),
1151 )
1152 }
1153 YuvConversionMode::Balanced => {
1154 yuv_nv12_to_rgbx_impl::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 13>(
1155 image,
1156 bgra,
1157 bgra_stride,
1158 range,
1159 matrix,
1160 NVRowHandler::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 13>::default(),
1161 NVRow420Handler::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 13>::default(
1162 ),
1163 )
1164 }
1165 #[cfg(feature = "professional_mode")]
1166 YuvConversionMode::Professional => {
1167 yuv_nv12_to_rgbx_impl::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 14>(
1168 image,
1169 bgra,
1170 bgra_stride,
1171 range,
1172 matrix,
1173 NVRowHandlerProfessional::<UV_ORDER, DESTINATION_CHANNELS, YUV_CHROMA_SAMPLING, 14>::default(),
1174 NVRow420HandlerProfessional::<
1175 UV_ORDER,
1176 DESTINATION_CHANNELS,
1177 YUV_CHROMA_SAMPLING,
1178 14,
1179 >::default(),
1180 )
1181 }
1182 }
1183}
1184
1185pub fn yuv_nv12_to_bgra(
1204 bi_planar_image: &YuvBiPlanarImage<u8>,
1205 bgra: &mut [u8],
1206 bgra_stride: u32,
1207 range: YuvRange,
1208 matrix: YuvStandardMatrix,
1209 mode: YuvConversionMode,
1210) -> Result<(), YuvError> {
1211 yuv_nv12_to_rgbx::<
1212 { YuvNVOrder::UV as u8 },
1213 { YuvSourceChannels::Bgra as u8 },
1214 { YuvChromaSubsampling::Yuv420 as u8 },
1215 >(bi_planar_image, bgra, bgra_stride, range, matrix, mode)
1216}
1217
1218pub fn yuv_nv16_to_bgra(
1237 bi_planar_image: &YuvBiPlanarImage<u8>,
1238 bgra: &mut [u8],
1239 bgra_stride: u32,
1240 range: YuvRange,
1241 matrix: YuvStandardMatrix,
1242 mode: YuvConversionMode,
1243) -> Result<(), YuvError> {
1244 yuv_nv12_to_rgbx::<
1245 { YuvNVOrder::UV as u8 },
1246 { YuvSourceChannels::Bgra as u8 },
1247 { YuvChromaSubsampling::Yuv422 as u8 },
1248 >(bi_planar_image, bgra, bgra_stride, range, matrix, mode)
1249}
1250
1251pub fn yuv_nv61_to_bgra(
1270 bi_planar_image: &YuvBiPlanarImage<u8>,
1271 bgra: &mut [u8],
1272 bgra_stride: u32,
1273 range: YuvRange,
1274 matrix: YuvStandardMatrix,
1275 mode: YuvConversionMode,
1276) -> Result<(), YuvError> {
1277 yuv_nv12_to_rgbx::<
1278 { YuvNVOrder::VU as u8 },
1279 { YuvSourceChannels::Bgra as u8 },
1280 { YuvChromaSubsampling::Yuv422 as u8 },
1281 >(bi_planar_image, bgra, bgra_stride, range, matrix, mode)
1282}
1283
1284pub fn yuv_nv21_to_bgra(
1303 bi_planar_image: &YuvBiPlanarImage<u8>,
1304 bgra: &mut [u8],
1305 bgra_stride: u32,
1306 range: YuvRange,
1307 matrix: YuvStandardMatrix,
1308 mode: YuvConversionMode,
1309) -> Result<(), YuvError> {
1310 yuv_nv12_to_rgbx::<
1311 { YuvNVOrder::VU as u8 },
1312 { YuvSourceChannels::Bgra as u8 },
1313 { YuvChromaSubsampling::Yuv420 as u8 },
1314 >(bi_planar_image, bgra, bgra_stride, range, matrix, mode)
1315}
1316
1317pub fn yuv_nv16_to_rgba(
1336 bi_planar_image: &YuvBiPlanarImage<u8>,
1337 rgba: &mut [u8],
1338 rgba_stride: u32,
1339 range: YuvRange,
1340 matrix: YuvStandardMatrix,
1341 mode: YuvConversionMode,
1342) -> Result<(), YuvError> {
1343 yuv_nv12_to_rgbx::<
1344 { YuvNVOrder::UV as u8 },
1345 { YuvSourceChannels::Rgba as u8 },
1346 { YuvChromaSubsampling::Yuv422 as u8 },
1347 >(bi_planar_image, rgba, rgba_stride, range, matrix, mode)
1348}
1349
1350pub fn yuv_nv61_to_rgba(
1369 bi_planar_image: &YuvBiPlanarImage<u8>,
1370 rgba: &mut [u8],
1371 rgba_stride: u32,
1372 range: YuvRange,
1373 matrix: YuvStandardMatrix,
1374 mode: YuvConversionMode,
1375) -> Result<(), YuvError> {
1376 yuv_nv12_to_rgbx::<
1377 { YuvNVOrder::VU as u8 },
1378 { YuvSourceChannels::Rgba as u8 },
1379 { YuvChromaSubsampling::Yuv422 as u8 },
1380 >(bi_planar_image, rgba, rgba_stride, range, matrix, mode)
1381}
1382
1383pub fn yuv_nv12_to_rgba(
1402 bi_planar_image: &YuvBiPlanarImage<u8>,
1403 rgba: &mut [u8],
1404 rgba_stride: u32,
1405 range: YuvRange,
1406 matrix: YuvStandardMatrix,
1407 mode: YuvConversionMode,
1408) -> Result<(), YuvError> {
1409 yuv_nv12_to_rgbx::<
1410 { YuvNVOrder::UV as u8 },
1411 { YuvSourceChannels::Rgba as u8 },
1412 { YuvChromaSubsampling::Yuv420 as u8 },
1413 >(bi_planar_image, rgba, rgba_stride, range, matrix, mode)
1414}
1415
1416pub fn yuv_nv21_to_rgba(
1435 bi_planar_image: &YuvBiPlanarImage<u8>,
1436 rgba: &mut [u8],
1437 rgba_stride: u32,
1438 range: YuvRange,
1439 matrix: YuvStandardMatrix,
1440 mode: YuvConversionMode,
1441) -> Result<(), YuvError> {
1442 yuv_nv12_to_rgbx::<
1443 { YuvNVOrder::VU as u8 },
1444 { YuvSourceChannels::Rgba as u8 },
1445 { YuvChromaSubsampling::Yuv420 as u8 },
1446 >(bi_planar_image, rgba, rgba_stride, range, matrix, mode)
1447}
1448
1449pub fn yuv_nv12_to_rgb(
1468 bi_planar_image: &YuvBiPlanarImage<u8>,
1469 rgb: &mut [u8],
1470 rgb_stride: u32,
1471 range: YuvRange,
1472 matrix: YuvStandardMatrix,
1473 mode: YuvConversionMode,
1474) -> Result<(), YuvError> {
1475 yuv_nv12_to_rgbx::<
1476 { YuvNVOrder::UV as u8 },
1477 { YuvSourceChannels::Rgb as u8 },
1478 { YuvChromaSubsampling::Yuv420 as u8 },
1479 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1480}
1481
1482pub fn yuv_nv12_to_bgr(
1501 bi_planar_image: &YuvBiPlanarImage<u8>,
1502 bgr: &mut [u8],
1503 bgr_stride: u32,
1504 range: YuvRange,
1505 matrix: YuvStandardMatrix,
1506 mode: YuvConversionMode,
1507) -> Result<(), YuvError> {
1508 yuv_nv12_to_rgbx::<
1509 { YuvNVOrder::UV as u8 },
1510 { YuvSourceChannels::Bgr as u8 },
1511 { YuvChromaSubsampling::Yuv420 as u8 },
1512 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1513}
1514
1515pub fn yuv_nv16_to_rgb(
1534 bi_planar_image: &YuvBiPlanarImage<u8>,
1535 rgb: &mut [u8],
1536 rgb_stride: u32,
1537 range: YuvRange,
1538 matrix: YuvStandardMatrix,
1539 mode: YuvConversionMode,
1540) -> Result<(), YuvError> {
1541 yuv_nv12_to_rgbx::<
1542 { YuvNVOrder::UV as u8 },
1543 { YuvSourceChannels::Rgb as u8 },
1544 { YuvChromaSubsampling::Yuv422 as u8 },
1545 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1546}
1547
1548pub fn yuv_nv16_to_bgr(
1567 bi_planar_image: &YuvBiPlanarImage<u8>,
1568 bgr: &mut [u8],
1569 bgr_stride: u32,
1570 range: YuvRange,
1571 matrix: YuvStandardMatrix,
1572 mode: YuvConversionMode,
1573) -> Result<(), YuvError> {
1574 yuv_nv12_to_rgbx::<
1575 { YuvNVOrder::UV as u8 },
1576 { YuvSourceChannels::Bgr as u8 },
1577 { YuvChromaSubsampling::Yuv422 as u8 },
1578 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1579}
1580
1581pub fn yuv_nv61_to_rgb(
1600 bi_planar_image: &YuvBiPlanarImage<u8>,
1601 rgb: &mut [u8],
1602 rgb_stride: u32,
1603 range: YuvRange,
1604 matrix: YuvStandardMatrix,
1605 mode: YuvConversionMode,
1606) -> Result<(), YuvError> {
1607 yuv_nv12_to_rgbx::<
1608 { YuvNVOrder::VU as u8 },
1609 { YuvSourceChannels::Rgb as u8 },
1610 { YuvChromaSubsampling::Yuv422 as u8 },
1611 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1612}
1613
1614pub fn yuv_nv61_to_bgr(
1633 bi_planar_image: &YuvBiPlanarImage<u8>,
1634 bgr: &mut [u8],
1635 bgr_stride: u32,
1636 range: YuvRange,
1637 matrix: YuvStandardMatrix,
1638 mode: YuvConversionMode,
1639) -> Result<(), YuvError> {
1640 yuv_nv12_to_rgbx::<
1641 { YuvNVOrder::VU as u8 },
1642 { YuvSourceChannels::Bgr as u8 },
1643 { YuvChromaSubsampling::Yuv422 as u8 },
1644 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1645}
1646
1647pub fn yuv_nv21_to_rgb(
1666 bi_planar_image: &YuvBiPlanarImage<u8>,
1667 rgb: &mut [u8],
1668 rgb_stride: u32,
1669 range: YuvRange,
1670 matrix: YuvStandardMatrix,
1671 mode: YuvConversionMode,
1672) -> Result<(), YuvError> {
1673 yuv_nv12_to_rgbx::<
1674 { YuvNVOrder::VU as u8 },
1675 { YuvSourceChannels::Rgb as u8 },
1676 { YuvChromaSubsampling::Yuv420 as u8 },
1677 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1678}
1679
1680pub fn yuv_nv21_to_bgr(
1699 bi_planar_image: &YuvBiPlanarImage<u8>,
1700 bgr: &mut [u8],
1701 bgr_stride: u32,
1702 range: YuvRange,
1703 matrix: YuvStandardMatrix,
1704 mode: YuvConversionMode,
1705) -> Result<(), YuvError> {
1706 yuv_nv12_to_rgbx::<
1707 { YuvNVOrder::VU as u8 },
1708 { YuvSourceChannels::Bgr as u8 },
1709 { YuvChromaSubsampling::Yuv420 as u8 },
1710 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1711}
1712
1713pub fn yuv_nv42_to_rgba(
1732 bi_planar_image: &YuvBiPlanarImage<u8>,
1733 rgba: &mut [u8],
1734 rgba_stride: u32,
1735 range: YuvRange,
1736 matrix: YuvStandardMatrix,
1737 mode: YuvConversionMode,
1738) -> Result<(), YuvError> {
1739 yuv_nv12_to_rgbx::<
1740 { YuvNVOrder::VU as u8 },
1741 { YuvSourceChannels::Rgba as u8 },
1742 { YuvChromaSubsampling::Yuv444 as u8 },
1743 >(bi_planar_image, rgba, rgba_stride, range, matrix, mode)
1744}
1745
1746pub fn yuv_nv24_to_rgb(
1765 bi_planar_image: &YuvBiPlanarImage<u8>,
1766 rgb: &mut [u8],
1767 rgb_stride: u32,
1768 range: YuvRange,
1769 matrix: YuvStandardMatrix,
1770 mode: YuvConversionMode,
1771) -> Result<(), YuvError> {
1772 yuv_nv12_to_rgbx::<
1773 { YuvNVOrder::UV as u8 },
1774 { YuvSourceChannels::Rgb as u8 },
1775 { YuvChromaSubsampling::Yuv444 as u8 },
1776 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1777}
1778
1779pub fn yuv_nv24_to_bgr(
1798 bi_planar_image: &YuvBiPlanarImage<u8>,
1799 bgr: &mut [u8],
1800 bgr_stride: u32,
1801 range: YuvRange,
1802 matrix: YuvStandardMatrix,
1803 mode: YuvConversionMode,
1804) -> Result<(), YuvError> {
1805 yuv_nv12_to_rgbx::<
1806 { YuvNVOrder::UV as u8 },
1807 { YuvSourceChannels::Bgr as u8 },
1808 { YuvChromaSubsampling::Yuv444 as u8 },
1809 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1810}
1811
1812pub fn yuv_nv24_to_rgba(
1831 bi_planar_image: &YuvBiPlanarImage<u8>,
1832 rgb: &mut [u8],
1833 rgb_stride: u32,
1834 range: YuvRange,
1835 matrix: YuvStandardMatrix,
1836 mode: YuvConversionMode,
1837) -> Result<(), YuvError> {
1838 yuv_nv12_to_rgbx::<
1839 { YuvNVOrder::UV as u8 },
1840 { YuvSourceChannels::Rgba as u8 },
1841 { YuvChromaSubsampling::Yuv444 as u8 },
1842 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1843}
1844
1845pub fn yuv_nv24_to_bgra(
1864 bi_planar_image: &YuvBiPlanarImage<u8>,
1865 rgb: &mut [u8],
1866 rgb_stride: u32,
1867 range: YuvRange,
1868 matrix: YuvStandardMatrix,
1869 mode: YuvConversionMode,
1870) -> Result<(), YuvError> {
1871 yuv_nv12_to_rgbx::<
1872 { YuvNVOrder::UV as u8 },
1873 { YuvSourceChannels::Bgra as u8 },
1874 { YuvChromaSubsampling::Yuv444 as u8 },
1875 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1876}
1877
1878pub fn yuv_nv42_to_rgb(
1897 bi_planar_image: &YuvBiPlanarImage<u8>,
1898 rgb: &mut [u8],
1899 rgb_stride: u32,
1900 range: YuvRange,
1901 matrix: YuvStandardMatrix,
1902 mode: YuvConversionMode,
1903) -> Result<(), YuvError> {
1904 yuv_nv12_to_rgbx::<
1905 { YuvNVOrder::VU as u8 },
1906 { YuvSourceChannels::Rgb as u8 },
1907 { YuvChromaSubsampling::Yuv444 as u8 },
1908 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1909}
1910
1911pub fn yuv_nv42_to_bgr(
1930 bi_planar_image: &YuvBiPlanarImage<u8>,
1931 bgr: &mut [u8],
1932 bgr_stride: u32,
1933 range: YuvRange,
1934 matrix: YuvStandardMatrix,
1935 mode: YuvConversionMode,
1936) -> Result<(), YuvError> {
1937 yuv_nv12_to_rgbx::<
1938 { YuvNVOrder::VU as u8 },
1939 { YuvSourceChannels::Bgr as u8 },
1940 { YuvChromaSubsampling::Yuv444 as u8 },
1941 >(bi_planar_image, bgr, bgr_stride, range, matrix, mode)
1942}
1943
1944pub fn yuv_nv42_to_bgra(
1963 bi_planar_image: &YuvBiPlanarImage<u8>,
1964 rgb: &mut [u8],
1965 rgb_stride: u32,
1966 range: YuvRange,
1967 matrix: YuvStandardMatrix,
1968 mode: YuvConversionMode,
1969) -> Result<(), YuvError> {
1970 yuv_nv12_to_rgbx::<
1971 { YuvNVOrder::VU as u8 },
1972 { YuvSourceChannels::Bgra as u8 },
1973 { YuvChromaSubsampling::Yuv444 as u8 },
1974 >(bi_planar_image, rgb, rgb_stride, range, matrix, mode)
1975}
1976
1977#[cfg(test)]
1978mod tests {
1979 use super::*;
1980 use crate::{rgb_to_yuv_nv12, rgb_to_yuv_nv16, rgb_to_yuv_nv24, YuvBiPlanarImageMut};
1981 use rand::Rng;
1982
1983 #[test]
1984 fn test_yuv444_nv_round_trip_full_range() {
1985 fn matrix(mode: YuvConversionMode, max_diff: i32) {
1986 let image_width = 256usize;
1987 let image_height = 256usize;
1988
1989 let random_point_x = rand::rng().random_range(0..image_width);
1990 let random_point_y = rand::rng().random_range(0..image_height);
1991
1992 const CHANNELS: usize = 3;
1993
1994 let pixel_points = [
1995 [0, 0],
1996 [image_width - 1, image_height - 1],
1997 [image_width - 1, 0],
1998 [0, image_height - 1],
1999 [(image_width - 1) / 2, (image_height - 1) / 2],
2000 [image_width / 5, image_height / 5],
2001 [0, image_height / 5],
2002 [image_width / 5, 0],
2003 [image_width / 5 * 3, image_height / 5],
2004 [image_width / 5 * 3, image_height / 5 * 3],
2005 [image_width / 5, image_height / 5 * 3],
2006 [random_point_x, random_point_y],
2007 ];
2008
2009 let mut image_rgb = vec![0u8; image_width * image_height * 3];
2010
2011 let or = rand::rng().random_range(0..256) as u8;
2012 let og = rand::rng().random_range(0..256) as u8;
2013 let ob = rand::rng().random_range(0..256) as u8;
2014
2015 for point in &pixel_points {
2016 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2017 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2018 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2019 }
2020
2021 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2022 image_width as u32,
2023 image_height as u32,
2024 YuvChromaSubsampling::Yuv444,
2025 );
2026
2027 rgb_to_yuv_nv24(
2028 &mut planar_image,
2029 &image_rgb,
2030 image_width as u32 * CHANNELS as u32,
2031 YuvRange::Full,
2032 YuvStandardMatrix::Bt709,
2033 mode,
2034 )
2035 .unwrap();
2036
2037 image_rgb.fill(0);
2038
2039 let fixed_planar = planar_image.to_fixed();
2040
2041 yuv_nv24_to_rgb(
2042 &fixed_planar,
2043 &mut image_rgb,
2044 image_width as u32 * CHANNELS as u32,
2045 YuvRange::Full,
2046 YuvStandardMatrix::Bt709,
2047 mode,
2048 )
2049 .unwrap();
2050
2051 for point in &pixel_points {
2052 let x = point[0];
2053 let y = point[1];
2054 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
2055 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
2056 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
2057
2058 let diff_r = (r as i32 - or as i32).abs();
2059 let diff_g = (g as i32 - og as i32).abs();
2060 let diff_b = (b as i32 - ob as i32).abs();
2061
2062 assert!(
2063 diff_r <= max_diff,
2064 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
2065 mode,
2066 [or, og, ob],
2067 [r, g, b],
2068 diff_r,
2069 );
2070 assert!(
2071 diff_g <= max_diff,
2072 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
2073 mode,
2074 [or, og, ob],
2075 [r, g, b],
2076 diff_g
2077 );
2078 assert!(
2079 diff_b <= max_diff,
2080 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, diff {}",
2081 mode,
2082 [or, og, ob],
2083 [r, g, b],
2084 diff_b
2085 );
2086 }
2087 }
2088 matrix(YuvConversionMode::Balanced, 3);
2089 #[cfg(feature = "fast_mode")]
2090 matrix(YuvConversionMode::Fast, 6);
2091 #[cfg(feature = "professional_mode")]
2092 matrix(YuvConversionMode::Professional, 3);
2093 }
2094
2095 #[test]
2096 fn test_yuv444_nv_round_trip_limited_range() {
2097 fn matrix(mode: YuvConversionMode, max_diff: i32) {
2098 let image_width = 256usize;
2099 let image_height = 256usize;
2100
2101 let random_point_x = rand::rng().random_range(0..image_width);
2102 let random_point_y = rand::rng().random_range(0..image_height);
2103
2104 let pixel_points = [
2105 [0, 0],
2106 [image_width - 1, image_height - 1],
2107 [image_width - 1, 0],
2108 [0, image_height - 1],
2109 [(image_width - 1) / 2, (image_height - 1) / 2],
2110 [image_width / 5, image_height / 5],
2111 [0, image_height / 5],
2112 [image_width / 5, 0],
2113 [image_width / 5 * 3, image_height / 5],
2114 [image_width / 5 * 3, image_height / 5 * 3],
2115 [image_width / 5, image_height / 5 * 3],
2116 [random_point_x, random_point_y],
2117 ];
2118
2119 const CHANNELS: usize = 3;
2120
2121 let mut image_rgb = vec![0u8; image_width * image_height * CHANNELS];
2122
2123 let or = rand::rng().random_range(0..256) as u8;
2124 let og = rand::rng().random_range(0..256) as u8;
2125 let ob = rand::rng().random_range(0..256) as u8;
2126
2127 for point in &pixel_points {
2128 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2129 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2130 image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2131 }
2132
2133 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2134 image_width as u32,
2135 image_height as u32,
2136 YuvChromaSubsampling::Yuv444,
2137 );
2138
2139 rgb_to_yuv_nv24(
2140 &mut planar_image,
2141 &image_rgb,
2142 image_width as u32 * CHANNELS as u32,
2143 YuvRange::Limited,
2144 YuvStandardMatrix::Bt709,
2145 mode,
2146 )
2147 .unwrap();
2148
2149 image_rgb.fill(0);
2150
2151 let fixed_planar = planar_image.to_fixed();
2152
2153 yuv_nv24_to_rgb(
2154 &fixed_planar,
2155 &mut image_rgb,
2156 image_width as u32 * CHANNELS as u32,
2157 YuvRange::Limited,
2158 YuvStandardMatrix::Bt709,
2159 mode,
2160 )
2161 .unwrap();
2162
2163 for point in &pixel_points {
2164 let x = point[0];
2165 let y = point[1];
2166 let r = image_rgb[x * CHANNELS + y * image_width * CHANNELS];
2167 let g = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1];
2168 let b = image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2];
2169
2170 let diff_r = (r as i32 - or as i32).abs();
2171 let diff_g = (g as i32 - og as i32).abs();
2172 let diff_b = (b as i32 - ob as i32).abs();
2173
2174 assert!(
2175 diff_r <= max_diff,
2176 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, actual diff {}",
2177 mode,
2178 [or, og, ob],
2179 [r, g, b],
2180 diff_r,
2181 );
2182 assert!(
2183 diff_g <= max_diff,
2184 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, actual diff {}",
2185 mode,
2186 [or, og, ob],
2187 [r, g, b],
2188 diff_g,
2189 );
2190 assert!(
2191 diff_b <= max_diff,
2192 "Matrix {}, Original RGB {:?}, Round-tripped RGB {:?}, actual diff {}",
2193 mode,
2194 [or, og, ob],
2195 [r, g, b],
2196 diff_b,
2197 );
2198 }
2199 }
2200 matrix(YuvConversionMode::Balanced, 37);
2201 #[cfg(feature = "fast_mode")]
2202 matrix(YuvConversionMode::Fast, 50);
2203 #[cfg(feature = "professional_mode")]
2204 matrix(YuvConversionMode::Professional, 37);
2205 }
2206
2207 #[test]
2208 fn test_yuv422_nv_round_trip_full_range() {
2209 fn matrix(mode: YuvConversionMode, max_diff: i32) {
2210 let image_width = 256usize;
2211 let image_height = 256usize;
2212
2213 let random_point_x = rand::rng().random_range(0..image_width);
2214 let random_point_y = rand::rng().random_range(0..image_height);
2215
2216 const CHANNELS: usize = 3;
2217
2218 let pixel_points = [
2219 [0, 0],
2220 [image_width - 1, image_height - 1],
2221 [image_width - 1, 0],
2222 [0, image_height - 1],
2223 [(image_width - 1) / 2, (image_height - 1) / 2],
2224 [image_width / 5, image_height / 5],
2225 [0, image_height / 5],
2226 [image_width / 5, 0],
2227 [image_width / 5 * 3, image_height / 5],
2228 [image_width / 5 * 3, image_height / 5 * 3],
2229 [image_width / 5, image_height / 5 * 3],
2230 [random_point_x, random_point_y],
2231 ];
2232
2233 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
2234
2235 let or = rand::rng().random_range(0..256) as u8;
2236 let og = rand::rng().random_range(0..256) as u8;
2237 let ob = rand::rng().random_range(0..256) as u8;
2238
2239 for point in &pixel_points {
2240 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2241 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2242 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2243
2244 let nx = (point[0] + 1).min(image_width - 1);
2245 let ny = point[1].min(image_height - 1);
2246
2247 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2248 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2249 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2250
2251 let nx = point[0].saturating_sub(1).min(image_width - 1);
2252 let ny = point[1].min(image_height - 1);
2253
2254 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2255 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2256 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2257 }
2258
2259 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2260 image_width as u32,
2261 image_height as u32,
2262 YuvChromaSubsampling::Yuv422,
2263 );
2264
2265 rgb_to_yuv_nv16(
2266 &mut planar_image,
2267 &source_rgb,
2268 image_width as u32 * CHANNELS as u32,
2269 YuvRange::Full,
2270 YuvStandardMatrix::Bt709,
2271 mode,
2272 )
2273 .unwrap();
2274
2275 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
2276
2277 let fixed_planar = planar_image.to_fixed();
2278
2279 yuv_nv16_to_rgb(
2280 &fixed_planar,
2281 &mut dest_rgb,
2282 image_width as u32 * CHANNELS as u32,
2283 YuvRange::Full,
2284 YuvStandardMatrix::Bt709,
2285 mode,
2286 )
2287 .unwrap();
2288
2289 for point in &pixel_points {
2290 let x = point[0];
2291 let y = point[1];
2292 let px = x * CHANNELS + y * image_width * CHANNELS;
2293
2294 let r = dest_rgb[px];
2295 let g = dest_rgb[px + 1];
2296 let b = dest_rgb[px + 2];
2297
2298 let diff_r = r as i32 - or as i32;
2299 let diff_g = g as i32 - og as i32;
2300 let diff_b = b as i32 - ob as i32;
2301
2302 assert!(
2303 diff_r <= max_diff,
2304 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2305 mode,
2306 diff_r,
2307 [or, og, ob],
2308 [r, g, b]
2309 );
2310 assert!(
2311 diff_g <= max_diff,
2312 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2313 mode,
2314 diff_g,
2315 [or, og, ob],
2316 [r, g, b]
2317 );
2318 assert!(
2319 diff_b <= max_diff,
2320 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2321 mode,
2322 diff_b,
2323 [or, og, ob],
2324 [r, g, b]
2325 );
2326 }
2327 }
2328 matrix(YuvConversionMode::Balanced, 3);
2329 #[cfg(feature = "fast_mode")]
2330 matrix(YuvConversionMode::Fast, 6);
2331 #[cfg(feature = "professional_mode")]
2332 matrix(YuvConversionMode::Professional, 3);
2333 }
2334
2335 #[test]
2336 fn test_yuv422_nv_round_trip_limited_range() {
2337 fn matrix(mode: YuvConversionMode, max_diff: i32) {
2338 let image_width = 256usize;
2339 let image_height = 256usize;
2340
2341 let random_point_x = rand::rng().random_range(0..image_width);
2342 let random_point_y = rand::rng().random_range(0..image_height);
2343
2344 const CHANNELS: usize = 3;
2345
2346 let pixel_points = [
2347 [0, 0],
2348 [image_width - 1, image_height - 1],
2349 [image_width - 1, 0],
2350 [0, image_height - 1],
2351 [(image_width - 1) / 2, (image_height - 1) / 2],
2352 [image_width / 5, image_height / 5],
2353 [0, image_height / 5],
2354 [image_width / 5, 0],
2355 [image_width / 5 * 3, image_height / 5],
2356 [image_width / 5 * 3, image_height / 5 * 3],
2357 [image_width / 5, image_height / 5 * 3],
2358 [random_point_x, random_point_y],
2359 ];
2360
2361 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
2362
2363 let or = rand::rng().random_range(0..256) as u8;
2364 let og = rand::rng().random_range(0..256) as u8;
2365 let ob = rand::rng().random_range(0..256) as u8;
2366
2367 for point in &pixel_points {
2368 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2369 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2370 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2371
2372 let nx = (point[0] + 1).min(image_width - 1);
2373 let ny = point[1].min(image_height - 1);
2374
2375 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2376 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2377 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2378
2379 let nx = point[0].saturating_sub(1).min(image_width - 1);
2380 let ny = point[1].min(image_height - 1);
2381
2382 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2383 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2384 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2385 }
2386
2387 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2388 image_width as u32,
2389 image_height as u32,
2390 YuvChromaSubsampling::Yuv422,
2391 );
2392
2393 rgb_to_yuv_nv16(
2394 &mut planar_image,
2395 &source_rgb,
2396 image_width as u32 * CHANNELS as u32,
2397 YuvRange::Limited,
2398 YuvStandardMatrix::Bt709,
2399 mode,
2400 )
2401 .unwrap();
2402
2403 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
2404
2405 let fixed_planar = planar_image.to_fixed();
2406
2407 yuv_nv16_to_rgb(
2408 &fixed_planar,
2409 &mut dest_rgb,
2410 image_width as u32 * CHANNELS as u32,
2411 YuvRange::Limited,
2412 YuvStandardMatrix::Bt709,
2413 mode,
2414 )
2415 .unwrap();
2416
2417 for point in &pixel_points {
2418 let x = point[0];
2419 let y = point[1];
2420 let px = x * CHANNELS + y * image_width * CHANNELS;
2421
2422 let r = dest_rgb[px];
2423 let g = dest_rgb[px + 1];
2424 let b = dest_rgb[px + 2];
2425
2426 let diff_r = r as i32 - or as i32;
2427 let diff_g = g as i32 - og as i32;
2428 let diff_b = b as i32 - ob as i32;
2429
2430 assert!(
2431 diff_r <= max_diff,
2432 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2433 mode,
2434 diff_r,
2435 [or, og, ob],
2436 [r, g, b]
2437 );
2438 assert!(
2439 diff_g <= max_diff,
2440 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2441 mode,
2442 diff_g,
2443 [or, og, ob],
2444 [r, g, b]
2445 );
2446 assert!(
2447 diff_b <= max_diff,
2448 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2449 mode,
2450 diff_b,
2451 [or, og, ob],
2452 [r, g, b]
2453 );
2454 }
2455 }
2456 matrix(YuvConversionMode::Balanced, 20);
2457 #[cfg(feature = "fast_mode")]
2458 matrix(YuvConversionMode::Fast, 26);
2459 #[cfg(feature = "professional_mode")]
2460 matrix(YuvConversionMode::Professional, 12);
2461 }
2462
2463 #[test]
2464 fn test_yuv420_nv_round_trip_full_range() {
2465 fn matrix(mode: YuvConversionMode, max_diff: i32) {
2466 let image_width = 256usize;
2467 let image_height = 256usize;
2468
2469 let random_point_x = rand::rng().random_range(0..image_width);
2470 let random_point_y = rand::rng().random_range(0..image_height);
2471
2472 const CHANNELS: usize = 3;
2473
2474 let pixel_points = [
2475 [0, 0],
2476 [image_width - 1, image_height - 1],
2477 [image_width - 1, 0],
2478 [0, image_height - 1],
2479 [(image_width - 1) / 2, (image_height - 1) / 2],
2480 [image_width / 5, image_height / 5],
2481 [0, image_height / 5],
2482 [image_width / 5, 0],
2483 [image_width / 5 * 3, image_height / 5],
2484 [image_width / 5 * 3, image_height / 5 * 3],
2485 [image_width / 5, image_height / 5 * 3],
2486 [random_point_x, random_point_y],
2487 ];
2488
2489 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
2490
2491 let or = rand::rng().random_range(0..256) as u8;
2492 let og = rand::rng().random_range(0..256) as u8;
2493 let ob = rand::rng().random_range(0..256) as u8;
2494
2495 for point in &pixel_points {
2496 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2497 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2498 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2499
2500 let nx = (point[0] + 1).min(image_width - 1);
2501 let ny = point[1].min(image_height - 1);
2502
2503 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2504 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2505 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2506
2507 let nx = (point[0] + 1).min(image_width - 1);
2508 let ny = (point[1] + 1).min(image_height - 1);
2509
2510 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2511 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2512 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2513
2514 let nx = point[0].min(image_width - 1);
2515 let ny = (point[1] + 1).min(image_height - 1);
2516
2517 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2518 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2519 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2520
2521 let nx = point[0].saturating_sub(1).min(image_width - 1);
2522 let ny = point[1].saturating_sub(1).min(image_height - 1);
2523
2524 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2525 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2526 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2527
2528 let nx = point[0].min(image_width - 1);
2529 let ny = point[1].saturating_sub(1).min(image_height - 1);
2530
2531 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2532 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2533 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2534
2535 let nx = point[0].saturating_sub(1).min(image_width - 1);
2536 let ny = point[1].min(image_height - 1);
2537
2538 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2539 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2540 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2541 }
2542
2543 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2544 image_width as u32,
2545 image_height as u32,
2546 YuvChromaSubsampling::Yuv420,
2547 );
2548
2549 rgb_to_yuv_nv12(
2550 &mut planar_image,
2551 &source_rgb,
2552 image_width as u32 * CHANNELS as u32,
2553 YuvRange::Full,
2554 YuvStandardMatrix::Bt709,
2555 mode,
2556 )
2557 .unwrap();
2558
2559 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
2560
2561 let fixed_planar = planar_image.to_fixed();
2562
2563 yuv_nv12_to_rgb(
2564 &fixed_planar,
2565 &mut dest_rgb,
2566 image_width as u32 * CHANNELS as u32,
2567 YuvRange::Full,
2568 YuvStandardMatrix::Bt709,
2569 mode,
2570 )
2571 .unwrap();
2572
2573 for point in &pixel_points {
2574 let x = point[0];
2575 let y = point[1];
2576 let px = x * CHANNELS + y * image_width * CHANNELS;
2577
2578 let r = dest_rgb[px];
2579 let g = dest_rgb[px + 1];
2580 let b = dest_rgb[px + 2];
2581
2582 let diff_r = r as i32 - or as i32;
2583 let diff_g = g as i32 - og as i32;
2584 let diff_b = b as i32 - ob as i32;
2585
2586 assert!(
2587 diff_r <= max_diff,
2588 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}, Point (x: {}, y: {})",
2589 mode,
2590 diff_r,
2591 [or, og, ob],
2592 [r, g, b],
2593 x,
2594 y,
2595 );
2596 assert!(
2597 diff_g <= max_diff,
2598 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}, Point (x: {}, y: {})",
2599 mode,
2600 diff_g,
2601 [or, og, ob],
2602 [r, g, b],
2603 x,
2604 y,
2605 );
2606 assert!(
2607 diff_b <= max_diff,
2608 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}, Point (x: {}, y: {})",
2609 mode,
2610 diff_b,
2611 [or, og, ob],
2612 [r, g, b],
2613 x,
2614 y,
2615 );
2616 }
2617 }
2618 matrix(YuvConversionMode::Balanced, 82);
2619 #[cfg(feature = "fast_mode")]
2620 matrix(YuvConversionMode::Fast, 84);
2621 #[cfg(feature = "professional_mode")]
2622 matrix(YuvConversionMode::Professional, 74);
2623 }
2624
2625 #[test]
2626 fn test_yuv420_nv_round_trip_limited_range() {
2627 fn matrix(mode: YuvConversionMode, max_diff: i32) {
2628 let image_width = 256usize;
2629 let image_height = 256usize;
2630
2631 let random_point_x = rand::rng().random_range(0..image_width);
2632 let random_point_y = rand::rng().random_range(0..image_height);
2633
2634 const CHANNELS: usize = 3;
2635
2636 let pixel_points = [
2637 [0, 0],
2638 [image_width - 1, image_height - 1],
2639 [image_width - 1, 0],
2640 [0, image_height - 1],
2641 [(image_width - 1) / 2, (image_height - 1) / 2],
2642 [image_width / 5, image_height / 5],
2643 [0, image_height / 5],
2644 [image_width / 5, 0],
2645 [image_width / 5 * 3, image_height / 5],
2646 [image_width / 5 * 3, image_height / 5 * 3],
2647 [image_width / 5, image_height / 5 * 3],
2648 [random_point_x, random_point_y],
2649 ];
2650
2651 let mut source_rgb = vec![0u8; image_width * image_height * CHANNELS];
2652
2653 let or = rand::rng().random_range(0..256) as u8;
2654 let og = rand::rng().random_range(0..256) as u8;
2655 let ob = rand::rng().random_range(0..256) as u8;
2656
2657 for point in &pixel_points {
2658 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
2659 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
2660 source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
2661
2662 let nx = (point[0] + 1).min(image_width - 1);
2663 let ny = point[1].min(image_height - 1);
2664
2665 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2666 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2667 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2668
2669 let nx = (point[0] + 1).min(image_width - 1);
2670 let ny = (point[1] + 1).min(image_height - 1);
2671
2672 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2673 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2674 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2675
2676 let nx = point[0].min(image_width - 1);
2677 let ny = (point[1] + 1).min(image_height - 1);
2678
2679 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2680 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2681 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2682
2683 let nx = point[0].saturating_sub(1).min(image_width - 1);
2684 let ny = point[1].saturating_sub(1).min(image_height - 1);
2685
2686 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2687 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2688 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2689
2690 let nx = point[0].min(image_width - 1);
2691 let ny = point[1].saturating_sub(1).min(image_height - 1);
2692
2693 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2694 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2695 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2696
2697 let nx = point[0].saturating_sub(1).min(image_width - 1);
2698 let ny = point[1].min(image_height - 1);
2699
2700 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
2701 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
2702 source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
2703 }
2704
2705 let mut planar_image = YuvBiPlanarImageMut::<u8>::alloc(
2706 image_width as u32,
2707 image_height as u32,
2708 YuvChromaSubsampling::Yuv420,
2709 );
2710
2711 rgb_to_yuv_nv12(
2712 &mut planar_image,
2713 &source_rgb,
2714 image_width as u32 * CHANNELS as u32,
2715 YuvRange::Limited,
2716 YuvStandardMatrix::Bt709,
2717 mode,
2718 )
2719 .unwrap();
2720
2721 let mut dest_rgb = vec![0u8; image_width * image_height * CHANNELS];
2722
2723 let fixed_planar = planar_image.to_fixed();
2724
2725 yuv_nv12_to_rgb(
2726 &fixed_planar,
2727 &mut dest_rgb,
2728 image_width as u32 * CHANNELS as u32,
2729 YuvRange::Limited,
2730 YuvStandardMatrix::Bt709,
2731 mode,
2732 )
2733 .unwrap();
2734
2735 for point in &pixel_points {
2736 let x = point[0];
2737 let y = point[1];
2738 let px = x * CHANNELS + y * image_width * CHANNELS;
2739
2740 let r = dest_rgb[px];
2741 let g = dest_rgb[px + 1];
2742 let b = dest_rgb[px + 2];
2743
2744 let diff_r = r as i32 - or as i32;
2745 let diff_g = g as i32 - og as i32;
2746 let diff_b = b as i32 - ob as i32;
2747
2748 assert!(
2749 diff_r <= max_diff,
2750 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2751 mode,
2752 diff_r,
2753 [or, og, ob],
2754 [r, g, b]
2755 );
2756 assert!(
2757 diff_g <= max_diff,
2758 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2759 mode,
2760 diff_g,
2761 [or, og, ob],
2762 [r, g, b]
2763 );
2764 assert!(
2765 diff_b <= max_diff,
2766 "Matrix {}, Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
2767 mode,
2768 diff_b,
2769 [or, og, ob],
2770 [r, g, b]
2771 );
2772 }
2773 }
2774 matrix(YuvConversionMode::Balanced, 78);
2775 #[cfg(feature = "fast_mode")]
2776 matrix(YuvConversionMode::Fast, 80);
2777 #[cfg(feature = "professional_mode")]
2778 matrix(YuvConversionMode::Professional, 70);
2779 }
2780}