Qt跨平台截图工具

news2024/9/17 8:53:12

Qt跨平台截图工具

文章目录

  • Qt跨平台截图工具
    • 1、概述
    • 2、实现效果
    • 3、软件构成
    • 4、关键代码
    • 5、源代码

更多精彩内容
👉个人内容分类汇总 👈
👉Qt自定义模块、工具👈

1、概述

  • Qt版本:V5.12.5
  • 兼容系统:
    • Windows:这里测试了Windows10,其它的版本没有测试;
    • Linux:这里测试了ubuntu18.04、20.04,其它的没有测试(ubuntu自带的截图软件没有这个功能);
    • Mac:等啥时候我有了Mac电脑再说。
  • 实现功能:
  1. 全屏截图功能,截取当前屏幕;
  2. 矩形截图功能,可通过鼠标选取矩形区域进行截图;
  3. 窗口截图功能,通过获取鼠标坐标位置的窗口,截取所在窗口的图像(由于ubuntu下自带的截图工具没有这个功能,就很烦);
  4. 保存截图功能,将截取的图像保存到本地;
  5. 实时显示截取图像功能,由于我不喜欢Windows自带截图软件会随着图片大小自动变大,所以我的这个窗口不会自动变大。
  6. 功能比Windows、ubuntu自带的截图工具更强大,目前还没开发完成,快捷键之类的后续再加上。

2、实现效果

  • Windows下实现效果

    在这里插入图片描述

  • Linux下实现效果

    在这里插入图片描述

3、软件构成

  • 截图软件主要由屏幕截取功能、矩形选区类、窗口选取类、自定义全局鼠标监听、截图显示类几部分组成。

    在这里插入图片描述

4、关键代码

  • 由于使用到了系统API,所以pro文件中需要链接系统库
win32 {
LIBS+= -luser32    # 使用WindowsAPI需要链接库
}
unix:!macx{
LIBS += -lX11      # linux获取窗口信息需要用到xlib
}
  • 图像截图功能
/**
 * @brief       开始截图
 * @param rect  截取的范围
 */
void MainWindow::grabPixmap(QRect rect)
{
#if defined(Q_OS_WIN)
    setWindowOpacity(0);       // 最好的方法是将当前窗口设置成完全透明
    QDesktopWidget *desk = QApplication::desktop();         // 获取桌面根窗口
    QScreen * screen = QGuiApplication::primaryScreen();    // 获取默认屏幕
    m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height());          // 抓取屏幕图像
    ui->centralwidget->updatePixmap(m_pixmap);                     // 显示捕获的图像
    setWindowOpacity(1);
#elif defined(Q_OS_LINUX)
    // linux下setWindowOpacity设置透明后截图还可以看到一个透明的边框,效果不是很好,所以使用hide
    this->hide();            // 截图之前将当前窗口隐藏,避免截取的图像中包含当前窗口,这种方法很慢,需要延时等待几百毫秒,否则还是会有当前窗口
    QThread::msleep(300);
    QDesktopWidget *desk = QApplication::desktop();         // 获取桌面根窗口
    QScreen * screen = QGuiApplication::primaryScreen();    // 获取默认屏幕
    m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height());          // 抓取屏幕图像
    ui->centralwidget->updatePixmap(m_pixmap);                     // 显示捕获的图像
    this->show();            // 截图完成后显示窗口
#endif
}
  • 截图保存功能
/**
 * @brief         保存截图
 * @param checked
 */
void MainWindow::on_saveImage(bool checked)
{
    Q_UNUSED(checked)
    if(m_pixmap.isNull()) return;

    // linux下getSaveFileName不会返回默认文件后缀,所以需要在文件名中添加后缀,否则QImage::save无法通过后缀推测出文件类型,就会保存失败
    QString name = QString("%1.png").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss"));
    QString strName = QFileDialog::getSaveFileName(this, "保存到", name, "便携式网络图形(*.png);;JPEG文件(*.jpg)");
    if(strName.isEmpty()) return;

    QImage image = m_pixmap.toImage();
    if(image.save(strName))
    {
        qDebug() << "保存成功!";
    }
    else
    {
        QMessageBox::warning(this, "注意!", "文件保存失败,请检查有没有输入文件后缀名。");
    }
}
  • 矩形选区功能
#include "screenrect.h"

#include <qapplication.h>
#include <qpainter.h>
#include <QDesktopWidget>
#include <qscreen.h>
#include <QDebug>
#include <QMouseEvent>

ScreenRect::ScreenRect(QWidget *parent) : QWidget(parent)
{
    this->setWindowFlag(Qt::FramelessWindowHint);           // 去除标题栏
    this->setAttribute(Qt::WA_TranslucentBackground);       // 背景透明
    QScreen * screen = QGuiApplication::primaryScreen();    // 获取默认屏幕
    m_path.addRect(screen->geometry());
    this->setGeometry(screen->geometry());
}

/**
 * @brief        鼠标选择的启点
 * @param event
 */
void ScreenRect::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        m_rect.setTopLeft(event->pos());
    }
    QWidget::mousePressEvent(event);
}

/**
 * @brief       鼠标释放时如果选择了区域则将选择的矩形发送出去
 * @param event
 */
void ScreenRect::mouseReleaseEvent(QMouseEvent *event)
{
    if((event->button() == Qt::LeftButton) && (m_rect.x() > 0 || m_rect.y() > 0))  // 这里使用x >0,y>0判断是否选择区域,而不是使用!m_rect.isEmpty()判断,避免反着选
    {
        this->hide();
        emit this->selectRect(m_rect.normalized());   // 使用normalized防止出现反着选,例如从下往上选,从右往左选
        m_rect = QRect();      // 置为空
    }

    QWidget::mouseReleaseEvent(event);
}

/**
 * @brief       鼠标选择的终点
 * @param event
 */
void ScreenRect::mouseMoveEvent(QMouseEvent *event)
{
    if(m_rect.x() > 0 || m_rect.y() > 0)
    {
        m_rect.setBottomRight(event->pos());
        this->update();
    }
    QWidget::mouseMoveEvent(event);
}

/**
 * @brief           绘制全屏遮罩和选择的矩形框
 * @param event
 */
void ScreenRect::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);

    QPainter painter(this);
    painter.setPen(QPen(QColor(85, 170, 255, 200), 1));
    painter.setBrush(QColor(85, 170, 255, 50));
    QPainterPath path;
    path.addRect(m_rect);
    painter.drawPath(m_path - path);       // 绘制选择的矩形框
}

  • 全局鼠标事件单例
/**
 *  全局鼠标事件单例信号类
 */
class MouseEvent : public QObject
{
    Q_OBJECT
public:
    static MouseEvent* getInstance()
    {
        static MouseEvent mouseEvent;
        return &mouseEvent;
    }

signals:
    void mouseSignal(QEvent* event);

private:
    MouseEvent(){}
};

  • 窗口选区功能
#include "windowrect.h"
#include <QDebug>
#include <QGridLayout>
#include <QEvent>
#include <QMouseEvent>

#if defined(Q_OS_WIN)
#include <Windows.h>
#include <windef.h>
#elif defined(Q_OS_LINUX)
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#elif defined(Q_OS_MAC)
#endif

#if defined(Q_OS_WIN)
static HHOOK g_hook = nullptr;
/**
 * @brief           处理鼠标事件的回调函数
 * @param nCode
 * @param wParam
 * @param lParam
 * @return
 */
LRESULT CALLBACK CallBackProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    switch (wParam)
    {
    case WM_LBUTTONDOWN:   // 鼠标左键按下
    {
        emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
        break;
    }
    default:
        break;
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);   // 注意这一行一定不能少,否则会出大问题
}
#endif

WindowRect::WindowRect(QWidget *parent) : QWidget(parent)
{
#if defined(Q_OS_WIN)
    // linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如窗口显示不全,所以这里不使用)
    // windows下如果不设置鼠标穿透则只能捕获到当前窗口
    this->setAttribute(Qt::WA_TransparentForMouseEvents, true);
#endif
    this->setWindowFlags(Qt::FramelessWindowHint);                            // 去掉边框、标题栏
    this->setAttribute(Qt::WA_TranslucentBackground);                         // 背景透明
    this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);     // 设置顶级窗口,防止遮挡

    // 在当前窗口上增加一层QWidget,否则不会显示边框
    QGridLayout* gridLayout = new QGridLayout(this);
    gridLayout->setSpacing(0);
    gridLayout->setContentsMargins(0, 0, 0, 0);
    gridLayout->addWidget(new QWidget(), 0, 0, 1, 1);

    this->setStyleSheet(" background-color: rgba(58, 196, 255, 40); border: 2px solid rgba(58, 196, 255, 200);"); // 设置窗口边框样式 dashed虚线,solid 实线

    connect(MouseEvent::getInstance(), &MouseEvent::mouseSignal, this, &WindowRect::on_mouseSignal);
    // 使用定时器定时获取当前鼠标位置的窗口位置信息
    connect(&m_timer, &QTimer::timeout, this, &WindowRect::on_timeout);
    m_timer.start(200);
}

WindowRect::~WindowRect()
{
#if defined(Q_OS_WIN)
    if(g_hook)
    {
        bool ret = UnhookWindowsHookEx(g_hook);
        if(ret)
        {
            qDebug() << "卸载鼠标钩子。";
        }
    }
#endif
}

/**
 * @brief         通过截图全局鼠标事件将当前窗口大小发生出去
 * @param event
 */
void WindowRect::on_mouseSignal(QEvent *event)
{
    delete event;
    this->hide();
    emit this->selectRect(QRect(this->pos(), this->size()));
}

/**
 * @brief        Windows使用全局鼠标钩子,显示窗口时挂载鼠标钩子
 * @param event
 */
void WindowRect::showEvent(QShowEvent *event)
{
#if defined(Q_OS_WIN)
    // 由于windows不透明的窗体如果不设置设置鼠标穿透WindowFromPoint只能捕捉到当前窗体,而设置鼠标穿透后想要获取鼠标事件只能通过鼠标钩子
    g_hook = SetWindowsHookExW(WH_MOUSE_LL, CallBackProc, GetModuleHandleW(nullptr), 0);  // 挂载全局鼠标钩子
    if (g_hook)
    {
        qDebug() << "鼠标钩子挂接成功,线程ID:" << GetCurrentThreadId();

    }
    else
    {
        qDebug() << "鼠标钩子挂接失败:" << GetLastError();
    }
#endif
    QWidget::showEvent(event);
}

/**
 * @brief       隐藏窗口时卸载鼠标钩子
 * @param event
 */
void WindowRect::hideEvent(QHideEvent *event)
{
#if defined(Q_OS_WIN)
    if(g_hook)
    {
        bool ret = UnhookWindowsHookEx(g_hook);
        if(ret)
        {
            qDebug() << "卸载鼠标钩子。";
            g_hook = nullptr;
        }
    }
#endif
    QWidget::hideEvent(event);
}

/**
 * @brief        linux下使用自带的鼠标点击事件就可以
 * @param event
 */
void WindowRect::mousePressEvent(QMouseEvent *event)
{
#if defined(Q_OS_LINUX)
    this->hide();
    emit this->selectRect(QRect(this->pos(), this->size()));
#endif
    QWidget::mousePressEvent(event);
}


void WindowRect::on_timeout()
{
    QPoint point = QCursor::pos();  // 获取鼠标当前位置
#if defined(Q_OS_WIN)
    POINT pos;
    pos.x = point.x();
    pos.y = point.y();

    HWND hwnd = nullptr;
    hwnd = WindowFromPoint(pos);   // 通过鼠标位置获取窗口句柄
    if(!hwnd) return;

    RECT lrect;
    bool ret = GetWindowRect(hwnd, &lrect);     //获取窗口位置
    if(!ret) return;

    QRect rect;
    rect.setX(lrect.left);
    rect.setY(lrect.top);
    rect.setWidth(lrect.right - lrect.left);
    rect.setHeight(lrect.bottom - lrect.top);
    this->setGeometry(rect);         // 设置窗口边框
#elif defined(Q_OS_LINUX)  // linux下使用x11获取的窗口大小有可能不太准确,例如浏览器的大小会偏小
    // 获取根窗口
    Display* display = XOpenDisplay(nullptr);
    Window rootWindow = DefaultRootWindow(display);

    Window root_return, parent_return;
    Window * children = nullptr;
    unsigned int nchildren = 0;
    // 函数详细说明见xlib文档:https://tronche.com/gui/x/xlib/window-information/XQueryTree.html
    // 该函数会返回父窗口的子窗口列表children,因为这里用的是当前桌面的根窗口作为父窗口,所以会返回所有子窗口
    // 注意:窗口顺序(z-order)为自底向上
    XQueryTree(display, rootWindow, &root_return, &parent_return, &children, &nchildren);

    QRect recte;                      // 保存鼠标当前所在窗口的范围
    for(unsigned int i = 0; i < nchildren; ++i)
    {
        if(children[i] == this->winId()) continue;           // 由于当前窗口一直在最顶层,所以这里要过滤掉当前窗口,否则一直获取到的就是当前窗口大小
        XWindowAttributes attrs;
        XGetWindowAttributes(display, children[i], &attrs);  // 获取窗口参数
        if (attrs.map_state == IsViewable)                   // 只处理可见的窗口, 三个状态:IsUnmapped, IsUnviewable, IsViewable
        {
#if 0
            QRect rect(attrs.x + 1, attrs.y, attrs.width, attrs.height);  // 这里x+1防止全屏显示,如果不+1,设置的大小等于屏幕大小是会自动切换成全屏显示状态,后面就无法缩小了
#else
            QRect rect(attrs.x, attrs.y, attrs.width, attrs.height);
#endif
            if(rect.contains(point))                        // 判断鼠标坐标是否在窗口范围内
            {
                recte = rect;                               // 记录最后一个窗口的范围
            }
        }
    }
#if 0  // 在linux下使用setGeometry设置窗口会有一些问题
    this->showNormal();               // 第一次显示是如果是屏幕大小,则后面无法缩小,这里需要设置还原
    this->setGeometry(recte);         // 设置窗口边框
#else  // 使用setFixedSize+move可以避免这些问题
    this->move(recte.x(), recte.y());
    this->setFixedSize(recte.width(), recte.height());
#endif
//    qDebug() << this->rect() <<recte<< this->windowState();

    // 注意释放资源
    XFree(children);
    XCloseDisplay(display);

#elif defined(Q_OS_MAC)
#endif
}

5、源代码

  • gitee
  • github
  • 当前示例所在模块

💡💡💡💡💡💡💡💡💡💡💡💡💡💡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68720.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022,记录与华为的这场会议

一、数据治理团体标准发布会 11月26日&#xff0c;中国计算机用户协会信息科技审计分会联合华为与擎创科技共同举办了“金融行业运维数据治理团体标准应用研讨暨2022年度调研报告线上发布会”。来自国家开发银行、中国建设银行、中国邮政储蓄银行、招商银行、兴业银行、中信银行…

【LeetCode_字符串_逻辑分析】9. 回文数

目录考察点第一次&#xff1a;2022年12月7日10:16:33解题思路代码展示题目描述给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左…

340页11万字智慧政务大数据资源平台大数据底座数据治理建设方案

目 录 第一章 项目概况 1.1 项目名称 1.2 项目单位 1.3 项目建设依据 1.4 项目建设内容和目标 1.4.1 建设内容 1.4.2 建设目标 1.5 项目投资估算及建设周期 1.5.1 项目投资估算 1.5.2 服务周期 第二章 现状 2.1 项目单位概况 2.1.1 单位职责、内设及下属机构、人员…

【配准图像】

MU-Net: A MULTISCALE UNSUPERVISED NETWORK FOR REMOTE SENSING IMAGE REGISTRATION &#xff08;MU-Net&#xff1a;一种多尺度无监督遥感图像配准网络&#xff09; 多传感器或多模态图像对的配准是许多遥感应用的基础性任务。为了实现高精度、低成本的遥感图像配准&#x…

彻底搞懂JS原型与原型链

说到JavaScript的原型和原型链&#xff0c;相关文章已有不少&#xff0c;但是大都晦涩难懂。本文将换一个角度出发&#xff0c;先理解原型和原型链是什么&#xff0c;有什么作用&#xff0c;再去分析那些令人头疼的关系。 一、引用类型皆为对象 原型和原型链都是来源于对象而…

浅谈Linux内核编程规范与代码风格

1 缩进 Tab的宽度是八个字符&#xff0c;因此缩进的宽度也是八个字符。有些异教徒想让缩进变成四个字符&#xff0c;甚至是两个字符的宽度&#xff0c;这些人和那些把 PI 定义为 3 的人是一个路子的。 注意&#xff1a;缩进的全部意义在于清晰地定义语句块的开始与结束&#…

《MongoDB》Mongo Shell中的基本操作-删除操作一览

前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e; 主页: oliver尹的主页 格言: 跌倒了爬起来就好&#xff5e; 来个关注吧&#xff0c;点个赞…

分布式事务,单JVM进程与多数据库,分布式事务技术选型,0-1过程,代码全。

由于很多小白程序员在单一JVM进程配合多数据库的架构环境中,总是考虑一主多从的mysql集群环境。还不知道mysql集群数据库按照表纵向分割以后,也是可以走数据库使用事务的。那么这里使用到的就是分布式事务,XA协议。现在大部分主流的数据库都支持XA协议。这里不用太多废话,直…

【Web智能聊天客服】之JavaScript、jQuery、AJAX讲解及实例(超详细必看 附源码)

觉得有帮助请点赞关注收藏~~~ 一、JavaScript基础 Javascript是网页编程语言&#xff0c;决定网页元素的动作。HTML页面中通过<script></script>指定Javascript内容&#xff0c;通过//或者 /* */执行代码的备注功能&#xff0c;并且区分大小写。 1&#xff1a;变…

《视觉SLAM十四讲》示例程序编译报错处理(上)

高翔博士《视觉SLAM十四讲》这本书中的代码很不错&#xff0c;适合初学者。可惜有一些可能因为版本的问题会报错&#xff0c;本文总结一下我遇到的问题。 在slambook2/3rdparty文件夹git submodule update&#xff0c;这个版本是和书中的版本一致的。但我已经重新安装了新版Ei…

Webpack中的高级特性

自从webpack4以后&#xff0c;官方帮我们集成了很多特性&#xff0c;比如在生产模式下代码压缩自动开启等&#xff0c;这篇文章我们一起来探讨一下webpack给我们提供的高级特性助力开发。 探索webpack的高级特性 特性&#xff1a;treeShaking 顾名思义treeShaking&#xff0…

C++ Reference: Standard C++ Library reference: Containers: deque: deque: swap

C官网参考里链接&#xff1a;https://cplusplus.com/reference/deque/deque/swap-free/ 函数模板 <deque> std::swap (deque) template <class T, class Alloc> void swap (deque<T,Alloc>& x, deque<T,Alloc>& y); 交换两个双端队列容器的…

【ESP32调试-快速入门】

文章目录ESP32调试一. 环境搭建二. 运行OpenOCD1. 烧入blink2. 找到芯片型号对应的脚本文件&#xff0c;并运行脚本命令三. 运行GDBESP32调试 一. 环境搭建 ESP_IDF环境搭建 二. 运行OpenOCD 1. 烧入blink 如&#xff1a;安装环境中的examples中的blink 路劲&#xff1a;安装…

华为机试 - 探索地块建立

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 给一块n*m的地块&#xff0c;相当于n*m的二维数组&#xff0c;每个元素的值表示这个小地块的发电量&#xff1b; 求在这块地上建立正方形的边长为c的发电站&#xff0c;发电量满足目标电量k的地块数量…

汽车保养app开发,扩充汽车服务市场发展商机

从汽车市场规模来看&#xff0c;从2017年开始始终保持增长的发展趋势&#xff0c;在2021年市场规模达到140877.18亿元。互联网时代发展下&#xff0c;汽车后市场大力推广电子商务&#xff0c;将互联网技术与汽车保养服务相结合是汽车服务行业强大的必由之路。二者的结合可以让消…

centos7下搭建rabbitmq的开发环境

我们在项目开发的时候都不可避免的会有异步化的问题,比较好的解决方案就是使用消息队列,可供选择的队列产品也有很多,比如轻量级的redis, 当然还有重量级的专业产品rabbitmq,rabbitmq好就好在是用erlang(二郎神)开发的,它那天生的OTP并行计算框架,轻而易举的进程间通…

阿里云ssl免费证书申请

目录为什么申请SSL证书SSL证书申请支持的域名类型ssl免费证书申请过程为什么申请SSL证书 由于web服务部署需要使用https安全协议&#xff0c;因此需要申请相应域名的SSL证书用于部署。测试阶段&#xff0c;为节省成本&#xff0c;使用阿里云提供的免费SSL证书。 SSL证书申请支…

在Web服务器(IIS)上安装SSL证书并绑定网站

以windows server 2016为例 一、安装中间证书 1.下载中间证书文件 如果是RSA加密算法类的&#xff0c;下载此处。 如果是ECC加密算法类的&#xff0c;下载此处。 2.安装 双击下载好的文件进行安装&#xff0c;注意&#xff0c;安装过程中&#xff0c;存储位置要设”为本地…

搜题接口系统

搜题接口系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xff09; 题库…

分类预测 | MATLAB实现ELM极限学习机多特征分类预测(二分类)

分类预测 | MATLAB实现ELM极限学习机多特征分类预测(二分类) 目录 分类预测 | MATLAB实现ELM极限学习机多特征分类预测(二分类)效果一览基本介绍程序设计学习总结参考资料效果一览 训练集正确率Accuracy = 89%(445/500) 测试集正确率Accuracy = 86.9565%(60/69) 基本介绍 MATLA…