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那么完美,例如不能根据一个色点判断就进行反色,完美点做就取出所有色点然后做一个相似比例。