[Python]用Qt6和Pillow实现截图小工具
本文章主要讲述的内容是,使用python语言借助PyQt6和Pillow库进行简单截图工具的开发,含义一个简单的范围裁剪和软件界面。
主要解决的问题是,在高DPI显示屏下,坐标点的偏差导致QWidget显示图片不全、剪裁范围偏差问题。
适合有一点点基础的朋友来看,使用的工具有:Qt Designer、PyUIC、Qt6、Pillow
截图与剪裁功能设计思路
一般截图功能的步骤是:
- 启用截图功能
- 将整个屏幕进行截取,保存截取的全屏图片
- 呈现出刚刚截取的全屏,由用户选择截取的范围。并对所选的范围进行剪裁
- 保存剪裁的图片,删除截取的全屏
利用QtDesigner对软件前端的简单制作
mainWindow-主界面
这里不是重点,就新建一个Main Window后放置一个pushButton就好了。
并使用PyUIC对保存后的ui转换成.py格式


minorWindow-副界面
创建一个简单的Widget就好了,副界面主要是作用是:呈现原图,提供剪裁的平台。
并使用PyUIC对保存后的ui转换成.py格式

主界面代码编写
主要是作用是:
- 为截图功能提供一个启动方法
- 保存截取的全屏幕截图。
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类中原有方法,是一个绘制组件的事件。被调用的情况有如下:
- 窗口初始化和显示
- 部件大小或位置发生变化
- 强制重绘,使用update()或repaint()时
- 系统事件触发,如窗口激活
像素比率
像素比率 = 物理像素尺寸 / 逻辑像素尺寸

为了适应不同应用,获得更好的视觉感官,一般可以调整缩放与布局。调整到比较高的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下截取出来的图片都是一样滴,大家可以去试一下
相关文章:
[Python]用Qt6和Pillow实现截图小工具
本文章主要讲述的内容是,使用python语言借助PyQt6和Pillow库进行简单截图工具的开发,含义一个简单的范围裁剪和软件界面。 主要解决的问题是,在高DPI显示屏下,坐标点的偏差导致QWidget显示图片不全、剪裁范围偏差问题。 适合有一点…...
Podman和Docker的区别
Podman 和 Docker 都是用于容器化的工具,但它们在架构、安全性、容器编排以及一些设计理念上有显著的区别: 架构设计: Docker 使用客户端-服务器(C/S)架构,包含一个名为 dockerd 的守护进程,该进程以 root …...
Go微服务: 分布式Cap定理和Base理论
分布式中的Cap定理 CAP理论 C: 一致性,是站在分布式的角度,要么读取到数据,要么读取失败,比如数据库主从,同步时的时候加锁,同步完成才能读到同步的数据,同步完成,才返回数据给程序&…...
Mysql学习(四)——SQL通用语法之DQL
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 DQLDQL-语法基本查询条件查询聚合函数分组查询排序查询分页查询 DQL DQL数据查询语言,用来查询数据库中表的记录。 DQL-语法 select 字段列表 from 表…...
【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…...
Vulnhub-DC-2
靶机IP:192.168.20.135 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) kaliIP:192.168.20.128 扫描靶机端口及服务版本 发现开放了80和7744端口 并且是wordpress建站 dirsearch扫描目录 访问前端界面,发现存在重定向 在hosts文件中增加192.168.2…...
VNC server ubuntu20 配置
介绍 最近想使用实验室的4卡服务器跑一些深度学习实验,因为跑的是三维建图实验,需要配上可视化界面,本来自带的IPMI可以可视化,但分辨率固定在640*480,看起来很别扭,就捣鼓服务器远程可视化访问了两天&…...
c++--priority_queue和仿函数
目录 1.priority_queue 实现: 2.仿函数 priority_queue仿函数 实现代码 1.priority_queue 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的,其实就是个堆,默认是大根堆。…...
Harmony os Next——关系型数据库relationalStore.RdbStore的使用
Harmony os Next——关系型数据库relationalStore.RdbStore的使用 描述数据库的使用建表定义表信息创建数据库表 创建数据库操作对象增更新查询删数据库的初始化 描述 本文通过存储一个简单的用户信息到数据库中为例,进行阐述relationalStore.RdbStore数据库的CRUD…...
快手直播限流怎么办?
直播限流怎么办?这期把直播间限流的所有原因都讲得明明白白,如果你直播间昨天还播的好好的,今天突然间贴地飞行,按照这个思路框架去排查,准没问题。 第一件事情肯定是排查一下评分问题, 信用分、口碑分、…...
【MySQL】数据库入门基础
文章目录 一、数据库的概念1. 什么是数据库2. 主流数据库3. mysql和mysqld的区别 二、MySQL基本使用1. 安装MySQL服务器在 CentOS 上安装 MySQL 服务器在 Ubuntu 上安装 MySQL 服务器验证安装 2. 服务器管理启动服务器查看服务器连接服务器停止服务器重启服务器 3. 服务器&…...
cannot allocate memory in static TLS block
如果不是内存太小,那是不是因为glibc太旧呢? 考虑 glibc 2.22 以后的版本。 glibc-2.22 中加入了如下commit:f8aeae347377f3dfa8cbadde057adf1827fb1d44 https://sourceware.org/git/?pglibc.git;acommit;hf8aeae347377f3dfa8cbadde057adf1…...
Leetcode 654:最大二叉树
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums 构建的 最大二叉树…...
uniapp小程序src引用服务器图片时全局变量与图片路径拼接
理论上,应该在main.js中定义一个全局变量,然后在页面的<image>标签上的是src直接使用即可 main.js 页面上 看上去挺靠谱的,实际上小程序后台会报一个错 很明显这种方式小程序是不认的,这就头疼了,还想过另外一个…...
比较PWM调光和无极调光
在比较PWM调光和无极调光哪种方式更节能时,需要综合考虑多个因素,如灯具类型、光源效率、调光范围以及使用场景等。 PWM调光系统通过调节LED驱动电流的占空比来实现LED亮度的调节,具有高精度、高稳定性、无闪烁现象以及适用范围广等优点。其节…...
【高校科研前沿】新疆生地所陈亚宁研究员团队在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全球变暖情景下,中亚地区…...
使用 OKhttp3 实现 智普AI ChatGLM HTTP 调用(SSE、异步、同步)
SSE 调用 SSE(Sever-Sent Event),就是浏览器向服务器发送一个HTTP请求,保持长连接,服务器不断单向地向浏览器推送“信息”(message),这么做是为了节约网络资源,不用一直…...
智慧校园教学模式的崛起:优化学习体验
在当今数字化时代,智慧校园教学模式正在成为教育界的热门话题。随着科技的不断发展,传统的教学方式已经无法满足现代学生的需求。智慧校园教学模式以其灵活性、互动性和个性化的特点,正逐渐改变着教育的面貌。 首先,智慧校园教学模…...
ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题
头文件: xencode.h #pragma once #include <mutex> #include<vector> struct AVCodecContext; struct AVPacket; struct AVFrame; class XEncode { public:///// 创建编码上下文/// para codec_id 编码器ID号,对应ffmpeg/// return 编码上…...
halo进阶-主题插件使用
开始捣鼓捣鼓halo,换换主题,加个页面 可参考:Halo 文档 安装/更新主题 主题如同壁纸,萝卜青菜各有所爱,大家按需更换即可; Halo好在一键更换主题,炒鸡方便。 安装/更新插件 此插件还扩展了插件…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
