yuvutils_rs/
rdp.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 2/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 */
29#![allow(clippy::excessive_precision)]
30
31use crate::internals::ProcessedOffset;
32use crate::yuv_error::check_rgba_destination;
33use crate::yuv_support::{CbCrForwardTransform, CbCrInverseTransform, YuvChromaRange};
34use crate::{YuvChromaSubsampling, YuvError, YuvPlanarImage, YuvPlanarImageMut, YuvRange};
35use std::fmt::{Display, Formatter};
36
37#[repr(u8)]
38#[derive(Debug, Copy, Clone, PartialEq, Eq)]
39pub(crate) enum RdpChannels {
40    Rgb = 0,
41    Rgba = 1,
42    Bgra = 2,
43    Bgr = 3,
44    Abgr = 4,
45    Argb = 5,
46}
47
48impl Display for RdpChannels {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        match self {
51            RdpChannels::Rgb => f.write_str("RdpChannels::Rgb"),
52            RdpChannels::Rgba => f.write_str("RdpChannels::Rgba"),
53            RdpChannels::Bgra => f.write_str("RdpChannels::Bgra"),
54            RdpChannels::Bgr => f.write_str("RdpChannels::Bgr"),
55            RdpChannels::Abgr => f.write_str("RdpChannels::Abgr"),
56            RdpChannels::Argb => f.write_str("RdpChannels::Argb"),
57        }
58    }
59}
60
61impl From<u8> for RdpChannels {
62    #[inline(always)]
63    fn from(value: u8) -> Self {
64        match value {
65            0 => RdpChannels::Rgb,
66            1 => RdpChannels::Rgba,
67            2 => RdpChannels::Bgra,
68            3 => RdpChannels::Bgr,
69            4 => RdpChannels::Abgr,
70            5 => RdpChannels::Argb,
71            _ => {
72                unimplemented!("Unknown value")
73            }
74        }
75    }
76}
77
78impl RdpChannels {
79    #[inline(always)]
80    pub const fn get_channels_count(&self) -> usize {
81        match self {
82            RdpChannels::Rgb | RdpChannels::Bgr => 3,
83            RdpChannels::Rgba | RdpChannels::Bgra | RdpChannels::Abgr | RdpChannels::Argb => 4,
84        }
85    }
86
87    #[inline(always)]
88    pub const fn has_alpha(&self) -> bool {
89        match self {
90            RdpChannels::Rgb | RdpChannels::Bgr => false,
91            RdpChannels::Rgba | RdpChannels::Bgra | RdpChannels::Abgr | RdpChannels::Argb => true,
92        }
93    }
94}
95
96impl RdpChannels {
97    #[inline(always)]
98    pub const fn get_r_channel_offset(&self) -> usize {
99        match self {
100            RdpChannels::Rgb => 0,
101            RdpChannels::Rgba => 0,
102            RdpChannels::Bgra => 2,
103            RdpChannels::Bgr => 2,
104            RdpChannels::Abgr => 3,
105            RdpChannels::Argb => 1,
106        }
107    }
108
109    #[inline(always)]
110    pub const fn get_g_channel_offset(&self) -> usize {
111        match self {
112            RdpChannels::Rgb | RdpChannels::Bgr => 1,
113            RdpChannels::Rgba | RdpChannels::Bgra => 1,
114            RdpChannels::Abgr | RdpChannels::Argb => 2,
115        }
116    }
117
118    #[inline(always)]
119    pub const fn get_b_channel_offset(&self) -> usize {
120        match self {
121            RdpChannels::Rgb => 2,
122            RdpChannels::Rgba => 2,
123            RdpChannels::Bgra => 0,
124            RdpChannels::Bgr => 0,
125            RdpChannels::Abgr => 1,
126            RdpChannels::Argb => 3,
127        }
128    }
129    #[inline(always)]
130    pub const fn get_a_channel_offset(&self) -> usize {
131        match self {
132            RdpChannels::Rgb | RdpChannels::Bgr => 0,
133            RdpChannels::Rgba | RdpChannels::Bgra => 3,
134            RdpChannels::Abgr | RdpChannels::Argb => 0,
135        }
136    }
137}
138
139type RgbEncoderHandler = Option<
140    unsafe fn(
141        transform: &CbCrForwardTransform<i32>,
142        y_plane: &mut [i16],
143        u_plane: &mut [i16],
144        v_plane: &mut [i16],
145        rgba: &[u8],
146        width: usize,
147    ) -> ProcessedOffset,
148>;
149
150struct RgbEncoder<const ORIGIN_CHANNELS: u8, const Q: i32> {
151    handler: RgbEncoderHandler,
152}
153
154impl<const ORIGIN_CHANNELS: u8, const Q: i32> Default for RgbEncoder<ORIGIN_CHANNELS, Q> {
155    fn default() -> Self {
156        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
157        {
158            #[cfg(feature = "avx")]
159            {
160                let use_avx = std::arch::is_x86_feature_detected!("avx2");
161                if use_avx {
162                    use crate::avx2::rdp_avx2_rgba_to_yuv;
163                    return RgbEncoder {
164                        handler: Some(rdp_avx2_rgba_to_yuv::<ORIGIN_CHANNELS, Q>),
165                    };
166                }
167            }
168        }
169        RgbEncoder { handler: None }
170    }
171}
172
173pub(crate) trait WideRdpRowForwardHandler<V, T, K> {
174    fn handle_row(
175        &self,
176        y_plane: &mut [V],
177        u_plane: &mut [V],
178        v_plane: &mut [V],
179        rgba: &[T],
180        width: u32,
181        chroma: YuvChromaRange,
182        transform: &CbCrForwardTransform<K>,
183    ) -> ProcessedOffset;
184}
185
186impl<const ORIGIN_CHANNELS: u8, const Q: i32> WideRdpRowForwardHandler<i16, u8, i32>
187    for RgbEncoder<ORIGIN_CHANNELS, Q>
188{
189    fn handle_row(
190        &self,
191        y_plane: &mut [i16],
192        u_plane: &mut [i16],
193        v_plane: &mut [i16],
194        rgba: &[u8],
195        width: u32,
196        _: YuvChromaRange,
197        transform: &CbCrForwardTransform<i32>,
198    ) -> ProcessedOffset {
199        if let Some(handler) = self.handler {
200            unsafe {
201                return handler(transform, y_plane, u_plane, v_plane, rgba, width as usize);
202            }
203        }
204        ProcessedOffset { cx: 0, ux: 0 }
205    }
206}
207
208fn to_rdp_yuv<const ORIGIN_CHANNELS: u8>(
209    planar_image: &mut YuvPlanarImageMut<i16>,
210    rgba: &[u8],
211    rgba_stride: u32,
212) -> Result<(), YuvError> {
213    let ch: RdpChannels = ORIGIN_CHANNELS.into();
214    let channels = ch.get_channels_count();
215    planar_image.check_constraints(YuvChromaSubsampling::Yuv444)?;
216    check_rgba_destination(
217        rgba,
218        rgba_stride,
219        planar_image.width,
220        planar_image.height,
221        channels,
222    )?;
223
224    let y_plane = planar_image.y_plane.borrow_mut();
225    let u_plane = planar_image.u_plane.borrow_mut();
226    let v_plane = planar_image.v_plane.borrow_mut();
227
228    const PRECISION: i32 = 15;
229    const SCALE: f32 = (1 << PRECISION) as f32;
230    const Y_R: i32 = (0.299 * SCALE) as i32;
231    const Y_G: i32 = (0.587 * SCALE) as i32;
232    const Y_B: i32 = (0.114 * SCALE) as i32;
233    const CB_R: i32 = -(0.168_935 * SCALE) as i32;
234    const CB_G: i32 = -(0.331_665 * SCALE) as i32;
235    const CB_B: i32 = (0.500_59 * SCALE) as i32;
236    const CR_R: i32 = (0.499_813 * SCALE) as i32;
237    const CR_G: i32 = -(0.418_531 * SCALE) as i32;
238    const CR_B: i32 = -(0.081_282 * SCALE) as i32;
239
240    let b_transform = CbCrForwardTransform {
241        yr: Y_R,
242        yg: Y_G,
243        yb: Y_B,
244        cb_r: CB_R,
245        cb_g: CB_G,
246        cb_b: CB_B,
247        cr_r: CR_R,
248        cr_g: CR_G,
249        cr_b: CR_B,
250    };
251
252    let handler = RgbEncoder::<ORIGIN_CHANNELS, 10>::default();
253
254    let iter = y_plane
255        .chunks_exact_mut(planar_image.y_stride as usize)
256        .zip(u_plane.chunks_exact_mut(planar_image.u_stride as usize))
257        .zip(v_plane.chunks_exact_mut(planar_image.v_stride as usize))
258        .zip(rgba.chunks_exact(rgba_stride as usize));
259
260    iter.for_each(|(((y_dst, u_dst), v_dst), rgba)| {
261        let offset = handler.handle_row(
262            y_dst,
263            u_dst,
264            v_dst,
265            rgba,
266            planar_image.width,
267            YuvChromaRange {
268                bias_y: 4096,
269                bias_uv: 0,
270                range: YuvRange::Full,
271                range_uv: 0,
272                range_y: 0,
273            },
274            &b_transform,
275        );
276
277        for (((y_dst, u_dst), v_dst), rgba) in y_dst
278            .iter_mut()
279            .zip(u_dst.iter_mut())
280            .zip(v_dst.iter_mut())
281            .zip(rgba.chunks_exact(channels))
282            .take(planar_image.width as usize)
283            .skip(offset.cx)
284        {
285            let r = rgba[ch.get_r_channel_offset()] as i32;
286            let g = rgba[ch.get_g_channel_offset()] as i32;
287            let b = rgba[ch.get_b_channel_offset()] as i32;
288
289            const Q: i32 = 10;
290
291            let y0 = ((r * b_transform.yr + g * b_transform.yg + b * b_transform.yb) >> Q) - 4096;
292            let u = (r * b_transform.cb_r + g * b_transform.cb_g + b * b_transform.cb_b) >> Q;
293            let v = (r * b_transform.cr_r + g * b_transform.cr_g + b * b_transform.cr_b) >> Q;
294
295            *y_dst = y0 as i16;
296            *u_dst = u as i16;
297            *v_dst = v as i16;
298        }
299    });
300
301    Ok(())
302}
303
304macro_rules! d_forward {
305    ($method: ident, $cn: expr, $name: ident, $stride_name: ident) => {
306        #[doc = concat!("RemoteFX conversion RGBx to YUV 4:4:4")]
307        pub fn $method(
308            planar_image: &mut YuvPlanarImageMut<i16>,
309            $name: &[u8],
310            $stride_name: u32,
311        ) -> Result<(), YuvError> {
312            to_rdp_yuv::<{ $cn as u8 }>(planar_image, $name, $stride_name)
313        }
314    };
315}
316
317d_forward!(rdp_rgb_to_yuv444, RdpChannels::Rgb, rgb, rgb_stride);
318d_forward!(rdp_rgba_to_yuv444, RdpChannels::Rgba, rgba, rgba_stride);
319d_forward!(rdp_bgra_to_yuv444, RdpChannels::Bgra, bgra, bgra_stride);
320d_forward!(rdp_abgr_to_yuv444, RdpChannels::Abgr, abgr, abgr_stride);
321d_forward!(rdp_bgr_to_yuv444, RdpChannels::Bgr, bgr, bgr_stride);
322d_forward!(rdp_argb_to_yuv444, RdpChannels::Argb, argb, argb_stride);
323
324#[inline(always)]
325fn qrshr_n<const PRECISION: i32, const BIT_DEPTH: usize>(val: i32) -> i32 {
326    let max_value: i32 = (1 << BIT_DEPTH) - 1;
327    ((val) >> PRECISION).min(max_value).max(0)
328}
329
330fn rdp_yuv_to_rgb<const ORIGIN_CHANNELS: u8>(
331    planar_image: &YuvPlanarImage<i16>,
332    rgba: &mut [u8],
333    rgba_stride: u32,
334) -> Result<(), YuvError> {
335    let ch: RdpChannels = ORIGIN_CHANNELS.into();
336    let channels = ch.get_channels_count();
337    planar_image.check_constraints(YuvChromaSubsampling::Yuv444)?;
338    check_rgba_destination(
339        rgba,
340        rgba_stride,
341        planar_image.width,
342        planar_image.height,
343        channels,
344    )?;
345
346    let y_plane = planar_image.y_plane;
347    let u_plane = planar_image.u_plane;
348    let v_plane = planar_image.v_plane;
349
350    const PRECISION: i32 = 16;
351    const Y_SCALE: i32 = 1 << PRECISION;
352    const B_Y: i32 = (1.402525f32 * Y_SCALE as f32) as i32;
353    const B_G_1: i32 = (0.343730f32 * Y_SCALE as f32) as i32;
354    const B_G_2: i32 = (0.714401f32 * Y_SCALE as f32) as i32;
355    const B_B_1: i32 = (1.769905 * Y_SCALE as f32) as i32;
356
357    let b_transform = CbCrInverseTransform::<i32> {
358        y_coef: Y_SCALE,
359        cr_coef: B_Y,
360        cb_coef: B_B_1,
361        g_coeff_1: B_G_1,
362        g_coeff_2: B_G_2,
363    };
364
365    let iter = y_plane
366        .chunks_exact(planar_image.y_stride as usize)
367        .zip(u_plane.chunks_exact(planar_image.u_stride as usize))
368        .zip(v_plane.chunks_exact(planar_image.v_stride as usize))
369        .zip(rgba.chunks_exact_mut(rgba_stride as usize));
370
371    iter.for_each(|(((y_dst, u_dst), v_dst), rgba)| {
372        let mut _cx = 0;
373
374        let mut _offset = ProcessedOffset { cx: 0, ux: 0 };
375
376        _cx = _offset.cx;
377
378        for (((&y_0, &u), &v), rgba) in y_dst
379            .iter()
380            .zip(u_dst.iter())
381            .zip(v_dst.iter())
382            .zip(rgba.chunks_exact_mut(channels))
383            .take(planar_image.width as usize)
384            .skip(_cx)
385        {
386            let y = y_0;
387            let yy = ((y + 4096) as i32) * Y_SCALE;
388            let r = qrshr_n::<21, 8>(yy + b_transform.cr_coef * v as i32);
389            let g = qrshr_n::<21, 8>(
390                yy - b_transform.g_coeff_2 * v as i32 - b_transform.g_coeff_1 * u as i32,
391            );
392            let b = qrshr_n::<21, 8>(yy + b_transform.cb_coef * u as i32);
393
394            rgba[ch.get_r_channel_offset()] = r as u8;
395            rgba[ch.get_g_channel_offset()] = g as u8;
396            rgba[ch.get_b_channel_offset()] = b as u8;
397            if ch.has_alpha() {
398                rgba[ch.get_a_channel_offset()] = 255;
399            }
400        }
401    });
402    Ok(())
403}
404
405macro_rules! d_backward {
406    ($method: ident, $cn: expr, $name: ident, $stride_name: ident, $px_name: expr) => {
407        #[doc = concat!("Converts RemoteFX YUV 4:4:4 to ", $px_name, "with 8 bit-depth precision.")]
408        pub fn $method(
409            planar_image: &YuvPlanarImage<i16>,
410            $name: &mut [u8],
411            $stride_name: u32,
412        ) -> Result<(), YuvError> {
413            rdp_yuv_to_rgb::<{ $cn as u8 }>(planar_image, $name, $stride_name)
414        }
415    };
416}
417
418d_backward!(rdp_yuv444_to_rgb, RdpChannels::Rgb, rgb, rgb_stride, "RGB");
419d_backward!(
420    rdp_yuv444_to_rgba,
421    RdpChannels::Rgba,
422    rgba,
423    rgba_stride,
424    "RGBA"
425);
426d_backward!(
427    rdp_yuv444_to_bgra,
428    RdpChannels::Bgra,
429    bgra,
430    bgra_stride,
431    "BGRA"
432);
433d_backward!(
434    rdp_yuv444_to_abgr,
435    RdpChannels::Abgr,
436    abgr,
437    abgr_stride,
438    "ABGR"
439);
440d_backward!(rdp_yuv444_to_bgr, RdpChannels::Bgr, bgr, bgr_stride, "BGR");
441d_backward!(
442    rdp_yuv444_to_argb,
443    RdpChannels::Argb,
444    argb,
445    argb_stride,
446    "ARGB"
447);
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use crate::BufferStoreMut;
453    #[test]
454    fn rgba_to_64x64_yuv() {
455        const WIDTH: usize = 64;
456        const HEIGHT: usize = 64;
457        let mut y = [0i16; WIDTH * HEIGHT];
458        let mut cb = [0i16; WIDTH * HEIGHT];
459        let mut cr = [0i16; WIDTH * HEIGHT];
460        let y_plane = BufferStoreMut::Borrowed(&mut y);
461        let u_plane = BufferStoreMut::Borrowed(&mut cb);
462        let v_plane = BufferStoreMut::Borrowed(&mut cr);
463        let mut plane = YuvPlanarImageMut {
464            y_plane,
465            y_stride: 64,
466            u_plane,
467            u_stride: 64,
468            v_plane,
469            v_stride: 64,
470            width: 10,
471            height: 20,
472        };
473        let stride = 30 * 4;
474        let input = vec![
475            0;
476            (stride * (plane.height - 1) + plane.width * 4)
477                .try_into()
478                .unwrap()
479        ];
480        rdp_rgba_to_yuv444(&mut plane, &input, stride).unwrap();
481        rdp_bgra_to_yuv444(&mut plane, &input, stride).unwrap();
482        rdp_abgr_to_yuv444(&mut plane, &input, stride).unwrap();
483        rdp_argb_to_yuv444(&mut plane, &input, stride).unwrap();
484    }
485}