yuvutils_rs/
ycgco_to_rgb.rs

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