ccap/
convert.rs

1use crate::error::{CcapError, Result};
2use crate::sys;
3use crate::types::ColorConversionBackend;
4use std::os::raw::c_int;
5
6/// Color conversion utilities
7pub struct Convert;
8
9/// Validate that the input buffer has sufficient size
10fn validate_buffer_size(data: &[u8], required: usize, name: &str) -> Result<()> {
11    if data.len() < required {
12        return Err(CcapError::InvalidParameter(format!(
13            "{} buffer too small: got {} bytes, need at least {} bytes",
14            name,
15            data.len(),
16            required
17        )));
18    }
19    Ok(())
20}
21
22impl Convert {
23    /// Get current color conversion backend
24    pub fn backend() -> ColorConversionBackend {
25        let backend = unsafe { sys::ccap_convert_get_backend() };
26        ColorConversionBackend::from_c_enum(backend)
27    }
28
29    /// Set color conversion backend
30    pub fn set_backend(backend: ColorConversionBackend) -> Result<()> {
31        let success = unsafe { sys::ccap_convert_set_backend(backend.to_c_enum()) };
32
33        if success {
34            Ok(())
35        } else {
36            Err(CcapError::BackendSetFailed)
37        }
38    }
39
40    /// Check if AVX2 is available
41    pub fn has_avx2() -> bool {
42        unsafe { sys::ccap_convert_has_avx2() }
43    }
44
45    /// Check if Apple Accelerate is available
46    pub fn has_apple_accelerate() -> bool {
47        unsafe { sys::ccap_convert_has_apple_accelerate() }
48    }
49
50    /// Check if NEON is available
51    pub fn has_neon() -> bool {
52        unsafe { sys::ccap_convert_has_neon() }
53    }
54
55    /// Convert YUYV to RGB24
56    ///
57    /// # Errors
58    ///
59    /// Returns `CcapError::InvalidParameter` if `src_data` is too small for the given dimensions.
60    pub fn yuyv_to_rgb24(
61        src_data: &[u8],
62        src_stride: usize,
63        width: u32,
64        height: u32,
65    ) -> Result<Vec<u8>> {
66        let required = src_stride * height as usize;
67        validate_buffer_size(src_data, required, "YUYV source")?;
68
69        let dst_stride = (width * 3) as usize;
70        let dst_size = dst_stride * height as usize;
71        let mut dst_data = vec![0u8; dst_size];
72
73        unsafe {
74            sys::ccap_convert_yuyv_to_rgb24(
75                src_data.as_ptr(),
76                src_stride as c_int,
77                dst_data.as_mut_ptr(),
78                dst_stride as c_int,
79                width as c_int,
80                height as c_int,
81                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
82            )
83        };
84
85        Ok(dst_data)
86    }
87
88    /// Convert YUYV to BGR24
89    ///
90    /// # Errors
91    ///
92    /// Returns `CcapError::InvalidParameter` if `src_data` is too small for the given dimensions.
93    pub fn yuyv_to_bgr24(
94        src_data: &[u8],
95        src_stride: usize,
96        width: u32,
97        height: u32,
98    ) -> Result<Vec<u8>> {
99        let required = src_stride * height as usize;
100        validate_buffer_size(src_data, required, "YUYV source")?;
101
102        let dst_stride = (width * 3) as usize;
103        let dst_size = dst_stride * height as usize;
104        let mut dst_data = vec![0u8; dst_size];
105
106        unsafe {
107            sys::ccap_convert_yuyv_to_bgr24(
108                src_data.as_ptr(),
109                src_stride as c_int,
110                dst_data.as_mut_ptr(),
111                dst_stride as c_int,
112                width as c_int,
113                height as c_int,
114                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
115            )
116        };
117
118        Ok(dst_data)
119    }
120
121    /// Convert RGB to BGR
122    ///
123    /// # Errors
124    ///
125    /// Returns `CcapError::InvalidParameter` if `src_data` is too small for the given dimensions.
126    pub fn rgb_to_bgr(
127        src_data: &[u8],
128        src_stride: usize,
129        width: u32,
130        height: u32,
131    ) -> Result<Vec<u8>> {
132        let required = src_stride * height as usize;
133        validate_buffer_size(src_data, required, "RGB source")?;
134
135        let dst_stride = (width * 3) as usize;
136        let dst_size = dst_stride * height as usize;
137        let mut dst_data = vec![0u8; dst_size];
138
139        unsafe {
140            sys::ccap_convert_rgb_to_bgr(
141                src_data.as_ptr(),
142                src_stride as c_int,
143                dst_data.as_mut_ptr(),
144                dst_stride as c_int,
145                width as c_int,
146                height as c_int,
147            )
148        };
149
150        Ok(dst_data)
151    }
152
153    /// Convert BGR to RGB
154    ///
155    /// # Errors
156    ///
157    /// Returns `CcapError::InvalidParameter` if `src_data` is too small for the given dimensions.
158    pub fn bgr_to_rgb(
159        src_data: &[u8],
160        src_stride: usize,
161        width: u32,
162        height: u32,
163    ) -> Result<Vec<u8>> {
164        let required = src_stride * height as usize;
165        validate_buffer_size(src_data, required, "BGR source")?;
166
167        let dst_stride = (width * 3) as usize;
168        let dst_size = dst_stride * height as usize;
169        let mut dst_data = vec![0u8; dst_size];
170
171        unsafe {
172            sys::ccap_convert_bgr_to_rgb(
173                src_data.as_ptr(),
174                src_stride as c_int,
175                dst_data.as_mut_ptr(),
176                dst_stride as c_int,
177                width as c_int,
178                height as c_int,
179            )
180        };
181
182        Ok(dst_data)
183    }
184
185    /// Convert NV12 to RGB24
186    ///
187    /// # Errors
188    ///
189    /// Returns `CcapError::InvalidParameter` if buffers are too small for the given dimensions.
190    pub fn nv12_to_rgb24(
191        y_data: &[u8],
192        y_stride: usize,
193        uv_data: &[u8],
194        uv_stride: usize,
195        width: u32,
196        height: u32,
197    ) -> Result<Vec<u8>> {
198        let y_required = y_stride * height as usize;
199        let uv_required = uv_stride * ((height as usize + 1) / 2);
200        validate_buffer_size(y_data, y_required, "NV12 Y plane")?;
201        validate_buffer_size(uv_data, uv_required, "NV12 UV plane")?;
202
203        let dst_stride = (width * 3) as usize;
204        let dst_size = dst_stride * height as usize;
205        let mut dst_data = vec![0u8; dst_size];
206
207        unsafe {
208            sys::ccap_convert_nv12_to_rgb24(
209                y_data.as_ptr(),
210                y_stride as c_int,
211                uv_data.as_ptr(),
212                uv_stride as c_int,
213                dst_data.as_mut_ptr(),
214                dst_stride as c_int,
215                width as c_int,
216                height as c_int,
217                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
218            )
219        };
220
221        Ok(dst_data)
222    }
223
224    /// Convert NV12 to BGR24
225    ///
226    /// # Errors
227    ///
228    /// Returns `CcapError::InvalidParameter` if buffers are too small for the given dimensions.
229    pub fn nv12_to_bgr24(
230        y_data: &[u8],
231        y_stride: usize,
232        uv_data: &[u8],
233        uv_stride: usize,
234        width: u32,
235        height: u32,
236    ) -> Result<Vec<u8>> {
237        let y_required = y_stride * height as usize;
238        let uv_required = uv_stride * ((height as usize + 1) / 2);
239        validate_buffer_size(y_data, y_required, "NV12 Y plane")?;
240        validate_buffer_size(uv_data, uv_required, "NV12 UV plane")?;
241
242        let dst_stride = (width * 3) as usize;
243        let dst_size = dst_stride * height as usize;
244        let mut dst_data = vec![0u8; dst_size];
245
246        unsafe {
247            sys::ccap_convert_nv12_to_bgr24(
248                y_data.as_ptr(),
249                y_stride as c_int,
250                uv_data.as_ptr(),
251                uv_stride as c_int,
252                dst_data.as_mut_ptr(),
253                dst_stride as c_int,
254                width as c_int,
255                height as c_int,
256                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
257            )
258        };
259
260        Ok(dst_data)
261    }
262
263    /// Convert I420 to RGB24
264    ///
265    /// # Errors
266    ///
267    /// Returns `CcapError::InvalidParameter` if buffers are too small for the given dimensions.
268    #[allow(clippy::too_many_arguments)]
269    pub fn i420_to_rgb24(
270        y_data: &[u8],
271        y_stride: usize,
272        u_data: &[u8],
273        u_stride: usize,
274        v_data: &[u8],
275        v_stride: usize,
276        width: u32,
277        height: u32,
278    ) -> Result<Vec<u8>> {
279        let y_required = y_stride * height as usize;
280        let uv_height = (height as usize + 1) / 2;
281        let u_required = u_stride * uv_height;
282        let v_required = v_stride * uv_height;
283        validate_buffer_size(y_data, y_required, "I420 Y plane")?;
284        validate_buffer_size(u_data, u_required, "I420 U plane")?;
285        validate_buffer_size(v_data, v_required, "I420 V plane")?;
286
287        let dst_stride = (width * 3) as usize;
288        let dst_size = dst_stride * height as usize;
289        let mut dst_data = vec![0u8; dst_size];
290
291        unsafe {
292            sys::ccap_convert_i420_to_rgb24(
293                y_data.as_ptr(),
294                y_stride as c_int,
295                u_data.as_ptr(),
296                u_stride as c_int,
297                v_data.as_ptr(),
298                v_stride as c_int,
299                dst_data.as_mut_ptr(),
300                dst_stride as c_int,
301                width as c_int,
302                height as c_int,
303                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
304            )
305        };
306
307        Ok(dst_data)
308    }
309
310    /// Convert I420 to BGR24
311    ///
312    /// # Errors
313    ///
314    /// Returns `CcapError::InvalidParameter` if buffers are too small for the given dimensions.
315    #[allow(clippy::too_many_arguments)]
316    pub fn i420_to_bgr24(
317        y_data: &[u8],
318        y_stride: usize,
319        u_data: &[u8],
320        u_stride: usize,
321        v_data: &[u8],
322        v_stride: usize,
323        width: u32,
324        height: u32,
325    ) -> Result<Vec<u8>> {
326        let y_required = y_stride * height as usize;
327        let uv_height = (height as usize + 1) / 2;
328        let u_required = u_stride * uv_height;
329        let v_required = v_stride * uv_height;
330        validate_buffer_size(y_data, y_required, "I420 Y plane")?;
331        validate_buffer_size(u_data, u_required, "I420 U plane")?;
332        validate_buffer_size(v_data, v_required, "I420 V plane")?;
333
334        let dst_stride = (width * 3) as usize;
335        let dst_size = dst_stride * height as usize;
336        let mut dst_data = vec![0u8; dst_size];
337
338        unsafe {
339            sys::ccap_convert_i420_to_bgr24(
340                y_data.as_ptr(),
341                y_stride as c_int,
342                u_data.as_ptr(),
343                u_stride as c_int,
344                v_data.as_ptr(),
345                v_stride as c_int,
346                dst_data.as_mut_ptr(),
347                dst_stride as c_int,
348                width as c_int,
349                height as c_int,
350                sys::CcapConvertFlag_CCAP_CONVERT_FLAG_DEFAULT,
351            )
352        };
353
354        Ok(dst_data)
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn test_backend_detection() {
364        // Should be able to get current backend without panic
365        let backend = Convert::backend();
366        println!("Current backend: {:?}", backend);
367    }
368
369    #[test]
370    fn test_simd_availability() {
371        // These should return booleans without panic
372        let has_avx2 = Convert::has_avx2();
373        let has_neon = Convert::has_neon();
374        let has_accelerate = Convert::has_apple_accelerate();
375
376        println!(
377            "AVX2: {}, NEON: {}, Accelerate: {}",
378            has_avx2, has_neon, has_accelerate
379        );
380
381        // At most one SIMD backend should be available (platform-dependent)
382        // On x86: AVX2 may be available
383        // On ARM: NEON may be available
384        // On macOS: Accelerate may be available
385    }
386
387    #[test]
388    fn test_rgb_bgr_conversion() {
389        let width = 4u32;
390        let height = 4u32;
391        let stride = (width * 3) as usize;
392
393        // Create a simple RGB pattern: red, green, blue, white
394        let mut rgb_data = vec![0u8; stride * height as usize];
395        for y in 0..height as usize {
396            for x in 0..width as usize {
397                let offset = y * stride + x * 3;
398                match (x + y) % 4 {
399                    0 => {
400                        rgb_data[offset] = 255;
401                        rgb_data[offset + 1] = 0;
402                        rgb_data[offset + 2] = 0;
403                    } // Red
404                    1 => {
405                        rgb_data[offset] = 0;
406                        rgb_data[offset + 1] = 255;
407                        rgb_data[offset + 2] = 0;
408                    } // Green
409                    2 => {
410                        rgb_data[offset] = 0;
411                        rgb_data[offset + 1] = 0;
412                        rgb_data[offset + 2] = 255;
413                    } // Blue
414                    _ => {
415                        rgb_data[offset] = 255;
416                        rgb_data[offset + 1] = 255;
417                        rgb_data[offset + 2] = 255;
418                    } // White
419                }
420            }
421        }
422
423        // Convert RGB to BGR
424        let bgr_data = Convert::rgb_to_bgr(&rgb_data, stride, width, height).unwrap();
425        assert_eq!(bgr_data.len(), rgb_data.len());
426
427        // Verify R and B channels are swapped
428        for y in 0..height as usize {
429            for x in 0..width as usize {
430                let offset = y * stride + x * 3;
431                assert_eq!(
432                    rgb_data[offset],
433                    bgr_data[offset + 2],
434                    "R->B at ({}, {})",
435                    x,
436                    y
437                );
438                assert_eq!(
439                    rgb_data[offset + 1],
440                    bgr_data[offset + 1],
441                    "G==G at ({}, {})",
442                    x,
443                    y
444                );
445                assert_eq!(
446                    rgb_data[offset + 2],
447                    bgr_data[offset],
448                    "B->R at ({}, {})",
449                    x,
450                    y
451                );
452            }
453        }
454
455        // Convert back: BGR to RGB should restore original
456        let restored_rgb = Convert::bgr_to_rgb(&bgr_data, stride, width, height).unwrap();
457        assert_eq!(
458            restored_rgb, rgb_data,
459            "Round-trip RGB->BGR->RGB should be identical"
460        );
461    }
462
463    #[test]
464    fn test_nv12_to_rgb_basic() {
465        let width = 16u32;
466        let height = 16u32;
467        let y_stride = width as usize;
468        let uv_stride = width as usize;
469
470        // Create neutral gray NV12 data (Y=128, U=128, V=128 -> gray in RGB)
471        let y_data = vec![128u8; y_stride * height as usize];
472        let uv_data = vec![128u8; uv_stride * (height as usize / 2)];
473
474        let rgb_data =
475            Convert::nv12_to_rgb24(&y_data, y_stride, &uv_data, uv_stride, width, height).unwrap();
476
477        // Verify output size
478        let expected_size = (width * 3) as usize * height as usize;
479        assert_eq!(rgb_data.len(), expected_size);
480
481        // All pixels should be approximately gray (128, 128, 128) with some YUV rounding tolerance
482        for pixel in rgb_data.chunks(3) {
483            assert!(
484                pixel[0] >= 100 && pixel[0] <= 156,
485                "R should be near 128, got {}",
486                pixel[0]
487            );
488            assert!(
489                pixel[1] >= 100 && pixel[1] <= 156,
490                "G should be near 128, got {}",
491                pixel[1]
492            );
493            assert!(
494                pixel[2] >= 100 && pixel[2] <= 156,
495                "B should be near 128, got {}",
496                pixel[2]
497            );
498        }
499    }
500
501    #[test]
502    fn test_nv12_to_bgr_basic() {
503        let width = 16u32;
504        let height = 16u32;
505        let y_stride = width as usize;
506        let uv_stride = width as usize;
507
508        let y_data = vec![128u8; y_stride * height as usize];
509        let uv_data = vec![128u8; uv_stride * (height as usize / 2)];
510
511        let bgr_data =
512            Convert::nv12_to_bgr24(&y_data, y_stride, &uv_data, uv_stride, width, height).unwrap();
513
514        let expected_size = (width * 3) as usize * height as usize;
515        assert_eq!(bgr_data.len(), expected_size);
516    }
517
518    #[test]
519    fn test_i420_to_rgb_basic() {
520        let width = 16u32;
521        let height = 16u32;
522        let y_stride = width as usize;
523        let u_stride = (width / 2) as usize;
524        let v_stride = (width / 2) as usize;
525
526        let y_data = vec![128u8; y_stride * height as usize];
527        let u_data = vec![128u8; u_stride * (height as usize / 2)];
528        let v_data = vec![128u8; v_stride * (height as usize / 2)];
529
530        let rgb_data = Convert::i420_to_rgb24(
531            &y_data, y_stride, &u_data, u_stride, &v_data, v_stride, width, height,
532        )
533        .unwrap();
534
535        let expected_size = (width * 3) as usize * height as usize;
536        assert_eq!(rgb_data.len(), expected_size);
537    }
538
539    #[test]
540    fn test_yuyv_to_rgb_basic() {
541        let width = 16u32;
542        let height = 16u32;
543        let stride = (width * 2) as usize; // YUYV: 2 bytes per pixel
544
545        // Create neutral YUYV data (Y=128, U=128, V=128)
546        let mut yuyv_data = vec![0u8; stride * height as usize];
547        for i in 0..(stride * height as usize / 4) {
548            yuyv_data[i * 4] = 128; // Y0
549            yuyv_data[i * 4 + 1] = 128; // U
550            yuyv_data[i * 4 + 2] = 128; // Y1
551            yuyv_data[i * 4 + 3] = 128; // V
552        }
553
554        let rgb_data = Convert::yuyv_to_rgb24(&yuyv_data, stride, width, height).unwrap();
555
556        let expected_size = (width * 3) as usize * height as usize;
557        assert_eq!(rgb_data.len(), expected_size);
558    }
559
560    #[test]
561    fn test_buffer_too_small_error() {
562        let width = 16u32;
563        let height = 16u32;
564
565        // Provide a buffer that's too small
566        let small_buffer = vec![0u8; 10];
567
568        let result = Convert::yuyv_to_rgb24(&small_buffer, width as usize * 2, width, height);
569        assert!(result.is_err());
570
571        if let Err(CcapError::InvalidParameter(msg)) = result {
572            assert!(
573                msg.contains("too small"),
574                "Error message should mention 'too small'"
575            );
576        } else {
577            panic!("Expected InvalidParameter error");
578        }
579    }
580
581    #[test]
582    fn test_nv12_buffer_validation() {
583        let width = 16u32;
584        let height = 16u32;
585        let y_stride = width as usize;
586        let uv_stride = width as usize;
587
588        // Y plane too small
589        let small_y = vec![0u8; 10];
590        let uv_data = vec![128u8; uv_stride * (height as usize / 2)];
591        let result = Convert::nv12_to_rgb24(&small_y, y_stride, &uv_data, uv_stride, width, height);
592        assert!(result.is_err());
593
594        // UV plane too small
595        let y_data = vec![128u8; y_stride * height as usize];
596        let small_uv = vec![0u8; 10];
597        let result = Convert::nv12_to_rgb24(&y_data, y_stride, &small_uv, uv_stride, width, height);
598        assert!(result.is_err());
599    }
600}