styx_codec/decoder/raw/
bgr.rs1use 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
41pub 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 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]; 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}