yuvutils_rs/
yuv_nv_to_rgba.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 10/2024. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1.  Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 *
14 * 3.  Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use 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
1185/// Convert YUV NV12 format to BGRA format.
1186///
1187/// This function takes YUV NV12 data with 8-bit precision,
1188/// and converts it to BGRA format with 8-bit per channel precision.
1189///
1190/// # Arguments
1191///
1192/// * `bi_planar_image` - Source Bi-Planar image.
1193/// * `bgra` - A mutable slice to store the converted BGRA data.
1194/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1195/// * `range` - The YUV range (limited or full).
1196/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1197///
1198/// # Panics
1199///
1200/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1201/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1202///
1203pub 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
1218/// Convert YUV NV16 format to BGRA format.
1219///
1220/// This function takes YUV NV16 data with 8-bit precision,
1221/// and converts it to BGRA format with 8-bit per channel precision.
1222///
1223/// # Arguments
1224///
1225/// * `bi_planar_image` - Source Bi-Planar image.
1226/// * `bgra` - A mutable slice to store the converted BGRA data.
1227/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1228/// * `range` - The YUV range (limited or full).
1229/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1230///
1231/// # Panics
1232///
1233/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1234/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1235///
1236pub 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
1251/// Convert YUV NV61 format to BGRA format.
1252///
1253/// This function takes YUV NV61 data with 8-bit precision,
1254/// and converts it to BGRA format with 8-bit per channel precision.
1255///
1256/// # Arguments
1257///
1258/// * `bi_planar_image` - Source Bi-Planar image.
1259/// * `bgra` - A mutable slice to store the converted BGRA data.
1260/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1261/// * `range` - The YUV range (limited or full).
1262/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1263///
1264/// # Panics
1265///
1266/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1267/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1268///
1269pub 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
1284/// Convert YUV NV21 format to BGRA format.
1285///
1286/// This function takes YUV NV12 data with 8-bit precision,
1287/// and converts it to BGRA format with 8-bit per channel precision.
1288///
1289/// # Arguments
1290///
1291/// * `bi_planar_image` - Source Bi-Planar image.
1292/// * `bgra` - A mutable slice to store the converted BGRA data.
1293/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1294/// * `range` - The YUV range (limited or full).
1295/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1296///
1297/// # Panics
1298///
1299/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1300/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1301///
1302pub 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
1317/// Convert YUV NV16 format to RGBA format.
1318///
1319/// This function takes YUV NV16 data with 8-bit precision,
1320/// and converts it to RGBA format with 8-bit per channel precision.
1321///
1322/// # Arguments
1323///
1324/// * `bi_planar_image` - Source Bi-Planar image.
1325/// * `rgba` - A mutable slice to store the converted RGBA data.
1326/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1327/// * `range` - The YUV range (limited or full).
1328/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1329///
1330/// # Panics
1331///
1332/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1333/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1334///
1335pub 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
1350/// Convert YUV NV61 format to RGBA format.
1351///
1352/// This function takes YUV NV61 data with 8-bit precision,
1353/// and converts it to RGBA format with 8-bit per channel precision.
1354///
1355/// # Arguments
1356///
1357/// * `bi_planar_image` - Source Bi-Planar image.
1358/// * `rgba` - A mutable slice to store the converted RGBA data.
1359/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1360/// * `range` - The YUV range (limited or full).
1361/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1362///
1363/// # Panics
1364///
1365/// This function panics if the lengths of the planes or the input RGBA data are not valid based
1366/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1367///
1368pub 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
1383/// Convert YUV NV12 format to RGBA format.
1384///
1385/// This function takes YUV NV12 data with 8-bit precision,
1386/// and converts it to RGBA format with 8-bit per channel precision.
1387///
1388/// # Arguments
1389///
1390/// * `bi_planar_image` - Source Bi-Planar image.
1391/// * `rgba` - A mutable slice to store the converted RGBA data.
1392/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1393/// * `range` - The YUV range (limited or full).
1394/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1395///
1396/// # Panics
1397///
1398/// This function panics if the lengths of the planes or the input RGBA data are not valid based
1399/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1400///
1401pub 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
1416/// Convert YUV NV21 format to RGBA format.
1417///
1418/// This function takes YUV NV21 data with 8-bit precision,
1419/// and converts it to RGBA format with 8-bit per channel precision.
1420///
1421/// # Arguments
1422///
1423/// * `bi_planar_image` - Source Bi-Planar image.
1424/// * `rgba` - A mutable slice to store the converted RGBA data.
1425/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1426/// * `range` - The YUV range (limited or full).
1427/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1428///
1429/// # Panics
1430///
1431/// This function panics if the lengths of the planes or the input RGBA data are not valid based
1432/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1433///
1434pub 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
1449/// Convert YUV NV12 format to RGB format.
1450///
1451/// This function takes YUV NV12 data with 8-bit precision,
1452/// and converts it to RGB format with 8-bit per channel precision.
1453///
1454/// # Arguments
1455///
1456/// * `bi_planar_image` - Source Bi-Planar image.
1457/// * `rgb` - A mutable slice to store the converted RGB data.
1458/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1459/// * `range` - The YUV range (limited or full).
1460/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1461///
1462/// # Panics
1463///
1464/// This function panics if the lengths of the planes or the input RGB data are not valid based
1465/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1466///
1467pub 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
1482/// Convert YUV NV12 format to BGR format.
1483///
1484/// This function takes YUV NV12 data with 8-bit precision,
1485/// and converts it to BGR format with 8-bit per channel precision.
1486///
1487/// # Arguments
1488///
1489/// * `bi_planar_image` - Source Bi-Planar image.
1490/// * `bgr` - A mutable slice to store the converted BGR data.
1491/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1492/// * `range` - The YUV range (limited or full).
1493/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1494///
1495/// # Panics
1496///
1497/// This function panics if the lengths of the planes or the input BGR data are not valid based
1498/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1499///
1500pub 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
1515/// Convert YUV NV16 format to RGB format.
1516///
1517/// This function takes YUV NV16 data with 8-bit precision,
1518/// and converts it to RGB format with 8-bit per channel precision.
1519///
1520/// # Arguments
1521///
1522/// * `bi_planar_image` - Source Bi-Planar image.
1523/// * `rgb` - A mutable slice to store the converted RGB data.
1524/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1525/// * `range` - The YUV range (limited or full).
1526/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1527///
1528/// # Panics
1529///
1530/// This function panics if the lengths of the planes or the input RGB data are not valid based
1531/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1532///
1533pub 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
1548/// Convert YUV NV16 format to BGR format.
1549///
1550/// This function takes YUV NV16 data with 8-bit precision,
1551/// and converts it to BGR format with 8-bit per channel precision.
1552///
1553/// # Arguments
1554///
1555/// * `bi_planar_image` - Source Bi-Planar image.
1556/// * `bgr` - A mutable slice to store the converted BGR data.
1557/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1558/// * `range` - The YUV range (limited or full).
1559/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1560///
1561/// # Panics
1562///
1563/// This function panics if the lengths of the planes or the input BGR data are not valid based
1564/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1565///
1566pub 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
1581/// Convert YUV NV61 format to RGB format.
1582///
1583/// This function takes YUV NV61 data with 8-bit precision,
1584/// and converts it to RGB format with 8-bit per channel precision.
1585///
1586/// # Arguments
1587///
1588/// * `bi_planar_image` - Source Bi-Planar image.
1589/// * `rgb` - A mutable slice to store the converted RGB data.
1590/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1591/// * `range` - The YUV range (limited or full).
1592/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1593///
1594/// # Panics
1595///
1596/// This function panics if the lengths of the planes or the input RGB data are not valid based
1597/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1598///
1599pub 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
1614/// Convert YUV NV61 format to BGR format.
1615///
1616/// This function takes YUV NV61 data with 8-bit precision,
1617/// and converts it to BGR format with 8-bit per channel precision.
1618///
1619/// # Arguments
1620///
1621/// * `bi_planar_image` - Source Bi-Planar image.
1622/// * `bgr` - A mutable slice to store the converted BGR data.
1623/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1624/// * `range` - The YUV range (limited or full).
1625/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1626///
1627/// # Panics
1628///
1629/// This function panics if the lengths of the planes or the input BGR data are not valid based
1630/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1631///
1632pub 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
1647/// Convert YUV NV21 format to RGB format.
1648///
1649/// This function takes YUV NV21 data with 8-bit precision,
1650/// and converts it to RGB format with 8-bit per channel precision.
1651///
1652/// # Arguments
1653///
1654/// * `bi_planar_image` - Source Bi-Planar image.
1655/// * `rgb` - A mutable slice to store the converted RGB data.
1656/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1657/// * `range` - The YUV range (limited or full).
1658/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1659///
1660/// # Panics
1661///
1662/// This function panics if the lengths of the planes or the input RGB data are not valid based
1663/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1664///
1665pub 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
1680/// Convert YUV NV21 format to BGR format.
1681///
1682/// This function takes YUV NV21 data with 8-bit precision,
1683/// and converts it to BGR format with 8-bit per channel precision.
1684///
1685/// # Arguments
1686///
1687/// * `bi_planar_image` - Source Bi-Planar image.
1688/// * `bgr` - A mutable slice to store the converted BGR data.
1689/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1690/// * `range` - The YUV range (limited or full).
1691/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1692///
1693/// # Panics
1694///
1695/// This function panics if the lengths of the planes or the input BGR data are not valid based
1696/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1697///
1698pub 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
1713/// Convert YUV NV24 format to RGBA format.
1714///
1715/// This function takes YUV NV24 data with 8-bit precision,
1716/// and converts it to RGBA format with 8-bit per channel precision.
1717///
1718/// # Arguments
1719///
1720/// * `bi_planar_image` - Source Bi-Planar image.
1721/// * `rgba` - A mutable slice to store the converted RGBA data.
1722/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1723/// * `range` - The YUV range (limited or full).
1724/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1725///
1726/// # Panics
1727///
1728/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1729/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1730///
1731pub 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
1746/// Convert YUV NV24 format to RGB format.
1747///
1748/// This function takes YUV NV24 data with 8-bit precision,
1749/// and converts it to RGB format with 8-bit per channel precision.
1750///
1751/// # Arguments
1752///
1753/// * `bi_planar_image` - Source Bi-Planar image.
1754/// * `rgb` - A mutable slice to store the converted RGB data.
1755/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1756/// * `range` - The YUV range (limited or full).
1757/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1758///
1759/// # Panics
1760///
1761/// This function panics if the lengths of the planes or the input RGB data are not valid based
1762/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1763///
1764pub 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
1779/// Convert YUV NV24 format to BGR format.
1780///
1781/// This function takes YUV NV24 data with 8-bit precision,
1782/// and converts it to BGR format with 8-bit per channel precision.
1783///
1784/// # Arguments
1785///
1786/// * `bi_planar_image` - Source Bi-Planar image.
1787/// * `bgr` - A mutable slice to store the converted BGR data.
1788/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1789/// * `range` - The YUV range (limited or full).
1790/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1791///
1792/// # Panics
1793///
1794/// This function panics if the lengths of the planes or the input BGR data are not valid based
1795/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1796///
1797pub 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
1812/// Convert YUV NV24 format to RGBA format.
1813///
1814/// This function takes YUV NV24 data with 8-bit precision,
1815/// and converts it to RGBA format with 8-bit per channel precision.
1816///
1817/// # Arguments
1818///
1819/// * `bi_planar_image` - Source Bi-Planar image.
1820/// * `rgba` - A mutable slice to store the converted RGBA data.
1821/// * `rgba_stride` - The stride (components per row) for the RGBA image data.
1822/// * `range` - The YUV range (limited or full).
1823/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1824///
1825/// # Panics
1826///
1827/// This function panics if the lengths of the planes or the input RGBA data are not valid based
1828/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1829///
1830pub 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
1845/// Convert YUV NV24 format to BGRA format.
1846///
1847/// This function takes YUV NV24 data with 8-bit precision,
1848/// and converts it to RGBA format with 8-bit per channel precision.
1849///
1850/// # Arguments
1851///
1852/// * `bi_planar_image` - Source Bi-Planar image.
1853/// * `bgra` - A mutable slice to store the converted BGRA data.
1854/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1855/// * `range` - The YUV range (limited or full).
1856/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1857///
1858/// # Panics
1859///
1860/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1861/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1862///
1863pub 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
1878/// Convert YUV NV42 format to RGB format.
1879///
1880/// This function takes YUV NV42 data with 8-bit precision,
1881/// and converts it to RGB format with 8-bit per channel precision.
1882///
1883/// # Arguments
1884///
1885/// * `bi_planar_image` - Source Bi-Planar image.
1886/// * `rgb` - A mutable slice to store the converted RGB data.
1887/// * `rgb_stride` - The stride (components per row) for the RGB image data.
1888/// * `range` - The YUV range (limited or full).
1889/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1890///
1891/// # Panics
1892///
1893/// This function panics if the lengths of the planes or the input RGB data are not valid based
1894/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1895///
1896pub 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
1911/// Convert YUV NV42 format to BGR format.
1912///
1913/// This function takes YUV NV42 data with 8-bit precision,
1914/// and converts it to BGR format with 8-bit per channel precision.
1915///
1916/// # Arguments
1917///
1918/// * `bi_planar_image` - Source Bi-Planar image.
1919/// * `bgr` - A mutable slice to store the converted BGR data.
1920/// * `bgr_stride` - The stride (components per row) for the BGR image data.
1921/// * `range` - The YUV range (limited or full).
1922/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1923///
1924/// # Panics
1925///
1926/// This function panics if the lengths of the planes or the input BGR data are not valid based
1927/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1928///
1929pub 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
1944/// Convert YUV NV42 format to BGRA format.
1945///
1946/// This function takes YUV NV42 data with 8-bit precision,
1947/// and converts it to RGB format with 8-bit per channel precision.
1948///
1949/// # Arguments
1950///
1951/// * `bi_planar_image` - Source Bi-Planar image.
1952/// * `bgra` - A mutable slice to store the converted BGRA data.
1953/// * `bgra_stride` - The stride (components per row) for the BGRA image data.
1954/// * `range` - The YUV range (limited or full).
1955/// * `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
1956///
1957/// # Panics
1958///
1959/// This function panics if the lengths of the planes or the input BGRA data are not valid based
1960/// on the specified width, height, and strides, or if invalid YUV range or matrix is provided.
1961///
1962pub 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}