1#![allow(unsafe_code)]
9#![allow(clippy::cast_ptr_alignment, clippy::cast_possible_truncation, clippy::similar_names)]
10
11#[cfg(feature = "simd")]
26mod cl;
27#[cfg(feature = "simd")]
28mod clcl;
29mod reordered;
30mod rgb555;
31#[cfg(feature = "simd")]
32mod rgb565;
33#[cfg(feature = "simd")]
34mod uyvy;
35#[cfg(feature = "simd")]
36mod yuv;
37
38#[cfg_attr(
41 all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")),
42 allow(dead_code)
43)]
44mod scalar;
45
46#[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
47mod neon;
48
49#[cfg(feature = "simd")]
51#[allow(unused_imports)]
52pub(crate) use clcl::clcl_row_to_bgra;
53pub(crate) use reordered::rgb555_pack_to_bgra;
54
55#[allow(dead_code)]
65pub(crate) const CL_NIBBLE_TABLE: [u8; 16] = [0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240];
66
67#[inline]
72#[must_use]
73pub(super) fn unpack_rgb565(pixel: u16) -> [u8; 4] {
74 let r5 = u32::from((pixel >> 11) & 0x1F);
75 let g6 = u32::from((pixel >> 5) & 0x3F);
76 let b5 = u32::from(pixel & 0x1F);
77 [msb_replicate_5(b5), msb_replicate_6(g6), msb_replicate_5(r5), 255]
78}
79
80#[inline]
81#[must_use]
82pub(super) fn msb_replicate_5(v: u32) -> u8 {
83 ((v << 3) | (v >> 2)) as u8
84}
85
86#[inline]
87#[must_use]
88#[allow(clippy::cast_possible_truncation)]
89pub(super) fn msb_replicate_6(v: u32) -> u8 {
90 ((v << 2) | (v >> 4)) as u8
91}
92
93#[inline]
94#[must_use]
95pub(super) fn unpack_rgb555(pixel: u16) -> [u8; 4] {
96 let r5 = u32::from((pixel >> 10) & 0x1F);
98 let g5 = u32::from((pixel >> 5) & 0x1F);
99 let b5 = u32::from(pixel & 0x1F);
100 [msb_replicate_5(b5), msb_replicate_5(g5), msb_replicate_5(r5), 255]
101}
102
103#[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
107#[allow(unsafe_op_in_unsafe_fn, clippy::cast_ptr_alignment)]
108pub(crate) unsafe fn fill_gray_row_sse2(gray: &[u8]) -> Vec<u8> {
109 use core::arch::x86_64::{
110 __m128i, _mm_loadl_epi64, _mm_or_si128, _mm_set1_epi32, _mm_setzero_si128, _mm_slli_epi32, _mm_storeu_si128,
111 _mm_unpackhi_epi16, _mm_unpacklo_epi8, _mm_unpacklo_epi16,
112 };
113 let n = gray.len();
114 let mut dst = vec![0u8; n * 4];
115 let mut i = 0;
116
117 let alpha_mask = _mm_set1_epi32(0xFFi32 << 24);
118 while i + 8 <= n {
119 let v = _mm_loadl_epi64(gray.as_ptr().add(i).cast::<__m128i>());
120 let w = _mm_unpacklo_epi8(v, _mm_setzero_si128());
121 let lo = _mm_unpacklo_epi16(w, _mm_setzero_si128());
122 let hi = _mm_unpackhi_epi16(w, _mm_setzero_si128());
123
124 let lo_sh8 = _mm_slli_epi32(lo, 8);
125 let lo_sh16 = _mm_slli_epi32(lo, 16);
126 let hi_sh8 = _mm_slli_epi32(hi, 8);
127 let hi_sh16 = _mm_slli_epi32(hi, 16);
128
129 let lo_bgra = _mm_or_si128(_mm_or_si128(_mm_or_si128(lo, lo_sh8), lo_sh16), alpha_mask);
130 let hi_bgra = _mm_or_si128(_mm_or_si128(_mm_or_si128(hi, hi_sh8), hi_sh16), alpha_mask);
131
132 _mm_storeu_si128(dst.as_mut_ptr().add(i * 4).cast::<__m128i>(), lo_bgra);
133 _mm_storeu_si128(dst.as_mut_ptr().add(i * 4 + 16).cast::<__m128i>(), hi_bgra);
134 i += 8;
135 }
136
137 for (j, &g) in gray.iter().enumerate().skip(i) {
138 let o = j * 4;
139 dst[o] = g;
140 dst[o + 1] = g;
141 dst[o + 2] = g;
142 dst[o + 3] = 255;
143 }
144 dst
145}
146
147#[inline]
162#[must_use]
163#[allow(clippy::trivially_copy_pass_by_ref)]
164pub fn uyvy_quad_to_bgra(quad: &[u8; 4]) -> [u8; 8] {
165 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
166 unsafe {
168 uyvy::uyvy_quad_to_bgra_sse2(quad)
169 }
170
171 #[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
172 unsafe {
174 return neon::uyvy_quad_to_bgra_neon(quad);
175 }
176
177 #[cfg(not(any(
178 all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")),
179 all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")),
180 )))]
181 scalar::uyvy_quad_to_bgra(quad)
182}
183
184#[inline]
189#[must_use]
190#[allow(clippy::trivially_copy_pass_by_ref)]
191pub fn uyvy_double_quad_to_bgra(quads: &[u8; 8]) -> [u8; 16] {
192 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
193 unsafe {
195 uyvy::uyvy_double_quad_to_bgra_sse2(quads)
196 }
197
198 #[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
199 unsafe {
201 return neon::uyvy_double_quad_to_bgra_neon(quads);
202 }
203
204 #[cfg(not(any(
205 all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")),
206 all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")),
207 )))]
208 scalar::uyvy_double_quad_to_bgra(quads)
209}
210
211#[cfg(feature = "simd")]
220#[inline]
221pub fn uyvy_row_to_bgra(src: &[u8], dst: &mut [u8]) {
222 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
223 if is_x86_feature_detected!("avx2") {
225 return unsafe { uyvy::uyvy_row_to_bgra_avx2(src, dst) };
226 }
227 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
228 if is_x86_feature_detected!("sse4.1") {
230 return unsafe { uyvy::uyvy_row_to_bgra_sse41(src, dst) };
231 }
232 let n = src.len();
233 debug_assert_eq!(dst.len(), (n / 4) * 8);
234 let full_end = (n / 16) * 16;
235 let mut i = 0usize;
236
237 while i < full_end {
239 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
240 unsafe {
242 let q0 = uyvy::uyvy_double_quad_to_bgra_sse2(&src[i..i + 8].try_into().unwrap());
243 let q1 = uyvy::uyvy_double_quad_to_bgra_sse2(&src[i + 8..i + 16].try_into().unwrap());
244 let d_off = i * 2;
245 dst[d_off..d_off + 16].copy_from_slice(&q0);
246 dst[d_off + 16..d_off + 32].copy_from_slice(&q1);
247 }
248
249 #[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
250 unsafe {
252 let q0 = neon::uyvy_double_quad_to_bgra_neon(&src[i..i + 8].try_into().unwrap());
253 let q1 = neon::uyvy_double_quad_to_bgra_neon(&src[i + 8..i + 16].try_into().unwrap());
254 let d_off = i * 2;
255 dst[d_off..d_off + 16].copy_from_slice(&q0);
256 dst[d_off + 16..d_off + 32].copy_from_slice(&q1);
257 }
258
259 #[cfg(not(any(
260 target_arch = "x86_64",
261 target_arch = "x86",
262 all(target_arch = "aarch64", not(target_os = "macos"))
263 )))]
264 {
265 let q0 = scalar::uyvy_double_quad_to_bgra(&src[i..i + 8].try_into().unwrap());
266 let q1 = scalar::uyvy_double_quad_to_bgra(&src[i + 8..i + 16].try_into().unwrap());
267 let d_off = i * 2;
268 dst[d_off..d_off + 16].copy_from_slice(&q0);
269 dst[d_off + 16..d_off + 32].copy_from_slice(&q1);
270 }
271
272 i += 16;
273 }
274
275 while i < n {
277 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
278 unsafe {
280 let px = uyvy::uyvy_quad_to_bgra_sse2(&src[i..i + 4].try_into().unwrap());
281 let d_off = i * 2;
282 dst[d_off..d_off + 8].copy_from_slice(&px);
283 }
284
285 #[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
286 unsafe {
288 let px = neon::uyvy_quad_to_bgra_neon(&src[i..i + 4].try_into().unwrap());
289 let d_off = i * 2;
290 dst[d_off..d_off + 8].copy_from_slice(&px);
291 }
292
293 #[cfg(not(any(
294 target_arch = "x86_64",
295 target_arch = "x86",
296 all(target_arch = "aarch64", not(target_os = "macos"))
297 )))]
298 {
299 let px = scalar::uyvy_quad_to_bgra(&src[i..i + 4].try_into().unwrap());
300 let d_off = i * 2;
301 dst[d_off..d_off + 8].copy_from_slice(&px);
302 }
303
304 i += 4;
305 }
306}
307
308#[inline]
321#[must_use]
322#[allow(clippy::trivially_copy_pass_by_ref)]
323pub fn yuv420_quad_to_bgra(quad: &[u8; 6]) -> [u8; 16] {
324 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
326 unsafe {
328 yuv::yuv420_quad_to_bgra_sse2(quad)
329 }
330
331 #[cfg(not(any(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")),)))]
332 scalar::yuv420_quad_to_bgra(quad)
334}
335
336#[cfg(feature = "simd")]
350#[inline]
351pub fn yuv420_row_pair_to_bgra(y_row: &[u8], cb_row: &[u8], cr_row: &[u8], dst: &mut [u8], w: usize, cb_w: usize) {
352 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
353 if is_x86_feature_detected!("avx2") {
355 return unsafe { yuv::yuv420_row_pair_to_bgra_avx2(y_row, cb_row, cr_row, dst, w, cb_w) };
356 }
357 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
358 if is_x86_feature_detected!("sse4.1") {
360 return unsafe { yuv::yuv420_row_pair_to_bgra_sse41(y_row, cb_row, cr_row, dst, w, cb_w) };
361 }
362 for cx in 0..cb_w {
363 let quad = [
364 y_row[cx * 2],
365 y_row[cx * 2 + 1],
366 y_row[w + cx * 2],
367 y_row[w + cx * 2 + 1],
368 cb_row[cx],
369 cr_row[cx],
370 ];
371 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
372 let out = unsafe { yuv::yuv420_quad_to_bgra_sse2(&quad) };
374 #[cfg(not(any(
375 target_arch = "x86_64",
376 target_arch = "x86",
377 all(target_arch = "aarch64", not(target_os = "macos"))
378 )))]
379 let out = scalar::yuv420_quad_to_bgra(&quad);
380
381 let off = cx * 8;
382 dst[off..off + 4].copy_from_slice(&out[0..4]);
383 dst[off + 4..off + 8].copy_from_slice(&out[4..8]);
384 let off2 = off + w * 4;
385 dst[off2..off2 + 4].copy_from_slice(&out[8..12]);
386 dst[off2 + 4..off2 + 8].copy_from_slice(&out[12..16]);
387 }
388}
389
390#[inline]
400pub(crate) fn rgb565_apply_row_to_bgra(src: &[u8], dst: &mut [u8]) {
401 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
402 if is_x86_feature_detected!("avx2") {
404 unsafe {
405 return rgb565::rgb565_row_to_bgra_avx2(src, dst);
406 }
407 }
408
409 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
410 unsafe {
412 rgb565::rgb565_row_to_bgra_sse2(src, dst);
413 }
414 #[cfg(not(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86"))))]
415 scalar::rgb565_row_to_bgra_scalar(src, dst);
416}
417
418#[inline]
428#[must_use]
429pub fn rgb565_row_to_bgra(src: &[u8]) -> Vec<u8> {
430 let n_pixels = src.len() / 2;
431 let mut dst = vec![0u8; n_pixels * 4];
432 rgb565_apply_row_to_bgra(src, &mut dst);
433 dst
434}
435
436#[inline]
446pub(crate) fn rgb555_apply_row_to_bgra(src: &[u8], dst: &mut [u8]) {
447 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
448 if is_x86_feature_detected!("avx2") {
450 unsafe {
451 return rgb555::rgb555_row_to_bgra_avx2(src, dst);
452 }
453 }
454
455 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
456 unsafe {
458 rgb555::rgb555_row_to_bgra_sse2(src, dst);
459 }
460
461 #[cfg(not(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86"))))]
462 scalar::rgb555_row_to_bgra_scalar(src, dst);
463}
464
465#[inline]
470#[must_use]
471pub fn rgb555_row_to_bgra(src: &[u8]) -> Vec<u8> {
472 let n_pixels = src.len() / 2;
473 let mut dst = vec![0u8; n_pixels * 4];
474 rgb555_apply_row_to_bgra(src, &mut dst);
475 dst
476}
477
478#[must_use]
484pub fn fill_gray_row(gray: &[u8]) -> Vec<u8> {
485 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
486 unsafe {
488 fill_gray_row_sse2(gray)
489 }
490
491 #[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
492 unsafe {
494 return neon::fill_gray_row_neon(gray);
495 }
496
497 #[cfg(not(any(
498 all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")),
499 all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")),
500 )))]
501 scalar::fill_gray_row(gray)
502}
503
504#[must_use]
512pub fn fill_yuv_row(luma: &[u8], cb: u8, cr: u8) -> Vec<u8> {
513 let n = luma.len();
514 let mut dst = vec![0u8; n * 4];
515 let mut i = 0;
516
517 while i + 4 <= n {
518 let quad = yuv420_quad_to_bgra(&[luma[i], luma[i + 1], luma[i + 2], luma[i + 3], cb, cr]);
519 let o = i * 4;
520 dst[o..o + 16].copy_from_slice(&quad);
521 i += 4;
522 }
523
524 for (j, &y) in luma.iter().enumerate().skip(i) {
525 let px = crate::yuv::yuv_to_bgra(y, cb, cr);
526 let o = j * 4;
527 dst[o..o + 4].copy_from_slice(&px);
528 }
529 dst
530}
531
532#[must_use]
540pub fn cl_quad_to_bgra(quad: &[u8; 8]) -> [u8; 16] {
541 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
542 unsafe {
544 if is_x86_feature_detected!("sse4.1") {
545 return cl::cl_quad_to_bgra_sse41(quad);
546 }
547 cl::cl_quad_to_bgra_sse2(quad)
548 }
549
550 #[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
551 unsafe {
553 return neon::cl_quad_to_bgra_neon(quad);
554 }
555 #[cfg(not(all(
556 feature = "simd",
557 any(
558 target_arch = "x86_64",
559 target_arch = "x86",
560 all(target_arch = "aarch64", not(target_os = "macos"))
561 )
562 )))]
563 scalar::cl_quad_to_bgra(*quad)
565}
566
567#[inline]
579pub(crate) fn cl_row_to_bgra(src: &[u8], dst: &mut [u8]) {
580 debug_assert_eq!(dst.len(), src.len() * 2);
581
582 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
584 if is_x86_feature_detected!("sse4.1") {
586 unsafe {
587 return cl::cl_row_to_bgra_sse41(src, dst);
588 }
589 }
590
591 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
593 unsafe {
595 cl::cl_row_to_bgra_sse2(src, dst);
596 }
597
598 #[cfg(all(feature = "simd", target_arch = "aarch64", not(target_os = "macos")))]
600 unsafe {
602 return neon::cl_row_to_bgra_neon(src, dst);
603 }
604
605 #[cfg(not(all(
606 feature = "simd",
607 any(
608 target_arch = "x86_64",
609 target_arch = "x86",
610 all(target_arch = "aarch64", not(target_os = "macos"))
611 )
612 )))]
613 scalar::cl_row_to_bgra_scalar(src, dst);
614}
615
616#[cfg(test)]
621#[allow(clippy::unwrap_used)]
622mod tests {
623 use super::*;
624
625 #[test]
626 fn uyvy_quad_neutral_chroma() {
627 let quad = [128u8, 100, 128, 200];
630 let result = uyvy_quad_to_bgra(&quad);
631 assert_eq!(result[..4], [100, 100, 100, 255], "pixel 0 gray");
632 assert_eq!(result[4..], [200, 200, 200, 255], "pixel 1 gray");
633 }
634
635 #[test]
636 fn uyvy_quad_matches_scalar() {
637 for u in [0u8, 64, 128, 192, 255] {
639 for v in [0u8, 64, 128, 192, 255] {
640 for y0 in [0u8, 64, 128, 192, 255] {
641 for y1 in [0u8, 64, 128, 192, 255] {
642 let quad = [u, y0, v, y1];
643 let result = uyvy_quad_to_bgra(&quad);
644 let expected_p0 = crate::yuv::yuv_to_bgra(y0, u, v);
645 let expected_p1 = crate::yuv::yuv_to_bgra(y1, u, v);
646 assert_eq!(result[..4], expected_p0, "p0 mismatch: u={u}, y0={y0}, v={v}");
647 assert_eq!(result[4..], expected_p1, "p1 mismatch: u={u}, y1={y1}, v={v}");
648 }
649 }
650 }
651 }
652 }
653
654 #[test]
655 fn uyvy_double_quad_matches_scalar() {
656 let quads = [128u8, 100, 128, 200, 64, 50, 192, 180];
657 let result = uyvy_double_quad_to_bgra(&quads);
658 let expected_left = uyvy_quad_to_bgra(&[128, 100, 128, 200]);
659 let expected_right = uyvy_quad_to_bgra(&[64, 50, 192, 180]);
660 assert_eq!(result[..8], expected_left);
661 assert_eq!(result[8..], expected_right);
662 }
663
664 #[test]
665 fn yuv420_quad_neutral_chroma_gray() {
666 let quad = [0u8, 128, 200, 255, 128, 128];
669 let result = yuv420_quad_to_bgra(&quad);
670 let expected: Vec<[u8; 4]> = (0..4)
671 .map(|i| {
672 let y = quad[i];
673 crate::yuv::yuv_to_bgra(y, 128, 128)
674 })
675 .collect();
676 assert_eq!(result[..4], expected[0], "pixel 0");
677 assert_eq!(result[4..8], expected[1], "pixel 1");
678 assert_eq!(result[8..12], expected[2], "pixel 2");
679 assert_eq!(result[12..], expected[3], "pixel 3");
680 }
681
682 #[test]
683 #[cfg_attr(miri, ignore)]
684 fn yuv420_quad_matches_scalar_exhaustive() {
685 for y0 in [0u8, 64, 128, 192, 255] {
687 for y1 in [0u8, 64, 128, 192, 255] {
688 for y2 in [0u8, 64, 128, 192, 255] {
689 for y3 in [0u8, 64, 128, 192, 255] {
690 for cb in [0u8, 64, 128, 192, 255] {
691 for cr in [0u8, 64, 128, 192, 255] {
692 let quad = [y0, y1, y2, y3, cb, cr];
693 let result = yuv420_quad_to_bgra(&quad);
694 let expected = [
695 crate::yuv::yuv_to_bgra(y0, cb, cr),
696 crate::yuv::yuv_to_bgra(y1, cb, cr),
697 crate::yuv::yuv_to_bgra(y2, cb, cr),
698 crate::yuv::yuv_to_bgra(y3, cb, cr),
699 ];
700 for (i, exp) in expected.iter().enumerate() {
701 let slice = &result[i * 4..(i + 1) * 4];
702 assert_eq!(slice, exp, "pixel {i}: yuv420({y0},{y1},{y2},{y3},cb={cb},cr={cr})");
703 }
704 }
705 }
706 }
707 }
708 }
709 }
710 }
711
712 #[test]
713 #[cfg_attr(miri, ignore)]
714 fn uyvy_quad_matches_scalar_10k_random() {
715 let mut state: u32 = 0xABCD_0001;
717 for _ in 0..10_000 {
718 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
719 let u = (state >> 16) as u8;
720 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
721 let y0 = (state >> 16) as u8;
722 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
723 let v = (state >> 16) as u8;
724 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
725 let y1 = (state >> 16) as u8;
726 let quad = [u, y0, v, y1];
727 let result = uyvy_quad_to_bgra(&quad);
728 let expected_p0 = crate::yuv::yuv_to_bgra(y0, u, v);
729 let expected_p1 = crate::yuv::yuv_to_bgra(y1, u, v);
730 assert_eq!(&result[..4], &expected_p0, "p0 mismatch");
731 assert_eq!(&result[4..], &expected_p1, "p1 mismatch");
732 }
733 }
734
735 #[test]
736 #[cfg_attr(miri, ignore)]
737 fn yuv420_quad_matches_scalar_10k_random() {
738 let mut state: u32 = 0xABCD_0001;
740 for _ in 0..10_000 {
741 let mut quad = [0u8; 6];
742 for b in &mut quad {
743 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
744 *b = (state >> 16) as u8;
745 }
746 let result = yuv420_quad_to_bgra(&quad);
747 let expected = [
748 crate::yuv::yuv_to_bgra(quad[0], quad[4], quad[5]),
749 crate::yuv::yuv_to_bgra(quad[1], quad[4], quad[5]),
750 crate::yuv::yuv_to_bgra(quad[2], quad[4], quad[5]),
751 crate::yuv::yuv_to_bgra(quad[3], quad[4], quad[5]),
752 ];
753 for (i, exp) in expected.iter().enumerate() {
754 let slice = &result[i * 4..(i + 1) * 4];
755 assert_eq!(slice, exp, "pixel {i}: quad={quad:?}");
756 }
757 }
758 }
759
760 fn random_u16(state: &mut u32) -> u16 {
764 *state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
765 (*state >> 16) as u16
766 }
767
768 #[test]
769 fn rgb565_row_matches_scalar_10k() {
770 let mut state: u32 = 0xABCD_0001;
772 let mut src = Vec::with_capacity(20_000);
773 for _ in 0..10_000 {
774 let p = random_u16(&mut state);
775 src.push((p & 0xFF) as u8);
776 src.push((p >> 8) as u8);
777 }
778
779 let result = super::rgb565_row_to_bgra(&src);
780 let mut expected = vec![0u8; 10_000 * 4];
781 super::scalar::rgb565_row_to_bgra_scalar(&src, &mut expected);
782 assert_eq!(result, expected, "RGB565 SIMD/scalar mismatch for 10K random pixels");
783 }
784
785 #[test]
786 fn rgb565_quad_edge_cases() {
787 let pixels: [u16; 8] = [0x0000, 0xFFFF, 0x001F, 0x07E0, 0xF800, 0xFFE0, 0x07FF, 0x1B6A];
788 for &p0 in &pixels {
789 for &p1 in &pixels {
790 let quad = [
791 (p0 & 0xFF) as u8,
792 (p0 >> 8) as u8,
793 (p1 & 0xFF) as u8,
794 (p1 >> 8) as u8,
795 (p0 & 0xFF) as u8,
796 (p0 >> 8) as u8,
797 (p1 & 0xFF) as u8,
798 (p1 >> 8) as u8,
799 ];
800
801 let mut expected = vec![0u8; 16];
802 super::scalar::rgb565_row_to_bgra_scalar(&quad, &mut expected);
803
804 let result = super::rgb565_row_to_bgra(&quad);
805 assert_eq!(
806 &result[..16],
807 &expected[..],
808 "RGB565 dispatch mismatch for pixels {p0:#06x}, {p1:#06x}"
809 );
810 }
811 }
812 }
813
814 #[test]
815 fn rgb565_row_remainder_handling() {
816 let mut state: u32 = 0xDEAD_BEEF;
818 let mut src = Vec::with_capacity(10);
819 for _ in 0..5 {
820 let p = random_u16(&mut state);
821 src.push((p & 0xFF) as u8);
822 src.push((p >> 8) as u8);
823 }
824
825 let result = super::rgb565_row_to_bgra(&src);
826 let mut expected = vec![0u8; 20];
827 super::scalar::rgb565_row_to_bgra_scalar(&src, &mut expected);
828 assert_eq!(result, expected, "RGB565 remainder handling mismatch");
829 }
830
831 #[test]
832 fn rgb565_row_single_pixel() {
833 let src = [0x00, 0xF8]; let result = super::rgb565_row_to_bgra(&src);
835 let mut expected = vec![0u8; 4];
836 super::scalar::rgb565_row_to_bgra_scalar(&src, &mut expected);
837 assert_eq!(result, expected, "RGB565 single pixel mismatch");
838 }
839
840 #[test]
843 fn rgb555_row_matches_scalar_10k() {
844 let mut state: u32 = 0xABCD_0001;
845 let mut src = Vec::with_capacity(20_000);
846 for _ in 0..10_000 {
847 let p = random_u16(&mut state) & 0x7FFF; src.push((p & 0xFF) as u8);
849 src.push((p >> 8) as u8);
850 }
851
852 let result = super::rgb555_row_to_bgra(&src);
853 let mut expected = vec![0u8; 10_000 * 4];
854 super::scalar::rgb555_row_to_bgra_scalar(&src, &mut expected);
855 assert_eq!(result, expected, "RGB555 SIMD/scalar mismatch for 10K random pixels");
856 }
857
858 #[test]
859 fn rgb555_quad_edge_cases() {
860 let pixels: [u16; 6] = [0x0000, 0x7FFF, 0x001F, 0x03E0, 0x7C00, 0x7C1F];
861 for &p0 in &pixels {
862 for &p1 in &pixels {
863 let quad = [
864 (p0 & 0xFF) as u8,
865 (p0 >> 8) as u8,
866 (p1 & 0xFF) as u8,
867 (p1 >> 8) as u8,
868 (p0 & 0xFF) as u8,
869 (p0 >> 8) as u8,
870 (p1 & 0xFF) as u8,
871 (p1 >> 8) as u8,
872 ];
873
874 let mut expected = vec![0u8; 16];
875 super::scalar::rgb555_row_to_bgra_scalar(&quad, &mut expected);
876
877 let result = super::rgb555_row_to_bgra(&quad);
878 assert_eq!(
879 &result[..16],
880 &expected[..],
881 "RGB555 dispatch mismatch for pixels {p0:#06x}, {p1:#06x}"
882 );
883 }
884 }
885 }
886
887 #[test]
888 fn rgb555_row_remainder_handling() {
889 let mut state: u32 = 0xCAFE_BABE;
890 let mut src = Vec::with_capacity(10);
891 for _ in 0..5 {
892 let p = random_u16(&mut state) & 0x7FFF;
893 src.push((p & 0xFF) as u8);
894 src.push((p >> 8) as u8);
895 }
896
897 let result = super::rgb555_row_to_bgra(&src);
898 let mut expected = vec![0u8; 20];
899 super::scalar::rgb555_row_to_bgra_scalar(&src, &mut expected);
900 assert_eq!(result, expected, "RGB555 remainder handling mismatch");
901 }
902
903 #[test]
904 fn rgb555_row_single_pixel() {
905 let src = [0x00, 0x7C]; let result = super::rgb555_row_to_bgra(&src);
907 let mut expected = vec![0u8; 4];
908 super::scalar::rgb555_row_to_bgra_scalar(&src, &mut expected);
909 assert_eq!(result, expected, "RGB555 single pixel mismatch");
910 }
911
912 #[test]
915 #[allow(clippy::cast_possible_truncation)]
916 fn fill_gray_row_matches_scalar_1000_random() {
917 let mut state: u32 = 0x1234_5678;
918 let lengths = [1usize, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 64, 128, 256];
919 for &len in &lengths {
920 for _ in 0..60 {
921 let mut gray = Vec::with_capacity(len);
922 for _ in 0..len {
923 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
924 gray.push((state >> 16) as u8);
925 }
926 let result = fill_gray_row(&gray);
927 let expected = super::scalar::fill_gray_row(&gray);
928 assert_eq!(result, expected, "fill_gray_row mismatch for len={len}");
929 }
930 }
931 }
932
933 #[test]
936 #[allow(clippy::cast_possible_truncation)]
937 fn fill_yuv_row_matches_scalar_1000_random() {
938 let mut state: u32 = 0x9ABC_DEF0;
939 let lengths = [1usize, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 64, 128, 256];
940 for &len in &lengths {
941 for _ in 0..60 {
942 let mut luma = Vec::with_capacity(len);
943 for _ in 0..len {
944 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
945 luma.push((state >> 16) as u8);
946 }
947 let cb = (state >> 8) as u8;
948 let cr = (state >> 16) as u8;
949 let result = fill_yuv_row(&luma, cb, cr);
950 let mut expected = vec![0u8; len * 4];
951 for (j, &y) in luma.iter().enumerate() {
952 let px = crate::yuv::yuv_to_bgra(y, cb, cr);
953 expected[j * 4..j * 4 + 4].copy_from_slice(&px);
954 }
955 assert_eq!(result, expected, "fill_yuv_row mismatch for len={len}");
956 }
957 }
958 }
959
960 #[test]
963 fn cl_quad_matches_scalar_all_nibble_combos() {
964 let y_vals = [0u8, 64, 128, 192, 255];
965 let n_vals = [0u8, 1, 7, 8, 15];
966 for &y0 in &y_vals {
967 for &y1 in &y_vals {
968 for &y2 in &y_vals {
969 for &y3 in &y_vals {
970 for &cb_n in &n_vals {
971 for &cr_n in &n_vals {
972 let chroma = (cr_n << 4) | cb_n;
973 let quad = [y0, y1, y2, y3, chroma, chroma, chroma, chroma];
974 let result = super::cl_quad_to_bgra(&quad);
975 let expected = super::scalar::cl_quad_to_bgra(quad);
976 assert_eq!(&result[..], &expected[..], "cl_quad mismatch");
977 }
978 }
979 }
980 }
981 }
982 }
983 }
984
985 #[test]
986 #[cfg_attr(miri, ignore)]
987 fn cl_quad_ssse3_exhaustive_nibble_pair() {
988 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
990 if is_x86_feature_detected!("ssse3") {
991 for cb_n in 0..=15u8 {
992 for cr_n in 0..=15u8 {
993 let chroma = (cr_n << 4) | cb_n;
994 let quad = [128u8, 128, 128, 128, chroma, chroma, chroma, chroma];
995 let result = unsafe { cl::cl_quad_to_bgra_ssse3(&quad) };
996 let cb = cb_n << 4;
997 let cr = cr_n << 4;
998 let expected = crate::yuv::yuv_to_bgra(128, cb, cr);
999 for p in 0..4 {
1000 let off = p * 4;
1001 assert_eq!(
1002 result[off..off + 4],
1003 expected,
1004 "SSSE3 pixel {p} mismatch at cb_n={cb_n} cr_n={cr_n}",
1005 );
1006 }
1007 }
1008 }
1009 }
1010 }
1011
1012 #[test]
1013 #[cfg_attr(miri, ignore)]
1014 fn cl_quad_avx2_exhaustive_nibble_pair() {
1015 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1017 if is_x86_feature_detected!("avx2") {
1018 for cb_n in 0..=15u8 {
1019 for cr_n in 0..=15u8 {
1020 let chroma = (cr_n << 4) | cb_n;
1021 let quad = [128u8, 128, 128, 128, chroma, chroma, chroma, chroma];
1022 let result = unsafe { cl::cl_quad_to_bgra_avx2(&quad) };
1023 let cb = cb_n << 4;
1024 let cr = cr_n << 4;
1025 let expected = crate::yuv::yuv_to_bgra(128, cb, cr);
1026 for p in 0..4 {
1027 let off = p * 4;
1028 assert_eq!(
1029 result[off..off + 4],
1030 expected,
1031 "AVX2 pixel {p} mismatch at cb_n={cb_n} cr_n={cr_n}",
1032 );
1033 }
1034 }
1035 }
1036 }
1037 }
1038 #[test]
1039 #[allow(clippy::cast_possible_truncation)]
1040 fn cl_quad_matches_scalar_varying_chroma() {
1041 let mut state: u32 = 0xDEAD_BEEF;
1042 for _ in 0..1000 {
1043 let mut quad = [0u8; 8];
1044 for b in &mut quad {
1045 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
1046 *b = (state >> 16) as u8;
1047 }
1048 let result = super::cl_quad_to_bgra(&quad);
1049 let expected = super::scalar::cl_quad_to_bgra(quad);
1050 assert_eq!(&result[..], &expected[..], "cl_quad mismatch for random quad");
1051 }
1052 }
1053
1054 #[test]
1057 #[allow(clippy::cast_possible_truncation)]
1058 fn cl_row_matches_scalar_at_various_widths() {
1059 let widths = [1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 64, 128, 256];
1062 let mut state: u32 = 0xDEAD_BEEF;
1063 for &w in &widths {
1064 for _ in 0..20 {
1065 let n = w;
1066 let mut src = vec![0u8; n * 2];
1067 for b in &mut src {
1068 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
1069 *b = (state >> 16) as u8;
1070 }
1071 let mut dst = vec![0u8; n * 4];
1072 cl_row_to_bgra(&src, &mut dst);
1073 let (y, chroma) = src.split_at(n);
1074 let mut expected = vec![0u8; n * 4];
1075 for i in 0..n {
1076 let cr = chroma[i] & 0xF0;
1077 let cb = (chroma[i] & 0x0F) << 4;
1078 let px = crate::yuv::yuv_to_bgra(y[i], cb, cr);
1079 let o = i * 4;
1080 expected[o..o + 4].copy_from_slice(&px);
1081 }
1082 assert_eq!(dst, expected, "cl_row_to_bgra mismatch at width={w}");
1083 }
1084 }
1085 }
1086
1087 #[test]
1090 fn rgb555_pack_known_values() {
1091 let white = [0xFFu8, 0x7Fu8];
1094 let result = super::rgb555_pack_to_bgra([white, white, white, white], false);
1095 assert_eq!(result, [0xFFu8; 16], "white");
1096
1097 let black = [0x00u8, 0x00u8];
1099 let result = super::rgb555_pack_to_bgra([black, black, black, black], false);
1100 let mut black_expected = [0u8; 16];
1102 for i in 0..4 {
1103 black_expected[i * 4 + 3] = 255;
1104 }
1105 assert_eq!(result, black_expected, "black");
1106
1107 let red = [0x00u8, 0x7Cu8];
1109 let result = super::rgb555_pack_to_bgra([red, red, red, red], false);
1110 for i in 0..4 {
1111 assert_eq!(result[i * 4..][..4], [0, 0, 255, 255], "red pixel {i}");
1112 }
1113
1114 let green = [0xE0u8, 0x03u8];
1116 let result = super::rgb555_pack_to_bgra([green, green, green, green], false);
1117 for i in 0..4 {
1118 assert_eq!(result[i * 4..][..4], [0, 255, 0, 255], "green pixel {i}");
1119 }
1120
1121 let blue = [0x1Fu8, 0x00u8];
1123 let result = super::rgb555_pack_to_bgra([blue, blue, blue, blue], false);
1124 for i in 0..4 {
1125 assert_eq!(result[i * 4..][..4], [255, 0, 0, 255], "blue pixel {i}");
1126 }
1127 }
1128
1129 #[test]
1130 fn rgb555_pack_swap_mode() {
1131 let pixel = [0x00u8, 0x7Cu8];
1134 let result = super::rgb555_pack_to_bgra([pixel, pixel, pixel, pixel], true);
1135 for i in 0..4 {
1136 assert_eq!(result[i * 4..][..4], [255, 0, 0, 255], "swap B=31->B_out pixel {i}");
1137 }
1138
1139 let pixel = [0x1Fu8, 0x00u8];
1141 let result = super::rgb555_pack_to_bgra([pixel, pixel, pixel, pixel], true);
1142 for i in 0..4 {
1143 assert_eq!(result[i * 4..][..4], [0, 0, 255, 255], "swap R=31->R_out pixel {i}");
1144 }
1145 }
1146
1147 #[test]
1148 fn rgb555_pack_different_pixels() {
1149 let white = [0xFFu8, 0x7Fu8]; let red = [0x00u8, 0x7Cu8]; let green = [0xE0u8, 0x03u8]; let blue = [0x1Fu8, 0x00u8]; let pixels = [white, red, green, blue];
1156 let result = super::rgb555_pack_to_bgra(pixels, false);
1157
1158 assert_eq!(result[0..4], [255, 255, 255, 255], "p0 white");
1159 assert_eq!(result[4..8], [0, 0, 255, 255], "p1 red");
1160 assert_eq!(result[8..12], [0, 255, 0, 255], "p2 green");
1161 assert_eq!(result[12..16], [255, 0, 0, 255], "p3 blue");
1162 }
1163
1164 #[test]
1165 #[allow(clippy::cast_possible_truncation)]
1166 fn rgb555_pack_1000_random_cross_check() {
1167 let mut state: u64 = 42;
1169 let mut rng = || {
1170 state = state
1171 .wrapping_mul(6_364_136_223_846_793_005)
1172 .wrapping_add(1_442_695_040_888_963_407);
1173 (state >> 33) as u32
1174 };
1175
1176 for _ in 0..1000 {
1177 let mut pixels = [[0u8; 2]; 4];
1178 for p in &mut pixels {
1179 let val = (rng() & 0x7FFF) as u16; *p = val.to_le_bytes();
1181 }
1182 let swap = (rng() & 1) != 0;
1183
1184 let result = super::rgb555_pack_to_bgra(pixels, swap);
1185
1186 let mut expected = [0u8; 16];
1188 for i in 0..4 {
1189 let raw = u16::from_le_bytes(pixels[i]);
1190 let (r5, g5, b5) = if swap {
1191 (raw & 0x1F, (raw >> 5) & 0x1F, (raw >> 10) & 0x1F)
1192 } else {
1193 ((raw >> 10) & 0x1F, (raw >> 5) & 0x1F, raw & 0x1F)
1194 };
1195 expected[i * 4] = ((b5 << 3) | (b5 >> 2)) as u8;
1196 expected[i * 4 + 1] = ((g5 << 3) | (g5 >> 2)) as u8;
1197 expected[i * 4 + 2] = ((r5 << 3) | (r5 >> 2)) as u8;
1198 expected[i * 4 + 3] = 255;
1199 }
1200
1201 assert_eq!(result, expected, "Mismatch for swap={swap}");
1202 }
1203 }
1204
1205 #[test]
1208 #[cfg(all(feature = "simd", any(target_arch = "x86_64", target_arch = "x86")))]
1209 #[cfg_attr(miri, ignore)]
1210 fn rgb555_pack_10k_random_ssse3() {
1211 use super::scalar;
1212 if !is_x86_feature_detected!("ssse3") {
1214 return;
1215 }
1216 let mut state: u64 = 42;
1217 let mut rng = || {
1218 state = state
1219 .wrapping_mul(6_364_136_223_846_793_005)
1220 .wrapping_add(1_442_695_040_888_963_407);
1221 (state >> 33) as u32
1222 };
1223 for _ in 0..10_000 {
1224 let mut pixels = [[0u8; 2]; 4];
1225 for p in &mut pixels {
1226 let val = (rng() & 0x7FFF) as u16;
1227 *p = val.to_le_bytes();
1228 }
1229 let swap = (rng() & 1) != 0;
1230 let expected = scalar::rgb555_pack_to_bgra(pixels, swap);
1231 let result;
1232 unsafe {
1234 result = super::reordered::rgb555_pack_to_bgra_ssse3(&pixels, swap);
1235 }
1236 assert_eq!(result, expected, "SSSE3 variant mismatch for swap={swap}");
1237 }
1238 }
1239
1240 #[test]
1241 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1242 #[cfg_attr(miri, ignore)]
1243 fn rgb555_pack_10k_random_avx2() {
1244 use super::scalar;
1245 if !is_x86_feature_detected!("avx2") {
1246 return;
1247 }
1248 let mut state: u64 = 42;
1249 let mut rng = || {
1250 state = state
1251 .wrapping_mul(6_364_136_223_846_793_005)
1252 .wrapping_add(1_442_695_040_888_963_407);
1253 (state >> 33) as u32
1254 };
1255 for _ in 0..10_000 {
1256 let mut pixels = [[0u8; 2]; 4];
1257 for p in &mut pixels {
1258 let val = (rng() & 0x7FFF) as u16;
1259 *p = val.to_le_bytes();
1260 }
1261 let swap = (rng() & 1) != 0;
1262 let expected = scalar::rgb555_pack_to_bgra(pixels, swap);
1263 let result;
1264 unsafe {
1266 result = super::reordered::rgb555_pack_to_bgra_avx2(&pixels, swap);
1267 }
1268 assert_eq!(result, expected, "AVX2 variant mismatch for swap={swap}");
1269 }
1270 }
1271}