#[derive(Debug, Default, Clone, Copy)]
pub struct FindResult {
pub matched: bool,
pub x: i32,
pub y: i32,
}
#[inline]
fn find_color_scalar(colors: &[u8], w: i32, target: (u8, u8, u8)) -> Option<(i32, i32)> {
let pixel_count = colors.len() / 4;
for i in 0..pixel_count {
let offset = i * 4;
if colors[offset] == target.0
&& colors[offset + 1] == target.1
&& colors[offset + 2] == target.2
{
let x = (i as i32) % w;
let y = (i as i32) / w;
return Some((x, y));
}
}
None
}
#[cfg(target_arch = "x86_64")]
#[inline]
unsafe fn find_color_swar(
colors: &[u8],
start_offset: usize,
remaining_bytes: usize,
w: i32,
target: (u8, u8, u8),
) -> Option<(i32, i32)> {
if remaining_bytes < 4 {
return None;
}
if remaining_bytes >= 8 {
let swar_pairs = remaining_bytes / 8;
for i in 0..swar_pairs {
let offset = start_offset + i * 8;
let chunk = std::ptr::read_unaligned(colors.as_ptr().add(offset) as *const u64);
if let Some(pixel_idx) = compare_two_pixels_swar(chunk, target) {
let global_idx = start_offset / 4 + i * 2 + pixel_idx;
let x = (global_idx as i32) % w;
let y = (global_idx as i32) / w;
return Some((x, y));
}
}
let processed_bytes = swar_pairs * 8;
let leftover_bytes = remaining_bytes - processed_bytes;
if leftover_bytes >= 4 {
let offset = start_offset + processed_bytes;
if colors[offset] == target.0
&& colors[offset + 1] == target.1
&& colors[offset + 2] == target.2
{
let global_idx = start_offset / 4 + swar_pairs * 2;
let x = (global_idx as i32) % w;
let y = (global_idx as i32) / w;
return Some((x, y));
}
}
} else if remaining_bytes >= 4 {
let offset = start_offset;
if colors[offset] == target.0
&& colors[offset + 1] == target.1
&& colors[offset + 2] == target.2
{
let global_idx = start_offset / 4;
let x = (global_idx as i32) % w;
let y = (global_idx as i32) / w;
return Some((x, y));
}
}
None
}
#[inline]
fn compare_two_pixels_swar(chunk: u64, target: (u8, u8, u8)) -> Option<usize> {
let b_mask: u64 = 0x000000FF_000000FF;
let g_mask: u64 = 0x0000FF00_0000FF00;
let r_mask: u64 = 0x00FF0000_00FF0000;
let b_vals = chunk & b_mask;
let g_vals = (chunk & g_mask) >> 8;
let r_vals = (chunk & r_mask) >> 16;
let first_pixel_match = (b_vals & 0xFF) == (target.0 as u64)
&& (g_vals & 0xFF) == (target.1 as u64)
&& (r_vals & 0xFF) == (target.2 as u64);
if first_pixel_match {
return Some(0);
}
let second_pixel_match = ((b_vals >> 32) & 0xFF) == (target.0 as u64)
&& ((g_vals >> 32) & 0xFF) == (target.1 as u64)
&& ((r_vals >> 32) & 0xFF) == (target.2 as u64);
if second_pixel_match {
return Some(1);
}
None
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn find_color_avx2(colors: &[u8], w: i32, target: (u8, u8, u8)) -> Option<(i32, i32)> {
use std::arch::x86_64::*;
let pixel_count = colors.len() / 4;
let simd_pixels = pixel_count / 8; let remaining_pixels = pixel_count % 8;
let target_b = _mm256_set1_epi8(target.0 as i8);
let target_g = _mm256_set1_epi8(target.1 as i8);
let target_r = _mm256_set1_epi8(target.2 as i8);
let mask_b = _mm256_set_epi8(
12, -1, -1, -1, 8, -1, -1, -1, 4, -1, -1, -1, 0, -1, -1, -1, 12, -1, -1, -1, 8, -1, -1, -1,
4, -1, -1, -1, 0, -1, -1, -1,
);
let mask_g = _mm256_set_epi8(
13, -1, -1, -1, 9, -1, -1, -1, 5, -1, -1, -1, 1, -1, -1, -1, 13, -1, -1, -1, 9, -1, -1, -1,
5, -1, -1, -1, 1, -1, -1, -1,
);
let mask_r = _mm256_set_epi8(
14, -1, -1, -1, 10, -1, -1, -1, 6, -1, -1, -1, 2, -1, -1, -1, 14, -1, -1, -1, 10, -1, -1,
-1, 6, -1, -1, -1, 2, -1, -1, -1,
);
let mut base_idx = 0;
for _ in 0..simd_pixels {
let data = _mm256_loadu_si256(colors.as_ptr().add(base_idx) as *const __m256i);
let b_channel = _mm256_shuffle_epi8(data, mask_b);
let g_channel = _mm256_shuffle_epi8(data, mask_g);
let r_channel = _mm256_shuffle_epi8(data, mask_r);
let cmp_b = _mm256_cmpeq_epi8(b_channel, target_b);
let cmp_g = _mm256_cmpeq_epi8(g_channel, target_g);
let cmp_r = _mm256_cmpeq_epi8(r_channel, target_r);
let matched = _mm256_and_si256(_mm256_and_si256(cmp_b, cmp_g), cmp_r);
let mask = _mm256_movemask_epi8(matched);
if mask != 0 {
let bit_pos = mask.trailing_zeros() as usize;
let pixel_idx = bit_pos / 4;
let global_idx = base_idx / 4 + pixel_idx;
let x = (global_idx as i32) % w;
let y = (global_idx as i32) / w;
return Some((x, y));
}
base_idx += 32; }
let remaining_start = simd_pixels * 32;
let remaining_bytes = remaining_pixels * 4;
find_color_swar(colors, remaining_start, remaining_bytes, w, target)
}
pub fn find_color_in_buffer(
colors: &[u8],
w: i32,
_h: i32,
target: (u8, u8, u8),
) -> FindResult {
let mut result = FindResult::default();
#[cfg(target_arch = "x86_64")]
let found = {
if std::is_x86_feature_detected!("avx2") {
unsafe { find_color_avx2(colors, w, target) }
} else {
find_color_scalar(colors, w, target)
}
};
#[cfg(not(target_arch = "x86_64"))]
let found = find_color_scalar(colors, w, target);
if let Some((local_x, local_y)) = found {
result.matched = true;
result.x = local_x;
result.y = local_y;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn create_bgra_pixel(r: u8, g: u8, b: u8, a: u8) -> [u8; 4] {
[b, g, r, a] }
fn create_solid_image(width: i32, height: i32, color: (u8, u8, u8)) -> Vec<u8> {
let mut buffer = Vec::with_capacity((width * height * 4) as usize);
for _ in 0..(width * height) {
buffer.extend_from_slice(&create_bgra_pixel(color.2, color.1, color.0, 255));
}
buffer
}
#[test]
fn test_find_color_basic() {
let width = 100i32;
let height = 100i32;
let bg_color = (100, 100, 100);
let target_color = (255, 0, 0);
let mut buffer = create_solid_image(width, height, bg_color);
let target_x = 50;
let target_y = 50;
let target_idx = ((target_y * width + target_x) * 4) as usize;
buffer[target_idx] = target_color.0; buffer[target_idx + 1] = target_color.1; buffer[target_idx + 2] = target_color.2; buffer[target_idx + 3] = 255;
let result = find_color_in_buffer(&buffer, width, height, target_color);
assert!(result.matched, "Should find the color");
assert_eq!(result.x, target_x, "X coordinate mismatch");
assert_eq!(result.y, target_y, "Y coordinate mismatch");
}
#[test]
fn test_find_color_not_found() {
let width = 100i32;
let height = 100i32;
let bg_color = (100, 100, 100);
let buffer = create_solid_image(width, height, bg_color);
let result = find_color_in_buffer(&buffer, width, height, (255, 0, 0));
assert!(!result.matched, "Should not find the color");
}
}