pixhunt 0.8.1

Fast screen finding: template matching (RGB tolerance / ZNCC), color-blob search and wait/poll APIs, with pluggable capture backends (cross-platform xcap, plus Windows GDI / DXGI / PrintWindow window capture).
docs.rs failed to build pixhunt-0.8.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

pixhunt

CI crates.io license

快速的屏幕找图库:截一帧屏幕,在其中定位一张小图(模板)的坐标。 纯 Rust,无需 OpenCV。适合自动化测试、脚本辅助、UI 定位等。

它做什么

给一张小图 template.png,pixhunt 告诉你在当前屏幕的哪个位置(用 Corr 时还给一个 相似度分数):

use pixhunt::{Finder, CaptureKind, MatchKind, Template};

let tpl = Template::load("template.png")?;
let mut finder = Finder::builder()
    .capture(CaptureKind::Monitor)             // 截图后端
    .matcher(MatchKind::Rgb { tolerance: 25 }) // 找法
    .build()?;

if let Some(m) = finder.find_on_screen(&tpl)? {
    // 注意:`Rgb` 是"容差内逐像素全对才算命中"的判定,它的 m.score 恒为 1.0;
    // 想要真正的相似度分数(0.0..=1.0)请用 MatchKind::Corr。
    let (cx, cy) = m.center(&tpl); // 要点击的是中心,不是左上角
    println!("命中 @ ({}, {}),中心 ({}, {})", m.x, m.y, cx, cy);
}

文档

  • 使用说明书(从零开始) —— 面向没写过 Rust 的读者:环境准备、最小语法课、 核心概念、全部对外 API 逐个详解(真实签名 + 参数 + 返回 + 坑)、任务配方、性能调参、 报错排查、API 速查表。
  • cargo doc --no-deps --open —— 本地生成的 API 参考(以源码注释为准)。
  • examples/ 三个可运行示例的用法见说明书 12.6。

设计:两个可插拔"插座"

  • Capture 负责"怎么拿到画面"。
  • Matcher 负责"怎么找模板"。

上层只跟"插座"打交道,新增后端/算法不改上层代码。

功能开关 (Cargo features)

feature 提供 平台
(默认) XCapCapture(基于 xcap)+ RgbMatcher 跨平台
capture-gdi GdiCapture(复用 DC + BitBlt,输出 BGRA) 仅 Windows
capture-dxgi DxgiCapture(桌面复制,GPU 取帧) 仅 Windows
capture-window WindowCapture(PrintWindow 截单个窗口客户区,遮挡也可截)+ CaptureKind::WindowByTitle 仅 Windows
match-corr CorrMatcher(corrmatch 的 ZNCC,灰度;调参见 CorrConfig) 跨平台
parallel RgbMatcher 按行并行(find / find_all,rayon);CorrMatcher 分层并行搜索 跨平台
tracing trace 级诊断事件(截图耗时、缓存跳过、命中与否);关闭零开销 跨平台
[dependencies]
pixhunt = { version = "0.8", features = ["capture-dxgi", "capture-gdi", "match-corr", "parallel"] }

启用后 CaptureKind 多出 Gdi / Dxgi / Auto(Auto 依次试 DXGI → GDI → xcap), MatchKind 多出 Corr:

let mut finder = Finder::builder()
    .capture(CaptureKind::Auto)     // 自动选最快可用的截图后端
    .matcher(MatchKind::Rgb { tolerance: 25 })
    .build()?;

后端与算法怎么选

截图后端 特点 成本(1920x1200 实测)
Monitor(xcap) 跨平台保底;唯一支持区域直抓的后端 全屏 ~34ms / 400x300 区域 ~16.5ms
Gdi 复用 DC + BitBlt + 免翻转 全屏 ~32ms
Dxgi 桌面复制,GPU 取帧 + 静态帧跳过 全屏 ~16ms(无新帧时更低)

(以上为同一台 1920x1200 桌面、--release、连续取帧的中位数;xcap / Gdi 走 GDI 路径,量级接近,xcap 的价值在于跨平台 + 区域直抓。)

匹配算法 适合 特点
Rgb 屏幕内容与模板几乎一致、追求速度 不转灰度、锚点 + 逐像素早失败,通道序自适应(RGBA/BGRA)
Corr 有光照/轻微缩放变化、追求稳 灰度 ZNCC + 金字塔,只做平移(不搜旋转),较慢但鲁棒

更多用法 (v0.2)

use pixhunt::{Finder, CaptureKind, MatchKind, Template, Rect};

// 1) 限定区域:只在该矩形内找,返回的仍是屏幕绝对坐标
let finder = Finder::builder()
    .capture(CaptureKind::Auto)
    .matcher(MatchKind::Rgb { tolerance: 25 })
    .region(Rect::new(100, 80, 640, 480))
    .build()?;

// 2) 多结果:找全部不重叠匹配(重叠自动去重),按 (y, x) 升序;max=0 表示不限。
//    ⚠️ 平坦画面(纯色背景 + 纯色小模板)上"每个位置都是命中",别随手写 0:
//    1920x1200 纯色帧 + 8x8 纯色模板实测返回 36000 个。给个真实上限(比如 20)能提前停止扫描。
let all = finder.find_all_on_screen(&tpl, 20)?;
for m in &all {
    let (cx, cy) = m.center(&tpl); // 多结果要点中心时用它,不用自己折算
    println!("命中 @ ({}, {}) → 点击 ({cx}, {cy})", m.x, m.y);
}

// 3) 批量:只截一屏,一次匹配多张模板(省掉重复截图)
let hits = finder.find_many_on_screen(&[&tpl_a, &tpl_b, &tpl_c])?;

匹配器层面也可直接调用 trait 方法: [Matcher::find] 整帧单个、[Matcher::find_in] 区域内单个、 [Matcher::find_all] 区域内多个。开启 parallel 后,RgbMatcher 用 rayon 把 扫描按行分到多核,全屏 find / find_all 在大分辨率下更快(结果与串行完全一致)。

等待与窗口 (v0.3)

use std::time::Duration;
use pixhunt::{Finder, CaptureKind, MatchKind, Template, WindowCapture};

// 4) 轮询等待:等按钮出现(命中即返回,超时返回 None);等遮罩消失同理。
//    timeout 可以传 Duration::MAX(永不超时,不会 panic)。
//    静态画面会被跳过搜索:DXGI 后端自己上报,或设了 region 时由库里逐字节比对区域帧。
let m = finder.find_until(&tpl, Duration::from_secs(10), Duration::from_millis(50))?;
let gone = finder.wait_gone(&loading_tpl, Duration::from_secs(30), Duration::from_millis(100))?;

// 5) 窗口级截图(Windows, feature `capture-window`):PrintWindow 渲染客户区,
//    窗口被遮挡也能截;返回坐标为窗口相对。也可用 CaptureKind::Window(hwnd)。
let cap = WindowCapture::from_title("无标题 - 记事本")?;
let mut finder = Finder::new(Box::new(cap), Box::new(pixhunt::RgbMatcher::new(25)));
let m = finder.find_on_screen(&tpl)?; // 相对该窗口客户区的坐标

与键鼠操作的关系(生态分工)

pixhunt 专注做眼睛(截图 + 定位),不做"手"——点击/输入交给 enigo、rdev 等成熟跨平台库,坐标就是两者的接口:

let m = finder.find_until(&button, Duration::from_secs(10), Duration::from_millis(80))?.unwrap();
let (cx, cy) = m.center(&button); // 匹配坐标是左上角,center() 折算成点击的中心
enigo.move_mouse(cx, cy, Coordinate::Abs)?;
enigo.button(Button::Left, Direction::Click)?;

完整可运行示例见 examples/wait_and_click.rs(Windows,--features capture-gdi)。

颜色搜索与诊断 (v0.4)

use pixhunt::{ColorSpec, Finder, CaptureKind, MatchKind};

// 6) 颜色范围搜索:没有模板、只有"大概这个颜色"的场景(血条/状态灯/高亮区)。
//    返回 8-连通色块(包围盒+面积),按 (y,x) 排序,min_area 过滤碎点。
let red_lights = finder.find_color_on_screen(&ColorSpec::new(255, 0, 0, 40), 50)?;
for b in red_lights {
    println!("色块 {:?} 面积 {} 中心 {:?}", b.bounds, b.area, b.center());
}
// 也可离线对任意帧用:pixhunt::color::find_blobs(&frame, &spec, region, min_area)

开 tracing feature 后,pixhunt 在 pixhunt target 下输出 trace 级事件: 后端名、截图耗时、静态帧缓存是否命中、扫描结果等——"为什么找不到"先看日志:

// 用户侧接一个 subscriber 即可看到
tracing_subscriber::fmt().with_max_level(tracing::Level::TRACE).init();
finder.find_on_screen(&tpl)?; // trace: op="find_on_screen" backend="dxgi" changed=false cache_hit=true ...

性能(参考)

⚠️ 下面所有数字都是 --release 构建实测。 debug 构建(cargo run / cargo build 默认)慢一个数量级:同一台机器、同一份 1920x1080 合成场景,全屏扫一遍 debug ~60ms / release ~4ms。别拿 debug 的耗时评价这个库。

另:Cargo.toml 里的 [profile.release](lto、codegen-units = 1)只对在本仓库里 跑示例/基准生效。你依赖 pixhunt 时,优化级别由你自己项目的 [profile.release] 决定 ——想吃到同样性能,请在你项目根 Cargo.toml 里也写上这三行(见 docs/GUIDE.md 10.4)。

同一台 8 逻辑核机器、--release 实测。截图为 1920x1200 真实桌面的连续取帧中位数, 纯匹配为 cargo bench --bench match(1920x1080 合成帧 + 64px 模板):

组合 纯匹配 截图 端到端(截图+匹配)
RGB + Monitor(xcap) 串行 ~3.0ms / parallel ~1.7ms ~34ms ~36ms
RGB + Gdi 同上 ~32ms ~34ms
RGB + Dxgi 同上 ~16ms(静态桌面更低) ~18ms

要点:找图瓶颈主要在截图,换更快的后端收益最大;RgbMatcher 本身已是毫秒级。 限定区域 + Monitor 会走区域直抓(400x300 实测截图 ~16.5ms,与"截全屏再裁剪" 逐字节一致),同一模板端到端从 ~47ms 降到 ~20ms。

掩码与差分另有两个实测数:rgb_find_masked_1080p(64px 模板掩掉 6% 像素)~3.4ms,比无掩码 慢 18% —— 掩码买的是正确性、不是速度;diff_since_last_400x300_on_1080p ~3.4ms (基线缓冲复用之后;复用前 ~5.6ms)。

Corr(ZNCC)不受截图后端制约,成本在搜索本身(同样 cargo bench --features match-corr[,parallel] --bench match):

组合 热路径(模板已缓存) 冷启动(含模板编译)
Corr ~13.7ms ~13.7ms
Corr + parallel(8 逻辑核) ~6.6ms ~6.8ms

模板只做平移匹配(compile_unrotated),不建角度模板库,因此冷启动≈热路径; 开 parallel 后 ZNCC 分层并行,约 2x(结果仍确定性)。

ZNCC 调参 (v0.5)

match-corr 下用 MatchKind::CorrWith(CorrConfig { .. }) 调搜索参数(只想用默认值 就继续写 MatchKind::Corr):

use pixhunt::{CorrConfig, Finder, CaptureKind, MatchKind, Template};

// 已知目标只在附近小范围移动:砍深层金字塔 + 缩小精修 ROI 换低延迟,
// 并用 min_score 把"长得像但不够像"的结果当未命中。
let mut finder = Finder::builder()
    .capture(CaptureKind::Auto)
    .matcher(MatchKind::CorrWith(CorrConfig {
        max_image_levels: 3,
        roi_radius: 4,
        min_score: 0.7,
        ..CorrConfig::default()
    }))
    .build()?;
let m = finder.find_on_screen(&Template::load("btn.png")?)?;
字段 默认 调小的收益 / 调大的收益
max_image_levels 6 粗筛更便宜 / 大位移、轻微缩放更稳
beam_width 8 每层候选更少更快 / 遮挡、伪峰多时更稳
roi_radius 8 精修扫描范围更小更快 / 容忍层间位移误差更大
min_score 不过滤 误命中更少(注意会漏判)
parallel 跟随 parallel feature —

要点:

  • 非法值(0、NaN、±inf)会被夹到安全下限而不是报错,不会出现"配错就永远找不到"。
  • parallel: true 只在开了 pixhunt parallel feature 时生效——该 feature 会把 rayon 传导给 corrmatch;否则会被归一为 false(否则 corrmatch 会在 validate() 直接报错, 表现为静默找不到)。
  • min_score 是最终结果的阈值,在 pixhunt 侧把关,不传给 corrmatch。corrmatch 自己的 同名字段是逐金字塔层的候选门槛,而粗筛层分数天然偏低,拿它当最终阈值会把真命中 整条链路削空。

v0.8.1 变更

修订号级别:没有破坏性改动(只新增一个方法),但有四处行为修正,升级后结果可能和 0.8.0 不同:

  • find_all 的返回顺序改成文档承诺的 (y, x) 升序(从上到下、从左到右)。此前实际按 x 优先排,且重叠去重"留先遇到的那个"因此留下的是最左而非最上的命中。现在与 find_color_on_screen 口径一致。依赖旧顺序的代码需要复核。
  • find_all 的去重从二次方改为按行增量,max 现在真的限制工作量。此前 max 只截断 结果长度、扫描和去重照样跑完全程:1920x1200 纯色帧 + 8x8 纯色模板、max=0(36000 命中) 实测 23.5 秒,max=1 也要 ~471ms;同一场景现在 串行 ~347ms / 开 parallel ~151ms, max=1 降到 ~0.28ms(parallel ~2.7ms,并行按行块扫描、块内至少扫完一整块)。 max 给 0 的旧代码不会变错,只是从"卡住半小时"变成能跑完。
  • 设了 region 且后端支持区域直抓(目前只有 XCapCapture)时,静态帧缓存开始生效。 此前这条路径把每帧都当"画面变了",于是"盯着一小块反复轮询"时缓存 100% 失效(实测静态 画面连查 3 次搜 3 次,现在搜 1 次)。复用结果与重新搜一遍完全一致。Gdi/Dxgi/ Window 走全屏路径,changed 由后端自己报,不受影响。
  • find_until / wait_gone 传 Duration::MAX 不再 panic。此前 Instant::now() + timeout 溢出会当场中止进程;现在算不出截止时刻就当永不超时。

新增:

  • Match::center(&tpl) -> (i32, i32):把命中换算成中心坐标。find_center_on_screen 只覆盖单结果,find_all / find_many 的结果此前要自己写 m.x + tpl.width as i32 / 2。
  • region 完全落在画面外时提示一次:tracing feature 开启后输出 warn(未改动返回值, 仍是 Ok(None)),用来区分"region 坐标写错了"和"屏幕上确实没这张图"。
  • 文档口径:README/Match 字段注释都写明 Rgb 的 score 恒为 1.0(此前只有说明书 里有);MatchKind::Corr 侧补上"ZNCC 走灰度、不认模板掩码"的提醒。

v0.8 新功能

  • 透明掩码模板:Template::load 自动识别 PNG alpha;新增 Template::from_rgba / with_mask;RgbMatcher 比较时跳过掩码像素。
  • find_center_on_screen:返回模板中心坐标,省去手动加半尺寸。
  • diff_since_last(rect):对比上一次截图,返回指定区域内颜色有变的像素数。
  • CaptureKind::WindowByTitle(..):FinderBuilder 直接按窗口标题精确匹配截窗口,无需先拿句柄。
  • 图片格式扩展:Template::load 现支持 PNG + JPEG + WebP(之前仅 PNG)。
  • GDI 后端不再 panic:BitBlt / GetDIBits 失败(锁屏、休眠唤醒瞬间等)改为返回 Err,与 DXGI / Window 后端行为一致。之前这里会 assert! 直接中止进程。
  • 截图后端会自己跟上桌面尺寸变化:GDI 每次抓取前比对桌面分辨率,变了就重建位图;DXGI 在复制对象失效(改分辨率、锁屏 / 休眠唤醒、显卡重置)后自动重建并重取。之前 GDI 会一直 输出错位画面、DXGI 会永远停在失效前的旧帧。
  • find_all 对带掩码的模板按可见区外接框去重:此前按整张模板外接框判定,大模板 + 小图标会把相邻目标漏报成一个。
  • diff_since_last 复用基线缓冲,不再每次重新分配整帧:1080p 实测 5.6 ms → 3.4 ms。

升级到 v0.8 (breaking)

破坏面集中在 Template 的字段与两处行为变化:

v0.7 v0.8
Template 只有 rgb / width / height 三个字段 多出 pub mask: Option<Vec<bool>>。用结构体字面量手工构造的代码需要补这个字段;走 load / from_rgb / from_rgba 构造的不受影响
find_all 按整张模板的外接框抑制重叠 带掩码的模板改按可见像素外接框抑制。同一张图上报的结果可能变多(小图标不再被大模板的外接框吞掉);无掩码时行为不变
GDI 截图失败时 assert! panic 返回 Err。原先靠捕获 panic 兜底的写法要改成处理 Result
改分辨率 / 锁屏唤醒后需重建 Finder 才能恢复 后端自己跟上桌面尺寸变化,无需重建

另一点:CorrMatcher 收到带掩码的模板时,debug 构建会 debug_assert! 提示,因为 ZNCC 那条路径 没有掩码接口、会给出与 RgbMatcher 不一致的结果。release 构建零开销,照常运行。

升级到 v0.7 (breaking)

Error::Capture 改成命名字段变体,让截图失败能保住错误链:

v0.6 v0.7
Error::Capture(String)(source() 恒为 None) Error::Capture { message, source }
手写 Error::Capture(msg.into()) Error::capture(msg);要带底层错误用 Error::capture_from(ctx, e)

message 是"在哪一步失败"(如 xcap Monitor::capture_image()),source 是原始错误 (xcap 内部还会再包一层 io / D-Bus / Win32 错误,不保留就取不到了)。Display 仍把两者 拼成一行,所以只做 e.to_string() / ? 上抛的代码输出不变,只有 match 到 Error::Capture(s) 的地方需要改成 Error::Capture { message, .. }。

升级到 v0.6 (breaking)

v0.6 把默认截图后端从已停维的 screenshots 换成 xcap:

v0.5 v0.6
CaptureKind::Screenshots CaptureKind::Monitor
ScreenshotsCapture XCapCapture(另有 from_point() 可绑指定显示器)
MSRV 1.75 MSRV 1.88(下限来自 xcap 在 Linux 上的依赖 zbus;开 match-corr 需 1.89)
限定区域 = 抓全屏再裁剪 Monitor 后端直接区域抓取(端到端实测 47ms → 20ms)

命中坐标语义不变:仍是屏幕绝对坐标。Gdi / Dxgi / Window 不支持区域直抓, 限定区域时自动回退为"抓全屏再裁剪",结果一致。

运行示例

# 默认(xcap / Monitor 后端)
cargo run --release --example find_on_screen -- path/to/template.png
# 用 Windows 快后端 + 自动选择
cargo run --release --features capture-dxgi,capture-gdi --example find_on_screen -- path/to/template.png

注意

  • 分辨率 / DPI:模板与截图需同一分辨率尺度,否则找不到。
  • 文档:cargo doc --no-deps --open 看完整 API 注释。docs.rs 上本 crate 的构建目前是 失败的(pixhunt-0.8.0 在 docs.rs 的 builds 页显示 failed):原因是要编译依赖 xcap, 而 xcap 的构建脚本需要系统库(libclang / PipeWire headers 等),docs.rs 环境没有这些包, 不是本 crate 的代码问题。连带后果:在线文档里看不到 CaptureKind::Gdi / Dxgi / Window / Auto 这些 Windows 专有变体 —— 想查这些请直接看本地文档或源码。
  • 截图 ≠ 匹配:两者是分开计时/分开的步骤,别把截图耗时算进算法。
  • DXGI 限制:RDP / 锁屏 / 无 GPU 时不可用,CaptureKind::Auto 会自动回退。
  • Linux 系统依赖:默认后端 xcap 在 X11 走 xcb、Wayland 走 PipeWire/Wayland, 编译需要:pkg-config libclang-dev libxcb1-dev libxrandr-dev libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev libgbm-dev(本仓库 CI 已按此配置)。 缺失时报的是链接错误(如 unable to find library -lgbm),不是编译错误。
  • 多显示器:Monitor / Gdi / Dxgi 都只覆盖主显示器。要抓副屏,用 XCapCapture::from_point(x, y)(按屏幕坐标落在哪块屏来选显示器),再交给 Finder::new(Box::new(cap), ...)。
  • 帧字节序:GDI / DXGI 产出 BGRA,xcap / PrintWindow 产出 RGBA;RgbMatcher 按帧的 PixelFormat 自动映射通道,无需你手动转换。
  • 平坦内容会让 Rgb 退化:当模板与搜索区域都近乎单色(相邻像素几乎没有差异) 时,锚点与逐像素早失败全部失效,扫描退化为暴力全量。1080p + 64px 实测:有纹理 ~3ms,零方差模板放在纯色背景上 ~1.9s(慢 600 倍),且平坦区里会有多个"等价"命中点。 find_all 在这种画面上还会返回海量命中(1920x1200 纯色帧 + 8x8 模板实测 36000 个), 所以 max 请给真实上限,别习惯性写 0。 裁模板请选有边缘、有纹理的区域,别从纯色背景上切一块。

说明:内容由 AI 生成

本仓库的代码、注释、测试与文档(含本 README)由 AI 编码助手生成或改写,并经 cargo test / cargo clippy -D warnings / GitHub Actions 验证。仓库内的性能数字均为 特定机器上的测量值,只能当量级参考。内容不保证逐行经过人工细读,请按对待任何 第三方 crate 的方式自行评审后再用于生产。

许可

Licensed under either of MIT or Apache License, Version 2.0 at your option. See LICENSE-MIT and LICENSE-APACHE.