Qt_Gif_Creator 基于Qt的屏幕gif录制工具

本文介绍了一个基于Qt框架的屏幕GIF录制工具的实现。该工具包含XYGifCreator类负责GIF创建逻辑,使用Gif.h库进行GIF编码;XYGifFrame类提供GUI界面,支持设置录制区域大小、帧率以及保存位置。工具采用多线程处理GIF编码,支持Windows和Linux平台,能够捕获屏幕内容并包含鼠标指针。实现功能包括:区域选择调整、GIF录制控制、系统托盘图标操作等。核心代码展示了如何通过Qt跨平台API获取屏幕截图,并针对不同操作系统处理鼠标指针绘制,最后将图像帧编码为GIF动画。

#include "xygifcreator.h"
#include <QImage>
#include <QThread>#include "gif.h"class XYGif : public QObject
{Q_OBJECT
public:explicit XYGif(){moveToThread(&mWorkThread);}~XYGif(){if (mWorkThread.isRunning()){mWorkThread.quit();mWorkThread.wait();}}public slots:void begin(const QString &file, int width, int height, int delay){GifBegin(&mGifWriter, file.toUtf8().data(), static_cast<quint32>(width), static_cast<quint32>(height), static_cast<quint32>(delay));}void frame(const QImage &img, int width, int height, int delay){GifWriteFrame(&mGifWriter, img.bits(),static_cast<quint32>(qMin(width, img.width())),static_cast<quint32>(qMin(height, img.height())),static_cast<quint32>(100.0 / delay));}void end(){GifEnd(&mGifWriter);mWorkThread.quit();}private:GifWriter mGifWriter;QThread   mWorkThread;friend class XYGifCreator;
};XYGifCreator::XYGifCreator(QObject *parent): QObject(parent)
{mGif = new XYGif;connect(&mGif->mWorkThread, &QThread::finished, this, &XYGifCreator::finished);
}XYGifCreator::~XYGifCreator()
{delete mGif;
}void XYGifCreator::startThread()
{mGif->mWorkThread.start();
}bool XYGifCreator::isRunning()
{return mGif->mWorkThread.isRunning();
}void XYGifCreator::begin(const QString &file, int width, int height, int delay, Qt::ConnectionType type)
{mWidth  = width;mHeight = height;QMetaObject::invokeMethod(mGif, "begin", type,QGenericReturnArgument(),Q_ARG(QString, file),Q_ARG(int, width),Q_ARG(int, height),Q_ARG(int, delay));
}void XYGifCreator::frame(const QImage &img, int delay, Qt::ConnectionType type)
{// gif.h 文件有描述目前只能是RGBA8888图片格式,并且alpha没有被使用QImage img32 = img.convertToFormat(QImage::Format_RGBA8888);QMetaObject::invokeMethod(mGif, "frame", type,QGenericReturnArgument(),Q_ARG(QImage, img32),Q_ARG(int, mWidth),Q_ARG(int, mHeight),Q_ARG(int, delay));
}void XYGifCreator::end(Qt::ConnectionType type)
{QMetaObject::invokeMethod(mGif, "end", type);
}#include "xygifcreator.moc"
#include "xygifframe.h"
#include "xypackimage.h"
#include "ui_xygifframe.h"
#include <QPainter>
#include <QFileDialog>
#include <QApplication>
#include <QScreen>
#include <QResizeEvent>
#include <QDateTime>
#include <QMessageBox>
#include <QDebug>
#include <QMenu>#ifdef Q_OS_WIN32
#include <windows.h>static QImage getScreenImage(int x, int y, int width, int height)
{HDC hScrDC = ::GetDC(nullptr);HDC hMemDC = nullptr;BYTE *lpBitmapBits = nullptr;int nWidth = width;int nHeight = height;hMemDC = ::CreateCompatibleDC(hScrDC);BITMAPINFO bi;ZeroMemory(&bi, sizeof(BITMAPINFO));bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);bi.bmiHeader.biWidth = nWidth;bi.bmiHeader.biHeight = nHeight;bi.bmiHeader.biPlanes = 1;bi.bmiHeader.biBitCount = 24;CURSORINFO hCur ;ZeroMemory(&hCur,sizeof(hCur));hCur.cbSize = sizeof(CURSORINFO);GetCursorInfo(&hCur);ICONINFO IconInfo = {};if(GetIconInfo(hCur.hCursor, &IconInfo)){hCur.ptScreenPos.x -= IconInfo.xHotspot;hCur.ptScreenPos.y -= IconInfo.yHotspot;if(nullptr != IconInfo.hbmMask)DeleteObject(IconInfo.hbmMask);if(nullptr != IconInfo.hbmColor)DeleteObject(IconInfo.hbmColor);}HBITMAP bitmap = ::CreateDIBSection(hMemDC, &bi, DIB_RGB_COLORS, (LPVOID*)&lpBitmapBits, nullptr, 0);HGDIOBJ oldbmp = ::SelectObject(hMemDC, bitmap);::BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, x, y, SRCCOPY);DrawIconEx(hMemDC, hCur.ptScreenPos.x - x, hCur.ptScreenPos.y - y, hCur.hCursor, 0, 0, 0, nullptr, DI_NORMAL | DI_COMPAT);BITMAPFILEHEADER bh;ZeroMemory(&bh, sizeof(BITMAPFILEHEADER));bh.bfType = 0x4d42;  //bitmapbh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);bh.bfSize = bh.bfOffBits + ((nWidth*nHeight)*3);int size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 3 * nWidth * nHeight;uchar *bmp = new uchar[size];uint offset = 0;memcpy(bmp, (char *)&bh, sizeof(BITMAPFILEHEADER));offset = sizeof(BITMAPFILEHEADER);memcpy(bmp + offset, (char *)&(bi.bmiHeader), sizeof(BITMAPINFOHEADER));offset += sizeof(BITMAPINFOHEADER);memcpy(bmp + offset, (char *)lpBitmapBits, 3 * nWidth * nHeight);::SelectObject(hMemDC, oldbmp);::DeleteObject(bitmap);::DeleteObject(hMemDC);::ReleaseDC(nullptr, hScrDC);QImage image = QImage::fromData(bmp, size);delete[] bmp;return image;
}
#else // linux x11
#include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h>
static void X11drawCursor(Display *display, QImage &image, int recording_area_x, int recording_area_y)
{// get the cursorXFixesCursorImage *xcim = XFixesGetCursorImage(display);if(xcim == NULL)return;// calculate the position of the cursorint x = xcim->x - xcim->xhot - recording_area_x;int y = xcim->y - xcim->yhot - recording_area_y;// calculate the part of the cursor that's visibleint cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image.width() - x);int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image.height() - y);unsigned int pixel_bytes, r_offset, g_offset, b_offset; // ARGBpixel_bytes = 4;r_offset = 2; g_offset = 1; b_offset = 0;// draw the cursor// XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since// 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha.for(int j = cursor_top; j < cursor_bottom; ++j) {unsigned long *cursor_row = xcim->pixels + xcim->width * j;uint8_t *image_row = (uint8_t*) image.bits() + image.bytesPerLine() * (y + j);for(int i = cursor_left; i < cursor_right; ++i) {unsigned long cursor_pixel = cursor_row[i];uint8_t *image_pixel = image_row + pixel_bytes * (x + i);int cursor_a = (uint8_t) (cursor_pixel >> 24);int cursor_r = (uint8_t) (cursor_pixel >> 16);int cursor_g = (uint8_t) (cursor_pixel >> 8);int cursor_b = (uint8_t) (cursor_pixel >> 0);if(cursor_a == 255) {image_pixel[r_offset] = cursor_r;image_pixel[g_offset] = cursor_g;image_pixel[b_offset] = cursor_b;} else {image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r;image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g;image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b;}}}// free the cursorXFree(xcim);
}
#endifXYGifFrame::XYGifFrame(QWidget *parent): XYMovableWidget(parent),mStartResize(false),ui(new Ui::XYGifFrame)
{ui->setupUi(this);mGifCreator = new XYGifCreator(this);mTimer.setSingleShot(false);setWindowFlags( Qt::WindowStaysOnTopHint);ui->width->installEventFilter(this);ui->height->installEventFilter(this);connect(ui->width, SIGNAL(editingFinished()), this, SLOT(doResize()));connect(ui->height, SIGNAL(editingFinished()), this, SLOT(doResize()));connect(ui->gif, SIGNAL(clicked()), this, SLOT(active()));connect(ui->img, SIGNAL(clicked()), this, SLOT(packImages()));connect(&mTimer, SIGNAL(timeout()), this, SLOT(frame()));connect(ui->save, SIGNAL(clicked()), this, SLOT(setGifFile()));connect(mGifCreator, &XYGifCreator::finished, this, [this](){this->ui->tips->setText(QStringLiteral("保存Gif完成!"));});ui->content->adjustSize();setMouseTracking(true);setMinimumSize(162, 150);ui->gif->setFocus();m_tray = new QSystemTrayIcon(this);//实例化QPixmap m_logo(":/gif.ico");m_tray->setIcon(QIcon(m_logo));//设置图标m_tray->show();connect(m_tray,&QSystemTrayIcon::activated,this,&XYGifFrame::TrayIconAction);m_menu = new QMenu(this);m_resetAction = new QAction(this);m_resetAction->setText("show");m_quitAction = new QAction(this);m_resetAction->setIcon(QIcon(m_logo));m_quitAction->setText("quit");m_quitAction->setIcon(QIcon(m_logo));connect(m_quitAction,&QAction::triggered,qApp,&QApplication::quit);connect(m_resetAction,&QAction::triggered,this,&XYGifFrame::restory);
//    connect(m_closeDialog,&closeDialog::traySiganal,this,&XYGifFrame::Tray);m_tray->setContextMenu(m_menu);//设置托盘菜单m_menu->addAction(m_resetAction);m_menu->addAction(m_quitAction);}XYGifFrame::~XYGifFrame()
{delete ui;
}void XYGifFrame::doResize()
{QRect rect(pos(), QSize(ui->width->value(), ui->height->value()));rect.adjust(-3, -3, 3, ui->content->height() + 5);resize(rect.size());
}void XYGifFrame::packImages()
{XYPackImage img(this);img.exec();
}void XYGifFrame::setGifFile()
{mGifFile = QFileDialog::getSaveFileName(this, "", QString("xy-%1.gif").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")));ui->save->setToolTip(mGifFile);
}void XYGifFrame::active()
{if (!mTimer.isActive()){if (mGifCreator->isRunning()){QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("正在保存Gif,请稍等!"));return;}start();}else{stop();}
}void XYGifFrame::start()
{if (!mGifFile.isEmpty()){mGifCreator->startThread();mGifCreator->begin(mGifFile.toUtf8().data(), ui->width->value(), ui->height->value(), 1);mPixs = 0;ui->gif->setText(QStringLiteral("停止录制"));frame();mTimer.start(static_cast<int>(1000.0 / ui->interval->value()));ui->width->setDisabled(true);ui->height->setDisabled(true);ui->interval->setDisabled(true);}else{QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("请先设置保存gif的位置!"));}
}void XYGifFrame::stop()
{mTimer.stop();ui->gif->setText(QStringLiteral("开始录制"));mGifCreator->end();ui->width->setEnabled(true);ui->height->setEnabled(true);ui->interval->setEnabled(true);this->ui->tips->setText(QStringLiteral("请等待保存Gif..."));
}void XYGifFrame::frame()
{QImage img = getCurScreenImage();if (!img.isNull()){mGifCreator->frame(img, ui->interval->value());mPixs++;ui->tips->setText(QStringLiteral("已保存 %1 张图片").arg(mPixs));}
}bool XYGifFrame::eventFilter(QObject *watched, QEvent *event)
{if (watched == ui->width|| watched == ui->height){if (event->type() == QEvent::Wheel){doResize();}}return XYMovableWidget::eventFilter(watched, event);
}void XYGifFrame::paintEvent(QPaintEvent *)
{QPainter painter(this);painter.fillRect(rect(), Qt::gray);
}void XYGifFrame::resizeEvent(QResizeEvent *)
{QRect rect = this->rect();rect.adjust(3, 3, -3, -(ui->content->height()  + 5));mRecordRect = rect;ui->width->setValue(mRecordRect.width());ui->height->setValue(mRecordRect.height());QRegion region(this->rect());setMask(region.xored(mRecordRect));qDebug()<<region.xored(mRecordRect);ui->content->move(width() - ui->content->width() - 3, height() - ui->content->height() - 3);
}void XYGifFrame::mousePressEvent(QMouseEvent *event)
{QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));if (rect.contains(event->pos()) && !mTimer.isActive()){mStartResize = true;mStartGeometry = QRect(event->globalPos(), size());}else{XYMovableWidget::mousePressEvent(event);}
}void XYGifFrame::mouseReleaseEvent(QMouseEvent *event)
{mStartResize = false;setCursor(Qt::ArrowCursor);XYMovableWidget::mouseReleaseEvent(event);
}void XYGifFrame::mouseMoveEvent(QMouseEvent *event)
{QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));if (mStartResize){QPoint ch = event->globalPos() - mStartGeometry.topLeft();resize(mStartGeometry.size() + QSize(ch.x(), ch.y()));}else if (rect.contains(event->pos()) && !mTimer.isActive()){setCursor(Qt::SizeFDiagCursor);}else{setCursor(Qt::ArrowCursor);XYMovableWidget::mouseMoveEvent(event);}
}void XYGifFrame::wheelEvent(QWheelEvent *event)
{if (event->delta() < 0){ui->content->move(ui->content->x() + 6, ui->content->y());}else{ui->content->move(ui->content->x() - 6, ui->content->y());}XYMovableWidget::wheelEvent(event);
}QImage XYGifFrame::getCurScreenImage()
{QImage img;
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)auto screen = qApp->screenAt(pos());
#elseQScreen *screen = nullptr;foreach (screen, qApp->screens()){if (screen->geometry().contains(pos())){break;}}
#endifif (screen != nullptr){
#ifdef Q_OS_WIN32img = getScreenImage(x() + mRecordRect.x(),y() + mRecordRect.y(),mRecordRect.width(),mRecordRect.height());
#elseimg = screen->grabWindow(0,x() + mRecordRect.x(),y() + mRecordRect.y(),mRecordRect.width(),mRecordRect.height()).toImage();// 需要系统是x11后端auto display = XOpenDisplay(NULL);X11drawCursor(display, img, x() + mRecordRect.x(), y() + mRecordRect.y());XCloseDisplay(display);
#endif}return img;
}void XYGifFrame::on_quit_clicked()
{this->hide();
}

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

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

相关文章

Linux实战:HAProxy全方位指南

一、负载均衡核心概念 1.1 负载均衡定义 负载均衡&#xff08;Load Balance&#xff0c;简称LB&#xff09;是一种基于硬件设备或软件服务的高可用反向代理技术。它将特定业务&#xff08;如Web服务、网络流量&#xff09;分发到后端的一个或多个服务器/设备&#xff0c;从而提…

22 BTLO 蓝队靶场 Countdown 解题记录

Tools: - ELK - CyberChef - OSINT (whole World Wide Web) Hunt #1: Brute Force DetectedSource: winevent-security (1/3) — 可疑暴力破解流量来自哪个IP地址 What is the IP address from which the suspicious brute force traffic is seen?? 我们需要寻找暴力破解…

文心一言4.5开源模型实战:ERNIE-4.5-0.3B轻量化部署与效能突破

文心一言4.5开源模型实战&#xff1a;ERNIE-4.5-0.3B轻量化部署与效能突破 文心一言4.5开源模型实战&#xff1a;ERNIE-4.5-0.3B轻量化部署与效能突破&#xff0c;本文介绍百度文心一言 4.5 开源模型中 ERNIE-4.5-0.3B 的轻量化部署与效能。该 3 亿参数模型破解大模型落地的算力…

SAP-MM-采购订单批量创建 excel 版

采购订单批量创建程序摘要:不含任何定制字段的导入,直接导入系统即可使用 该SAP ABAP程序实现采购订单的批量创建功能,主要特性包括: 支持通过Excel文件批量导入采购订单数据(XLS/XLSX格式) 提供数据校验功能,包括: 物料号有效性检查 采购凭证存在性验证 科目分配类别…

2_软件重构_一种组件化开发方式

一、碎碎念 首先先考虑下&#xff0c;什么情况下软件需要重构&#xff1f;我觉得答案有很多种&#xff0c;而且还有范围。当日益增长的需求与现有软件结构越来越无法匹配时——①具体表现可能为新增需求所导致的bug越来越多&#xff0c;一个新功能的改动牵一发而动全身&a…

今日行情明日机会——20250728

上证指数量能持续在200天均量线上&#xff0c;最近今天横盘震荡&#xff0c;今天依然收在5天均线上方&#xff0c;个股涨跌个数基本相同。目前依然强势&#xff0c;有望冲击3674的前高。需要注意板块的高低切换。深证指数今天缩量收小阳线&#xff0c;均线多头的趋势明显&#…

【iOS】类和分类的加载过程

目录 前言 _objc_init方法 environ_init tis->init方法 static_init方法 &#x1f4a1; _objc_init 是由 libc 调用的&#xff0c;目的是&#xff1a; ❗️“必须自己实现” 是什么意思&#xff1f; runtime_init exception_init cache_t::init _imp_implementati…

大模型算法面试笔记——常用优化器SGD,Momentum,Adagrad,RMSProp,Adam

常用参数&#xff1a;ttt-步数&#xff0c;α\alphaα-学习率&#xff0c;θ\thetaθ-参数&#xff0c;f(θ)f(\theta)f(θ)-目标函数&#xff0c;gtg_tgt​-梯度&#xff0c;β1\beta_1β1​-一阶矩衰减系数&#xff0c;通常取0.9&#xff0c;β2\beta_2β2​-二阶矩&#xff…

【计算机毕业设计】基于SSM的小型超市管理系统+LW

博主介绍&#xff1a;✌全网粉丝3W,csdn特邀作者、CSDN新星计划导师、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、…

火线、零线、地线

我们可以用 “水流” 来比喻 “电流”&#xff0c;这样理解起来会很简单&#xff1a;想象一下你家的电路就像一个 “闭合的水循环系统”&#xff1a;&#x1f525; 1. 火线 (Live Wire) - 好比 “进水管的高压端”作用&#xff1a; 从发电厂或变压器输送 高压电 到你家的插座或…

基于Vue3.0+Express的前后端分离的任务清单管理系统

文章目录 一、前端 0、项目介绍 0.1 主要功能介绍 0.2 UI展示 1、首页 2、待办事项管理 2.1 添加待办事项 2.2 展示待办事项 2.3 修改待办事项 2.4 删除待办事项 3、分类管理 3.1 添加分类 3.2 展示分类 3.3 修改分类 3.4 删除分类 4、团队成员管理 4.1 展示团队成员 二、后端 …

基于单片机智能交通灯设计

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 随着城市化进程的加快&#xff0c;城市交通流量日益增大&#xff0c;传统的固定配时交通灯已难以…

Datawhale AI夏令营——列车信息智能问答——科大讯飞AI大赛(基于结构化数据的用户意图理解和知识问答挑战赛)

前言 坐火车的你&#xff0c;遇到过这样的场景吗&#xff1f; 一次又一次查车次信息&#xff1f;赶火车狂奔&#xff0c;找检票口找到怀疑人生…想查“最早到北京的车”&#xff1f;时刻表翻到眼瞎&#xff01;列车晚点&#xff1f;新出发时间算到脑壳疼&#xff01; 我们这次将…

UVA11990 ``Dynamic‘‘ Inversion

UVA11990 Dynamic Inversion题目链接题意输入格式输出格式分析CDQ分治嵌套&#xff08;树状数组套BST&#xff09;分块k-D Tree题目链接 UVA11990 Dynamic’’ Inversion 题意 给一个 1~n 的排列A&#xff0c;要求按照某种顺序删除一些数&#xff08;其他数顺序不变&#xff0…

银河麒麟“安装器”安装方法

书接上回&#xff1a;银河麒麟安装软件商店方法-CSDN博客 过了几天发现当时一不小心把系统自带的“安装器”软件也卸载掉了&#xff0c;导致现在deb文件只能通过命令行安装&#xff0c;寻思这可不行&#xff0c;就想一下应该怎么安装。 首先&#xff0c;为了确认一下安装器的…

计算机毕设分享-基于SpringBoot的健身房管理系统(开题报告+前后端源码+Lun文+开发文档+数据库设计文档)

基于SpringBoot的健身房管理系统分享一套完整的基于SpringBoot的健身房管理系统毕业设计&#xff08;开题报告完整前后端源码Lun文 开发文档数据库设计文档&#xff09;系统分为三个角色功能如下&#xff1a;用户功能需求描述管理员功能需求描述教练功能需求描述开题报告系统功…

代码审计与web安全选择题1

软件供应链安全的基础是&#xff08; &#xff09;A.完善的需求分析B.源代码安全C.渗透测试D.软件测试参考答案&#xff1a;B保证源代码安全的主要措施包括&#xff08; &#xff09;A.开发工具和环境的安全B.代码安全C.渗透测试D.代码审计E.软件的说明文档完整参考…

python基本数据类型 数据类型转换 数字 菜鸟教程笔记

python基本数据类型 数据类型转换 数字 菜鸟教程笔记 1.基本数据类型 Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"…

USRP X410 X440 5G及未来通信技术的非地面网络(NTN)

概述 在本白皮书中&#xff0c;我们将介绍NTN的现状、正处于探索阶段的一些新应用&#xff0c;以及最重要的一点&#xff0c;我们需要克服哪些技术挑战才能让这个市场充满活力。最后&#xff0c;我们将概述为实现实用高效的测试&#xff0c;NI围绕NTN所做的努力&#xff0c;该测…

基于SpringBoot+Vue的电脑维修管理系统(WebSocket实时聊天、Echarts图形化分析)

“ &#x1f388;系统亮点&#xff1a;WebSocket实时聊天、Echarts图形化分析”01系统开发工具与环境搭建—前后端分离架构项目架构&#xff1a;B/S架构运行环境&#xff1a;win10/win11、jdk17小程序端&#xff1a;技术&#xff1a;Uniapp&#xff1b;UI库&#xff1a;colorUI…