yuvutils_rs/
yuv_p16_rgba_f16.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 01/2025. 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::ProcessedOffset;
30use crate::internals::WideDRowInversionHandler;
31use crate::numerics::{qrshr, to_ne};
32use crate::yuv_error::check_rgba_destination;
33use crate::yuv_support::{
34    get_yuv_range, search_inverse_transform, CbCrInverseTransform, YuvBytesPacking, YuvChromaRange,
35    YuvChromaSubsampling, YuvEndianness, YuvRange, YuvSourceChannels, YuvStandardMatrix,
36};
37use crate::{YuvError, YuvPlanarImage};
38use core::f16;
39#[cfg(feature = "rayon")]
40use rayon::iter::{IndexedParallelIterator, ParallelIterator};
41#[cfg(feature = "rayon")]
42use rayon::prelude::{ParallelSlice, ParallelSliceMut};
43
44struct WideRowAnyHandler<
45    const DESTINATION_CHANNELS: u8,
46    const SAMPLING: u8,
47    const ENDIANNESS: u8,
48    const BYTES_POSITION: u8,
49    const PRECISION: i32,
50    const BIT_DEPTH: usize,
51> {
52    handler: Option<
53        unsafe fn(
54            y_ld_ptr: &[u16],
55            u_ld_ptr: &[u16],
56            v_ld_ptr: &[u16],
57            rgba: &mut [f16],
58            width: u32,
59            range: &YuvChromaRange,
60            transform: &CbCrInverseTransform<i32>,
61            start_cx: usize,
62            start_ux: usize,
63        ) -> ProcessedOffset,
64    >,
65}
66
67impl<
68        const DESTINATION_CHANNELS: u8,
69        const SAMPLING: u8,
70        const ENDIANNESS: u8,
71        const BYTES_POSITION: u8,
72        const PRECISION: i32,
73        const BIT_DEPTH: usize,
74    > Default
75    for WideRowAnyHandler<
76        DESTINATION_CHANNELS,
77        SAMPLING,
78        ENDIANNESS,
79        BYTES_POSITION,
80        PRECISION,
81        BIT_DEPTH,
82    >
83{
84    fn default() -> WideRowAnyHandler<
85        DESTINATION_CHANNELS,
86        SAMPLING,
87        ENDIANNESS,
88        BYTES_POSITION,
89        PRECISION,
90        BIT_DEPTH,
91    > {
92        if PRECISION != 13 {
93            return WideRowAnyHandler { handler: None };
94        }
95        assert_eq!(PRECISION, 13);
96        #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
97        {
98            if BIT_DEPTH <= 12 {
99                use crate::neon::neon_yuv_p16_to_rgba_f16_row;
100                return WideRowAnyHandler {
101                    handler: Some(
102                        neon_yuv_p16_to_rgba_f16_row::<
103                            DESTINATION_CHANNELS,
104                            SAMPLING,
105                            ENDIANNESS,
106                            BYTES_POSITION,
107                            PRECISION,
108                            BIT_DEPTH,
109                        >,
110                    ),
111                };
112            }
113        }
114        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
115        {
116            #[cfg(feature = "avx")]
117            {
118                let use_avx = std::arch::is_x86_feature_detected!("avx2");
119                let has_f16c = std::arch::is_x86_feature_detected!("f16c");
120                if use_avx && has_f16c && BIT_DEPTH <= 12 {
121                    use crate::avx2::avx_yuv_p16_to_rgba_f16_row;
122                    return WideRowAnyHandler {
123                        handler: Some(
124                            avx_yuv_p16_to_rgba_f16_row::<
125                                DESTINATION_CHANNELS,
126                                SAMPLING,
127                                ENDIANNESS,
128                                BYTES_POSITION,
129                                BIT_DEPTH,
130                                PRECISION,
131                            >,
132                        ),
133                    };
134                }
135            }
136        }
137        WideRowAnyHandler { handler: None }
138    }
139}
140
141impl<
142        const DESTINATION_CHANNELS: u8,
143        const SAMPLING: u8,
144        const ENDIANNESS: u8,
145        const BYTES_POSITION: u8,
146        const PRECISION: i32,
147        const BIT_DEPTH: usize,
148    > WideDRowInversionHandler<u16, f16, i32>
149    for WideRowAnyHandler<
150        DESTINATION_CHANNELS,
151        SAMPLING,
152        ENDIANNESS,
153        BYTES_POSITION,
154        PRECISION,
155        BIT_DEPTH,
156    >
157{
158    #[inline]
159    fn handle_row(
160        &self,
161        y_plane: &[u16],
162        u_plane: &[u16],
163        v_plane: &[u16],
164        rgba: &mut [f16],
165        width: u32,
166        yuv_chroma_range: YuvChromaRange,
167        transform: &CbCrInverseTransform<i32>,
168    ) -> ProcessedOffset {
169        if let Some(handler) = self.handler {
170            unsafe {
171                return handler(
172                    y_plane,
173                    u_plane,
174                    v_plane,
175                    rgba,
176                    width,
177                    &yuv_chroma_range,
178                    transform,
179                    0,
180                    0,
181                );
182            }
183        }
184        ProcessedOffset { cx: 0, ux: 0 }
185    }
186}
187
188fn yuv_p16_to_image_p16_ant<
189    const DESTINATION_CHANNELS: u8,
190    const SAMPLING: u8,
191    const ENDIANNESS: u8,
192    const BYTES_POSITION: u8,
193    const BIT_DEPTH: usize,
194>(
195    image: &YuvPlanarImage<u16>,
196    rgba16: &mut [f16],
197    rgba_stride: u32,
198    range: YuvRange,
199    matrix: YuvStandardMatrix,
200) -> Result<(), YuvError> {
201    let dst_chans: YuvSourceChannels = DESTINATION_CHANNELS.into();
202    let channels = dst_chans.get_channels_count();
203
204    let chroma_subsampling: YuvChromaSubsampling = SAMPLING.into();
205    let chroma_range = get_yuv_range(BIT_DEPTH as u32, range);
206
207    image.check_constraints(chroma_subsampling)?;
208    check_rgba_destination(rgba16, rgba_stride, image.width, image.height, channels)?;
209
210    let kr_kb = matrix.get_kr_kb();
211    let max_range_p16 = ((1u32 << BIT_DEPTH as u32) - 1) as i32;
212
213    const PRECISION: i32 = 13;
214
215    let i_transform = search_inverse_transform(
216        PRECISION,
217        BIT_DEPTH as u32,
218        range,
219        matrix,
220        chroma_range,
221        kr_kb,
222    );
223
224    let cr_coef = i_transform.cr_coef;
225    let cb_coef = i_transform.cb_coef;
226    let y_coef = i_transform.y_coef;
227    let g_coef_1 = i_transform.g_coeff_1;
228    let g_coef_2 = i_transform.g_coeff_2;
229
230    let bias_y = chroma_range.bias_y as i32;
231    let bias_uv = chroma_range.bias_uv as i32;
232
233    let msb_shift = (16 - BIT_DEPTH) as i32;
234    let wide_row_handler = WideRowAnyHandler::<
235        DESTINATION_CHANNELS,
236        SAMPLING,
237        ENDIANNESS,
238        BYTES_POSITION,
239        PRECISION,
240        BIT_DEPTH,
241    >::default();
242
243    let default_scale = (1f32 / max_range_p16 as f32) as f16;
244
245    let process_halved_chroma_row = |y_plane: &[u16],
246                                     u_plane: &[u16],
247                                     v_plane: &[u16],
248                                     rgba: &mut [f16]| {
249        let cx = wide_row_handler
250            .handle_row(
251                y_plane,
252                u_plane,
253                v_plane,
254                rgba,
255                image.width,
256                chroma_range,
257                &i_transform,
258            )
259            .cx;
260
261        if cx != image.width as usize {
262            for (((rgba, y_src), &u_src), &v_src) in rgba
263                .chunks_exact_mut(channels * 2)
264                .zip(y_plane.chunks_exact(2))
265                .zip(u_plane.iter())
266                .zip(v_plane.iter())
267                .skip(cx / 2)
268            {
269                let y_value0 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[0], msb_shift) as i32
270                    - bias_y)
271                    * y_coef;
272                let cb_value =
273                    to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
274                let cr_value =
275                    to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
276
277                let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value) as f16
278                    * default_scale;
279                let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value) as f16
280                    * default_scale;
281                let g0 = qrshr::<PRECISION, BIT_DEPTH>(
282                    y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
283                ) as f16
284                    * default_scale;
285
286                let rgba0 = &mut rgba[0..channels];
287
288                rgba0[dst_chans.get_r_channel_offset()] = r0;
289                rgba0[dst_chans.get_g_channel_offset()] = g0;
290                rgba0[dst_chans.get_b_channel_offset()] = b0;
291                if dst_chans.has_alpha() {
292                    rgba0[dst_chans.get_a_channel_offset()] = 1.;
293                }
294
295                let y_value1 = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src[1], msb_shift) as i32
296                    - bias_y)
297                    * y_coef;
298
299                let r1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cr_coef * cr_value) as f16
300                    * default_scale;
301                let b1 = qrshr::<PRECISION, BIT_DEPTH>(y_value1 + cb_coef * cb_value) as f16
302                    * default_scale;
303                let g1 = qrshr::<PRECISION, BIT_DEPTH>(
304                    y_value1 - g_coef_1 * cr_value - g_coef_2 * cb_value,
305                ) as f16
306                    * default_scale;
307
308                let rgba1 = &mut rgba[channels..channels * 2];
309
310                rgba1[dst_chans.get_r_channel_offset()] = r1;
311                rgba1[dst_chans.get_g_channel_offset()] = g1;
312                rgba1[dst_chans.get_b_channel_offset()] = b1;
313                if dst_chans.has_alpha() {
314                    rgba1[dst_chans.get_a_channel_offset()] = 1.;
315                }
316            }
317
318            if image.width & 1 != 0 {
319                let y_value0 =
320                    (to_ne::<ENDIANNESS, BYTES_POSITION>(*y_plane.last().unwrap(), msb_shift)
321                        as i32
322                        - bias_y)
323                        * y_coef;
324                let cb_value =
325                    to_ne::<ENDIANNESS, BYTES_POSITION>(*u_plane.last().unwrap(), msb_shift) as i32
326                        - bias_uv;
327                let cr_value =
328                    to_ne::<ENDIANNESS, BYTES_POSITION>(*v_plane.last().unwrap(), msb_shift) as i32
329                        - bias_uv;
330                let rgba = rgba.chunks_exact_mut(channels).last().unwrap();
331                let rgba0 = &mut rgba[0..channels];
332
333                let r0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cr_coef * cr_value) as f16
334                    * default_scale;
335                let b0 = qrshr::<PRECISION, BIT_DEPTH>(y_value0 + cb_coef * cb_value) as f16
336                    * default_scale;
337                let g0 = qrshr::<PRECISION, BIT_DEPTH>(
338                    y_value0 - g_coef_1 * cr_value - g_coef_2 * cb_value,
339                ) as f16
340                    * default_scale;
341                rgba0[dst_chans.get_r_channel_offset()] = r0;
342                rgba0[dst_chans.get_g_channel_offset()] = g0;
343                rgba0[dst_chans.get_b_channel_offset()] = b0;
344                if dst_chans.has_alpha() {
345                    rgba0[dst_chans.get_a_channel_offset()] = 1.;
346                }
347            }
348        }
349    };
350
351    if chroma_subsampling == YuvChromaSubsampling::Yuv444 {
352        let iter;
353        #[cfg(feature = "rayon")]
354        {
355            iter = rgba16
356                .par_chunks_exact_mut(rgba_stride as usize)
357                .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
358                .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
359                .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
360        }
361        #[cfg(not(feature = "rayon"))]
362        {
363            iter = rgba16
364                .chunks_exact_mut(rgba_stride as usize)
365                .zip(image.y_plane.chunks_exact(image.y_stride as usize))
366                .zip(image.u_plane.chunks_exact(image.u_stride as usize))
367                .zip(image.v_plane.chunks_exact(image.v_stride as usize));
368        }
369        iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
370            let y_plane = &y_plane[0..image.width as usize];
371            let cx = wide_row_handler
372                .handle_row(
373                    y_plane,
374                    u_plane,
375                    v_plane,
376                    rgba,
377                    image.width,
378                    chroma_range,
379                    &i_transform,
380                )
381                .cx;
382            if cx != image.width as usize {
383                for (((rgba, &y_src), &u_src), &v_src) in rgba
384                    .chunks_exact_mut(channels)
385                    .zip(y_plane.iter())
386                    .zip(u_plane.iter())
387                    .zip(v_plane.iter())
388                    .skip(cx)
389                {
390                    let y_value = (to_ne::<ENDIANNESS, BYTES_POSITION>(y_src, msb_shift) as i32
391                        - bias_y)
392                        * y_coef;
393                    let cb_value =
394                        to_ne::<ENDIANNESS, BYTES_POSITION>(u_src, msb_shift) as i32 - bias_uv;
395                    let cr_value =
396                        to_ne::<ENDIANNESS, BYTES_POSITION>(v_src, msb_shift) as i32 - bias_uv;
397
398                    let r = qrshr::<PRECISION, BIT_DEPTH>(y_value + cr_coef * cr_value) as f16
399                        * default_scale;
400                    let b = qrshr::<PRECISION, BIT_DEPTH>(y_value + cb_coef * cb_value) as f16
401                        * default_scale;
402                    let g = qrshr::<PRECISION, BIT_DEPTH>(
403                        y_value - g_coef_1 * cr_value - g_coef_2 * cb_value,
404                    ) as f16
405                        * default_scale;
406
407                    rgba[dst_chans.get_r_channel_offset()] = r;
408                    rgba[dst_chans.get_g_channel_offset()] = g;
409                    rgba[dst_chans.get_b_channel_offset()] = b;
410                    if dst_chans.has_alpha() {
411                        rgba[dst_chans.get_a_channel_offset()] = 1.;
412                    }
413                }
414            }
415        });
416    } else if chroma_subsampling == YuvChromaSubsampling::Yuv422 {
417        let iter;
418        #[cfg(feature = "rayon")]
419        {
420            iter = rgba16
421                .par_chunks_exact_mut(rgba_stride as usize)
422                .zip(image.y_plane.par_chunks_exact(image.y_stride as usize))
423                .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
424                .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
425        }
426        #[cfg(not(feature = "rayon"))]
427        {
428            iter = rgba16
429                .chunks_exact_mut(rgba_stride as usize)
430                .zip(image.y_plane.chunks_exact(image.y_stride as usize))
431                .zip(image.u_plane.chunks_exact(image.u_stride as usize))
432                .zip(image.v_plane.chunks_exact(image.v_stride as usize));
433        }
434        iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
435            process_halved_chroma_row(
436                &y_plane[0..image.width as usize],
437                &u_plane[0..(image.width as usize).div_ceil(2)],
438                &v_plane[0..(image.width as usize).div_ceil(2)],
439                &mut rgba[0..image.width as usize * channels],
440            );
441        });
442    } else if chroma_subsampling == YuvChromaSubsampling::Yuv420 {
443        let iter;
444        #[cfg(feature = "rayon")]
445        {
446            iter = rgba16
447                .par_chunks_exact_mut(rgba_stride as usize * 2)
448                .zip(image.y_plane.par_chunks_exact(image.y_stride as usize * 2))
449                .zip(image.u_plane.par_chunks_exact(image.u_stride as usize))
450                .zip(image.v_plane.par_chunks_exact(image.v_stride as usize));
451        }
452        #[cfg(not(feature = "rayon"))]
453        {
454            iter = rgba16
455                .chunks_exact_mut(rgba_stride as usize * 2)
456                .zip(image.y_plane.chunks_exact(image.y_stride as usize * 2))
457                .zip(image.u_plane.chunks_exact(image.u_stride as usize))
458                .zip(image.v_plane.chunks_exact(image.v_stride as usize));
459        }
460        iter.for_each(|(((rgba, y_plane), u_plane), v_plane)| {
461            for (rgba, y_plane) in rgba
462                .chunks_exact_mut(rgba_stride as usize)
463                .zip(y_plane.chunks_exact(image.y_stride as usize))
464            {
465                process_halved_chroma_row(
466                    &y_plane[0..image.width as usize],
467                    &u_plane[0..(image.width as usize).div_ceil(2)],
468                    &v_plane[0..(image.width as usize).div_ceil(2)],
469                    &mut rgba[0..image.width as usize * channels],
470                );
471            }
472        });
473
474        if image.height & 1 != 0 {
475            let rgba = rgba16
476                .chunks_exact_mut(rgba_stride as usize)
477                .last()
478                .unwrap();
479            let u_plane = image
480                .u_plane
481                .chunks_exact(image.u_stride as usize)
482                .last()
483                .unwrap();
484            let v_plane = image
485                .v_plane
486                .chunks_exact(image.v_stride as usize)
487                .last()
488                .unwrap();
489            let y_plane = image
490                .y_plane
491                .chunks_exact(image.y_stride as usize)
492                .last()
493                .unwrap();
494            process_halved_chroma_row(
495                &y_plane[0..image.width as usize],
496                &u_plane[0..(image.width as usize).div_ceil(2)],
497                &v_plane[0..(image.width as usize).div_ceil(2)],
498                &mut rgba[0..image.width as usize * channels],
499            );
500        }
501    } else {
502        unreachable!();
503    }
504
505    Ok(())
506}
507
508macro_rules! build_cnv {
509    ($method: ident, $px_fmt: expr, $sampling: expr, $bit_depth: expr, $sampling_written: expr, $px_written: expr, $px_written_small: expr) => {
510        #[doc = concat!("
511Convert ",$sampling_written, " planar format with ", $bit_depth," bit pixel format to ", $px_written," float16 format.
512
513This function takes ", $sampling_written, " planar data with ",$bit_depth," bit precision.
514and converts it to ", $px_written," format with float16 image.
515
516# Arguments
517
518* `planar_image` - Source ",$sampling_written," planar image.
519* `", $px_written_small, "` - A mutable slice to store the converted ", $px_written," float16 format.
520* `", $px_written_small, "_stride` - The stride (components per row) for ", $px_written," float16 format.
521* `range` - The YUV range (limited or full).
522* `matrix` - The YUV standard matrix (BT.601 or BT.709 or BT.2020 or other).
523
524# Panics
525
526This function panics if the lengths of the planes or the input ", $px_written," data are not valid based
527on the specified width, height, and strides, or if invalid YUV range or matrix is provided.")]
528        pub fn $method(
529            planar_image: &YuvPlanarImage<u16>,
530            dst: &mut [f16],
531            dst_stride: u32,
532            range: YuvRange,
533            matrix: YuvStandardMatrix,
534        ) -> Result<(), YuvError> {
535            yuv_p16_to_image_p16_ant::<
536                            { $px_fmt as u8 },
537                            { $sampling as u8 },
538                            { YuvEndianness::LittleEndian as u8 },
539                            { YuvBytesPacking::LeastSignificantBytes as u8 },
540            $bit_depth>(planar_image, dst, dst_stride, range, matrix)
541        }
542    };
543}
544
545build_cnv!(
546    i010_to_rgba_f16,
547    YuvSourceChannels::Rgba,
548    YuvChromaSubsampling::Yuv420,
549    10,
550    "I010",
551    "RGBA",
552    "rgba"
553);
554
555build_cnv!(
556    i010_to_rgb_f16,
557    YuvSourceChannels::Rgb,
558    YuvChromaSubsampling::Yuv420,
559    10,
560    "YUV 420",
561    "RGB",
562    "rgb"
563);
564
565build_cnv!(
566    i012_to_rgba_f16,
567    YuvSourceChannels::Rgba,
568    YuvChromaSubsampling::Yuv420,
569    12,
570    "I010",
571    "RGBA",
572    "rgba"
573);
574
575build_cnv!(
576    i012_to_rgb_f16,
577    YuvSourceChannels::Rgb,
578    YuvChromaSubsampling::Yuv420,
579    12,
580    "I012",
581    "RGB",
582    "rgb"
583);
584
585build_cnv!(
586    i014_to_rgba_f16,
587    YuvSourceChannels::Rgba,
588    YuvChromaSubsampling::Yuv420,
589    14,
590    "I014",
591    "RGBA",
592    "rgba"
593);
594
595build_cnv!(
596    i014_to_rgb_f16,
597    YuvSourceChannels::Rgb,
598    YuvChromaSubsampling::Yuv420,
599    14,
600    "I014",
601    "RGB",
602    "rgb"
603);
604
605build_cnv!(
606    i210_to_rgba_f16,
607    YuvSourceChannels::Rgba,
608    YuvChromaSubsampling::Yuv422,
609    10,
610    "I210",
611    "RGBA",
612    "rgba"
613);
614
615build_cnv!(
616    i210_to_rgb_f16,
617    YuvSourceChannels::Rgb,
618    YuvChromaSubsampling::Yuv422,
619    10,
620    "I210",
621    "RGB",
622    "rgb"
623);
624
625build_cnv!(
626    i212_to_rgba_f16,
627    YuvSourceChannels::Rgba,
628    YuvChromaSubsampling::Yuv422,
629    12,
630    "I212",
631    "RGBA",
632    "rgba"
633);
634
635build_cnv!(
636    i212_to_rgb_f16,
637    YuvSourceChannels::Rgb,
638    YuvChromaSubsampling::Yuv422,
639    12,
640    "I212",
641    "RGB",
642    "rgb"
643);
644
645build_cnv!(
646    i214_to_rgba_f16,
647    YuvSourceChannels::Rgba,
648    YuvChromaSubsampling::Yuv422,
649    14,
650    "I214",
651    "RGBA",
652    "rgba"
653);
654
655build_cnv!(
656    i214_to_rgb_f16,
657    YuvSourceChannels::Rgb,
658    YuvChromaSubsampling::Yuv422,
659    14,
660    "I214",
661    "RGB",
662    "rgb"
663);
664
665build_cnv!(
666    i410_to_rgba_f16,
667    YuvSourceChannels::Rgba,
668    YuvChromaSubsampling::Yuv444,
669    10,
670    "I410",
671    "RGBA",
672    "rgba"
673);
674
675build_cnv!(
676    i410_to_rgb_f16,
677    YuvSourceChannels::Rgb,
678    YuvChromaSubsampling::Yuv444,
679    10,
680    "I410",
681    "RGB",
682    "rgb"
683);
684
685build_cnv!(
686    i412_to_rgba_f16,
687    YuvSourceChannels::Rgba,
688    YuvChromaSubsampling::Yuv444,
689    12,
690    "I412",
691    "RGBA",
692    "rgba"
693);
694
695build_cnv!(
696    i412_to_rgb_f16,
697    YuvSourceChannels::Rgb,
698    YuvChromaSubsampling::Yuv444,
699    12,
700    "I412",
701    "RGB",
702    "rgb"
703);
704
705build_cnv!(
706    i414_to_rgba_f16,
707    YuvSourceChannels::Rgba,
708    YuvChromaSubsampling::Yuv444,
709    14,
710    "I414",
711    "RGBA",
712    "rgba"
713);
714
715build_cnv!(
716    i414_to_rgb_f16,
717    YuvSourceChannels::Rgb,
718    YuvChromaSubsampling::Yuv444,
719    14,
720    "I414",
721    "RGB",
722    "rgb"
723);
724
725#[cfg(test)]
726#[cfg(feature = "nightly_f16")]
727mod tests {
728    use super::*;
729    use crate::{rgb10_to_i210, rgb10_to_i410, YuvPlanarImageMut};
730    use rand::Rng;
731
732    #[test]
733    fn test_yuv444_f16_round_trip_full_range() {
734        let image_width = 256usize;
735        let image_height = 256usize;
736
737        let random_point_x = rand::rng().random_range(0..image_width);
738        let random_point_y = rand::rng().random_range(0..image_height);
739
740        const CHANNELS: usize = 3;
741
742        let pixel_points = [
743            [0, 0],
744            [image_width - 1, image_height - 1],
745            [image_width - 1, 0],
746            [0, image_height - 1],
747            [(image_width - 1) / 2, (image_height - 1) / 2],
748            [image_width / 5, image_height / 5],
749            [0, image_height / 5],
750            [image_width / 5, 0],
751            [image_width / 5 * 3, image_height / 5],
752            [image_width / 5 * 3, image_height / 5 * 3],
753            [image_width / 5, image_height / 5 * 3],
754            [random_point_x, random_point_y],
755        ];
756        let mut image_rgb = vec![0u16; image_width * image_height * 3];
757
758        let or = rand::rng().random_range(0..1024) as u16;
759        let og = rand::rng().random_range(0..1024) as u16;
760        let ob = rand::rng().random_range(0..1024) as u16;
761
762        for point in &pixel_points {
763            image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
764            image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
765            image_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
766        }
767
768        let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
769            image_width as u32,
770            image_height as u32,
771            YuvChromaSubsampling::Yuv444,
772        );
773
774        rgb10_to_i410(
775            &mut planar_image,
776            &image_rgb,
777            image_width as u32 * CHANNELS as u32,
778            YuvRange::Full,
779            YuvStandardMatrix::Bt709,
780        )
781        .unwrap();
782
783        let mut image_rgb: Vec<f16> = vec![0.; image_width * image_height * 3];
784
785        let fixed_planar = planar_image.to_fixed();
786
787        i410_to_rgb_f16(
788            &fixed_planar,
789            &mut image_rgb,
790            image_width as u32 * CHANNELS as u32,
791            YuvRange::Full,
792            YuvStandardMatrix::Bt709,
793        )
794        .unwrap();
795
796        for point in &pixel_points {
797            let x = point[0];
798            let y = point[1];
799            let r = (image_rgb[x * CHANNELS + y * image_width * CHANNELS] as f32 * 1023.).round();
800            let g =
801                (image_rgb[x * CHANNELS + y * image_width * CHANNELS + 1] as f32 * 1023.).round();
802            let b =
803                (image_rgb[x * CHANNELS + y * image_width * CHANNELS + 2] as f32 * 1023.).round();
804
805            let diff_r = (r as i32 - or as i32).abs();
806            let diff_g = (g as i32 - og as i32).abs();
807            let diff_b = (b as i32 - ob as i32).abs();
808
809            assert!(
810                diff_r <= 150,
811                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
812                diff_r,
813                [or, og, ob],
814                [r, g, b]
815            );
816            assert!(
817                diff_g <= 150,
818                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
819                diff_g,
820                [or, og, ob],
821                [r, g, b]
822            );
823            assert!(
824                diff_b <= 150,
825                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
826                diff_b,
827                [or, og, ob],
828                [r, g, b]
829            );
830        }
831    }
832
833    #[test]
834    fn test_yuv422_f16_round_trip_limited_range() {
835        let image_width = 256usize;
836        let image_height = 256usize;
837
838        let random_point_x = rand::rng().random_range(0..image_width);
839        let random_point_y = rand::rng().random_range(0..image_height);
840
841        const CHANNELS: usize = 3;
842
843        let pixel_points = [
844            [0, 0],
845            [image_width - 1, image_height - 1],
846            [image_width - 1, 0],
847            [0, image_height - 1],
848            [(image_width - 1) / 2, (image_height - 1) / 2],
849            [image_width / 5, image_height / 5],
850            [0, image_height / 5],
851            [image_width / 5, 0],
852            [image_width / 5 * 3, image_height / 5],
853            [image_width / 5 * 3, image_height / 5 * 3],
854            [image_width / 5, image_height / 5 * 3],
855            [random_point_x, random_point_y],
856        ];
857
858        let mut source_rgb = vec![0u16; image_width * image_height * CHANNELS];
859
860        let or = rand::rng().random_range(0..1024) as u16;
861        let og = rand::rng().random_range(0..1024) as u16;
862        let ob = rand::rng().random_range(0..1024) as u16;
863
864        for point in &pixel_points {
865            source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS] = or;
866            source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 1] = og;
867            source_rgb[point[0] * CHANNELS + point[1] * image_width * CHANNELS + 2] = ob;
868
869            let nx = (point[0] + 1).min(image_width - 1);
870            let ny = point[1].min(image_height - 1);
871
872            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
873            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
874            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
875
876            let nx = point[0].saturating_sub(1).min(image_width - 1);
877            let ny = point[1].min(image_height - 1);
878
879            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS] = or;
880            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 1] = og;
881            source_rgb[nx * CHANNELS + ny * image_width * CHANNELS + 2] = ob;
882        }
883
884        let mut planar_image = YuvPlanarImageMut::<u16>::alloc(
885            image_width as u32,
886            image_height as u32,
887            YuvChromaSubsampling::Yuv422,
888        );
889
890        rgb10_to_i210(
891            &mut planar_image,
892            &source_rgb,
893            image_width as u32 * CHANNELS as u32,
894            YuvRange::Limited,
895            YuvStandardMatrix::Bt709,
896        )
897        .unwrap();
898
899        let mut dest_rgb: Vec<f16> = vec![0.; image_width * image_height * CHANNELS];
900
901        let fixed_planar = planar_image.to_fixed();
902
903        i210_to_rgb_f16(
904            &fixed_planar,
905            &mut dest_rgb,
906            image_width as u32 * CHANNELS as u32,
907            YuvRange::Limited,
908            YuvStandardMatrix::Bt709,
909        )
910        .unwrap();
911
912        for point in &pixel_points {
913            let x = point[0];
914            let y = point[1];
915            let px = x * CHANNELS + y * image_width * CHANNELS;
916
917            let r = (dest_rgb[px] as f32 * 1023.).round();
918            let g = (dest_rgb[px + 1] as f32 * 1023.).round();
919            let b = (dest_rgb[px + 2] as f32 * 1023.).round();
920
921            let diff_r = r as i32 - or as i32;
922            let diff_g = g as i32 - og as i32;
923            let diff_b = b as i32 - ob as i32;
924
925            assert!(
926                diff_r <= 264,
927                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
928                diff_r,
929                [or, og, ob],
930                [r, g, b]
931            );
932            assert!(
933                diff_g <= 264,
934                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
935                diff_g,
936                [or, og, ob],
937                [r, g, b]
938            );
939            assert!(
940                diff_b <= 264,
941                "Actual diff {}, Original RGB {:?}, Round-tripped RGB {:?}",
942                diff_b,
943                [or, og, ob],
944                [r, g, b]
945            );
946        }
947    }
948}