You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C++快速评估4个点的像素颜色?自动点击器开发问询

嘿,我来帮你解决这个自动点击器里高效检测像素颜色的问题!因为C++没有跨平台的原生屏幕像素读取API,我会分平台给你最快的实现方案——核心思路都是一次性捕获目标区域的像素到内存,直接在内存中读取颜色值,避免反复调用系统API导致的性能损耗。

一、Windows平台最快实现

Windows下直接用GetPixel效率极低(每次调用都会触发GDI上下文切换),最优方案是用GDI的BitBlt把目标区域复制到内存DC,再通过GetDIBits获取像素缓冲区,之后直接在内存中读取四个点的颜色。

步骤1:初始化全局/静态资源(只做一次)

#include <windows.h>
#include <vector>
#include <algorithm> // 用于min/max函数

// 全局变量,初始化一次即可
HDC hScreenDC = nullptr;
HDC hMemDC = nullptr;
HBITMAP hBitmap = nullptr;
BITMAPINFO bmi = {0};
std::vector<BYTE> pixelBuffer;
int captureWidth, captureHeight;
int minCaptureX, minCaptureY;

// 初始化屏幕捕获资源,传入四个点的坐标来计算最小捕获区域
void initScreenCapture(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
    hScreenDC = GetDC(nullptr); // 获取整个屏幕的DC
    hMemDC = CreateCompatibleDC(hScreenDC);
    
    // 计算四个点的最小包围矩形,减少捕获范围
    minCaptureX = std::min({x1, x2, x3, x4});
    int maxCaptureX = std::max({x1, x2, x3, x4});
    minCaptureY = std::min({y1, y2, y3, y4});
    int maxCaptureY = std::max({y1, y2, y3, y4});
    captureWidth = maxCaptureX - minCaptureX + 1;
    captureHeight = maxCaptureY - minCaptureY + 1;
    
    // 设置BITMAPINFO,24位真彩色(BGR顺序)
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = captureWidth;
    bmi.bmiHeader.biHeight = -captureHeight; // 负号表示从上到下的像素顺序
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 24;
    bmi.bmiHeader.biCompression = BI_RGB;
    
    hBitmap = CreateCompatibleBitmap(hScreenDC, captureWidth, captureHeight);
    SelectObject(hMemDC, hBitmap);
    
    // 计算缓冲区大小:宽度*高度*3字节(BGR)
    int bufferSize = captureWidth * captureHeight * 3;
    pixelBuffer.resize(bufferSize);
}

步骤2:实现高效的evaluate函数

// 阈值可以自定义,比如红色低于50时触发点击
void evaluate(int x, int y, int redThreshold) {
    // 把目标区域的屏幕内容复制到内存DC
    BitBlt(hMemDC, 0, 0, captureWidth, captureHeight, hScreenDC, minCaptureX, minCaptureY, SRCCOPY);
    
    // 获取像素缓冲区到内存
    GetDIBits(hMemDC, hBitmap, 0, captureHeight, pixelBuffer.data(), &bmi, DIB_RGB_COLORS);
    
    // 计算该点在缓冲区中的索引:(相对Y坐标 * 宽度 + 相对X坐标) * 3
    int relX = x - minCaptureX;
    int relY = y - minCaptureY;
    int index = (relY * captureWidth + relX) * 3;
    // 注意:24位DIB是BGR顺序,红色通道是第三个字节
    BYTE redValue = pixelBuffer[index + 2];
    
    if (redValue < redThreshold) {
        // 用SendInput执行点击(比mouse_event更现代,支持高DPI)
        INPUT input = {0};
        input.type = INPUT_MOUSE;
        // 转换为绝对坐标(范围0-65535)
        input.mi.dx = (LONG)(x * 65535.0 / GetSystemMetrics(SM_CXSCREEN));
        input.mi.dy = (LONG)(y * 65535.0 / GetSystemMetrics(SM_CYSCREEN));
        input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP;
        SendInput(1, &input, sizeof(INPUT));
    }
}
二、Linux平台最快实现

Linux下用X11的API,同样避免多次调用XGetPixel,而是一次性获取目标区域的图像数据:

步骤1:初始化X11资源

#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <vector>
#include <algorithm>

Display* display = nullptr;
Window rootWindow;
XImage* xImage = nullptr;
int captureWidth, captureHeight;
int minCaptureX, minCaptureY;

void initScreenCapture(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
    display = XOpenDisplay(nullptr);
    rootWindow = DefaultRootWindow(display);
    
    // 计算最小包围矩形
    minCaptureX = std::min({x1, x2, x3, x4});
    int maxCaptureX = std::max({x1, x2, x3, x4});
    minCaptureY = std::min({y1, y2, y3, y4});
    int maxCaptureY = std::max({y1, y2, y3, y4});
    captureWidth = maxCaptureX - minCaptureX + 1;
    captureHeight = maxCaptureY - minCaptureY + 1;
}

步骤2:evaluate函数实现

void evaluate(int x, int y, int redThreshold) {
    // 一次性获取目标区域的图像
    if (xImage) XDestroyImage(xImage);
    xImage = XGetImage(display, rootWindow, minCaptureX, minCaptureY, captureWidth, captureHeight, AllPlanes, ZPixmap);
    
    // 计算相对坐标
    int relX = x - minCaptureX;
    int relY = y - minCaptureY;
    // 读取红色通道(XImage的像素格式为RGB,移位取决于位深,这里假设24位)
    unsigned long pixel = XGetPixel(xImage, relX, relY);
    BYTE redValue = (pixel >> 16) & 0xFF;
    
    if (redValue < redThreshold) {
        // 用XTest扩展模拟点击(编译时需要链接-lXtst)
        XTestFakeMotionEvent(display, DefaultScreen(display), x, y, CurrentTime);
        XTestFakeButtonEvent(display, Button1, True, CurrentTime);
        XTestFakeButtonEvent(display, Button1, False, CurrentTime);
        XFlush(display);
    }
}
三、macOS平台最快实现

macOS用Quartz框架来捕获屏幕,同样一次性获取图像数据:

步骤1:导入Quartz框架

#include <CoreGraphics/CoreGraphics.h>
#include <vector>
#include <algorithm>

int captureWidth, captureHeight;
int minCaptureX, minCaptureY;

void initScreenCapture(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
    // 计算最小包围矩形
    minCaptureX = std::min({x1, x2, x3, x4});
    int maxCaptureX = std::max({x1, x2, x3, x4});
    minCaptureY = std::min({y1, y2, y3, y4});
    int maxCaptureY = std::max({y1, y2, y3, y4});
    captureWidth = maxCaptureX - minCaptureX + 1;
    captureHeight = maxCaptureY - minCaptureY + 1;
}

步骤2:evaluate函数实现

void evaluate(int x, int y, int redThreshold) {
    // 捕获目标区域的屏幕图像
    CGRect captureRect = CGRectMake(minCaptureX, minCaptureY, captureWidth, captureHeight);
    CGImageRef screenImage = CGDisplayCreateImageForRect(CGMainDisplayID(), captureRect);
    if (!screenImage) return;
    
    // 获取图像数据缓冲区
    CGDataProviderRef provider = CGImageGetDataProvider(screenImage);
    CFDataRef data = CGDataProviderCopyData(provider);
    const BYTE* pixelBuffer = CFDataGetBytePtr(data);
    
    // 计算相对坐标和索引(macOS的CGImage是BGRA顺序,4字节每像素)
    int relX = x - minCaptureX;
    int relY = y - minCaptureY;
    int bytesPerRow = CGImageGetBytesPerRow(screenImage);
    int index = relY * bytesPerRow + relX * 4;
    BYTE redValue = pixelBuffer[index + 2]; // 红色通道是第三个字节
    
    if (redValue < redThreshold) {
        // 用CGEvent模拟点击
        CGEventRef clickDown = CGEventCreateMouseEvent(nullptr, kCGEventLeftMouseDown, CGPointMake(x, y), kCGMouseButtonLeft);
        CGEventRef clickUp = CGEventCreateMouseEvent(nullptr, kCGEventLeftMouseUp, CGPointMake(x, y), kCGMouseButtonLeft);
        CGEventPost(kCGHIDEventTap, clickDown);
        CGEventPost(kCGHIDEventTap, clickUp);
        CFRelease(clickDown);
        CFRelease(clickUp);
    }
    
    CFRelease(data);
    CGImageRelease(screenImage);
}
适配你的main函数

把初始化代码和循环逻辑结合起来,示例如下(以Windows为例):

#include <iostream>
using namespace std;

int main() {
    int x1, y1, x2, y2, x3, y3, x4, y4;
    cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> x4 >> y4;
    
    // 初始化屏幕捕获资源
    initScreenCapture(x1, y1, x2, y2, x3, y3, x4, y4);
    
    const int RED_THRESHOLD = 50; // 自定义红色阈值
    while(1) {
        // 一次捕获屏幕,检查四个点(比每个点单独捕获效率高)
        // 可以把捕获逻辑提到循环外,只做一次捕获,再依次检查四个点
        BitBlt(hMemDC, 0, 0, captureWidth, captureHeight, hScreenDC, minCaptureX, minCaptureY, SRCCOPY);
        GetDIBits(hMemDC, hBitmap, 0, captureHeight, pixelBuffer.data(), &bmi, DIB_RGB_COLORS);
        
        // 检查第一个点
        int relX = x1 - minCaptureX;
        int relY = y1 - minCaptureY;
        int index = (relY * captureWidth + relX) * 3;
        BYTE redValue = pixelBuffer[index + 2];
        if (redValue < RED_THRESHOLD) {
            // 执行点击逻辑...
        }
        
        // 重复检查x2/y2、x3/y3、x4/y4...
        
        // 可选:添加延迟降低CPU占用
        Sleep(10);
    }
    
    // 退出前释放资源
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    ReleaseDC(nullptr, hScreenDC);
    
    return 0;
}
通用优化建议
  • 减少捕获频率:如果不需要毫秒级检测,可以在循环中加入短延迟,降低CPU占用。
  • 单次捕获多检查:循环中只捕获一次屏幕,然后依次检查四个点,避免重复捕获。
  • 资源清理:程序退出时务必释放初始化时创建的DC、Bitmap、X11 Display等资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Tanmoy Krishna Das

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:28:35