styx_codec/decoder/raw/
bgr.rs

1use styx_core::prelude::*;
2
3#[cfg(feature = "image")]
4use crate::decoder::{ImageDecode, process_to_dynamic};
5use crate::{Codec, CodecDescriptor, CodecError, CodecKind};
6use rayon::prelude::*;
7
8#[cfg(target_arch = "aarch64")]
9#[inline(always)]
10unsafe fn bgr_row_to_rgb24_neon(src: &[u8], dst: &mut [u8], width: usize) {
11    use std::arch::aarch64::{uint8x16x3_t, vld3q_u8, vst3q_u8};
12    debug_assert!(src.len() >= width * 3);
13    debug_assert!(dst.len() >= width * 3);
14
15    let src_ptr = src.as_ptr();
16    let dst_ptr = dst.as_mut_ptr();
17
18    let mut x = 0usize;
19    while x + 16 <= width {
20        unsafe {
21            let bgr = vld3q_u8(src_ptr.add(x * 3));
22            let rgb = uint8x16x3_t(bgr.2, bgr.1, bgr.0);
23            vst3q_u8(dst_ptr.add(x * 3), rgb);
24        }
25        x += 16;
26    }
27    for x in x..width {
28        unsafe {
29            let si = x * 3;
30            let di = x * 3;
31            let b = *src_ptr.add(si);
32            let g = *src_ptr.add(si + 1);
33            let r = *src_ptr.add(si + 2);
34            *dst_ptr.add(di) = r;
35            *dst_ptr.add(di + 1) = g;
36            *dst_ptr.add(di + 2) = b;
37        }
38    }
39}
40
41/// BGR24 → RGB24 decoder (channel swap).
42pub struct BgrToRgbDecoder {
43    descriptor: CodecDescriptor,
44    pool: BufferPool,
45}
46
47impl BgrToRgbDecoder {
48    pub fn new(max_width: u32, max_height: u32) -> Self {
49        let bytes = max_width as usize * max_height as usize * 3;
50        Self::with_input(
51            BufferPool::with_limits(2, bytes, 4),
52            FourCc::new(*b"BGR3"),
53            "bgr-swap",
54        )
55    }
56
57    pub fn with_pool(pool: BufferPool) -> Self {
58        Self::with_input(pool, FourCc::new(*b"BGR3"), "bgr-swap")
59    }
60
61    pub fn with_input(pool: BufferPool, input: FourCc, impl_name: &'static str) -> Self {
62        Self {
63            descriptor: CodecDescriptor {
64                kind: CodecKind::Decoder,
65                input,
66                output: FourCc::new(*b"RG24"),
67                name: "bgr2rgb",
68                impl_name,
69            },
70            pool,
71        }
72    }
73
74    pub fn with_input_for_max(
75        input: FourCc,
76        impl_name: &'static str,
77        max_width: u32,
78        max_height: u32,
79    ) -> Self {
80        let bytes = max_width as usize * max_height as usize * 3;
81        Self::with_input(BufferPool::with_limits(2, bytes, 4), input, impl_name)
82    }
83
84    /// Decode into a caller-provided tightly-packed RGB24 buffer.
85    ///
86    /// `dst` must be at least `width * height * 3` bytes.
87    pub fn decode_into(&self, input: &FrameLease, dst: &mut [u8]) -> Result<FrameMeta, CodecError> {
88        let meta = input.meta();
89        if meta.format.code != self.descriptor.input {
90            return Err(CodecError::FormatMismatch {
91                expected: self.descriptor.input,
92                actual: meta.format.code,
93            });
94        }
95        let plane = input
96            .planes()
97            .into_iter()
98            .next()
99            .ok_or_else(|| CodecError::Codec("bgr frame missing plane".into()))?;
100
101        let width = meta.format.resolution.width.get() as usize;
102        let height = meta.format.resolution.height.get() as usize;
103        let stride = plane.stride().max(width * 3);
104        let required = stride
105            .checked_mul(height)
106            .ok_or_else(|| CodecError::Codec("bgr stride overflow".into()))?;
107        if plane.data().len() < required {
108            return Err(CodecError::Codec("bgr plane buffer too short".into()));
109        }
110
111        let row_bytes = width * 3;
112        let out_len = row_bytes
113            .checked_mul(height)
114            .ok_or_else(|| CodecError::Codec("bgr output overflow".into()))?;
115        if dst.len() < out_len {
116            return Err(CodecError::Codec("bgr dst buffer too short".into()));
117        }
118
119        let src = plane.data();
120        dst[..out_len]
121            .par_chunks_mut(row_bytes)
122            .enumerate()
123            .for_each(|(y, dst_line)| {
124                let src_line = &src[y * stride..][..width * 3];
125                #[cfg(target_arch = "aarch64")]
126                unsafe {
127                    bgr_row_to_rgb24_neon(src_line, dst_line, width);
128                    return;
129                }
130                #[cfg(not(target_arch = "aarch64"))]
131                {
132                    for (dst_px, src_px) in dst_line
133                        .chunks_exact_mut(3)
134                        .zip(src_line.chunks_exact(3))
135                    {
136                        dst_px[0] = src_px[2];
137                        dst_px[1] = src_px[1];
138                        dst_px[2] = src_px[0];
139                    }
140                }
141            });
142
143        Ok(FrameMeta::new(
144            MediaFormat::new(
145                self.descriptor.output,
146                meta.format.resolution,
147                meta.format.color,
148            ),
149            meta.timestamp,
150        ))
151    }
152}
153
154impl Codec for BgrToRgbDecoder {
155    fn descriptor(&self) -> &CodecDescriptor {
156        &self.descriptor
157    }
158
159    fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
160        let layout = plane_layout_from_dims(
161            input.meta().format.resolution.width,
162            input.meta().format.resolution.height,
163            3,
164        );
165        let mut buf = self.pool.lease();
166        unsafe {
167            buf.resize_uninit(layout.len);
168        }
169        let meta = self.decode_into(&input, buf.as_mut_slice())?;
170
171        Ok(unsafe {
172            FrameLease::single_plane_uninit(
173                meta,
174                buf,
175                layout.len,
176                layout.stride,
177            )
178        })
179    }
180}
181
182#[cfg(test)]
183#[allow(clippy::items_after_test_module)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn decode_into_matches_process() {
189        let res = Resolution::new(2, 1).unwrap();
190        let format = MediaFormat::new(FourCc::new(*b"BGR3"), res, ColorSpace::Srgb);
191        let src = [1u8, 2, 3, 4, 5, 6]; // BGR BGR
192        let make_frame = || {
193            let mut buf = BufferPool::with_limits(1, 6, 1).lease();
194            buf.resize(6);
195            buf.as_mut_slice().copy_from_slice(&src);
196            FrameLease::single_plane(FrameMeta::new(format, 123), buf, 6, 6)
197        };
198
199        let dec = BgrToRgbDecoder::with_pool(BufferPool::with_limits(1, 6, 1));
200        let processed = dec.process(make_frame()).unwrap();
201        let plane = processed.planes().into_iter().next().unwrap();
202
203        let mut out = vec![0u8; 6];
204        let frame = make_frame();
205        dec.decode_into(&frame, &mut out).unwrap();
206
207        assert_eq!(plane.data(), out.as_slice());
208    }
209}
210
211#[cfg(feature = "image")]
212impl ImageDecode for BgrToRgbDecoder {
213    fn decode_image(&self, frame: FrameLease) -> Result<image::DynamicImage, CodecError> {
214        process_to_dynamic(self, frame)
215    }
216}