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