QtC++截图支持获取鼠标光标

介绍

在截图工具中你会发现,屏幕截图大部分软件都无法获取鼠标指针,显示鼠标样式这个功能使用频率较低,对于专业的截图工具会有此功能,例如Snipaste。

1.获取当前鼠标图像绘制到截图中,并反色处理

此函数调用winapi来返回QImage 类型的图像,尺寸固定32,32,这里有一个坑不能使用bmpCursor.bmWidth,鼠标信息的宽高,这个值可能会为0,导致无法获取图像。


#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QImage>
#include <QPainter>
#include <QPixmap>
#include <Windows.h>
#include <QtWinExtras>
#include <QDebug>
#include <QElapsedTimer>

#include <Windows.h>
#include <QImage>
#include <QtWinExtras>
#include <QThread>
#include <QtWidgets>


QImage getMouseImg(int& hotspotX, int& hotspotY) {
    // 获取桌面窗口句柄
    HWND hwnd = GetDesktopWindow();
    // 获取桌面窗口的设备上下文 (HDC),用于图形操作
    HDC hdc = GetWindowDC(hwnd);
    // 创建一个内存设备上下文 (HDC),用于在内存中绘制图像
    HDC hdcMem = CreateCompatibleDC(hdc);

    // 创建一个兼容的位图,用于存储光标的图像,大小为光标的宽和高
    HBITMAP hbitmap = nullptr;
    hbitmap = CreateCompatibleBitmap(hdc, 32, 32);
    // 将新创建的位图选入内存设备上下文 (HDC)
    SelectObject(hdcMem, hbitmap);

    // 初始化 CURSORINFO 结构体,用于存储当前光标的信息
    CURSORINFO cursor = { sizeof(cursor) };

    // 检查是否成功获取到当前光标的信息,且光标是否显示
    if (GetCursorInfo(&cursor) && cursor.flags == CURSOR_SHOWING) {
        // 获取桌面窗口的尺寸
        RECT rect;
        GetWindowRect(hwnd, &rect);

        // 初始化 ICONINFO 结构体,获取光标的图标信息
        ICONINFO info = { sizeof(info) };
        if (GetIconInfo(cursor.hCursor, &info)) {
            // 获取热点位置
            hotspotX = info.xHotspot;
            hotspotY = info.yHotspot;

            // 定义 BITMAP 结构体来存储光标的位图信息
            BITMAP bmpCursor = { 0 };
            GetObject(info.hbmColor, sizeof(bmpCursor), &bmpCursor);

            // 将当前光标绘制到内存设备上下文的位图上
            DrawIconEx(hdcMem, 0, 0, cursor.hCursor, 32, 32, 0, nullptr, DI_NORMAL);

            // 使用 QtWin::imageFromHBITMAP 函数,将 Windows 的 HBITMAP 转换为 Qt 的 QImage 对象
            QImage cursorImage = QtWin::imageFromHBITMAP(hdcMem, hbitmap, 32, 32);

            // 释放资源
            DeleteObject(info.hbmColor);
            DeleteObject(info.hbmMask);
            ReleaseDC(hwnd, hdc);
            DeleteDC(hdcMem);

            return cursorImage;
        }
    }

    // 如果光标不可见或获取失败,返回一个空的 QImage 对象
    return QImage();
}


MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

QScreen *getScreenOfRect(const int &x)
{
    for (QScreen *screen : QGuiApplication::screens())
    {
        if (screen->geometry().contains(x, 0))
        {
            return screen;
        }
    }
    return nullptr;
}

// 反色函数
QImage invertImageColors(const QImage& image) {
    QImage invertedImage = image.copy();  // 创建图像副本

    // 遍历图像的每个像素,并进行反色
    for (int y = 0; y < invertedImage.height(); ++y) {
        for (int x = 0; x < invertedImage.width(); ++x) {
            QColor pixelColor = invertedImage.pixelColor(x, y);
            QColor invertedColor = QColor(255 - pixelColor.red(),
                                          255 - pixelColor.green(),
                                          255 - pixelColor.blue(),
                                          pixelColor.alpha());  // 保留透明度
            invertedImage.setPixelColor(x, y, invertedColor);
        }
    }

    return invertedImage;
}

// 判断颜色是否相似
bool isColorSimilar(const QColor& c1, const QColor& c2, int threshold = 50) {
    int rDiff = abs(c1.red() - c2.red());
    int gDiff = abs(c1.green() - c2.green());
    int bDiff = abs(c1.blue() - c2.blue());

    // 如果颜色的 RGB 差异小于阈值,认为颜色相似
    return (rDiff < threshold && gDiff < threshold && bDiff < threshold);
}

// 检查鼠标区域是否容易看见
QImage checkCursorVisibilityAndAdjust(const QImage& largeImage, int x, int y, QImage cursorImage) {
    bool needsInversion = false;

    // 确保鼠标的区域不会超出 largeImage 的范围
    if (x < 0 || y < 0 || x + cursorImage.width() > largeImage.width() || y + cursorImage.height() > largeImage.height()) {
        qWarning() << "Mouse position or cursor image exceeds the bounds of largeImage.";
        return cursorImage;  // 返回原始鼠标图像
    }

    // 遍历光标图像区域,并与 largeImage 的对应区域进行比较
    for (int cy = 0; cy < cursorImage.height(); ++cy) {
        for (int cx = 0; cx < cursorImage.width(); ++cx) {
            QColor cursorPixel = cursorImage.pixelColor(cx, cy);

            // 如果鼠标像素是透明的 (A == 0),跳过该像素的比较
            if (cursorPixel.alpha() == 0) {
                continue;
            }

            QColor backgroundPixel = largeImage.pixelColor(x + cx, y + cy);

            // 如果光标像素和背景像素颜色非常相似,标记需要反色处理
            if (isColorSimilar(cursorPixel, backgroundPixel)) {
                needsInversion = true;
                break;  // 发现需要反色时,停止进一步检查
            }
        }
        if (needsInversion) break;
    }

    // 如果颜色太相似,对鼠标图像进行反色处理
    if (needsInversion) {
        return invertImageColors(cursorImage);  // 反色后返回新的图像
    }

    return cursorImage;  // 如果不需要反色,返回原图像
}

void MainWindow::on_pushButton_clicked()
{
    QThread::msleep(2000);
    QElapsedTimer s;
    s.start();


    //获取屏幕
    QScreen* screen = getScreenOfRect(QCursor::pos().x());
    //屏幕截图
    QImage largeImage = screen->grabWindow(0).toImage();

    // 获取鼠标图及其热点位置
    int hotspotX = 0, hotspotY = 0;
    QImage cursorImage = getMouseImg(hotspotX, hotspotY);


    // 获取当前鼠标位置
    POINT mousePos;
    GetCursorPos(&mousePos);

    // 检查鼠标绘制区域是否容易看见,若不易见则进行反色处理
    cursorImage = checkCursorVisibilityAndAdjust(largeImage, mousePos.x, mousePos.y, cursorImage);


    // 调整鼠标绘制位置,考虑热点偏移
    int drawX = mousePos.x - hotspotX;
    int drawY = mousePos.y - hotspotY;

    // 在 largeImage 上绘制光标
    QPainter painter(&largeImage);
    painter.drawImage(drawX, drawY, cursorImage);
    painter.end();

    largeImage.save("C:/1111111111111111.png");
    qDebug() << s.elapsed();
}

2.关于鼠标样式反色问题

在windows系统例如输入样式的鼠标类似于 工 字,这个鼠标样式在白底下是黑色,在黑底下是白色,那么对于getMouseImg接口返回的只会是纯白色,所以我们需要对此做一个反色处理,以及判断颜色。

结尾

以上代码就是完整代码,可自行复制接口拼接项,int threshold = 50是相似度判断,可自行修改判断反色。
注意:反色做的不是很完美,可能没有win那么完美,例如不能根据一个色点判断就进行反色,完美点做就取出所有色点然后做一个相似比例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值