admin管理员组文章数量:1436834
PyQt 截图小工具
前言
时不时要截个屏,圈画重点,再复制给同事或存档,工作效率却总被琐碎的操作拉低。偶然的一次灵感:何不自己动手,做一个 “一键框选截图 + 涂鸦标注 + 复制/保存” 的小工具?这样既能练练 PyQt 的功力,又能打造一个真正好用的小利器。
一、动机与需求
工作中,我经常需要:
- 用鼠标框选任意屏幕区域截图;
- 对截图进行自由涂鸦、箭头、文本标注;
- 一键点击即可保存到本地,或复制到系统剪贴板,方便粘贴到聊天窗口;
- 通过快捷键(如 Ctrl+Shift+S)随时呼出截图界面,不打断当前窗口。
市场上虽有类似软件,但大多臃肿或闭源,不易二次定制。于是,我决定用 PyQt 从零打造一款 轻量、定制化 的截图标注工具。
二、技术选型
为什么选择 PyQt?主要原因有三点:
- 窗口透明与事件拦截:Qt 支持透明窗口、鼠标穿透和拦截,可自定义截图蒙层;
- 强大的绘图 API:
QPainter
+QPixmap
组合,可高效实现涂鸦与文字绘制; - 系统交互:Qt 提供对剪贴板(
QClipboard
)、快捷键(QShortcut
)等友好封装;
此外,Python 生态下的标准库和第三方库(如 pynput
或 keyboard
)可以补充全局热键监听。
三、整体架构设计
在开始写代码前,我先做了一个模块交互图,理清各部分职责和信号流转。
主要模块:
- HotkeyListener:全局监听截图快捷键(例如
Ctrl+Shift+S
),调用截图流程。 - ScreenshotOverlay:全屏透明窗口,拦截鼠标事件,绘制选区框并捕获所选区域。
- AnnotationCanvas:基于
QWidget
的画布,承载截图位图与用户涂鸦、文字注释操作。 - FileSaver:将最终图像保存到指定路径;
- ClipboardManager:将图像复制到系统剪贴板;
模块职责单一,信号槽衔接清晰,方便后续拓展。
四、项目结构一览
我在项目根目录下组织代码,简要如下:
代码语言:python代码运行次数:0运行复制custom_screenshot/
├── main.py
├── hotkey_listener.py
├── screenshot_overlay.py
├── annotation_canvas.py
├── utils.py
└── resources/
└── style.qss
- main.py:程序入口,加载 QSS 样式,初始化全局热键监听和隐藏主窗口。
- hotkey_listener.py:可选使用
keyboard
库或 Qt 的本地快捷键方案,触发截图。 - screenshot_overlay.py:实现透明截图蒙层与鼠标框选捕获逻辑。
- annotation_canvas.py:实现对截图结果的涂鸦、文字、保存与复制功能。
- utils.py:封装剪贴板操作、文件对话框、图像格式处理等公共逻辑。
- resources/style.qss:简洁现代的 UI 样式表。
五、全局快捷键监听
要实现“任意时刻按快捷键呼出截图”,可以选两种方案:
- 第三方库
keyboard
:跨平台但需管理员权限; - Qt 本地热键:只在应用有焦点时生效,不够“全局”。
我最终选用 pynput
库监听全局热键,它对 Python3 支持良好。
# hotkey_listener.py
from pynput import keyboard
from PyQt5.QtCore import QObject, pyqtSignal
class HotkeyListener(QObject):
trigger = pyqtSignal()
def __init__(self, combo={keyboard.Key.ctrl_l, keyboard.Key.shift, keyboard.KeyCode(char='s')}):
super().__init__()
selfbo = combo
self.current = set()
self.listener = keyboard.Listener(on_press=self._on_press,
on_release=self._on_release)
self.listener.start()
def _on_press(self, key):
self.current.add(key)
if all(k in self.current for k in selfbo):
self.trigger.emit()
def _on_release(self, key):
if key in self.current:
self.current.remove(key)
关键点:
使用
pynput.keyboard.Listener
监听全局按键; 当检测到Ctrl+Shift+S
同时按下时,发射trigger
信号; 在 PyQt 主线程中,可用listener.daemon = True
确保程序退出时自动结束监听。
六、截图覆盖层 ScreenshotOverlay
最棘手的部分就是,一个全屏透明窗口,既要拦截所有鼠标事件,又要在选区绘制半透明蒙层、实线矩形框,并准确生成截图。
1. 透明无边框全屏窗口
代码语言:python代码运行次数:0运行复制# screenshot_overlay.py
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtGui import QPainter, QColor, QPen, QGuiApplication, QPixmap
from PyQt5.QtCore import Qt, QRect
class ScreenshotOverlay(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
# 半透明黑色背景
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.screen = QGuiApplication.primaryScreen()
self.full_pixmap = self.screen.grabWindow(0)
self.origin = None
self.current = None
self.selection = QRect()
def showEvent(self, event):
self.resize(self.screen.size())
self.showFullScreen()
def paintEvent(self, event):
painter = QPainter(self)
# 绘制截图背景
painter.drawPixmap(0, 0, self.full_pixmap)
# 蒙层
painter.fillRect(self.rect(), QColor(0, 0, 0, 100))
# 清除选区蒙层
if not self.selection.isNull():
painter.setCompositionMode(QPainter.CompositionMode_Clear)
painter.fillRect(self.selection, QColor(0,0,0,0))
# 画选区边框
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
pen = QPen(QColor(255,255,255), 2)
painter.setPen(pen)
painter.drawRect(self.selection)
painter.end()
这里的要点:
WA_TranslucentBackground
让窗口支持透明;- 使用
screen.grabWindow(0)
抓取当前屏幕内容,做为背景; - 蒙层效果:先填充半透明黑,再用
CompositionMode_Clear
清除选区区域; - 绘制白色矩形框,高亮边界。
2. 鼠标事件处理
代码语言:python代码运行次数:0运行复制# screenshot_overlay.py 续...
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.origin = event.pos()
self.selection = QRect(self.origin, self.origin)
def mouseMoveEvent(self, event):
if self.origin:
self.current = event.pos()
self.selection = QRect(self.origin, self.current).normalized()
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and not self.selection.isNull():
# 截取选区
cropped = self.full_pixmap.copy(self.selection)
# 进入注释画布
self.open_annotation(cropped)
self.close()
- 鼠标按下时记录起点
origin
; - 鼠标移动时更新
current
,并用normalized()
确保矩形正向; - 鼠标释放后,
full_pixmap.copy(selection)
得到选区截图; - 调用
open_annotation()
进入下一步。
七、注释画布 AnnotationCanvas
捕获到 cropped
的 QPixmap
后,需要打开一个新的窗口,让用户进行涂鸦和文字标注。
# annotation_canvas.py
from PyQt5.QtWidgets import QWidget, QPushButton
from PyQt5.QtGui import QPainter, QPen, QFont, QPixmap
from PyQt5.QtCore import Qt, QPoint
class AnnotationCanvas(QWidget):
def __init__(self, pixmap):
super().__init__()
self.base = pixmap
self.temp = QPixmap(pixmap.size())
self.temp.fill(Qt.transparent)
self.drawing = False
self.last_point = QPoint()
self.pen = QPen(Qt.red, 3, Qt.SolidLine)
self.init_ui()
def init_ui(self):
self.resize(self.base.size())
self.setWindowFlags(Qt.WindowStaysOnTopHint)
# 保存与复制按钮
self.btn_save = QPushButton("保存", self)
self.btn_copy = QPushButton("复制", self)
self.btn_save.move(10,10)
self.btn_copy.move(90,10)
self.btn_save.clicked.connect(self.save)
self.btn_copy.clicked.connect(self.copy)
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(0,0,self.base)
painter.drawPixmap(0,0,self.temp)
painter.end()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.last_point = event.pos()
def mouseMoveEvent(self, event):
if self.drawing:
painter = QPainter(self.temp)
painter.setPen(self.pen)
painter.drawLine(self.last_point, event.pos())
self.last_point = event.pos()
painter.end()
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = False
核心思路:
- 双图层设计:
self.base
保存原始截图;self.temp
用于实时绘制,最后合并;- 涂鸦逻辑:在
mouseMoveEvent
中,将线段绘制到self.temp
,再调用update()
重绘; - 按钮交互:用户可点击“保存”或“复制”进行后续操作。
八、文字标注与多种绘制工具
涂鸦之后,最常用的是在截图上添加文字说明或箭头指示。为此,我在注释画布中增加工具栏,用户可切换“画笔模式”和“文本模式”。
1. 工具切换 UI
在 AnnotationCanvas
的 init_ui
方法里,加入工具按钮:
# annotation_canvas.py 扩展 init_ui
from PyQt5.QtWidgets import QToolButton, QAction, QButtonGroup
def init_ui(self):
# … 之前的保存/复制按钮 …
# 工具栏按钮
self.btn_pen = QToolButton(self)
self.btn_text = QToolButton(self)
self.btn_pen.setText("✏️")
self.btn_text.setText("
本文标签:
PyQt 截图小工具
版权声明:本文标题:PyQt 截图小工具 内容由网友自发贡献,该文观点仅代表作者本人,
转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1747401103a2694040.html,
本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论