use crate::frame::Frame;
use crate::template::Template;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Match {
pub x: i32,
pub y: i32,
pub score: f32,
}
pub trait Matcher {
fn find(&self, frame: &Frame, tpl: &Template) -> Option<Match>;
}
#[derive(Clone, Copy, Debug)]
pub struct RgbMatcher {
pub tolerance: i32,
}
impl RgbMatcher {
pub fn new(tolerance: i32) -> Self {
RgbMatcher { tolerance }
}
}
impl Matcher for RgbMatcher {
fn find(&self, frame: &Frame, tpl: &Template) -> Option<Match> {
let (ro, go, bo) = frame.rgb_offsets();
let (x, y) = find_rgb(
&frame.pixels,
frame.width,
frame.height,
(ro, go, bo),
&tpl.rgb,
tpl.width,
tpl.height,
self.tolerance,
)?;
Some(Match { x, y, score: 1.0 })
}
}
fn find_rgb(
screen: &[u8],
sw: usize,
sh: usize,
(ro, go, bo): (usize, usize, usize),
tpl: &[u8],
tw: usize,
th: usize,
thr: i32,
) -> Option<(i32, i32)> {
if tw == 0 || th == 0 || tw > sw || th > sh {
return None;
}
let sw4 = sw * 4;
let tw3 = tw * 3;
let ax = tw / 2;
let ay = th / 2;
let ai = ay * tw3 + ax * 3;
let (ar, ag, ab) = (tpl[ai] as i32, tpl[ai + 1] as i32, tpl[ai + 2] as i32);
let x_end = sw - tw + 1;
let y_end = sh - th + 1;
for y0 in 0..y_end {
let anchor_row = (y0 + ay) * sw4 + ax * 4;
for x0 in 0..x_end {
let p = anchor_row + x0 * 4;
if (screen[p + ro] as i32 - ar).abs() > thr {
continue;
}
if (screen[p + go] as i32 - ag).abs() > thr {
continue;
}
if (screen[p + bo] as i32 - ab).abs() > thr {
continue;
}
if verify(screen, sw4, (ro, go, bo), tpl, tw, th, tw3, thr, x0, y0) {
return Some((x0 as i32, y0 as i32));
}
}
}
None
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn verify(
screen: &[u8],
sw4: usize,
(ro, go, bo): (usize, usize, usize),
tpl: &[u8],
tw: usize,
th: usize,
tw3: usize,
thr: i32,
x0: usize,
y0: usize,
) -> bool {
let w4 = tw * 4;
for ty in 0..th {
let sy = (y0 + ty) * sw4 + x0 * 4;
let ti = ty * tw3;
let srow = &screen[sy..sy + w4];
let trow = &tpl[ti..ti + tw3];
let mut si = 0usize;
let mut tj = 0usize;
for _ in 0..tw {
if (srow[si + ro] as i32 - trow[tj] as i32).abs() > thr
|| (srow[si + go] as i32 - trow[tj + 1] as i32).abs() > thr
|| (srow[si + bo] as i32 - trow[tj + 2] as i32).abs() > thr
{
return false;
}
si += 4;
tj += 3;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rgb_finds_embedded_region() {
let (w, h) = (64usize, 64usize);
let mut px = vec![0u8; w * h * 4];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
px[i] = x as u8;
px[i + 1] = y as u8;
px[i + 2] = (x + y) as u8;
px[i + 3] = 255;
}
}
let frame = Frame::rgba8(w, h, px.clone());
let tpl = crop_rgb(&px, w, 20, 15, 8, 8);
let t = Template::from_rgb(tpl, 8, 8);
let r = RgbMatcher::new(0).find(&frame, &t).expect("应能命中");
assert_eq!((r.x, r.y), (20, 15));
}
#[test]
fn rgb_handles_bgra_frame() {
let (w, h) = (48usize, 48usize);
let mut bgra = vec![0u8; w * h * 4];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
bgra[i] = (x + y) as u8; bgra[i + 1] = y as u8; bgra[i + 2] = x as u8; bgra[i + 3] = 255;
}
}
let frame = Frame::bgra8(w, h, bgra.clone());
let mut tpl = Vec::new();
for y in 10..10 + 6 {
for x in 10..10 + 6 {
let i = (y * w + x) * 4;
tpl.extend_from_slice(&[bgra[i + 2], bgra[i + 1], bgra[i]]); }
}
let t = Template::from_rgb(tpl, 6, 6);
let r = RgbMatcher::new(0).find(&frame, &t).expect("BGRA 应能命中");
assert_eq!((r.x, r.y), (10, 10));
}
#[test]
fn rgb_returns_none_when_absent() {
let (w, h) = (32usize, 32usize);
let px = vec![10u8; w * h * 4];
let frame = Frame::rgba8(w, h, px);
let tpl = vec![200u8; 4 * 4 * 3];
let t = Template::from_rgb(tpl, 4, 4);
assert!(RgbMatcher::new(0).find(&frame, &t).is_none());
}
fn crop_rgb(px: &[u8], w: usize, tx: usize, ty: usize, tw: usize, th: usize) -> Vec<u8> {
let mut tpl = Vec::with_capacity(tw * th * 3);
for y in ty..ty + th {
for x in tx..tx + tw {
let i = (y * w + x) * 4;
tpl.extend_from_slice(&[px[i], px[i + 1], px[i + 2]]);
}
}
tpl
}
}