前言
经过上次学习,我们安装了tesseract识别引擎,并通过pytesseract实现了快速识别图片的内容。然后通过PyQt5DesignMode
项目模板创建了我们的项目,并且已经绘制好了软件的界面,为截图按钮添加了一个简单的点击事件。
本篇我们将继续完善这个项目,实现QQ截图的功能。
一、任务目的
在上节的基础上,实现QQ截屏的功能,并将这个图片保存,显示在软件界面上。
要求
- 可以使用快捷键截屏
- 可以点击截屏按钮截屏
- 截屏显示在软件上
二、实现截图功能
1. 截图功能分析
理论依据
1. 截图功能
Qt提供了可以截屏的方法——grabWindow
,函数原型
def grabWindow(WID window, x=0, y=0, width=-1, height=-1)
参数是窗口ID
和要截取的区域
(x、y、width和height组成的矩形区域)。
窗口ID
可以通过QWidget的winId()获得,若截取整个屏幕窗口ID传入0。
2. 支持鼠标移动事件
Qt的QWidgets提供了可以使窗口支持鼠标移动事件的方法——mouseTracking
,表示窗口的鼠标跟踪属性是否生效。函数原型
def setMouseTracking(self, bool)
参数只有一个bool
类型的参数,表示是否开启鼠标跟踪事件。
3. 窗口无边框
Qt的QWidgets提供了可以使窗口无边框的方法——setWindowFlag
,需要传入窗口的Flag,也就是Qt_WindowType
来启用不同风格的窗口。函数原型
def setWindowFlag(self, Qt_WindowType, on=True)
这里我们主要用到的参数就是Qt_WindowType
,表示窗口的风格,其可选项如下
Qt::Widget
: QWidget构造函数的默认值,如新的窗口部件没有父窗口部件,则它是一个独立的窗口,否则就是一个子窗口部件。Qt::Window
: 无论是否有父窗口部件,新窗口部件都是一个窗口,通常有一个窗口边框和一个标题栏。Qt::Dialog
: 新窗口部件是一个对话框Qt::Sheet
: 新窗口部件是一个Macintosh表单。Qt::Drawer
: 新窗口部件是一个Macintosh抽屉。Qt::Popup
: 新窗口部件是一个弹出式顶层窗口。Qt::Tool
: 新窗口部件是一个工具窗口,它通常是一个用于显示工具按钮的小窗口,
如果一个工具窗口有父窗口部件,则它将显示在父窗口部件的上面,否则,将相当于使用了Qt::WindowStaysOnTopHint
展示。Qt::Tooltip
: 新窗口部件是一个提示窗口,没有标题栏和窗口边框.Qt::SplashScreen
: 新窗口部件是一个欢迎窗口,它是QSplashScreen构造函数的默认值。Qt::Desktop
: 新窗口部件是桌面,它是QDesktopWidget构造函数的默认值。Qt::SubWindow
: 新窗口部件是一个子窗口,而无论该窗口部件是否有父窗口部件。Qt::X11BypassWindowManagerHint
: 完全忽视窗口管理器,它的作用是产生一个根本不被管理器的无窗口边框的窗口,此时,用户无法使用键盘进行输入,除非手动调用QWidget::ActivateWindow()函数。Qt::FramelessWindowHint
: 产生一个无窗口边框的窗口,此时用户无法移动该窗口和改变它的大小。Qt::CustomizeWindowHint
: 关闭默认的窗口标题提示。
4. 窗口全屏
Qt的QWidgets提供了可以使窗口全屏展示的方法——setWindowState
,表示设置窗口的状态。函数原型
def setWindowState(self, Union, Qt_WindowStates=None, Qt_WindowState=None):
我们在使用时,主要使用的是Qt_WindowState
,其可选项如下
Qt: :WindowNoState
正常状态Qt: :WindowMinimized
窗口最小化Qt:: WindowMaximized
窗口最大化Qt::WindowFullScreen
窗口填充整个屏幕,而且没有边框Qt:: Window Active
变为活动的窗口,例如可以接收键盘输入
实施思路
继承QWidgets类实现一个无边框占满全屏的窗口,覆盖到整个屏幕,并且给予一个黑色透明的背景。如下图
处理以下几个事件
- 鼠标左键按下,记录开始坐标
- 鼠标右键按下,取消操作
- 鼠标移动,记录鼠标坐标
- 鼠标左键放开,记录鼠标移动结束坐标
- 鼠标双击,保存截图内容
- 绘图事件,实时绘制鼠标拖动的方框
此处的逻辑应该是这样的。当鼠标左键按下,就开始记录鼠标点击位置的坐标,鼠标移动时,更新一个新坐标,这个新坐标和之前的坐标就可以实时计算出这块矩形区域的x,y,width,height。当鼠标左键放开,就会记录下这一点的坐标,与第一个起始坐标进行计算,就可以得出截图区域的坐标。
鼠标移动事件是一直要运行的,因为要实时的预览我们截图的效果,因此要一直取得鼠标的位置。同时绘图事件也是需要一直运行的,它需要进行实时的计算我们预览的矩形区域,并且绘制在界面上。
鼠标右键的事件很简单,就是取消当前的操作,它的逻辑就是如果当前已经选择好区域了,那就取消这块区域,如果没有选择区域,那就关闭窗口。最终实现效果如下。
2. 截图功能实现
以下代码思路来自CSDN用户
@Karbob
,感谢大佬提供的思路。
1. 创建窗口
创建个继承QWidget的一个窗口,我们将窗口进行基础的设置。init_window
方法是我们用来初始化窗口的,我们开启了鼠标追踪功能,设置了鼠标光标,设置了窗口无边框和窗口全屏。
class CaptureScreen(QWidget): def __init__(self): super(QWidget, self).__init__() self.init_window() # 初始化窗口 self.capture_full_screen() # 获取全屏 def init_window(self): self.setMouseTracking(True) # 鼠标追踪 self.setCursor(Qt.CrossCursor) # 设置光标 self.setWindowFlag(Qt.FramelessWindowHint) # 窗口无边框 self.setWindowState(Qt.WindowFullScreen) # 窗口全屏
值得一提的是,我们在窗口初始化的时候就对桌面全屏截了个图,并且保存到窗口的字段中去,在我们后面绘制截图的时候会用得到。
self.capture_full_screen() # 获取全屏
他的实现就是调用grabWindow
方法对桌面进行截屏,其实现代码如下,
def capture_full_screen(self): self.full_screen_image = QGuiApplication.primaryScreen().grabWindow(QApplication.desktop().winId())
还有一些变量是需要初始化的
begin_position = None end_position = None full_screen_image = None capture_image = None is_mouse_pressLeft = None painter = QPainter()
begin_position
代表鼠标的开始坐标end_position
代表鼠标的结束坐标full_screen_image
存放全屏截图的图片capture_image
截取的图片is_mouse_pressLeft
鼠标左键是否按下painter
进行绘制的对象
2. 实现鼠标按下事件
鼠标按下事件主要做两件事,
- 判断是按下鼠标左键还是鼠标右键
- 如果是鼠标左键就记录鼠标起始位置,否则就取消当前操作
事件处理程序流程图如下所示,
开始是否左键?记录当前坐标设置属性is_mouse_pressLeft为True结束处理右键是否选择截图区域?取消截图区域取消当前操作yesnoyesno
实现代码如下,
def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.begin_position = event.pos() self.is_mouse_pressLeft = True if event.button() == Qt.RightButton: # 如果选取了图片,则按一次右键开始重新截图 if self.capture_image is not None: self.capture_image = None self.paint_background_image() self.update() else: self.close()
3. 实现鼠标移动事件
鼠标移动事件主要的工作是获取当实时的鼠标坐标,然后调用widgets的update方法,更新界面,这里的代码如下
def mouseMoveEvent(self, event): if self.is_mouse_pressLeft is True: self.end_position = event.pos() self.update()
4. 实现鼠标放开事件
鼠标释放事件主要工作是记录鼠标释放时的坐标,然后将is_mouse_pressLeft
置为false,标识鼠标不再是按下了,其代码如下
def mouseReleaseEvent(self, event): self.end_position = event.pos() self.is_mouse_pressLeft = False
5. 实现绘图事件
绘图事件其实是执行的两件事,一是将背景图片设置为桌面的截图,并且加深颜色,一是将没有加深颜色的矩形图片放到鼠标框选的区域
def paintEvent(self, event): self.painter.begin(self) # 开始重绘 self.paint_background_image() pen_color = QColor(30, 144, 245) # 画笔颜色 self.painter.setPen(QPen(pen_color, 1, Qt.SolidLine, Qt.RoundCap)) # 设置画笔,蓝色,1px大小,实线,圆形笔帽 if self.is_mouse_pressLeft is True: pick_rect = self.get_rectangle(self.begin_position, self.end_position) # 获得要截图的矩形框 self.capture_image = self.full_screen_image.copy(pick_rect) # 捕获截图矩形框内的图片 self.painter.drawPixmap(pick_rect.topLeft(), self.capture_image) # 填充截图的图片 self.painter.drawRect(pick_rect) # 画矩形边框 self.painter.end() # 结束重绘
代码中,self.paint_background_image()
是绘制灰黑色背景的方法,他的实现代码如下
def paint_background_image(self): shadow_color = QColor(0, 0, 0, 100) # 黑色半透明 self.painter.drawPixmap(0, 0, self.full_screen_image) self.painter.fillRect(self.full_screen_image.rect(), shadow_color) # 填充矩形阴影
self.get_rectangle(self.begin_position, self.end_position)
计算鼠标框选区域矩形,其实现代码如下,
def get_rectangle(self, begin_point, end_point): pick_rect_width = int(qAbs(begin_point.x() - end_point.x())) pick_rect_height = int(qAbs(begin_point.y() - end_point.y())) pick_rect_top = begin_point.x() if begin_point.x() < end_point.x() else end_point.x() pick_rect_left = begin_point.y() if begin_point.y() < end_point.y() else end_point.y() pick_rect = QRect(pick_rect_top, pick_rect_left, pick_rect_width, pick_rect_height) # 避免高度宽度为0时候报错 if pick_rect_width == 0: pick_rect.setWidth(2) if pick_rect_height == 0: pick_rect.setHeight(2) return pick_rect
计算矩形其实很简单,他的思想就是记录开始坐标和结束坐标,这样就可以通过结束坐标计算出矩形的宽度和高度,因此得出以下两个公式
H ( z ) = y 1 − y , W ( z ) = x 1 − x H(z) = y1-y, W(z) = x1-xH(z)=y1−y,W(z)=x1−x
这里还有两个重要的地方,就是截取鼠标矩形区域的图形,通过上面的方法计算出鼠标框选区域,然后拷贝鼠标框选区域的原始图片,我们采用full_screen_image.copy
来实现
pick_rect = self.get_rectangle(self.begin_position, self.end_position) # 获得要截图的矩形框 self.capture_image = self.full_screen_image.copy(pick_rect) # 捕获截图矩形框内的图片
这里实际上实现的过程如下
通过开始坐标,和矩形的w和h,就可以确定我们要的是那一块的区域,传入full_screen_image.copy
就能得到对应区域的截图,QRect就是这么一个数据类型,因此我们只要传入QRect类型的数据就可以了。
6. 鼠标双击事件
鼠标双击事件我们是调用了保存图片功能,然后关闭该窗口。
这里和后面的窗口程序之间的联动有联系,因此要联合后续内容看
其实现代码如下
def mouseDoubleClickEvent(self, event): if self.capture_image is not None: self.save_image() self.close()
3. 将截图传递到主界面
在controller包下创建文件CaptureScreen.py
,并且继承UI_CaptureScreen.py
,我们来写窗口之间的逻辑
我们在鼠标双击事件中调用了save_image(),这里我们实现一下
def save_image(self): self._signal[QPixmap].emit(self.capture_image)
这里触发了一个信号,以下是信号的定义
_signal = pyqtSignal(QPixmap)
触发信号可以给窗口之间传递数据,我们这里就是定义了一个QPixmap类型的信号,然后我们在MainWindow中来使用一个槽接收这个数据
self.screenWindow._signal[QPixmap].connect(self.handle_capture_picture)
然后就是处理这个数据的方法,就是收到数据以后,显示到Label上
@pyqtSlot(QPixmap) def handle_capture_picture(self, img): print("获取到图片", img) self.img_raw = img local_img = QPixmap(img).scaled(self.picture_label.width(), self.picture_label.height()) # self.picture_label.setScaledContents(True) self.picture_label.setPixmap(local_img)
以上逻辑就是当save_image触发信号以后,MainWIndow的handle_capture_picture就会处理这个信号,然后将图片放到界面上。
4. 实现快捷键截图
实现快捷键截图这里使用的是system_hotkey库,也是使用的信号槽实现的全局热键,首先定义一个信号
sig_keyhot = pyqtSignal(str)
然后连接到处理函数
self.sig_keyhot[str].connect(self.MKey_pressEvent)
初始化两个热键,并且注册热键
self.hk_start, self.hk_stop = SystemHotkey(), SystemHotkey() self.hk_start.register(('control', '1'), callback=lambda x: self.send_key_event("capture_start")) self.hk_stop.register(('control', '2'), callback=lambda x: self.send_key_event("None"))
然后就是处理函数
@pyqtSlot(str) def MKey_pressEvent(self, i_str): if i_str == 'capture_start': self.screenWindow.show() elif i_str == 'None': QMessageBox.information(self, '温馨提示', '其他功能请等待后续添加哦')
总结
以上就是本篇的全部内容。本篇完成本项目的截图功能,采用pyqt原生的方式实现截图,并且与主界面进行响应,这也是PyQt5DesignMode的强大功能之一,提高了程序的开发效率。
本次的小工具只是这个项目中的一部分,实际上后面我们还会加入pdf格式转化等工具,直到本项目做到可以发布的地步,请期待后面的文章吧。
PyQt5DesignMode是本人结合MVC思想与pyqt5实现的一个项目模板,旨在可以用pyqt5实现多窗口应用,如果你感兴趣就给我个star
吧。
欢迎订阅本专栏,学习更多python知识。