1use crate::error::{CcapError, Result};
2use crate::sys;
3use crate::types::ColorConversionBackend;
4use std::os::raw::c_int;
5
6pub struct Convert;
8
9fn 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 pub fn backend() -> ColorConversionBackend {
25 let backend = unsafe { sys::ccap_convert_get_backend() };
26 ColorConversionBackend::from_c_enum(backend)
27 }
28
29 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 pub fn has_avx2() -> bool {
42 unsafe { sys::ccap_convert_has_avx2() }
43 }
44
45 pub fn has_apple_accelerate() -> bool {
47 unsafe { sys::ccap_convert_has_apple_accelerate() }
48 }
49
50 pub fn has_neon() -> bool {
52 unsafe { sys::ccap_convert_has_neon() }
53 }
54
55 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 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 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 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 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 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 #[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 #[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 let backend = Convert::backend();
366 println!("Current backend: {:?}", backend);
367 }
368
369 #[test]
370 fn test_simd_availability() {
371 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 }
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 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 } 1 => {
405 rgb_data[offset] = 0;
406 rgb_data[offset + 1] = 255;
407 rgb_data[offset + 2] = 0;
408 } 2 => {
410 rgb_data[offset] = 0;
411 rgb_data[offset + 1] = 0;
412 rgb_data[offset + 2] = 255;
413 } _ => {
415 rgb_data[offset] = 255;
416 rgb_data[offset + 1] = 255;
417 rgb_data[offset + 2] = 255;
418 } }
420 }
421 }
422
423 let bgr_data = Convert::rgb_to_bgr(&rgb_data, stride, width, height).unwrap();
425 assert_eq!(bgr_data.len(), rgb_data.len());
426
427 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 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 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 let expected_size = (width * 3) as usize * height as usize;
479 assert_eq!(rgb_data.len(), expected_size);
480
481 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; 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; yuyv_data[i * 4 + 1] = 128; yuyv_data[i * 4 + 2] = 128; yuyv_data[i * 4 + 3] = 128; }
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 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 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 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}