1use crate::common::rect::Rect as DroidRect;
2use crate::error::{DroidError, Result};
3use image::{DynamicImage, GenericImageView};
4use imageproc::template_matching::{self, MatchTemplateMethod};
5use std::path::Path;
6
7#[derive(Debug, Clone, Copy)]
8pub struct MatchResult {
9 pub rect: DroidRect,
10 pub confidence: f32,
11}
12
13pub fn find_template(
14 haystack: &DynamicImage,
15 needle: &DynamicImage,
16 threshold: f32,
17 needle_path: &Path,
18 search_rect: Option<DroidRect>,
19) -> Result<MatchResult> {
20 log::debug!(
21 "Searching for template {:?} with threshold {:.2} inside region {:?}",
22 needle_path,
23 threshold,
24 search_rect
25 );
26
27 let needle_gray = needle.to_luma8();
28 let haystack_gray_full = haystack.to_luma8();
29
30 let (haystack_to_search, offset_x, offset_y) = if let Some(rect) = search_rect {
31 let cropped_view = haystack_gray_full.view(rect.x, rect.y, rect.width, rect.height);
32 (cropped_view.to_image(), rect.x, rect.y)
33 } else {
34 (haystack_gray_full, 0, 0)
35 };
36
37 let result = template_matching::match_template_parallel(
38 &haystack_to_search,
39 &needle_gray,
40 MatchTemplateMethod::CrossCorrelationNormalized,
41 );
42
43 let extremes = imageproc::template_matching::find_extremes(&result);
44 let best_match_value = extremes.max_value;
45 let mut best_match_location = extremes.max_value_location;
46
47 best_match_location.0 += offset_x;
48 best_match_location.1 += offset_y;
49
50 log::trace!(
51 "Best match found with confidence {:.4} at absolute point ({}, {})",
52 best_match_value,
53 best_match_location.0,
54 best_match_location.1
55 );
56
57 if best_match_value >= threshold {
58 let (needle_width, needle_height) = needle.dimensions();
59 let result_rect = DroidRect::new(
60 best_match_location.0,
61 best_match_location.1,
62 needle_width,
63 needle_height,
64 );
65
66 let match_result = MatchResult {
67 rect: result_rect,
68 confidence: best_match_value,
69 };
70
71 log::debug!("Match found: {:?}", match_result);
72 Ok(match_result)
73 } else {
74 log::warn!(
75 "No match found for {:?}. Best confidence was {:.4}, which is below threshold {:.4}",
76 needle_path,
77 best_match_value,
78 threshold
79 );
80 Err(DroidError::ImageNotFound(needle_path.to_path_buf()))
81 }
82}