【PyQt6] 框选截图功能

1 简介

书接上回, 全屏截图实现起来很简单, 来点稍微复杂点的, 框选截图
原理很简单, 弄个控件实现全屏半透视, 在全屏控件上画一个选框或者再弄一个几乎全透的子控件,实现鼠标拖动,缩放,移动, 键盘wasd 微调

用一个控件实现起来会很完美, 但是逻辑全部堆砌在一起,看代码会很累, 用一个子控件分开来写,逻辑清晰, 看着也舒服点,有机会以后在组合在一起,关键实现了一个独立的橡皮框控件, 想复用也容易.

Qt6 自身有个橡皮筋控件, 尝试了下, 可以当简单的框选工具, 比如选择多个item之类的,如果功能不需要复杂,倒是可以直接使用.

原本打算扩展这个控件[QRubberBand]的,想想又没必要, 干脆就直接扩展 QWidget

2 Demo 代码

2.1 Rubber 控件

框选控件, 双击鼠标左键 发出选中的区域
在这里插入图片描述

from PyQt6.QtCore import QEvent, Qt, QPoint, QPointF, QRectF, QRect, QTimer, pyqtSignal, QLineF
from PyQt6.QtGui import QEnterEvent, QMouseEvent, QPainter, QPaintEvent, QPen, QVector2D, QKeyEvent, QCursor, QColor
from PyQt6.QtWidgets import QWidget, QApplicationclass Rubber(QWidget):confirm_selected = pyqtSignal(QRect)# ===============  构造函数 ==============================def __init__(self, parent=None) -> None:super().__init__(parent)self.hide()  # 默认隐藏# self.corners = [QRectF]   # 保存4个角的rectself.corner_i = -1  # 保存角的索引self.orgin = QPoint()   # 保存鼠标按下的坐标self.select_rect = QRectF()  # 保存调整后的区域,即实线区self.txt_height = 10        # 留出高度 显示字符串self.isLeftButton = False       # 鼠标是否为移动状态# ===============  绘制事件 ==============================def paintEvent(self, a0: QPaintEvent | None) -> None:"""绘制事件"""painter = QPainter(self)painter.setClipRect(a0.rect())# 填充背景painter.fillRect(self.rect(), QColor(255, 255, 255, 1))pen = QPen()color = Qt.GlobalColor.redpen.setColor(color)pen.setWidth(0)painter.setPen(pen)# 绘制 4 个角区域 以及 选区框self.drawCornors(painter)w = int(self.select_rect.width() * self.devicePixelRatio())h = int(self.select_rect.height() * self.devicePixelRatio())txt = f'{w}x{h}'painter.drawText(0, self.txt_height, txt)# ===============  鼠标进入事件 ==============================def enterEvent(self, event: QEnterEvent | None) -> None:self.setMouseTracking(True)self.setFocus()# self.__setCursorShape(Qt.CursorShape.SizeAllCursor)# ===============  鼠标离开事件 ==============================def leaveEvent(self, a0: QEvent | None) -> None:self.setMouseTracking(False)self.clearFocus()# return super().leaveEvent(a0)# ===============  鼠标按下事件 ==============================def mousePressEvent(self, a0: QMouseEvent | None) -> None:self.orgin = a0.pos()self.orgin1 = a0.pos()# print(self.orgin1 == self.orgin, id(self.orgin1), id(self.orgin))if a0.buttons() == Qt.MouseButton.LeftButton:self.isLeftButton = True# ===============  鼠标移动事件 ==============================def mouseMoveEvent(self, a0: QMouseEvent | None) -> None:pos_c = a0.pos()    # 当前鼠标位置# 鼠标左键按下后, 可以调整 大小if self.isLeftButton:self.set_geometry_mouse(pos_c)return# 判断4个角是否包含鼠标位置, 设置相应的光标形状self.changeCursor(pos_c)# ===============  鼠标释放事件 ==============================def mouseReleaseEvent(self, a0: QMouseEvent | None) -> None:self.orgin = QPoint()self.corner_i = -1self.isLeftButton = Falsedef mouseDoubleClickEvent(self, a0: QMouseEvent | None) -> None:self.hide()QTimer.singleShot(200, self.__sendSign)# return super().mouseDoubleClickEvent(a0)# ===============  按键事件 ===============def keyPressEvent(self, a0: QKeyEvent | None) -> None:match a0.key():# ESC 键case Qt.Key.Key_Escape:self.hide()case Qt.Key.Key_A:self.set_geometry_key('a')case Qt.Key.Key_S:self.set_geometry_key('s')case Qt.Key.Key_D:self.set_geometry_key('d')case Qt.Key.Key_W:self.set_geometry_key('w')# ===============  设置光标形状方法 ==============================def __setCursorShape(self, shape: Qt.CursorShape):cursor = self.cursor()cursor.setShape(shape)self.setCursor(cursor)def __sendSign(self):global_pos = self.mapToGlobal(QPoint(0, 0))rect = self.select_rect.adjusted(global_pos.x(), global_pos.y(), global_pos.x(), global_pos.y()).toRect()self.confirm_selected.emit(rect)def set_geometry_key(self, cmd: str):"""通过按键调整 geometry"""if self.isLeftButton is False:returncur_pos = QCursor.pos()sp = self.cursor().shape()if cmd == 'a' and sp != Qt.CursorShape.SizeVerCursor:cur_pos += QPoint(-1, 0)elif cmd == 's' and sp != Qt.CursorShape.SizeHorCursor:cur_pos += QPoint(0, 1)elif cmd == 'd' and sp != Qt.CursorShape.SizeVerCursor:cur_pos += QPoint(1, 0)elif cmd == 'w' and sp != Qt.CursorShape.SizeHorCursor:cur_pos += QPoint(0, -1)QCursor.setPos(cur_pos)def drawCornors(self, painter: QPainter):""" 绘制4个角 以及 选区框线Note:self.corners.append : 4个角的添加顺序为:  上左0 上右1 下右2 下左3"""# 4个角 的顺序  上左 上右 下右 下左off = 2.0# self.corners.clear()corners = []geometricF = self.rect().toRectF()corners.append(QRectF(tl := geometricF.topLeft() + QPointF(0, self.txt_height), tl + QPointF(off, off)).normalized())corners.append(QRectF(tr := geometricF.topRight() + QPointF(0, self.txt_height), tr + QPointF(-off, off)).normalized())corners.append(QRectF(br := geometricF.bottomRight(), br + QPointF(-off, -off)).normalized())corners.append(QRectF(bl := geometricF.bottomLeft(), bl + QPointF(off, -off)).normalized())# print(self.corners)# print('='*30, '>')for rect in corners:# print(rect)painter.fillRect(rect, painter.pen().color())# print('='*30, '<')self.select_rect = QRectF(corners[0].center(), corners[2].center())painter.drawRect(self.select_rect)def set_geometry_mouse(self, pos: QPoint):""" 通过鼠标的位置调整 geometryArgs:pos: 鼠标的当前位置"""offset = pos - self.orgin     # 左上角坐标有变化,不需要更新offset1 = pos - self.orgin1   # 其他需要更新rect = self.geometry()# x1, y1, x2, y2 = self.geometry().getCoords()sp = self.cursor().shape()# 根据光标形状来实现相应方法match sp:case Qt.CursorShape.SizeFDiagCursor:    # \if self.corner_i == 0:rect.adjust(offset.x(), offset.y(), 0, 0)elif self.corner_i == 2:rect.adjust(0, 0, offset1.x(), offset1.y())case Qt.CursorShape.SizeBDiagCursor:    # /if self.corner_i == 1:rect.adjust(0, offset.y(), offset1.x(), 0)elif self.corner_i == 3:rect.adjust(offset.x(), 0, 0, offset1.y())case Qt.CursorShape.SizeHorCursor:  # -if self.corner_i == 1:rect.adjust(0, 0, offset1.x(), 0)if self.corner_i == 3:rect.adjust(offset.x(), 0, 0, 0)case Qt.CursorShape.SizeVerCursor:  # +if self.corner_i == 0:rect.adjust(0, offset.y(), 0, 0)if self.corner_i == 2:rect.adjust(0, 0, 0, offset1.y())case Qt.CursorShape.SizeAllCursor:rect.adjust(offset.x(), offset.y(), offset.x(), offset.y())self.setGeometry(rect.normalized())self.orgin1 = pos  # 更新数据def changeCursor(self, pos: QPoint):r"""根据鼠标的位置确定光标的形状SizeFDiagCursor:     /SizeBDiagCursor:     \\SizeVerCursor:       |SizeHorCursor:       -SizeAllCursor:       +"""# 在 4 个 边角区域  distance < 5 ,认为相交cors = []cors.append(self.select_rect.topLeft())cors.append(self.select_rect.topRight())cors.append(self.select_rect.bottomRight())cors.append(self.select_rect.bottomLeft())idx = self.corner_i = -1for i, p in enumerate(cors):distance = QLineF(pos.toPointF(), p).length()if distance < 5:if i == 0 or i == 2:self.__setCursorShape(Qt.CursorShape.SizeFDiagCursor)elif i == 1 or i == 3:self.__setCursorShape(Qt.CursorShape.SizeBDiagCursor)self.corner_i = ireturn# 判断4 条边是否 和 鼠标相交 如果 distance < 5 ,认为相交# idx = -1for i in range(4):s_2d = QVector2D(cors[i])          # 起点e_2d = QVector2D(cors[(i+1) % 4])  # 终点pos_2d = QVector2D(pos)distance = pos_2d.distanceToLine(s_2d, (e_2d-s_2d).normalized())if distance < 5:idx = ibreakif idx == 0 or idx == 2:self.__setCursorShape(Qt.CursorShape.SizeVerCursor)  # |elif idx == 1 or idx == 3:self.__setCursorShape(Qt.CursorShape.SizeHorCursor)  # -else:self.__setCursorShape(Qt.CursorShape.SizeAllCursor)  # +self.corner_i = idx  # 更新属性if __name__ == '__main__':app = QApplication([])w = QWidget()w.setWindowOpacity(0.5)w.resize(400, 300)c = Rubber(w)c.setVisible(True)w.show()app.exec()# sys.exit(app.exec())

2.2 全屏选区截图

再来一个全屏的半透明控件,整体效果如图:
样式和操作参考了 ABBYY Screenshot 以及 Snipaste

只是可惜了在橡皮筋控件捕捉到鼠标移动事件时, 就只能正常正选,不能反选, 应该把这一部分逻辑挪到 全屏控件里的!!
在这里插入图片描述

from PyQt6.QtCore import Qt, QRect, QPoint
from PyQt6.QtGui import QKeyEvent, QMouseEvent, QPainter, QPaintEvent, QColor, QRegion
from PyQt6.QtWidgets import QApplication, QWidget
from Rubber import Rubberclass FullScreen(QWidget):# ===============  构造函数 =============================================def __init__(self):super(FullScreen, self).__init__()# ------------- 窗口标志设置  -------------self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint   # 置顶| Qt.WindowType.FramelessWindowHint  # 无框| Qt.WindowType.Tool)         # 无任务栏图标self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)  # 设置窗口背景透明# ------------- 橡皮筋  -------------self.rubber = Rubber(self)self.rubber.confirm_selected.connect(self.__grab)# ===============  绘制事件 =============================================def paintEvent(self, a0: QPaintEvent | None) -> None:painter = QPainter(self)# 设置绘制区域 = 全部区域 - 选中区域region = QRegion(self.rect())slt_r = self.rubber.rect()rubber_p = self.rubber.pos()if self.rubber.isVisible():slt_r.adjust(rubber_p.x(), rubber_p.y() + self.rubber.txt_height,rubber_p.x(), rubber_p.y())region -= QRegion(slt_r)painter.setClipRegion(region)painter.fillRect(self.rect(), QColor(0, 0, 127, 100))return super().paintEvent(a0)# ===============  按键事件 =============================================def keyPressEvent(self, a0: QKeyEvent | None) -> None:if a0.key() == Qt.Key.Key_Escape:   # ESC 键app.quit()return super().keyPressEvent(a0)# ===============  鼠标按下事件 =============================================def mousePressEvent(self, a0: QMouseEvent | None) -> None:self.orgin = a0.pos()self.rubber.setGeometry(QRect(self.orgin, QPoint(0, 0)))self.rubber.show()return super().mousePressEvent(a0)# ===============  鼠标移动事件 =============================================def mouseMoveEvent(self, a0: QMouseEvent | None) -> None:self.rubber.setGeometry(QRect(self.orgin, a0.pos()).normalized())return super().mouseMoveEvent(a0)# ===============  抓取选区截图 =============================================def __grab(self, rect: QRect):screen = QApplication.primaryScreen()pixmap = screen.grabWindow(0,rect.x(),rect.y(),rect.width(),rect.height())pixmap.save("CWidget.png")self.close()# clipboard = QApplication.clipboard()# clipboard.setImage(self.pixmap.toImage())# ===============  Main =============================================
if __name__ == '__main__':app = QApplication([])screen = FullScreen()screen.resize(400, 300)screen.show()screen.showFullScreen()app.exec()# sys.exit(app.exec())

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

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

相关文章

Mac电脑玩《幻兽帕鲁》卡怎么办?2024年最新解决方法

幻兽帕鲁目前已经在steam卖出了100多万份数&#xff0c;可谓是爆火现象级的游戏。如今在游戏中&#xff0c;我们可以实现工农业自动化&#xff0c;为了实现自动化&#xff0c;将手工作业交给帕鲁就尤为重要。建造工厂&#xff0c;并安排帕鲁在其中工作吧。只要有足够的食物&…

K8s服务发现组件之CoreDNS/NodeLocalDNS /kubeDNS

1 coredns 1.1 概述 1.1.1 什么是CoreDNS CoreDNS 是一个灵活可扩展的 DNS 服务器&#xff0c;可以作为 Kubernetes 集群 DNS&#xff0c;在Kubernetes1.12版本之后成为了默认的DNS服务。 与 Kubernetes 一样&#xff0c;CoreDNS 项目由 CNCF 托管。 coredns在K8S中的用途,…

spring aop @annotation的用法

直接看原文: spring aop annotation的用法-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- annotation用在定义连接点时&#xff0c;对连接点进行限制。比如我们想对标注了…

prometheus+mysql_exporter监控mysql

prometheus+mysql_exporter监控mysql 一.安装mysql 1.下载:wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm 2.安装客户端:yum -y install mysql57-community-release-el7-10.noarch.rpm 3.安装服务端:yum -y install mysql-community-se…

day10:分割链表

问题描述&#xff1a; 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4…

【HTML】SVG实现炫酷的描边动画

前沿 今天闲来无事&#xff0c;看到Antfu大佬的个性签名&#xff0c;觉得还是非常炫酷的&#xff0c;于是也想要搞一个自己的个性签名用来装饰自己的门面&#xff0c;不过由于手写的签名太丑了&#xff0c;遂放弃。于是尝试理解原理&#xff0c;深入研究此等密法&#xff0c;终…

VLM多模态图像识别小模型UForm

参考:https://github.com/unum-cloud/uform https://huggingface.co/unum-cloud/uform-gen2-qwen-500m https://baijiahao.baidu.com/s?id=1787054120353641459&wfr=spider&for=pc demo:https://huggingface.co/spaces/unum-cloud/uform-gen2-qwen-500m-demo UF…

市场复盘总结 20240219

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 22% 最常用的…

Shiro-05-5 分钟入门 shiro 安全框架实战笔记

序言 大家好&#xff0c;我是老马。 前面我们学习了 web 安全之 Spring Security 入门教程 这次我们来一起学习下另一款 java 安全框架 shiro。 什么是Apache Shiro&#xff1f; Apache Shiro是一个功能强大且易于使用的Java安全框架&#xff0c;它为开发人员提供了一种直…

Redis 数据类型及其常用命令二(bitmap、geo、hyperloglog、bitfield、stream)

上文中我们介绍了Redis常使用的5中数据类型&#xff0c;对于一些特殊的场景&#xff0c;我们需要使用特殊的数据类型&#xff0c;本文将详细介绍5种特殊的数据类型。 1、bitmap 类型 用String类型作为底层数据结构实现的一种统计二值状态的数据类型。位图本质是数组&#xff0…

《剑指 Offer》专项突破版 - 面试题 45 和 46 : 二叉树最低层最左边的值和二叉树的右侧视图(C++ 实现)

目录 面试题 45 : 二叉树最低层最左边的值 面试题 46 : 二叉树的右侧视图 面试题 45 : 二叉树最低层最左边的值 题目&#xff1a; 如何在一棵二叉树中找出它最低层最左边节点的值&#xff1f;假设二叉树中最少有一个节点。例如&#xff0c;在下图所示的二叉树中最低层最左边…

Codeforces Round 924 (Div. 2)题解(A-D)

A - Rectangle Cutting 链接&#xff1a;A - Rectangle Cutting 思路 考虑横边和纵边&#xff0c;若为偶数&#xff0c;则从中间分开&#xff0c;重新组合为一个长方形&#xff0c;检测是否与原来的长方形一致。 代码 #include <bits/stdc.h> using namespace std;i…

探秘OpenAI的神奇之作:Sora技术揭秘

探秘OpenAI的神奇之作&#xff1a;Sora技术揭秘 1. 引言 在当今科技快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;正日益成为各个领域的关键技术。而在人工智能领域中&#xff0c;OpenAI公司一直以来都扮演着重要的角色。他们的最新创新——Sora技术&#x…

基于python的遥感影像灰色关联矩阵纹理特征计算

遥感影像纹理特征是描述影像中像素间空间关系的统计特征&#xff0c;常用于地物分类、目标识别和变化检测等遥感应用中。常见的纹理特征计算方式包括灰度共生矩阵&#xff08;GLCM&#xff09;、灰度差异矩阵&#xff08;GLDM&#xff09;、灰度不均匀性矩阵&#xff08;GLRLM&…

常见面试题:TCP的四次挥手和TCP的滑动窗口

说一说 TCP 的四次挥手。 挥手即终止 TCP 连接&#xff0c;所谓的四次挥手就是指断开一个 TCP 连接时。需要客户端和服务端总共发出四个包&#xff0c;已确认连接的断开在 socket 编程中&#xff0c;这一过程由客户端或服务端任意一方执行 close 来触发。这里我们假设由客户端…

unity学习(29)——GameInfo角色信息

1.把GameInfo.cs PlayerModel.cs Vector3.cs Vector4.cs PlayerStateConstans.cs GameState.cs依次粘到model文件夹中&#xff0c;此时项目没有错误&#xff0c;如下图所示&#xff1b; 对应处所修改的代码如下&#xff1a; case LoginProtocol.LOGIN_SRES://1 {Debug.Log(&qu…

考研查分,别再只知道研招网了!

查分时间基本已经敲定在2月26日左右了。倒计时7天&#xff01;每年查询分数的时候经常因为查询人数太多&#xff0c;进不去研招网&#xff0c;还有哪些方法可以查询分数呢&#xff1f; 我为大家整理了四种常用的查成绩方式&#xff0c;附带部分已公布查分时间院校名单。 一、…

Java学习心得感悟

在我踏入Java学习的道路之前&#xff0c;我对编程只是一知半解&#xff0c;对于代码的世界充满了好奇和向往。然而&#xff0c;当我真正开始学习Java时&#xff0c;我才意识到&#xff0c;学习Java不仅仅是学习一门编程语言&#xff0c;更是一种思维方式和解决问题的能力的培养…

【AI视野·今日Sound 声学论文速览 第四十九期】Wed, 17 Jan 2024

AI视野今日CS.Sound 声学论文速览 Wed, 17 Jan 2024 Totally 23 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers From Coarse to Fine: Efficient Training for Audio Spectrogram Transformers Authors Jiu Feng, Mehmet Hamza Erol, Joon Son Chung,…

Pandas Series Mastery: 从基础到高级应用的完整指南【第83篇—Series Mastery】

Pandas Series Mastery: 从基础到高级应用的完整指南 Pandas是Python中一流的数据处理库&#xff0c;它为数据科学家和分析师提供了强大的工具&#xff0c;简化了数据清理、分析和可视化的流程。在Pandas中&#xff0c;Series对象是最基本的数据结构之一&#xff0c;它为我们处…