[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 time

from PIL import ImageGrab
from PyQt6 import QtWidgets

from shDemo import mainWindow
from shDemo import minorWindow


  # 继承我们前面编写的主界面的前端.py,以及对应的QMainWindow
class screenshot(QtWidgets.QMainWindow, mainWindow.Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)  # 调用主界面的setupUI

        self.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 typing

from PIL import ImageGrab
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtGui import QPainter, QPixmap, QPen, QColor


class 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中图片放到组件上一般要转成pixmap
        pixmap = QPixmap('./屏幕快照.png')
        # 获取主屏幕对象
        screen = QtGui.QGuiApplication.primaryScreen()
        # 获取设备像素比率  物理像素与逻辑像素之间的比率
        self.device_pixel_ratio = screen.devicePixelRatio()
        # 计算实际绘制尺寸
        # 在显示和编程的时候,是按照逻辑像素取进行展示与设计
        # 逻辑尺寸= 物理尺寸 / 像素比  计算出符合当前屏幕的尺寸
        actual_width = pixmap.width() / self.device_pixel_ratio
        actual_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.firstPoint
        image = 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.translate
        jieping.setWindowTitle(_translate("jieping", "Form"))

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于 Redis 实现分布式锁的全过程

前言 这一篇文章拖了有点久,虽然在项目中使用分布式锁的频率比较高,但整理成文章发布出来还是花了一点时间。在一些移动端、用户量大的互联网项目中,经常会使用到 Redis 分布式锁作为控制访问高并发的工具。 一、关于分布式锁 总结&#x…

QT 信号和槽教程,窗体和控件对象之间的沟通一般都使用信号和槽

Qt的信号和槽(Signals and Slots)机制是一种强大的对象间通信方式,它允许对象在完全解耦的情况下相互通信。以下是关于Qt信号和槽的简明教程: 基本概念 信号(Signal):信号是由Qt对象发出的通知…

OpenAI已全面开放自定义GPT以及文件上传等功能

今天,OpenAI兑现了前段时间做出的承诺:免费向所有用户开放GPT-4o。这意味着所有的免费用户都能使用自定义GPT模型、分析图表等其他GPT-4o新功能了。现在ChatGPT界面长这样: 可以看出,免费用户也能使用GPT store中定义好的模型&…

构建智慧监控系统的功能架构,保障安全与便利

智慧监控系统作为现代城市安全管理的重要工具,不仅能够提供有效的安防监控,还能为人们的生活带来更多的便利。本文将探讨智慧监控系统的功能架构,以实现安全和便利的双重目标。 ### 1. 智慧监控系统背景 随着城市化进程的加速,人…

构建高效便捷的家政平台系统——打造优质家政服务的关键

随着人们生活节奏的加快和工作压力的增大,家政服务的需求日益增长。为了满足这一需求,家政平台系统应运而生。本文将探讨家政平台系统的整体架构,以实现高效便捷的家政服务,打造优质家政体验。 ### 1. 家政平台系统背景 随着现代…

语音降噪算法库介绍

一.语音降噪技术方向介绍 软件上进行语音降噪目前主要是两个方向:传统降噪算法和AI降噪算法,他们各有千秋,目前看他们各有千秋,有各自适用场景。 推荐一个不错的人工智能学习网站,通俗易懂,内容全面&#…

vue3组件传值---vue组件通过属性,事件和provide,inject进行传值

通过属性传值(父传子) vue的组件具有props自建属性(自定义名称,类似于class,id的属性),通过这个属性,父组件可以向子组件传递参数,从而实现组件之间的信息传递&#xff0…

SpringSecurity6从入门到实战之Filter过滤器回顾

SpringSecurity6从入门到实战之Filter过滤器回顾 如果没有SpringSecurity这个框架,我们应该通过什么去实现客户端向服务端发送请求时,先检查用户是否登录,登录了才能访问.否则重定向到登录页面 流程图如下 官方文档:https://docs.spring.io/spring-security/referen…

自动化办公01 smtplib 邮件⾃动发送

目录 一、准备需要发送邮件的邮箱账号 二、发送邮箱的基本步骤 1. 登录邮箱 2. 准备数据 3. 发送邮件 三、特殊内容的发送 1. 发送附件 2. 发送图片 3. 发送超文本内容 4.邮件模板内容 SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议…

单点11.2.0.3备份恢复到单点11.2.0.4

保命法则:先备份再操作,磁盘空间紧张无法备份就让满足,给自己留退路。 场景说明: 1.本文档的环境为同平台、不同版本(操作系统版本可以不同,数据库小版本不同),源机器和目标机器部…

设计模式(二)工厂模式

文章目录 工厂模式简介简单工厂(Simple Factory)结构具体实现优缺点 工厂方法(Factory Method)结构具体实现优缺点 抽象工厂(Abstract Factory)结构具体实现优缺点 工厂模式简介 工厂模式是一种创建型模式…

[数据集][目标检测]焊接处缺陷检测数据集VOC+YOLO格式3400张8类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):3400 标注数量(xml文件个数):3400 标注数量(txt文件个数):3400 标注…

能源SCI期刊,中科院4区,审稿快,IF=3.858

一、期刊名称 Frontiers in Energy Research 二、期刊简介概况 期刊类型:SCI 学科领域:能源 影响因子:3.858 中科院分区:4区 三、期刊征稿范围 能源研究前沿出版了整个领域的严格同行评审研究,重点是可持续和环境…

YOLOv8 深度详解!一文看懂,快速上手

YOLOv8 深度详解!一文看懂,快速上手 原文:YOLOv8 深度详解!一文看懂,快速上手 - 知乎 (zhihu.com) YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本,目前支持图像分类…

Low Memory Killer in Android

目录 低内存管理(Linux vs Android) Linux内存回收 shrink_slab原理 shrink_zone原理 oom killer oom killer设计原则 OOM killer具体实现 android的lmk(Low Memory Killer) Android系统特点 oom killer在android中的不足 ​​​​​​​LMK概…

乡村振兴与农村环境整治:加强农村环境治理,改善农村人居环境,打造干净整洁、生态宜居的美丽乡村

目录 一、引言 二、农村环境整治的重要性 1、提升农民生活质量 2、促进农村经济发展 3、保护农村生态环境 三、当前农村环境面临的问题 1、垃圾处理不当 2、污水处理设施缺乏 3、农业面源污染严重 四、加强农村环境治理的措施 1、完善农村垃圾处理体系 2、加强农村…

2010-2015 年阿拉斯加北坡苔原植物功能类型连续覆盖图

ABoVE: Tundra Plant Functional Type Continuous-Cover, North Slope, Alaska, 2010-2015 2010-2015 年阿拉斯加北坡苔原植物功能类型连续覆盖图 简介 文件修订日期:2021-08-27 数据集版本: 1 摘要 该数据集以 30 米的分辨率提供了阿拉斯加北坡约 12.5 万平方…

CodeMirror 创建标签计算编辑器

在日常开发中对于一些数据计算场景可能会遇到标签计算的需求&#xff0c;下面关于如何使用CodeMirror实现标签计算编辑功能。 1&#xff0c;结果图 2&#xff0c;主体代码逻辑 大家只需要复制粘贴主要codeMirror使用逻辑即可 <template><el-dialogref"dialogRe…

7.2 Go 使用error类型

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

godot.bk

1.搜索godot国内镜像&#xff0c;直接安装&#xff0c;mono是csharp版本 2.直接解压&#xff0c;50m&#xff0c;无需安装&#xff0c;直接运行 3.godot里分为场景&#xff0c;节点 主场景用control场景&#xff0c;下面挂textureact放背景图片&#xff0c;右键实例化子场景把…