yuvutils_rs/
shuffle.rs

1/*
2 * Copyright (c) Radzivon Bartoshyk, 12/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::YuvSourceChannels;
31use crate::YuvError;
32
33pub(crate) trait ShuffleConverter<V: Copy, const SRC: u8, const DST: u8> {
34    fn convert(&self, src: &[V], dst: &mut [V], width: usize);
35}
36
37trait ShuffleConverterFactory<V> {
38    fn make_converter<const SRC: u8, const DST: u8>() -> Box<dyn ShuffleConverter<V, SRC, DST>>;
39}
40
41struct Rgba8DefaultConverter<const SRC: u8, const DST: u8> {}
42
43impl<const SRC: u8, const DST: u8> Default for Rgba8DefaultConverter<SRC, DST> {
44    fn default() -> Self {
45        Rgba8DefaultConverter {}
46    }
47}
48
49impl<const SRC: u8, const DST: u8> ShuffleConverter<u8, SRC, DST>
50    for Rgba8DefaultConverter<SRC, DST>
51{
52    fn convert(&self, src: &[u8], dst: &mut [u8], _: usize) {
53        let src_channels: YuvSourceChannels = SRC.into();
54        let dst_channels: YuvSourceChannels = DST.into();
55        for (dst, src) in dst
56            .chunks_exact_mut(dst_channels.get_channels_count())
57            .zip(src.chunks_exact(src_channels.get_channels_count()))
58        {
59            dst[dst_channels.get_r_channel_offset()] = src[src_channels.get_r_channel_offset()];
60            dst[dst_channels.get_g_channel_offset()] = src[src_channels.get_g_channel_offset()];
61            dst[dst_channels.get_b_channel_offset()] = src[src_channels.get_b_channel_offset()];
62            if dst_channels.has_alpha() {
63                let a = if src_channels.has_alpha() {
64                    src[src_channels.get_a_channel_offset()]
65                } else {
66                    255
67                };
68                dst[dst_channels.get_a_channel_offset()] = a;
69            }
70        }
71    }
72}
73
74impl ShuffleConverterFactory<u8> for u8 {
75    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
76    fn make_converter<const SRC: u8, const DST: u8>() -> Box<dyn ShuffleConverter<u8, SRC, DST>> {
77        let mut _converter: Box<dyn ShuffleConverter<u8, SRC, DST>> =
78            Box::new(Rgba8DefaultConverter::default());
79        #[cfg(feature = "avx")]
80        if std::arch::is_x86_feature_detected!("avx2") {
81            let src_channels: YuvSourceChannels = SRC.into();
82            let dst_channels: YuvSourceChannels = DST.into();
83            use crate::avx2::{ShuffleConverterAvx2, ShuffleQTableConverterAvx2};
84            if src_channels.get_channels_count() == 4 && dst_channels.get_channels_count() == 4 {
85                _converter = Box::new(ShuffleQTableConverterAvx2::<SRC, DST>::create());
86            } else {
87                _converter = Box::new(ShuffleConverterAvx2::<SRC, DST>::default());
88            }
89            return _converter;
90        }
91        #[cfg(feature = "sse")]
92        {
93            use crate::sse::{ShuffleConverterSse, ShuffleQTableConverterSse};
94            let src_channels: YuvSourceChannels = SRC.into();
95            let dst_channels: YuvSourceChannels = DST.into();
96            if std::arch::is_x86_feature_detected!("sse4.1") {
97                if src_channels.get_channels_count() == 4 && dst_channels.get_channels_count() == 4
98                {
99                    _converter = Box::new(ShuffleQTableConverterSse::<SRC, DST>::create());
100                } else {
101                    _converter = Box::new(ShuffleConverterSse::<SRC, DST>::default());
102                }
103                return _converter;
104            }
105        }
106        _converter
107    }
108
109    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
110    fn make_converter<const SRC: u8, const DST: u8>() -> Box<dyn ShuffleConverter<u8, SRC, DST>> {
111        use crate::neon::ShuffleConverterNeon;
112        Box::new(ShuffleConverterNeon::<SRC, DST>::default())
113    }
114
115    #[cfg(not(any(
116        all(target_arch = "aarch64", target_feature = "neon"),
117        any(target_arch = "x86", target_arch = "x86_64")
118    )))]
119    fn make_converter<const SRC: u8, const DST: u8>() -> Box<dyn ShuffleConverter<u8, SRC, DST>> {
120        Box::new(Rgba8DefaultConverter::<SRC, DST>::default())
121    }
122}
123
124/// Channel reshuffling implementation
125fn shuffle_impl<
126    V: Copy + ShuffleConverterFactory<V>,
127    const SRC: u8,
128    const DST: u8,
129    const BIT_DEPTH: usize,
130>(
131    src: &[V],
132    src_stride: u32,
133    dst: &mut [V],
134    dst_stride: u32,
135    width: u32,
136    height: u32,
137) -> Result<(), YuvError> {
138    let src_channels: YuvSourceChannels = SRC.into();
139    let dst_channels: YuvSourceChannels = DST.into();
140    check_rgba_destination(
141        src,
142        src_stride,
143        width,
144        height,
145        src_channels.get_channels_count(),
146    )?;
147    check_rgba_destination(
148        dst,
149        dst_stride,
150        width,
151        height,
152        dst_channels.get_channels_count(),
153    )?;
154
155    let converter = V::make_converter::<SRC, DST>();
156
157    for (dst, src) in dst
158        .chunks_exact_mut(dst_stride as usize)
159        .zip(src.chunks_exact(src_stride as usize))
160    {
161        let dst = &mut dst[0..dst_channels.get_channels_count() * width as usize];
162        let src = &src[0..src_channels.get_channels_count() * width as usize];
163        converter.convert(src, dst, dst_stride as usize);
164    }
165
166    Ok(())
167}
168
169/// Converts RGBA8 to BGRA8
170///
171/// # Arguments
172///
173/// * `src`: Source slice
174/// * `src_stride`: Source slice stride
175/// * `dst`: Destination slice
176/// * `dst_stride`: Destination slice stride
177/// * `width`: Image width
178/// * `height`: Image height
179///
180/// returns: Result<(), YuvError>
181///
182pub fn rgba_to_bgra(
183    src: &[u8],
184    src_stride: u32,
185    dst: &mut [u8],
186    dst_stride: u32,
187    width: u32,
188    height: u32,
189) -> Result<(), YuvError> {
190    shuffle_impl::<u8, { YuvSourceChannels::Rgba as u8 }, { YuvSourceChannels::Bgra as u8 }, 8>(
191        src, src_stride, dst, dst_stride, width, height,
192    )
193}
194
195/// Converts RGBA8 to BGR8
196///
197/// # Arguments
198///
199/// * `src`: Source slice
200/// * `src_stride`: Source slice stride
201/// * `dst`: Destination slice
202/// * `dst_stride`: Destination slice stride
203/// * `width`: Image width
204/// * `height`: Image height
205///
206/// returns: Result<(), YuvError>
207///
208pub fn rgba_to_bgr(
209    src: &[u8],
210    src_stride: u32,
211    dst: &mut [u8],
212    dst_stride: u32,
213    width: u32,
214    height: u32,
215) -> Result<(), YuvError> {
216    shuffle_impl::<u8, { YuvSourceChannels::Rgba as u8 }, { YuvSourceChannels::Bgr as u8 }, 8>(
217        src, src_stride, dst, dst_stride, width, height,
218    )
219}
220
221/// Converts RGBA8 to RGB8
222///
223/// # Arguments
224///
225/// * `src`: Source slice
226/// * `src_stride`: Source slice stride
227/// * `dst`: Destination slice
228/// * `dst_stride`: Destination slice stride
229/// * `width`: Image width
230/// * `height`: Image height
231///
232/// returns: Result<(), YuvError>
233///
234pub fn rgba_to_rgb(
235    src: &[u8],
236    src_stride: u32,
237    dst: &mut [u8],
238    dst_stride: u32,
239    width: u32,
240    height: u32,
241) -> Result<(), YuvError> {
242    shuffle_impl::<u8, { YuvSourceChannels::Rgba as u8 }, { YuvSourceChannels::Rgb as u8 }, 8>(
243        src, src_stride, dst, dst_stride, width, height,
244    )
245}
246
247/// Converts RGB8 to BGBA8
248///
249/// # Arguments
250///
251/// * `src`: Source slice
252/// * `src_stride`: Source slice stride
253/// * `dst`: Destination slice
254/// * `dst_stride`: Destination slice stride
255/// * `width`: Image width
256/// * `height`: Image height
257///
258/// returns: Result<(), YuvError>
259///
260pub fn rgb_to_bgra(
261    src: &[u8],
262    src_stride: u32,
263    dst: &mut [u8],
264    dst_stride: u32,
265    width: u32,
266    height: u32,
267) -> Result<(), YuvError> {
268    shuffle_impl::<u8, { YuvSourceChannels::Rgb as u8 }, { YuvSourceChannels::Bgra as u8 }, 8>(
269        src, src_stride, dst, dst_stride, width, height,
270    )
271}
272
273/// Converts RGB8 to BGB8
274///
275/// # Arguments
276///
277/// * `src`: Source slice
278/// * `src_stride`: Source slice stride
279/// * `dst`: Destination slice
280/// * `dst_stride`: Destination slice stride
281/// * `width`: Image width
282/// * `height`: Image height
283///
284/// returns: Result<(), YuvError>
285///
286pub fn rgb_to_bgr(
287    src: &[u8],
288    src_stride: u32,
289    dst: &mut [u8],
290    dst_stride: u32,
291    width: u32,
292    height: u32,
293) -> Result<(), YuvError> {
294    shuffle_impl::<u8, { YuvSourceChannels::Rgb as u8 }, { YuvSourceChannels::Bgr as u8 }, 8>(
295        src, src_stride, dst, dst_stride, width, height,
296    )
297}
298
299/// Converts BGR8 to RGB8
300///
301/// # Arguments
302///
303/// * `src`: Source slice
304/// * `src_stride`: Source slice stride
305/// * `dst`: Destination slice
306/// * `dst_stride`: Destination slice stride
307/// * `width`: Image width
308/// * `height`: Image height
309///
310/// returns: Result<(), YuvError>
311///
312pub fn bgr_to_rgb(
313    src: &[u8],
314    src_stride: u32,
315    dst: &mut [u8],
316    dst_stride: u32,
317    width: u32,
318    height: u32,
319) -> Result<(), YuvError> {
320    shuffle_impl::<u8, { YuvSourceChannels::Bgr as u8 }, { YuvSourceChannels::Rgb as u8 }, 8>(
321        src, src_stride, dst, dst_stride, width, height,
322    )
323}
324
325/// Converts BGR8 to RGBA8
326///
327/// # Arguments
328///
329/// * `src`: Source slice
330/// * `src_stride`: Source slice stride
331/// * `dst`: Destination slice
332/// * `dst_stride`: Destination slice stride
333/// * `width`: Image width
334/// * `height`: Image height
335///
336/// returns: Result<(), YuvError>
337///
338pub fn bgr_to_rgba(
339    src: &[u8],
340    src_stride: u32,
341    dst: &mut [u8],
342    dst_stride: u32,
343    width: u32,
344    height: u32,
345) -> Result<(), YuvError> {
346    shuffle_impl::<u8, { YuvSourceChannels::Bgr as u8 }, { YuvSourceChannels::Rgba as u8 }, 8>(
347        src, src_stride, dst, dst_stride, width, height,
348    )
349}
350
351/// Converts BGR8 to BGRA8
352///
353/// # Arguments
354///
355/// * `src`: Source slice
356/// * `src_stride`: Source slice stride
357/// * `dst`: Destination slice
358/// * `dst_stride`: Destination slice stride
359/// * `width`: Image width
360/// * `height`: Image height
361///
362/// returns: Result<(), YuvError>
363///
364pub fn bgr_to_bgra(
365    src: &[u8],
366    src_stride: u32,
367    dst: &mut [u8],
368    dst_stride: u32,
369    width: u32,
370    height: u32,
371) -> Result<(), YuvError> {
372    shuffle_impl::<u8, { YuvSourceChannels::Bgr as u8 }, { YuvSourceChannels::Bgra as u8 }, 8>(
373        src, src_stride, dst, dst_stride, width, height,
374    )
375}
376
377/// Converts RGB8 to RGBA8
378///
379/// # Arguments
380///
381/// * `src`: Source slice
382/// * `src_stride`: Source slice stride
383/// * `dst`: Destination slice
384/// * `dst_stride`: Destination slice stride
385/// * `width`: Image width
386/// * `height`: Image height
387///
388/// returns: Result<(), YuvError>
389///
390pub fn rgb_to_rgba(
391    src: &[u8],
392    src_stride: u32,
393    dst: &mut [u8],
394    dst_stride: u32,
395    width: u32,
396    height: u32,
397) -> Result<(), YuvError> {
398    shuffle_impl::<u8, { YuvSourceChannels::Rgb as u8 }, { YuvSourceChannels::Rgba as u8 }, 8>(
399        src, src_stride, dst, dst_stride, width, height,
400    )
401}
402
403/// Converts BGRA to RGBA8
404///
405/// # Arguments
406///
407/// * `src`: Source slice
408/// * `src_stride`: Source slice stride
409/// * `dst`: Destination slice
410/// * `dst_stride`: Destination slice stride
411/// * `width`: Image width
412/// * `height`: Image height
413///
414/// returns: Result<(), YuvError>
415///
416pub fn bgra_to_rgba(
417    src: &[u8],
418    src_stride: u32,
419    dst: &mut [u8],
420    dst_stride: u32,
421    width: u32,
422    height: u32,
423) -> Result<(), YuvError> {
424    shuffle_impl::<u8, { YuvSourceChannels::Bgra as u8 }, { YuvSourceChannels::Rgba as u8 }, 8>(
425        src, src_stride, dst, dst_stride, width, height,
426    )
427}
428
429/// Converts BGRA to RGB8
430///
431/// # Arguments
432///
433/// * `src`: Source slice
434/// * `src_stride`: Source slice stride
435/// * `dst`: Destination slice
436/// * `dst_stride`: Destination slice stride
437/// * `width`: Image width
438/// * `height`: Image height
439///
440/// returns: Result<(), YuvError>
441///
442pub fn bgra_to_rgb(
443    src: &[u8],
444    src_stride: u32,
445    dst: &mut [u8],
446    dst_stride: u32,
447    width: u32,
448    height: u32,
449) -> Result<(), YuvError> {
450    shuffle_impl::<u8, { YuvSourceChannels::Bgra as u8 }, { YuvSourceChannels::Rgb as u8 }, 8>(
451        src, src_stride, dst, dst_stride, width, height,
452    )
453}
454
455/// Converts BGRA to RGB8
456///
457/// # Arguments
458///
459/// * `src`: Source slice
460/// * `src_stride`: Source slice stride
461/// * `dst`: Destination slice
462/// * `dst_stride`: Destination slice stride
463/// * `width`: Image width
464/// * `height`: Image height
465///
466/// returns: Result<(), YuvError>
467///
468pub fn bgra_to_bgr(
469    src: &[u8],
470    src_stride: u32,
471    dst: &mut [u8],
472    dst_stride: u32,
473    width: u32,
474    height: u32,
475) -> Result<(), YuvError> {
476    shuffle_impl::<u8, { YuvSourceChannels::Bgra as u8 }, { YuvSourceChannels::Bgr as u8 }, 8>(
477        src, src_stride, dst, dst_stride, width, height,
478    )
479}
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484
485    #[test]
486    fn check_4_chan_reshuffling() {
487        let image_width = 256usize;
488        let image_height = 256usize;
489
490        const CHANNELS: usize = 4;
491
492        let mut image_rgba = vec![0u8; image_width * image_height * CHANNELS];
493        let mut image_bgra = vec![0u8; image_width * image_height * CHANNELS];
494
495        let center = image_width / 2 * CHANNELS + (image_height / 2) * CHANNELS * image_width;
496
497        image_rgba[center] = 127;
498        image_rgba[center + 1] = 17;
499        image_rgba[center + 2] = 2;
500        image_rgba[center + 3] = 90;
501
502        rgba_to_bgra(
503            &image_rgba,
504            CHANNELS as u32 * image_width as u32,
505            &mut image_bgra,
506            CHANNELS as u32 * image_width as u32,
507            image_width as u32,
508            image_height as u32,
509        )
510        .unwrap();
511
512        assert_eq!(image_bgra[center], 2);
513        assert_eq!(image_bgra[center + 1], 17);
514        assert_eq!(image_bgra[center + 2], 127);
515        assert_eq!(image_bgra[center + 3], 90);
516    }
517
518    #[test]
519    fn check_3_to_4_chan_reshuffling() {
520        let image_width = 256usize;
521        let image_height = 256usize;
522
523        const SRC: usize = 3;
524        const DST: usize = 4;
525
526        let mut image_rgb = vec![0u8; image_width * image_height * SRC];
527        let mut image_bgra = vec![0u8; image_width * image_height * DST];
528
529        let center_src = image_width / 2 * SRC + (image_height / 2) * SRC * image_width;
530        let center_dst = image_width / 2 * DST + (image_height / 2) * DST * image_width;
531
532        image_rgb[center_src] = 127;
533        image_rgb[center_src + 1] = 17;
534        image_rgb[center_src + 2] = 2;
535        image_rgb[center_src + 3] = 90;
536
537        rgb_to_bgra(
538            &image_rgb,
539            SRC as u32 * image_width as u32,
540            &mut image_bgra,
541            DST as u32 * image_width as u32,
542            image_width as u32,
543            image_height as u32,
544        )
545        .unwrap();
546
547        assert_eq!(image_bgra[center_dst], 2);
548        assert_eq!(image_bgra[center_dst + 1], 17);
549        assert_eq!(image_bgra[center_dst + 2], 127);
550        assert_eq!(image_bgra[center_dst + 3], 255);
551    }
552
553    #[test]
554    fn check_4_to_3_chan_reshuffling() {
555        let image_width = 256usize;
556        let image_height = 256usize;
557
558        const SRC: usize = 4;
559        const DST: usize = 3;
560
561        let mut image_rgba = vec![0u8; image_width * image_height * SRC];
562        let mut image_bgr = vec![0u8; image_width * image_height * DST];
563
564        let center_src = image_width / 2 * SRC + (image_height / 2) * SRC * image_width;
565        let center_dst = image_width / 2 * DST + (image_height / 2) * DST * image_width;
566
567        image_rgba[center_src] = 127;
568        image_rgba[center_src + 1] = 17;
569        image_rgba[center_src + 2] = 2;
570        image_rgba[center_src + 3] = 90;
571
572        rgba_to_bgr(
573            &image_rgba,
574            SRC as u32 * image_width as u32,
575            &mut image_bgr,
576            DST as u32 * image_width as u32,
577            image_width as u32,
578            image_height as u32,
579        )
580        .unwrap();
581
582        assert_eq!(image_bgr[center_dst], 2);
583        assert_eq!(image_bgr[center_dst + 1], 17);
584        assert_eq!(image_bgr[center_dst + 2], 127);
585    }
586}