[Python]用Qt6和Pillow实现截图小工具

        本文章主要讲述的内容是,使用python语言借助PyQt6和Pillow库进行简单截图工具的开发,含义一个简单的范围裁剪和软件界面。

        主要解决的问题是,在高DPI显示屏下,坐标点的偏差导致QWidget显示图片不全、剪裁范围偏差问题。

        适合有一点点基础的朋友来看,使用的工具有:Qt Designer、PyUIC、Qt6、Pillow

截图与剪裁功能设计思路

一般截图功能的步骤是:

  1. 启用截图功能
  2. 将整个屏幕进行截取,保存截取的全屏图片
  3. 呈现出刚刚截取的全屏,由用户选择截取的范围。并对所选的范围进行剪裁
  4. 保存剪裁的图片,删除截取的全屏

利用QtDesigner对软件前端的简单制作

mainWindow-主界面

这里不是重点,就新建一个Main Window后放置一个pushButton就好了。

并使用PyUIC对保存后的ui转换成.py格式

 minorWindow-副界面

创建一个简单的Widget就好了,副界面主要是作用是:呈现原图,提供剪裁的平台。

并使用PyUIC对保存后的ui转换成.py格式

主界面代码编写

主要是作用是:

  1. 为截图功能提供一个启动方法
  2. 保存截取的全屏幕截图。
import timefrom PIL import ImageGrab
from PyQt6 import QtWidgetsfrom shDemo import mainWindow
from shDemo import minorWindow# 继承我们前面编写的主界面的前端.py,以及对应的QMainWindow
class screenshot(QtWidgets.QMainWindow, mainWindow.Ui_MainWindow):def __init__(self):super().__init__()self.setupUi(self)  # 调用主界面的setupUIself.pushButton.clicked.connect(self.screenshot)  # 绑定pushButton按钮到screenshot事件上# 按钮被点击后触发此方法def screenshot(self):# 把当前窗口最小化self.showMinimized()# 等待1秒,给窗口最小化的时间time.sleep(1)# 截取全屏img = ImageGrab.grab()# 暂存全屏图片 保存到本地img.save('屏幕快照.png')# 生成副窗口self.childWidget = minorWindow.Ui_jieping()# 展示副窗口self.childWidget.show()# 完成剪裁工作,恢复主窗口self.showNormal()if __name__ == '__main__':app = QtWidgets.QApplication([])window = screenshot()window.show()app.exec()

副界面代码编写

因为此处的副界面是被调用的,我们直接在其ui转换后的.py文件上进行编写,拓展其方法

继承一下QWidget,调用一下setupUi

class Ui_jieping(QtWidgets.QWidget):def __init__(self):super().__init__()self.setupUi(self)

原截图呈现、范围绘制与范围截取

要注意的就是,呈现的像素比率,截取的坐标点

主要思路是,将保存好的原截图,呈现到一个QWidget(副界面)上进行显示。

这里有一个问题,就是关于屏幕DPI不同

重写一下paintEvent方法,这是一个QWidget类中原有方法,是一个绘制组件的事件。被调用的情况有如下:

  1. 窗口初始化和显示
  2. 部件大小或位置发生变化
  3. 强制重绘,使用update()或repaint()时
  4. 系统事件触发,如窗口激活

像素比率 

像素比率 = 物理像素尺寸 / 逻辑像素尺寸

         为了适应不同应用,获得更好的视觉感官,一般可以调整缩放与布局。调整到比较高的DPI,获得一个更好体验。

        屏幕缩放比例为125%,意味着逻辑像素将比物理像素更大,以便内容在屏幕上看起来更大。缩放比例125%可以表示为1.25的倍数。

        在缩放比例为125%的情况下,1920*1080的显示屏中逻辑像素的分辨率将变为1536x864。 

        显示图片时需要转换为逻辑尺寸,以确保在不同DPI的显示器上图像显示的尺寸一致。然而,截图抓取的坐标点是物理像素坐标的,因为截图本质上是对屏幕上实际像素的捕捉。

        所以在显示的时候,按照屏幕的逻辑尺寸进行展示。实际抓取的时候,要转成物理尺寸进行截取,根据像素比率对图片显示进行对应调整后就不影响图片的显示或坐标点的偏差

import typingfrom PIL import ImageGrab
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtGui import QPainter, QPixmap, QPen, QColorclass Ui_jieping(QtWidgets.QWidget):def __init__(self):super().__init__()self.setupUi(self)# 记录截取的第一个坐标点self.firstPoint = QtCore.QPoint()# 记录截取的第二个坐标点self.endPoint = QtCore.QPoint()# 将子窗口设置在屏幕最上层# self.setWindowFlag(QtCore.Qt.WindowType.WindowStaysOnTopHint)# 让其全屏显示self.setWindowState(QtCore.Qt.WindowState.WindowFullScreen)# 重写QWidget的painEvent方法,这个在初始启动的时候会调用def paintEvent(self, a0: typing.Optional[QtGui.QPaintEvent]) -> None:# 生成一个画板painter = QPainter(self)# 读取本地先前在主界面截图的图像# 在QT中图片放到组件上一般要转成pixmappixmap = QPixmap('./屏幕快照.png')# 获取主屏幕对象screen = QtGui.QGuiApplication.primaryScreen()# 获取设备像素比率  物理像素与逻辑像素之间的比率self.device_pixel_ratio = screen.devicePixelRatio()# 计算实际绘制尺寸# 在显示和编程的时候,是按照逻辑像素取进行展示与设计# 逻辑尺寸= 物理尺寸 / 像素比  计算出符合当前屏幕的尺寸actual_width = pixmap.width() / self.device_pixel_ratioactual_height = pixmap.height() / self.device_pixel_ratio# 绘制图片# 0,0的意思是,从屏幕左上角作为起始点,如果此时的逻辑尺寸与屏幕的一致,就作为全屏展示painter.drawPixmap(0, 0, int(actual_width), int(actual_height), pixmap)# 将截图画框显示为红色pen = QPen(QColor(255, 0, 0))painter.setPen(pen)# 绘制矩形的方法,其中的参数来自鼠标事件 显示要截图的范围 在绘制的时候还会调用update来触发paintEvent方法# 从第一个记录点开始# 记住0,0是屏幕最坐上角# 向右self.endPoint.x() - self.firstPoint.x()个像素 作为长# 向下self.endPoint.y() - self.firstPoint.y()个像素 作为高# 得到负数也没关系噢,x方向上负数就是往左, y方向上负数是向上painter.drawRect(self.firstPoint.x(), self.firstPoint.y(), self.endPoint.x() - self.firstPoint.x(),self.endPoint.y() - self.firstPoint.y())# 在鼠标按下的时候触发此事件def mousePressEvent(self, a0: typing.Optional[QtGui.QMouseEvent]) -> None:# 记录按下的第一个坐标点self.firstPoint = a0.pos()# 在鼠标移动的时候触发此事件def mouseMoveEvent(self, a0: typing.Optional[QtGui.QMouseEvent]) -> None:# 记录移动过程中的当前鼠标的坐标点self.endPoint = a0.pos()self.update()  # 触发paintEvent,在移动鼠标的时候不断重绘截图边框# 在鼠标松开的时候触发此事件def mouseReleaseEvent(self, a0: typing.Optional[QtGui.QMouseEvent]) -> None:self.endPoint = a0.pos()  # 锁定最后松开的坐标self.update()  # 更新在Widget上的所选范围矩形# 在原图上进行对所选区域的截取# 此处截图的时候,也要记得调整一下 从逻辑像素转换成为物理像素进行抓取# 不然截图出来会有偏差# 物理像素 = 逻辑像素 * 像素比率self.firstPoint.setX(int(self.firstPoint.x() * self.device_pixel_ratio))self.firstPoint.setY(int(self.firstPoint.y() * self.device_pixel_ratio))self.endPoint.setX(int(self.endPoint.x() * self.device_pixel_ratio))self.endPoint.setY(int(self.endPoint.y() * self.device_pixel_ratio))# 最后借助PIL进行对屏幕固定范围进行抓取# 这里有一个坑 在从右向左,从下到上进行画范围截图的时候,会有一个报错# 因为grab的参数是,左上角和右下角坐标点的x和y值# firstPoint和endPoint又是一开始写死的# 可以比较一下两者的位置,如果endPoint比firstPoint小,就可以互换一下if self.firstPoint.x() > self.endPoint.x() and self.firstPoint.y() > self.endPoint.y():self.firstPoint, self.endPoint = self.endPoint, self.firstPointimage = ImageGrab.grab(bbox=(self.firstPoint.x() + 1, self.firstPoint.y() + 1, self.endPoint.x() - 1, self.endPoint.y() - 1))# 将范围截取下来的进行保存image.save('hello.png')# 就可以将先前截的全屏删掉了# os.remove('./屏幕快照.png')# 关闭全屏显示的子窗口self.close()def setupUi(self, jieping):jieping.setObjectName("jieping")jieping.resize(400, 300)self.retranslateUi(jieping)QtCore.QMetaObject.connectSlotsByName(jieping)def retranslateUi(self, jieping):_translate = QtCore.QCoreApplication.translatejieping.setWindowTitle(_translate("jieping", "Form"))

在不同的DPI下截取出来的图片都是一样滴,大家可以去试一下

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

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

相关文章

性能飙升50%,react-virtualized-list如何优化大数据集滚动渲染

在处理大规模数据集渲染时,前端性能常常面临巨大的挑战。本文将探讨 react-virtualized-list 库如何通过虚拟化技术和 Intersection Observer API,实现前端渲染性能飙升 50% 的突破!除此之外,我们一同探究下该库还支持哪些新的特性…

前端传参数后端变量类型能够接受到List却无法接收到值

问题描述 今天写了个接口,下图所示 ReqVO里是这样的: 然后前端去请求,从请求结果中看发现这里值是在的(有经验的可能就看出来了otherInfo.id: 这样以参数后端是接收不到的,但是当时没发现) 传进来后端…

第二十六章CSS3续~

3.CSS3渐变属性 CSS3渐变(gradients)可以在两个或多个指定的颜色之间显示平稳的过渡。 以前,我们必须使用图像来实现这些效果。但是,通过使用CSS3渐变(gradients),可以减少下载的事件和宽带的使用。由于渐变(gradient)是由浏览器生成的&…

初学者如何对大模型进行微调?

粗略地说,大模型训练有四个主要阶段:预训练、有监督微调、奖励建模、强化学习。 预训练消耗的时间占据了整个训练pipeline的99%,其他三个阶段是微调阶段,更多地遵循少量 GPU 和数小时或数天的路线。预训练对于算力和数据的要求非…

java第二十课 —— 面向对象习题

类与对象练习题 编写类 A01,定义方法 max,实现求某个 double 数组的最大值,并返回。 public class Chapter7{public static void main(String[] args){A01 m new A01();double[] doubleArray null;Double res m.max(doubleArray);if(res !…

【Qt知识】disconnect

在Qt框架中,disconnect函数用于断开信号与槽之间的连接。当不再需要某个信号触发特定槽函数时,或者为了防止内存泄漏和重复执行问题,你可以使用disconnect来取消这种关联。disconnect函数的基本用法可以根据不同的需求采用多种形式&#xff0…

【Python学习1】matplotlib和pandas库绘制人口数变化曲线

✍🏻记录学习过程中的输出,坚持每天学习一点点~ ❤️希望能给大家提供帮助~欢迎点赞👍🏻收藏⭐评论✍🏻指点🙏 一、Python库说明 Matplotlib Matplotlib是一个功能强大的Python 2D绘图库,它允…

【Linux网络】传输层协议 - UDP

文章目录 一、传输层(运输层)运输层的特点复用和分用再谈端口号端口号范围划分认识知名端口号(Well-Know Port Number)两个问题① 一个进程是否可以绑定多个端口号?② 一个端口号是否可以被多个进程绑定? n…

最大的游戏交流社区Steam服务器意外宕机 玩家服务受影响

易采游戏网6月3日消息:众多Steam游戏玩家报告称,他们无法访问Steam平台上的个人资料、好友列表和社区市场等服务。同时,社区的讨论功能也无法正常使用。经过第三方网站SteamDB的确认,,这一现象是由于Steam社区服务器突…

德国西门子论未来质量管理 - 如何与明天相遇?

未来制造业的质量 -- 如何用软件方案满足质量要求 作者:Bill Butcher 翻译&编辑:数字化营销工兵 【前言】在Frost&Sullivan最近发表的一份白皮书中,他们讨论了制造业的质量投资。质量是制造过程的关键要素,但似乎比其他…

《精通ChatGPT:从入门到大师的Prompt指南》大纲目录

第一部分:入门指南 第1章:认识ChatGPT 1.1 ChatGPT是什么 1.2 ChatGPT的应用领域 1.3 为什么需要了解Prompt 第2章:Prompt的基本概念 2.1 什么是Prompt 2.2 好Prompt的特征 2.3 常见的Prompt类型 第二部分:Prompt设计技巧 第…

MySQL报ERROR 2002 (HY000)解决

今天在连接客户服务器时MySQL的时候报: ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql/mysql.sock’ (2) [rootXXX ~]# mysql -uroot -p Enter password: ERROR 2002 (HY000): Can’t connect to local MySQL server through socket…

【高校科研前沿】新疆生地所陈亚宁研究员团队在GeoSus发文:在1.5°C和2°C全球升温情景下,中亚地区暴露于极端降水的人口增加

目录 文章简介 1.研究内容 2.相关图件 3.文章引用 文章简介 论文名称:Increased population exposures to extreme precipitation in Central Asia under 1.5 ◦C and 2 ◦C global warming scenarios(在1.5C和2C全球变暖情景下,中亚地区…

服务器硬件基础知识学习

服务器硬件基础知识涵盖了从CPU到存储,再到网络连接和总线技术等关键组件。 1. 处理器 - 两大流派:我们常用的处理器主要分为Intel和AMD两大阵营。Intel的Xeon系列和AMD的EPYC系列都是专为服务器设计的,它们支持多核处理,能够应对…

【ARFoundation自学05】人脸追踪(AR Face manager)实现

1. 修改摄像机朝向渲染方式-选中user 这个方式就会调用前置摄像头 2 创建 AR Session、XR Origin,然后在XR Origin上面添加组件 注意:XR Origin 老版本仍然叫 AR Session Origin 接下来在XR Origin上面添加AR Face Manager组件,如下图&am…

【算法速查】万字图解带你快速入门八大排序(下)

君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们,这里是君兮_,首先在这里祝大家中秋国庆双节同乐!!抓住假期的小尾巴,今天来把算法速查的八大排序的后续写完,当…

【数据结构】C语言实现二叉树的基本操作——二叉树的遍历(先序遍历、中序遍历、后序遍历)

C语言实现二叉树的基本操作 导读一、二叉树的遍历二、先序遍历三、中序遍历四、后序遍历五、结点序列六、递归算法与非递归算法的转化结语 导读 大家好,很高兴又和大家见面啦!!! 通过前面的介绍,我们已经认识了二叉树…

一文搞懂常见的数据拆分方案

常见的几种数据拆分方案 1、客户端分片 直接在应用层实现读取分片规则,解析规则,根据规则实现切分逻辑。 这种方案优缺点: 侵入业务(缺点); 实现简单,适合快速上线,容易定位问题; 对开发人员…

QT 信号和槽 一对多关联示例,一个信号,多个槽函数响应,一个信号源如何绑定多个槽函数

在窗体里放置一个单行文本编辑控件(QLineEdit)、一个标签控件(QLabel)和一个文本浏览控件(QTextBrowser),在单行文 本编辑控件里的文本被编辑时,标签控件和文本浏览控件都会同步显示…

Polar Web 【简单】- 被黑掉的站

Polar Web 【简单】- 被黑掉的站 Contents Polar Web 【简单】- 被黑掉的站思路EXP运行&总结 思路 如题目所述,这是一个被黑掉的站点,由此不禁要了解该黑客发现了哪些可以入手的路径,或是留下了什么样的文件供持续访问。 目录扫描该站点发…