yuvutils_rs/
rgb_ar30.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 11/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::yuv_error::check_rgba_destination;
30use crate::yuv_support::{Rgb30, YuvSourceChannels};
31use crate::{Rgb30ByteOrder, YuvError};
32
33type RgbRa30RowHandler<V> = unsafe fn(src: &[V], dst: &mut [u8]);
34
35#[inline(always)]
36fn default_row_converter<
37    const AR30_LAYOUT: usize,
38    const AR30_BYTE_ORDER: usize,
39    const RGBA_LAYOUT: u8,
40>(
41    src: &[u8],
42    dst: &mut [u8],
43) {
44    let rgba_layout: YuvSourceChannels = RGBA_LAYOUT.into();
45    let ar30_layout: Rgb30 = AR30_LAYOUT.into();
46    for (src, dst) in src
47        .chunks_exact(rgba_layout.get_channels_count())
48        .zip(dst.chunks_exact_mut(4))
49    {
50        let r = src[rgba_layout.get_r_channel_offset()];
51        let g = src[rgba_layout.get_g_channel_offset()];
52        let b = src[rgba_layout.get_b_channel_offset()];
53
54        let r = u16::from_ne_bytes([r, r]) >> 6;
55        let g = u16::from_ne_bytes([g, g]) >> 6;
56        let b = u16::from_ne_bytes([b, b]) >> 6;
57
58        let packed = if rgba_layout.has_alpha() {
59            ar30_layout.pack_w_a::<AR30_BYTE_ORDER>(
60                r as i32,
61                g as i32,
62                b as i32,
63                src[3] as i32 >> 6,
64            )
65        } else {
66            ar30_layout.pack::<AR30_BYTE_ORDER>(r as i32, g as i32, b as i32)
67        };
68        let v_bytes = packed.to_ne_bytes();
69        dst[0] = v_bytes[0];
70        dst[1] = v_bytes[1];
71        dst[2] = v_bytes[2];
72        dst[3] = v_bytes[3];
73    }
74}
75
76#[inline(always)]
77fn default_row_converter_hb<
78    const AR30_LAYOUT: usize,
79    const AR30_BYTE_ORDER: usize,
80    const RGBA_LAYOUT: u8,
81    const BIT_DEPTH: usize,
82>(
83    src: &[u16],
84    dst: &mut [u8],
85) {
86    let rgba_layout: YuvSourceChannels = RGBA_LAYOUT.into();
87    let ar30_layout: Rgb30 = AR30_LAYOUT.into();
88    let target_shift = BIT_DEPTH - 10;
89    let alpha_shift = BIT_DEPTH - 2;
90    for (src, dst) in src
91        .chunks_exact(rgba_layout.get_channels_count())
92        .zip(dst.chunks_exact_mut(4))
93    {
94        let r = src[rgba_layout.get_r_channel_offset()];
95        let g = src[rgba_layout.get_g_channel_offset()];
96        let b = src[rgba_layout.get_b_channel_offset()];
97
98        let r = r >> target_shift;
99        let g = g >> target_shift;
100        let b = b >> target_shift;
101
102        let packed = if rgba_layout.has_alpha() {
103            ar30_layout.pack_w_a::<AR30_BYTE_ORDER>(
104                r as i32,
105                g as i32,
106                b as i32,
107                src[3] as i32 >> alpha_shift,
108            )
109        } else {
110            ar30_layout.pack::<AR30_BYTE_ORDER>(r as i32, g as i32, b as i32)
111        };
112        let v_bytes = packed.to_ne_bytes();
113        dst[0] = v_bytes[0];
114        dst[1] = v_bytes[1];
115        dst[2] = v_bytes[2];
116        dst[3] = v_bytes[3];
117    }
118}
119
120#[cfg(all(
121    any(target_arch = "x86", target_arch = "x86_64"),
122    feature = "nightly_avx512"
123))]
124#[target_feature(enable = "avx512bw")]
125unsafe fn default_row_converter_avx512<
126    const AR30_LAYOUT: usize,
127    const AR30_BYTE_ORDER: usize,
128    const RGBA_LAYOUT: u8,
129>(
130    src: &[u8],
131    dst: &mut [u8],
132) {
133    default_row_converter::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>(src, dst);
134}
135
136#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "avx"))]
137#[target_feature(enable = "avx2")]
138unsafe fn default_row_converter_avx2<
139    const AR30_LAYOUT: usize,
140    const AR30_BYTE_ORDER: usize,
141    const RGBA_LAYOUT: u8,
142>(
143    src: &[u8],
144    dst: &mut [u8],
145) {
146    default_row_converter::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>(src, dst);
147}
148
149#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
150#[target_feature(enable = "sse4.1")]
151unsafe fn default_row_converter_sse4_1<
152    const AR30_LAYOUT: usize,
153    const AR30_BYTE_ORDER: usize,
154    const RGBA_LAYOUT: u8,
155>(
156    src: &[u8],
157    dst: &mut [u8],
158) {
159    default_row_converter::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>(src, dst);
160}
161
162trait ConverterFactory<V> {
163    fn make_converter<
164        const AR30_LAYOUT: usize,
165        const AR30_BYTE_ORDER: usize,
166        const RGBA_LAYOUT: u8,
167        const BIT_DEPTH: usize,
168    >() -> RgbRa30RowHandler<V>;
169}
170
171impl ConverterFactory<u8> for u8 {
172    fn make_converter<
173        const AR30_LAYOUT: usize,
174        const AR30_BYTE_ORDER: usize,
175        const RGBA_LAYOUT: u8,
176        const BIT_DEPTH: usize,
177    >() -> RgbRa30RowHandler<u8> {
178        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
179        {
180            #[cfg(feature = "nightly_avx512")]
181            {
182                if std::arch::is_x86_feature_detected!("avx512bw") {
183                    return default_row_converter_avx512::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>;
184                }
185            }
186            #[cfg(feature = "avx")]
187            {
188                if std::arch::is_x86_feature_detected!("avx2") {
189                    return default_row_converter_avx2::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>;
190                }
191            }
192            #[cfg(feature = "sse")]
193            {
194                if std::arch::is_x86_feature_detected!("sse4.1") {
195                    return default_row_converter_sse4_1::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>;
196                }
197            }
198        }
199        default_row_converter::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT>
200    }
201}
202
203#[cfg(all(
204    any(target_arch = "x86", target_arch = "x86_64"),
205    feature = "nightly_avx512"
206))]
207#[target_feature(enable = "avx512bw")]
208unsafe fn default_row_converter_hb_avx512<
209    const AR30_LAYOUT: usize,
210    const AR30_BYTE_ORDER: usize,
211    const RGBA_LAYOUT: u8,
212    const BIT_DEPTH: usize,
213>(
214    src: &[u16],
215    dst: &mut [u8],
216) {
217    default_row_converter_hb::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT, BIT_DEPTH>(src, dst);
218}
219
220#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "avx"))]
221#[target_feature(enable = "avx2")]
222unsafe fn default_row_converter_hb_avx2<
223    const AR30_LAYOUT: usize,
224    const AR30_BYTE_ORDER: usize,
225    const RGBA_LAYOUT: u8,
226    const BIT_DEPTH: usize,
227>(
228    src: &[u16],
229    dst: &mut [u8],
230) {
231    default_row_converter_hb::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT, BIT_DEPTH>(src, dst);
232}
233
234#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "sse"))]
235#[target_feature(enable = "sse4.1")]
236unsafe fn default_row_converter_hb_sse4_1<
237    const AR30_LAYOUT: usize,
238    const AR30_BYTE_ORDER: usize,
239    const RGBA_LAYOUT: u8,
240    const BIT_DEPTH: usize,
241>(
242    src: &[u16],
243    dst: &mut [u8],
244) {
245    default_row_converter_hb::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT, BIT_DEPTH>(src, dst);
246}
247
248impl ConverterFactory<u16> for u16 {
249    fn make_converter<
250        const AR30_LAYOUT: usize,
251        const AR30_BYTE_ORDER: usize,
252        const RGBA_LAYOUT: u8,
253        const BIT_DEPTH: usize,
254    >() -> RgbRa30RowHandler<u16> {
255        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
256        {
257            #[cfg(feature = "nightly_avx512")]
258            {
259                if std::arch::is_x86_feature_detected!("avx512bw") {
260                    return default_row_converter_hb_avx512::<
261                        AR30_LAYOUT,
262                        AR30_BYTE_ORDER,
263                        RGBA_LAYOUT,
264                        BIT_DEPTH,
265                    >;
266                }
267            }
268            #[cfg(feature = "avx")]
269            {
270                if std::arch::is_x86_feature_detected!("avx2") {
271                    return default_row_converter_hb_avx2::<
272                        AR30_LAYOUT,
273                        AR30_BYTE_ORDER,
274                        RGBA_LAYOUT,
275                        BIT_DEPTH,
276                    >;
277                }
278            }
279            #[cfg(feature = "sse")]
280            {
281                if std::arch::is_x86_feature_detected!("sse4.1") {
282                    return default_row_converter_hb_sse4_1::<
283                        AR30_LAYOUT,
284                        AR30_BYTE_ORDER,
285                        RGBA_LAYOUT,
286                        BIT_DEPTH,
287                    >;
288                }
289            }
290        }
291        default_row_converter_hb::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT, BIT_DEPTH>
292    }
293}
294
295#[inline]
296fn rgb_to_ar30_impl<
297    V: Copy + 'static + ConverterFactory<V>,
298    const AR30_LAYOUT: usize,
299    const AR30_BYTE_ORDER: usize,
300    const RGBA_LAYOUT: u8,
301    const BIT_DEPTH: usize,
302>(
303    ar30: &mut [u8],
304    ar30_stride: u32,
305    rgba: &[V],
306    rgba_stride: u32,
307    width: u32,
308    height: u32,
309) -> Result<(), YuvError> {
310    let rgba_layout: YuvSourceChannels = RGBA_LAYOUT.into();
311    check_rgba_destination(ar30, ar30_stride, width, height, 4)?;
312    check_rgba_destination(
313        rgba,
314        rgba_stride,
315        width,
316        height,
317        rgba_layout.get_channels_count(),
318    )?;
319
320    let row_handler = V::make_converter::<AR30_LAYOUT, AR30_BYTE_ORDER, RGBA_LAYOUT, BIT_DEPTH>();
321
322    for (src, dst) in rgba
323        .chunks_exact(rgba_stride as usize)
324        .zip(ar30.chunks_exact_mut(ar30_stride as usize))
325    {
326        let src = &src[0..width as usize * rgba_layout.get_channels_count()];
327        let dst = &mut dst[0..width as usize * 4];
328        unsafe {
329            row_handler(src, dst);
330        }
331    }
332    Ok(())
333}
334
335macro_rules! rgb102_cnv {
336    (
337        $method_name:ident,
338        $ab_format:expr,
339        $ab_f_format:expr,
340        $ab_l_format:expr,
341        $ar_dst: expr,
342        $rgb_src: expr,
343        $rgb_s: expr,
344        $rgb_l: expr,
345        $tpz: ident,
346        $bp: expr
347    ) => {
348        #[doc = concat!("Converts ",$rgb_l," ", stringify!($bp)," bit depth to ",$ab_format, " (", $ab_f_format, ")\n",
349                                                                                        "# Arguments
350
351* `", $ab_l_format, "`: Dest ", $ab_format, " data
352* `", $ab_l_format, "_stride`: Dest ", $ab_format, " stride
353* `byte_order`: See [Rgb30ByteOrder] for more info
354* `", $rgb_s,"`: Destination ",$rgb_l, stringify!($bp)," data
355* `", $rgb_s,"_stride`: Destination ",$rgb_l, stringify!($bp)," stride
356* `width`: Image width
357* `height`: Image height")]
358        pub fn $method_name(
359            ar30: &mut [u8],
360            ar30_stride: u32,
361            byte_order: Rgb30ByteOrder,
362            rgb: &[$tpz],
363            rgb_stride: u32,
364            width: u32,
365            height: u32,
366        ) -> Result<(), YuvError> {
367            match byte_order {
368                Rgb30ByteOrder::Host => rgb_to_ar30_impl::<
369                    $tpz,
370                    { $ar_dst as usize },
371                    { Rgb30ByteOrder::Host as usize },
372                    { $rgb_src as u8 },
373                    $bp,
374                >(ar30, ar30_stride, rgb, rgb_stride, width, height),
375                Rgb30ByteOrder::Network => {
376                    rgb_to_ar30_impl::<
377                        $tpz,
378                        { $ar_dst as usize },
379                        { Rgb30ByteOrder::Network as usize },
380                        { $rgb_src as u8 },
381                        $bp,
382                    >(ar30, ar30_stride, rgb, rgb_stride, width, height)
383                }
384            }
385        }
386    };
387}
388
389rgb102_cnv!(
390    rgb8_to_ar30,
391    "AR30",
392    "ARGB2101010",
393    "ar30",
394    Rgb30::Ar30,
395    YuvSourceChannels::Rgb,
396    "rgb",
397    "RGB",
398    u8,
399    8
400);
401rgb102_cnv!(
402    rgb8_to_ra30,
403    "RA30",
404    "RGBA1010102",
405    "ra30",
406    Rgb30::Ra30,
407    YuvSourceChannels::Rgb,
408    "rgb",
409    "RGB",
410    u8,
411    8
412);
413rgb102_cnv!(
414    rgba8_to_ar30,
415    "AR30",
416    "ARGB2101010",
417    "ar30",
418    Rgb30::Ar30,
419    YuvSourceChannels::Rgba,
420    "rgba",
421    "RGBA",
422    u8,
423    8
424);
425rgb102_cnv!(
426    rgba8_to_ra30,
427    "RA30",
428    "RGBA1010102",
429    "ra30",
430    Rgb30::Ra30,
431    YuvSourceChannels::Rgba,
432    "rgba",
433    "RGBA",
434    u8,
435    8
436);
437
438rgb102_cnv!(
439    rgb10_to_ar30,
440    "AR30",
441    "ARGB2101010",
442    "ar30",
443    Rgb30::Ar30,
444    YuvSourceChannels::Rgb,
445    "rgb",
446    "RGB",
447    u16,
448    10
449);
450rgb102_cnv!(
451    rgb10_to_ra30,
452    "RA30",
453    "RGBA1010102",
454    "ra30",
455    Rgb30::Ra30,
456    YuvSourceChannels::Rgb,
457    "rgb",
458    "RGB",
459    u16,
460    10
461);
462rgb102_cnv!(
463    rgb12_to_ar30,
464    "AR30",
465    "ARGB2101010",
466    "ar30",
467    Rgb30::Ar30,
468    YuvSourceChannels::Rgb,
469    "rgb",
470    "RGB",
471    u16,
472    12
473);
474rgb102_cnv!(
475    rgb12_to_ra30,
476    "RA30",
477    "RGBA1010102",
478    "ra30",
479    Rgb30::Ra30,
480    YuvSourceChannels::Rgb,
481    "rgb",
482    "RGB",
483    u16,
484    12
485);
486
487rgb102_cnv!(
488    rgba10_to_ar30,
489    "AR30",
490    "ARGB2101010",
491    "ar30",
492    Rgb30::Ar30,
493    YuvSourceChannels::Rgba,
494    "rgba",
495    "RGBA",
496    u16,
497    10
498);
499rgb102_cnv!(
500    rgba10_to_ra30,
501    "RA30",
502    "RGBA1010102",
503    "ra30",
504    Rgb30::Ra30,
505    YuvSourceChannels::Rgba,
506    "rgba",
507    "RGBA",
508    u16,
509    10
510);
511rgb102_cnv!(
512    rgba12_to_ar30,
513    "AR30",
514    "ARGB2101010",
515    "ar30",
516    Rgb30::Ar30,
517    YuvSourceChannels::Rgba,
518    "rgba",
519    "RGBA",
520    u16,
521    12
522);
523rgb102_cnv!(
524    rgba12_to_ra30,
525    "RA30",
526    "RGBA1010102",
527    "ra30",
528    Rgb30::Ra30,
529    YuvSourceChannels::Rgba,
530    "rgba",
531    "RGBA",
532    u16,
533    12
534);
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539    use crate::ar30_to_rgb8;
540    use rand::Rng;
541
542    #[test]
543    fn ar30_rgb_round_trip_host() {
544        let image_width = 64usize;
545        let image_height = 64usize;
546
547        let random_point_x = rand::rng().random_range(0..image_width);
548        let random_point_y = rand::rng().random_range(0..image_height);
549
550        let random_r = rand::rng().random_range(0..255) as u8;
551        let random_g = rand::rng().random_range(0..255) as u8;
552        let random_b = rand::rng().random_range(0..255) as u8;
553
554        const CN: usize = 3;
555
556        let mut source_rgb = vec![0u8; image_width * image_height * CN];
557
558        for chunk in source_rgb.chunks_exact_mut(CN) {
559            chunk[0] = random_r;
560            chunk[1] = random_g;
561            chunk[2] = random_b;
562        }
563
564        let mut dst_ar30 = vec![0u8; image_width * image_height * 4];
565        rgb8_to_ar30(
566            &mut dst_ar30,
567            image_width as u32 * 4,
568            Rgb30ByteOrder::Host,
569            &source_rgb,
570            image_width as u32 * CN as u32,
571            image_width as u32,
572            image_height as u32,
573        )
574        .unwrap();
575
576        ar30_to_rgb8(
577            &dst_ar30,
578            image_width as u32 * 4,
579            Rgb30ByteOrder::Host,
580            &mut source_rgb,
581            image_width as u32 * CN as u32,
582            image_width as u32,
583            image_height as u32,
584        )
585        .unwrap();
586
587        assert_eq!(
588            source_rgb[random_point_x * CN],
589            random_r,
590            "R invalid {}, expected {} Point ({}, {})",
591            source_rgb[random_point_x * CN],
592            random_r,
593            random_point_x,
594            random_point_y
595        );
596        assert_eq!(
597            source_rgb[random_point_x * CN + 1],
598            random_g,
599            "G invalid {}, expected {} Point ({}, {})",
600            source_rgb[random_point_x * CN + 1],
601            random_r,
602            random_point_x,
603            random_point_y
604        );
605        assert_eq!(
606            source_rgb[random_point_x * CN + 2],
607            random_b,
608            "B invalid {}, expected {} Point ({}, {})",
609            source_rgb[random_point_x * CN + 2],
610            random_r,
611            random_point_x,
612            random_point_y
613        );
614    }
615
616    #[test]
617    fn ar30_rgb_round_trip_network() {
618        let image_width = 64usize;
619        let image_height = 64usize;
620
621        let random_point_x = rand::rng().random_range(0..image_width);
622        let random_point_y = rand::rng().random_range(0..image_height);
623
624        let random_r = rand::rng().random_range(0..255) as u8;
625        let random_g = rand::rng().random_range(0..255) as u8;
626        let random_b = rand::rng().random_range(0..255) as u8;
627
628        const CN: usize = 3;
629
630        let mut source_rgb = vec![0u8; image_width * image_height * CN];
631
632        for chunk in source_rgb.chunks_exact_mut(CN) {
633            chunk[0] = random_r;
634            chunk[1] = random_g;
635            chunk[2] = random_b;
636        }
637
638        let mut dst_ar30 = vec![0u8; image_width * image_height * 4];
639        rgb8_to_ar30(
640            &mut dst_ar30,
641            image_width as u32 * 4,
642            Rgb30ByteOrder::Network,
643            &source_rgb,
644            image_width as u32 * CN as u32,
645            image_width as u32,
646            image_height as u32,
647        )
648        .unwrap();
649
650        ar30_to_rgb8(
651            &dst_ar30,
652            image_width as u32 * 4,
653            Rgb30ByteOrder::Network,
654            &mut source_rgb,
655            image_width as u32 * CN as u32,
656            image_width as u32,
657            image_height as u32,
658        )
659        .unwrap();
660
661        assert_eq!(
662            source_rgb[random_point_x * CN],
663            random_r,
664            "R invalid {}, expected {} Point ({}, {})",
665            source_rgb[random_point_x * CN],
666            random_r,
667            random_point_x,
668            random_point_y
669        );
670        assert_eq!(
671            source_rgb[random_point_x * CN + 1],
672            random_g,
673            "G invalid {}, expected {} Point ({}, {})",
674            source_rgb[random_point_x * CN + 1],
675            random_r,
676            random_point_x,
677            random_point_y
678        );
679        assert_eq!(
680            source_rgb[random_point_x * CN + 2],
681            random_b,
682            "B invalid {}, expected {} Point ({}, {})",
683            source_rgb[random_point_x * CN + 2],
684            random_r,
685            random_point_x,
686            random_point_y
687        );
688    }
689}