【PC桌面自动化测试工具开发笔记】(六)实现实时调试的截图工具

该文章介绍了一个用Python和PyQt实现的截图工具,它模仿了AirtestIDE的功能,包括实时调整截图参数、运行代码自动填充以及图像识别。用户可以在截图时实时调整截图区域,工具会自动生成Airtest代码,并在屏幕上显示识别结果。此外,它还支持多目标图像识别,帮助用户更好地定位和排除干扰项。
摘要由CSDN通过智能技术生成

前言

之前在使用AirtestIDE的时候发现了一些使用上不顺手的地方,比如截图时不能调整微调截图大小,截图时无法找到干扰项等。仍然是自己瞎鼓捣写出来了,感谢CSDN大佬们的文章。
以下是功能点:

  1. 实现仿AirtestIDE截图。
  2. 运行代码自动填充。
  3. 截图时可实时调整截图参数调试图像识别。

仿AirtestIDE截图

AirtestIDE截图文件生成的图片名称与当前时间戳有关,图片名称以"tpl"开头。
AirtestIDE截图时会记录当前截图区域的中心坐标,代码里进行了换算(不知道具体为啥),这部分从airtest源码里可以看到。
airtest的图像识别Template类是读取本地图片数据,再进行图像识别,为减少代码量,我们继承Template类并重写其_imread()函数使我们直接用截图区域进行图像识别。

class MatchPicture(Template):
    """
    继承Template类,支持内存图像识别
    """

    def _imread(self):
        return self.filename  # 不做imread处理,实际返回pic,传参为screenshot内存截图结果


def get_record_pos(pos):
    """
    中心坐标转换

    :param pos: 截图中心点
    :return: record_pos,resolution
    """
    w, h = win32api.GetSystemMetrics(win32con.SM_CXSCREEN), win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
    delta_x = round((pos[0] - w * 0.5) / w, 3)
    delta_y = round((pos[1] - h * 0.5) / w, 3)
    return (delta_x, delta_y), (w, h)


def default_pic_name():
    """
    时间戳命名

    :return:
    """
    pic_name = f'tpl{int(time.time() * 1000)}.png'
    return pic_name

除了上面讲到的,airtest截图还支持RGB识别,threshold调整阈值,调整target_pos目标位置,这些功能我们都需要加到实时调试里,并在屏幕上给出反馈结果。

运行代码自动填充

实际上还是拼接字符串。
截图后代码自动拷贝到剪切板,同时会发送一个code_name信号,可以绑定需要的槽函数实现代码自动填充。
我的这部分功能在接入主程序后图片保存路径由程序自动决定,请根据实际调整。当前提供的源码在未传入main_win的情况下截图文件保存到当前脚本执行路径下。

    def before_save(self):
        pic_name = default_pic_name()
        air_case_path = ""  # 根据实际修改目标路径
        if self.parent().main_win:
            if self.parent().main_win.edit_mode and os.path.isfile(self.parent().main_win.air_case_file_path):
                air_case_path = self.parent().main_win.air_case_file_path[
                                :self.parent().main_win.air_case_file_path.rfind('\\')]
        if air_case_path and os.path.exists(air_case_path):
            pic_path = os.path.join(air_case_path, pic_name)
        else:
            pic_path = pic_name
        print(f"截图保存路径:{pic_path}")
        center_pos = (int((self.parent().screenArea._pt_start.x() + self.parent().screenArea._pt_end.x()) / 2),
                      int((self.parent().screenArea._pt_start.y() + self.parent().screenArea._pt_end.y()) / 2))
        record_pos, resolution = get_record_pos(center_pos)
        self.parent().save_to_local(pic_path)
        code_list = [f'Template(r"{pic_name}"']
        if self.get_threshold() != ST.THRESHOLD:  # 添加threshold参数
            code_list.append(f' threshold={self.get_threshold()}')
        if self.set_rgb.isChecked():  # 添加rgb设置
            code_list.append(' rgb=True')
        if self.location != 5:  # 添加target location设置
            code_list.append(f' target_pos={self.location}')
        code_list += [f' record_pos={record_pos}', f' resolution={resolution})']
        code_name = ",".join(code_list)
        if self.mode:  # 添加模式代码
            if 'assert' in self.mode:  # 添加断言信息
                code_name = ''.join([self.mode, '(', code_name, ',"请输入测试点")'])
            elif self.mode == 'touch' and self.right_click is True:
                code_name = ''.join([self.mode, '(', code_name, ',"right_click=True")'])
            else:
                code_name = ''.join([self.mode, '(', code_name, ')'])
        print(f"执行代码拷贝到剪切板:{code_name}")
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, code_name)
        win32clipboard.CloseClipboard()
        self.code_name.emit(code_name)
        self.mode = None
        return pic_path

截图实时调试

实际就是在截图区域存在时调用图像识别,这里我们需要支持多目标图像识别以便更好地定位干扰项,函数功能airtest也实现好了直接调用就可以用,最后通过paintevent绘制到屏幕上。

    def paint_center_area(self, center_rect_f):
        """绘制已选定的截图区域"""
        self.painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)  # 反走样
        # 在截图区域左上角显示截图区域宽高
        if center_rect_f.topLeft().y() > 20:
            label_pos = center_rect_f.topLeft() + QPointF(5, -5)
        else:  # 拖拽截图区域到贴近屏幕上边缘时“宽x高”移动到截图区域左上角的下侧
            label_pos = center_rect_f.topLeft() + QPointF(5, 15)
        center_physical_rect = self.screenArea.center_physical_rect_f().toRect()
        self.painter.setPen(self.pen_white)
        self.painter.setFont(self.font_normal)
        if not self.toolbar.set_debug.isChecked():  # 非debug模式,显示选取区域外轮廓,显示十字线和取点位置
            self.painter.drawText(label_pos, '%s x %s' % (center_physical_rect.width(), center_physical_rect.height()))
            # 1.绘制矩形线框
            self.painter.setPen(self.pen_DashLine_lightBlue)
            self.painter.drawRect(center_rect_f)
            # 2.绘制十字线
            half_width = center_rect_f.width() / 2
            half_height = center_rect_f.height() / 2
            self.painter.setPen(self.pen_SolidLine_lightBlue)
            self.painter.drawLine(QPointF(center_rect_f.x(), center_rect_f.y() + half_height),
                                  QPointF(center_rect_f.width() + center_rect_f.x(), center_rect_f.y() + half_height))
            self.painter.drawLine(QPointF(center_rect_f.x() + half_width, center_rect_f.y()),
                                  QPointF(center_rect_f.x() + half_width, center_rect_f.height() + center_rect_f.y()))
            # 3.绘制矩形线框4个端点和4条边框的中间点
            if center_rect_f.width() >= 30 and center_rect_f.height() >= 30:
                dot_radius = QPointF(2.5, 2.5)  # 椭圆点
                if center_rect_f.width() >= 300 and center_rect_f.height() >= 300:
                    dot_radius = QPointF(3, 3)  # 椭圆点
                center_point = QPointF(
                    int(center_rect_f.x() + center_rect_f.width() / 2),
                    int(center_rect_f.y() + center_rect_f.height() / 2))
                points = [  # 点坐标
                    center_rect_f.topLeft(), self.screenArea.center_top_mid(), center_rect_f.topRight(),
                    self.screenArea.center_left_mid(), center_point, self.screenArea.center_right_mid(),
                    center_rect_f.bottomLeft(), self.screenArea.center_bottom_mid(), center_rect_f.bottomRight()
                ]
                count = 0
                for point in points:
                    count += 1
                    if count == self.toolbar.location:
                        self.painter.setBrush(QColor(255, 0, 0))
                    else:
                        self.painter.setBrush(self.color_lightBlue)
                    self.painter.drawEllipse(QRectF(point - dot_radius, point + dot_radius))
        else:  # debug模式,图像识别
            if not self.isCapturing and not self.isAdjusting and not self.isMoving:  # 静止时执行图像识别
                pic = self.screenArea.center_physical_pixmap().toImage()
                pic = self.convert_q_image_to_mat(pic)
                target = MatchPicture(pic, threshold=self.img_threshold.value(), rgb=self.toolbar.set_rgb.isChecked())
                result_pos = label_pos + QPointF(80, 0)
                if not self.toolbar.match_all_mode.isChecked():
                    result = target._cv_match(screenshot(""))
                    if result:
                        p1 = QPointF(result['rectangle'][0][0], result['rectangle'][0][1])
                        p2 = QPointF(result['rectangle'][2][0], result['rectangle'][2][1])
                        result_pos = p2 + QPointF(5, 0)
                        self.painter.setPen(QPen(QColor(0, 255, 0)))
                        self.painter.drawRect(self.screenArea.normalize_rect_f(p1, p2))
                        self.painter.drawText(result_pos, str(result['confidence'])[:4])
                    else:
                        self.painter.drawText(result_pos, "未找到目标")
                else:
                    result = target._find_all_template(target._imread(), screenshot(""))
                    if result:
                        for num in range(len(result)):
                            if num == 0:
                                self.painter.setPen(QPen(QColor(0, 255, 0)))
                            else:
                                self.painter.setPen(QPen(QColor(0, 145, 198)))
                            p1 = QPointF(result[num]['rectangle'][0][0], result[num]['rectangle'][0][1])
                            p2 = QPointF(result[num]['rectangle'][2][0], result[num]['rectangle'][2][1])
                            self.painter.drawRect(self.screenArea.normalize_rect_f(p1, p2))
                            result_pos = p2 + QPointF(5, 0)
                            self.painter.drawText(result_pos, str(result[num]['confidence'])[:4])
                    else:
                        self.painter.drawText(result_pos, "未找到目标")

源码

截图参考文章:用pyqt原生功能实现自由屏幕截图

CaptureScreen类的main_win变量是接入到主程序后用来隐藏程序主窗口的,可根据实际需求选择是否传入变量。
我的程序是用来测试Windows QT客户端的,所以device我写死了,请根据实际修改。

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import os
import sys
import time

import numpy as np
import win32api
import win32clipboard
import win32con
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QRectF, QRect, QSizeF, QPointF, QPoint, QMarginsF, pyqtSignal, Qt
from PyQt5.QtGui import QPainter, QPen, QPixmap, QColor, QIcon
from PyQt5.QtWidgets import *
from airtest.core.api import auto_setup, touch
from airtest.core.cv import Template
from airtest.core.settings import Settings as ST
from airtest.core.win.screen import screenshot


class MatchPicture(Template):
    """
    继承Template类,支持内存图像识别
    """

    def _imread(self):
        return self.filename  # 不做imread处理,实际返回pic,传参为screenshot内存截图结果


def get_record_pos(pos):
    """
    中心坐标转换

    :param pos: 截图中心点
    :return: record_pos,resolution
    """
    w, h = win32api.GetSystemMetrics(win32con.SM_CXSCREEN), win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
    delta_x = round((pos[0] - w * 0.5) / w, 3)
    delta_y = round((pos[1] - h * 0.5) / w, 3)
    return (delta_x, delta_y), (w, h)


def default_pic_name():
    """
    时间戳命名

    :return:
    """
    pic_name = f'tpl{int(time.time() * 1000)}.png'
    return pic_name


class ScreenShotToolBar(QToolBar):
    """截图区域工具条"""
    code_name = pyqtSignal(str)

    def __init__(self, parent):
        super().__init__(parent)
        self.setToolButtonStyle(QtCore.Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
        self.setStyleSheet("QToolBar {border-radius: 5px;padding: 3px;background-color: #eeeeef;}")
        self.mode = None  # 截图模式标志位
        self.set_target_location()
        self.addSeparator()
        self.touch = QAction(QIcon(r"icon\touch.png"), 'touch', self)
        self.menu_touch_settings = QMenu()
        self.touch_after_save = QAction('do touch', self)
        self.touch_after_save.setCheckable(True)
        self.menu_touch_settings.addAction(self.touch_after_save)
        self.right_click = QAction('right click', self)
        self.right_click.setCheckable(True)
        self.menu_touch_settings.addAction(self.right_click)
        self.touch.setMenu(self.menu_touch_settings)
        self.touch.triggered.connect(self.touch_pic)
        self.addAction(self.touch)
        self.wait = QAction(QIcon(r"icon\wait.png"), 'wait', self)
        self.wait.triggered.connect(self.wait_pic)
        self.addAction(self.wait)
        self.exists = QAction(QIcon(r"icon\exists.png"), 'exists', self)
        self.exists.triggered.connect(self.exists_pic)
        self.addAction(self.exists)
        self.assert_exists = QAction(QIcon(r"icon\assert_exists.png"), 'assert\nexists', self)
        self.assert_exists.triggered.connect(self.assert_exists_pic)
        self.addAction(self.assert_exists)
        self.assert_not_exists = QAction(QIcon(r"icon\assert_not_exists.png"), 'assert\nnot exists', self)
        self.assert_not_exists.triggered.connect(self.assert_not_exists_pic)
        self.addAction(self.assert_not_exists)
        self.touch_scroll = QAction(QIcon(r"icon\touch_scroll.png"), 'touch scroll', self)
        self.touch_scroll.triggered.connect(self.touch_scroll_pic)
        self.addAction(self.touch_scroll)
        self.FindAll = QAction(QIcon(r"icon\FindAll.png"), 'FindAll', self)
        self.FindAll.triggered.connect(self.find_all_pic)
        self.addAction(self.FindAll)
        self.save = QAction(QIcon(r"icon\save.png"), '保存', self)
        self.save.triggered.connect(self.before_save)
        self.addAction(self.save)
        self.addSeparator()
        self.set_rgb = QAction("RGB", self)
        self.set_rgb.setCheckable(True)
        self.addAction(self.set_rgb)
        self.set_location = QAction("target\nlocation", self)
        self.set_location.setMenu(self.menu_location_settings)
        self.set_location.triggered.connect(self.update_location)
        self.addAction(self.set_location)
        self.set_threshold = QAction('threshold', self)
        self.set_threshold.setCheckable(True)
        self.set_threshold.triggered.connect(self.update_threshold)
        self.addAction(self.set_threshold)
        self.set_debug = QAction('debug', self)
        self.menu_debug_settings = QMenu()
        self.match_all_mode = QAction('match all', self)
        self.match_all_mode.setCheckable(True)
        self.menu_debug_settings.addAction(self.match_all_mode)
        self.set_debug.setMenu(self.menu_debug_settings)
        self.set_debug.setCheckable(True)
        self.set_debug.triggered.connect(self.open_debug_mode)
        self.addAction(self.set_debug)
        self.addAction(QAction('退出', self, triggered=self.parent().close))

    def update_threshold(self):
        if not self.set_threshold.isChecked():
            self.parent().img_threshold.setValue(0.70)

    def get_threshold(self):
        """
        获取threshold输入框数值
        """
        return round(self.parent().img_threshold.value(), 2)

    def set_target_location(self):
        self.menu_location_settings = QMenu()
        self.loc_1 = QAction('1', self)
        self.loc_1.setCheckable(True)
        self.loc_1.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_1)
        self.loc_2 = QAction('2', self)
        self.loc_2.setCheckable(True)
        self.loc_2.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_2)
        self.loc_3 = QAction('3', self)
        self.loc_3.setCheckable(True)
        self.loc_3.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_3)
        self.loc_4 = QAction('4', self)
        self.loc_4.setCheckable(True)
        self.loc_4.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_4)
        self.loc_5 = QAction('5', self)
        self.loc_5.setCheckable(True)
        self.loc_5.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_5)
        self.loc_6 = QAction('6', self)
        self.loc_6.setCheckable(True)
        self.loc_6.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_6)
        self.loc_7 = QAction('7', self)
        self.loc_7.setCheckable(True)
        self.loc_7.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_7)
        self.loc_8 = QAction('8', self)
        self.loc_8.setCheckable(True)
        self.loc_8.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_8)
        self.loc_9 = QAction('9', self)
        self.loc_9.setCheckable(True)
        self.loc_9.triggered.connect(self.update_location)
        self.menu_location_settings.addAction(self.loc_9)
        self.loc_action_group = QActionGroup(self)
        loc_action_list = [self.loc_1, self.loc_2, self.loc_3, self.loc_4, self.loc_5, self.loc_6, self.loc_7,
                           self.loc_8, self.loc_9]
        for action in loc_action_list:
            self.loc_action_group.addAction(action)
        self.loc_action_group.setExclusive(True)
        self.location = 5  # 初始化
        self.loc_5.setChecked(True)

    def update_location(self):
        if self.sender() in self.loc_action_group.actions():  # 根据信号发送来源进行按钮状态更新
            self.location = int(self.sender().text())
            self.parent().repaint()  # 立即重绘
        else:
            self.location = 5
            self.loc_5.setChecked(True)

    def touch_pic(self):
        self.mode = "touch"
        pic = self.before_save()
        if self.touch_after_save.isChecked():
            auto_setup(__file__, devices=['Windows:///'])
            touch(Template(pic, threshold=self.get_threshold(), target_pos=self.location, rgb=self.set_rgb.isChecked()),
                  right_click=self.right_click.isChecked())

    def wait_pic(self):
        self.mode = "wait"
        self.before_save()

    def exists_pic(self):
        self.mode = "exists"
        self.before_save()

    def assert_exists_pic(self):
        self.mode = "assert_exists"
        self.before_save()

    def assert_not_exists_pic(self):
        self.mode = "assert_not_exists"
        self.before_save()

    def touch_scroll_pic(self):
        self.mode = "touch_scroll"
        self.before_save()

    def find_all_pic(self):
        self.mode = "FindAll"
        self.before_save()

    def open_debug_mode(self):
        if self.set_debug.isChecked():
            self.parent().repaint()  # 立即重绘
            auto_setup(__file__, devices=['Windows:///'])

    def before_save(self):
        pic_name = default_pic_name()
        air_case_path = ""  # 根据实际修改目标路径
        if self.parent().main_win:
            if self.parent().main_win.edit_mode and os.path.isfile(self.parent().main_win.air_case_file_path):
                air_case_path = self.parent().main_win.air_case_file_path[
                                :self.parent().main_win.air_case_file_path.rfind('\\')]
        if air_case_path and os.path.exists(air_case_path):
            pic_path = os.path.join(air_case_path, pic_name)
        else:
            pic_path = pic_name
        print(f"截图保存路径:{pic_path}")
        center_pos = (int((self.parent().screenArea._pt_start.x() + self.parent().screenArea._pt_end.x()) / 2),
                      int((self.parent().screenArea._pt_start.y() + self.parent().screenArea._pt_end.y()) / 2))
        record_pos, resolution = get_record_pos(center_pos)
        self.parent().save_to_local(pic_path)
        code_list = [f'Template(r"{pic_name}"']
        if self.get_threshold() != ST.THRESHOLD:  # 添加threshold参数
            code_list.append(f' threshold={self.get_threshold()}')
        if self.set_rgb.isChecked():  # 添加rgb设置
            code_list.append(' rgb=True')
        if self.location != 5:  # 添加target location设置
            code_list.append(f' target_pos={self.location}')
        code_list += [f' record_pos={record_pos}', f' resolution={resolution})']
        code_name = ",".join(code_list)
        if self.mode:  # 添加模式代码
            if 'assert' in self.mode:  # 添加断言信息
                code_name = ''.join([self.mode, '(', code_name, ',"请输入测试点")'])
            elif self.mode == 'touch' and self.right_click is True:
                code_name = ''.join([self.mode, '(', code_name, ',"right_click=True")'])
            else:
                code_name = ''.join([self.mode, '(', code_name, ')'])
        print(f"执行代码拷贝到剪切板:{code_name}")
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardData(win32clipboard.CF_UNICODETEXT, code_name)
        win32clipboard.CloseClipboard()
        self.code_name.emit(code_name)
        self.mode = None
        return pic_path

    def enterEvent(self, event):
        self.parent().setCursor(QtCore.Qt.CursorShape.ArrowCursor)  # 工具条上显示标准箭头cursor

    def leaveEvent(self, event):
        self.parent().setCursor(QtCore.Qt.CursorShape.CrossCursor)  # 十字无箭头


class ScreenArea(QtCore.QObject):
    """屏幕区域(提供各种算法的核心类),划分为9个子区域:
    TopLeft,Top,TopRight
    Left,Center,Right
    BottomLeft,Bottom,BottomRight
    其中Center根据start、end两个QPointF确定
    """

    def __init__(self, god):
        super().__init__()
        self.god = god
        self._pt_start = QPointF()  # 划定截图区域时鼠标左键按下的位置(topLeft)
        self._pt_end = QPointF()  # 划定截图区域时鼠标左键松开的位置(bottomRight)
        self._rt_toolbar = QRectF()  # 工具条的矩形
        self._painter = QPainter()  # 独立于ScreenShotWidget之外的画家类
        self.screen_capture()

    def screen_capture(self):
        """抓取整个屏幕的截图"""
        self._screenPixmap = QApplication.primaryScreen().grabWindow(QApplication.desktop().winId())
        self._pixelRatio = self._screenPixmap.devicePixelRatio()  # 设备像素比
        self._rt_screen = self.screen_logical_rect_f()
        self.remake_night_area()

    @staticmethod
    def normalize_rect_f(top_left_point, bottom_right_point):
        """根据起止点生成宽高非负数的QRectF,通常用于bottom_right_point比top_left_point更左更上的情况
        入参可以是QPoint或QPointF"""
        rect_field = QRectF(top_left_point, bottom_right_point)
        x = rect_field.x()
        y = rect_field.y()
        w = rect_field.width()
        h = rect_field.height()
        if w < 0:  # bottom_right_point在top_left_point左侧时,top_left_point往左移动
            x = x + w
            w = -w
        if h < 0:  # bottom_right_point在top_left_point上侧时,top_left_point往上移动
            y = y + h
            h = -h
        return QRectF(x, y, w, h)

    def physical_rect_f(self, rect_field):
        """计算划定的截图区域的(缩放倍率1.0的)原始矩形(会变大)
        rect_field:划定的截图区域的矩形。可为QRect或QRectF"""
        return QRectF(rect_field.x() * self._pixelRatio, rect_field.y() * self._pixelRatio,
                      rect_field.width() * self._pixelRatio, rect_field.height() * self._pixelRatio)

    def logical_rect_f(self, physical_rect_f):
        """根据原始矩形计算缩放后的矩形(会变小)
        physical_rect_f:缩放倍率1.0的原始矩形。可为QRect或QRectF"""
        return QRectF(physical_rect_f.x() / self._pixelRatio, physical_rect_f.y() / self._pixelRatio,
                      physical_rect_f.width() / self._pixelRatio, physical_rect_f.height() / self._pixelRatio)

    def physical_pixmap(self, rect_field):
        """根据指定区域获取其原始大小的(缩放倍率1.0的)QPixmap
        rect_field:指定区域。可为QRect或QRectF
        """
        return self._screenPixmap.copy(self.physical_rect_f(rect_field).toRect())

    def screen_physical_rect_f(self):
        return QRectF(self._screenPixmap.rect())

    def screen_logical_rect_f(self):
        return QRectF(QPointF(0, 0), self.screen_logical_size_f())  # 即当前屏幕显示的大小

    def screen_physical_size_f(self):
        return QSizeF(self._screenPixmap.size())

    def screen_logical_size_f(self):
        return QSizeF(self._screenPixmap.width() / self._pixelRatio, self._screenPixmap.height() / self._pixelRatio)

    def screen_physical_pixmap_copy(self):
        return self._screenPixmap.copy()

    def screen_logical_pixmap_copy(self):
        return self._screenPixmap.scaled(self.screen_logical_size_f().toSize())

    def center_physical_rect_f(self):
        return self.physical_rect_f(self._rt_center)

    def center_logical_rect_f(self):
        """根据屏幕上的start、end两个QPointF确定"""
        return self._rt_center

    def center_physical_pixmap(self):
        """
        截图区域的QPixmap
        """
        return self.physical_pixmap(self._rt_center + QMarginsF(-1, -1, 1, 1))

    def center_top_mid(self):
        return self._pt_centerTopMid

    def center_bottom_mid(self):
        return self._pt_centerBottomMid

    def center_left_mid(self):
        return self._pt_centerLeftMid

    def center_right_mid(self):
        return self._pt_centerRightMid

    def set_start_point(self, pos, remake=False):
        self._pt_start = pos
        if remake:
            self.remake_night_area()

    def set_end_point(self, pos, remake=False):
        self._pt_end = pos
        if remake:
            self.remake_night_area()

    def set_center_area(self, start, end):
        self._pt_start = start
        self._pt_end = end
        self.remake_night_area()

    def remake_night_area(self):
        """重新划分九宫格区域。根据中央截图区域计算出来的其他8个区域,截图区域四个边框中点坐标等都是logical的"""
        self._rt_center = self.normalize_rect_f(self._pt_start, self._pt_end)
        # 中央区域边框的中点,用于调整大小
        self._pt_centerTopMid = (self._rt_center.topLeft() + self._rt_center.topRight()) / 2
        self._pt_centerBottomMid = (self._rt_center.bottomLeft() + self._rt_center.bottomRight()) / 2
        self._pt_centerLeftMid = (self._rt_center.topLeft() + self._rt_center.bottomLeft()) / 2
        self._pt_centerRightMid = (self._rt_center.topRight() + self._rt_center.bottomRight()) / 2
        # 以截图区域左上、上中、右上、左中、右中、左下、下中、右下为中心的正方形区域,用于调整大小
        self._square_topLeft = self.square_area_by_center(self._rt_center.topLeft())
        self._square_topRight = self.square_area_by_center(self._rt_center.topRight())
        self._square_bottomLeft = self.square_area_by_center(self._rt_center.bottomLeft())
        self._square_bottomRight = self.square_area_by_center(self._rt_center.bottomRight())
        self._square_topMid = self.square_area_by_center(self._pt_centerTopMid)
        self._square_bottomMid = self.square_area_by_center(self._pt_centerBottomMid)
        self._square_leftMid = self.square_area_by_center(self._pt_centerLeftMid)
        self._square_rightMid = self.square_area_by_center(self._pt_centerRightMid)
        # 除中央截图区域外的8个区域
        self._rt_topLeft = QRectF(self._rt_screen.topLeft(), self._rt_center.topLeft())
        self._rt_top = QRectF(QPointF(self._rt_center.topLeft().x(), 0), self._rt_center.topRight())
        self._rt_topRight = QRectF(QPointF(self._rt_center.topRight().x(), 0),
                                   QPointF(self._rt_screen.width(), self._rt_center.topRight().y()))
        self._rt_left = QRectF(QPointF(0, self._rt_center.topLeft().y()), self._rt_center.bottomLeft())
        self._rt_right = QRectF(self._rt_center.topRight(),
                                QPointF(self._rt_screen.width(), self._rt_center.bottomRight().y()))
        self._rt_bottomLeft = QRectF(QPointF(0, self._rt_center.bottomLeft().y()),
                                     QPointF(self._rt_center.bottomLeft().x(), self._rt_screen.height()))
        self._rt_bottom = QRectF(self._rt_center.bottomLeft(),
                                 QPointF(self._rt_center.bottomRight().x(), self._rt_screen.height()))
        self._rt_bottomRight = QRectF(self._rt_center.bottomRight(), self._rt_screen.bottomRight())

    @staticmethod
    def square_area_by_center(pos):
        """以QPointF为中心的正方形QRectF"""
        rect_field = QRectF(0, 0, 15, 15)
        rect_field.moveCenter(pos)
        return rect_field

    def around_area_in_8_direction(self):
        """中央区域周边的8个方向的区域(无交集)"""
        return [self._rt_topLeft, self._rt_top, self._rt_topRight,
                self._rt_left, self._rt_right,
                self._rt_bottomLeft, self._rt_bottom, self._rt_bottomRight]

    def around_area_in_4_direction(self):
        """中央区域周边的4个方向的区域(有交集)
        上区域(左上、上、右上):0, 0, maxX, topRight.y
        下区域(左下、下、右下):0, bottomLeft.y, maxX, maxY-bottomLeft.y
        左区域(左上、左、左下):0, 0, bottomLeft.x, maxY
        右区域(右上、右、右下):topRight.x, 0, maxX - topRight.x, maxY"""
        screen_size_f = self.screen_logical_size_f()
        pt_top_right = self._rt_center.topRight()
        pt_bottom_left = self._rt_center.bottomLeft()
        return [QRectF(0, 0, screen_size_f.width(), pt_top_right.y()),
                QRectF(0, pt_bottom_left.y(), screen_size_f.width(), screen_size_f.height() - pt_bottom_left.y()),
                QRectF(0, 0, pt_bottom_left.x(), screen_size_f.height()),
                QRectF(pt_top_right.x(), 0, screen_size_f.width() - pt_top_right.x(), screen_size_f.height())]

    def around_area_without_intersection(self):
        """中央区域周边的4个方向的区域(无交集)
        上区域(左上、上、右上):0, 0, maxX, topRight.y
        下区域(左下、下、右下):0, bottomLeft.y, maxX, maxY-bottomLeft.y
        左区域(左):0, topRight.y, bottomLeft.x-1, center.height
        右区域(右):topRight.x+1, topRight.y, maxX - topRight.x, center.height"""
        screen_size_f = self.screen_logical_size_f()
        pt_top_right = self._rt_center.topRight()
        pt_bottom_left = self._rt_center.bottomLeft()
        center_height = pt_bottom_left.y() - pt_top_right.y()
        return [QRectF(0, 0, screen_size_f.width(), pt_top_right.y()),
                QRectF(0, pt_bottom_left.y(), screen_size_f.width(), screen_size_f.height() - pt_bottom_left.y()),
                QRectF(0, pt_top_right.y(), pt_bottom_left.x() - 1, center_height),
                QRectF(pt_top_right.x() + 1, pt_top_right.y(), screen_size_f.width() - pt_top_right.x(), center_height)]

    def set_begin_drag_point(self, pos):
        """计算开始拖拽位置距离截图区域左上角的向量"""
        self._drag_vector = pos - self._rt_center.topLeft()

    def get_new_pos_after_drag(self, pos):
        """计算拖拽后截图区域左上角的新位置"""
        return pos - self._drag_vector

    def move_center_area_to(self, pos):
        """限制拖拽不能超出屏幕范围"""
        self._rt_center.moveTo(self.get_new_pos_after_drag(pos))
        start_point_f = self._rt_center.topLeft()
        if start_point_f.x() < 0:
            self._rt_center.moveTo(0, start_point_f.y())
            start_point_f = self._rt_center.topLeft()
        if start_point_f.y() < 0:
            self._rt_center.moveTo(start_point_f.x(), 0)
        screen_size_f = self.screen_logical_size_f()
        end_point_f = self._rt_center.bottomRight()
        if end_point_f.x() > screen_size_f.width():
            self._rt_center.moveBottomRight(QPointF(screen_size_f.width(), end_point_f.y()))
            end_point_f = self._rt_center.bottomRight()
        if end_point_f.y() > screen_size_f.height():
            self._rt_center.moveBottomRight(QPointF(end_point_f.x(), screen_size_f.height()))
        self.set_center_area(self._rt_center.topLeft(), self._rt_center.bottomRight())

    def set_begin_adjust_point(self, pos):
        """判断开始调整截图区域大小时鼠标左键在哪个区(不可能是中央区域),用于判断调整大小的意图方向"""
        self._mousePos = self.get_mouse_pos_by(pos)

    def get_mouse_pos_by(self, pos):
        if self._square_topLeft.contains(pos):
            return 'TL'
        elif self._square_topMid.contains(pos):
            return 'T'
        elif self._square_topRight.contains(pos):
            return 'TR'
        elif self._square_leftMid.contains(pos):
            return 'L'
        elif self._rt_center.contains(pos):
            return 'CENTER'
        elif self._square_rightMid.contains(pos):
            return 'R'
        elif self._square_bottomLeft.contains(pos):
            return 'BL'
        elif self._square_bottomMid.contains(pos):
            return 'B'
        elif self._square_bottomRight.contains(pos):
            return 'BR'
        else:
            return 'ERROR'

    def adjust_center_area_by(self, pos):
        """根据开始调整截图区域大小时鼠标左键在哪个区(不可能是中央区域),判断调整大小的意图方向,判定开始、结束位置"""
        start_point_f = self._rt_center.topLeft()
        end_point_f = self._rt_center.bottomRight()
        if self._mousePos == 'TL':
            start_point_f = pos
        elif self._mousePos == 'T':
            start_point_f = QPointF(start_point_f.x(), pos.y())
        elif self._mousePos == 'TR':
            start_point_f = QPointF(start_point_f.x(), pos.y())
            end_point_f = QPointF(pos.x(), end_point_f.y())
        elif self._mousePos == 'L':
            start_point_f = QPointF(pos.x(), start_point_f.y())
        elif self._mousePos == 'R':
            end_point_f = QPointF(pos.x(), end_point_f.y())
        elif self._mousePos == 'BL':
            start_point_f = QPointF(pos.x(), start_point_f.y())
            end_point_f = QPointF(end_point_f.x(), pos.y())
        elif self._mousePos == 'B':
            end_point_f = QPointF(end_point_f.x(), pos.y())
        elif self._mousePos == 'BR':
            end_point_f = pos
        else:  # 'ERROR'
            return
        new_rect_f = self.normalize_rect_f(start_point_f, end_point_f)
        self.set_center_area(new_rect_f.topLeft(), new_rect_f.bottomRight())

    def get_mouse_shape_by(self, pos):
        """根据鼠标位置返回对应的鼠标样式"""
        if self._rt_center.contains(pos):
            return QtCore.Qt.CursorShape.SizeAllCursor  # 十字有箭头
            # return QtCore.Qt.CursorShape.OpenHandCursor  # 打开的手,表示可拖拽
        elif self._square_topLeft.contains(pos) or self._square_bottomRight.contains(pos):
            return QtCore.Qt.CursorShape.SizeFDiagCursor  # ↖↘
        elif self._square_topMid.contains(pos) or self._square_bottomMid.contains(pos):
            return QtCore.Qt.CursorShape.SizeVerCursor  # ↑↓
        elif self._square_topRight.contains(pos) or self._square_bottomLeft.contains(pos):
            return QtCore.Qt.CursorShape.SizeBDiagCursor  # ↙↗
        elif self._square_leftMid.contains(pos) or self._square_rightMid.contains(pos):
            return QtCore.Qt.CursorShape.SizeHorCursor  # ←→
        else:
            return QtCore.Qt.CursorShape.CrossCursor  # 十字无箭头

    def is_mouse_pos_in_center_rect_f(self, pos):
        return self._rt_center.contains(pos)

    def paint_magnifying_glass_pixmap(self, pos, glass_size):
        """绘制放大镜内的图像(含纵横十字线)
        pos:鼠标光标位置
        glass_size:放大镜边框大小"""
        pixmap_rect = QRect(0, 0, 20, 20)  # 以鼠标光标为中心的正方形区域,最好是偶数
        pixmap_rect.moveCenter(pos)
        glass_pixmap = self.physical_pixmap(pixmap_rect)
        glass_pixmap.setDevicePixelRatio(1.0)
        glass_pixmap = glass_pixmap.scaled(glass_size, glass_size, QtCore.Qt.AspectRatioMode.KeepAspectRatio)
        # 在放大后的QPixmap上画纵横十字线
        self._painter.begin(glass_pixmap)
        half_width = glass_pixmap.width() / 2
        half_height = glass_pixmap.height() / 2
        self._painter.setPen(self.god.pen_SolidLine_lightBlue)
        self._painter.drawLine(QPointF(0, half_height), QPointF(glass_pixmap.width(), half_height))
        self._painter.drawLine(QPointF(half_width, 0), QPointF(half_width, glass_pixmap.height()))
        self._painter.end()
        return glass_pixmap


class CaptureScreen(QWidget):
    def __init__(self, main_win=None):
        super().__init__()
        self.main_win = main_win
        self.setMouseTracking(True)
        self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.init_painter_tool()
        self.init_functional_flag()
        self.screenArea = ScreenArea(self)
        self.toolbar = ScreenShotToolBar(self)
        self.img_threshold = QDoubleSpinBox()
        self.init_threshold_input()
        layout = QVBoxLayout()
        layout.addWidget(self.img_threshold)
        self.setLayout(layout)
        # 设置 screenPixmap 为窗口背景
        # palette = QtGui.QPalette()
        # palette.setBrush(QtGui.QPalette.ColorRole.Window, QtGui.QBrush(self.screenArea.screen_physical_pixmap_copy()))
        # self.setPalette(palette)

    def closeEvent(self, event):
        if self.main_win:
            self.main_win.show()
        super().closeEvent(event)

    def start(self):
        self.screenArea.screen_capture()
        self.setGeometry(self.screenArea.screen_physical_rect_f().toRect())
        self.clear_screen_shot_area()
        self.showFullScreen()
        self.toolbar.update_location()
        self.toolbar.right_click.setChecked(False)

    def init_painter_tool(self):
        self.painter = QPainter()
        self.color_transparent = QtCore.Qt.GlobalColor.transparent
        self.color_black = QColor(0, 0, 0, 64)  # 黑色背景
        self.color_lightBlue = QColor(30, 120, 255)  # 浅蓝色。深蓝色QtCore.Qt.GlobalColor.blue
        self.font_normal = QtGui.QFont('微软雅黑', 9, QtGui.QFont.Weight.Normal)
        self.pen_transparent = QPen(QtCore.Qt.PenStyle.NoPen)  # 没有笔迹,画不出线条
        self.pen_white = QPen(QtCore.Qt.GlobalColor.white)
        self.pen_SolidLine_lightBlue = QPen(self.color_lightBlue)  # 实线,浅蓝色
        self.pen_SolidLine_lightBlue.setStyle(QtCore.Qt.PenStyle.DashLine)  # 实线SolidLine,虚线DashLine,点线DotLine
        self.pen_SolidLine_lightBlue.setWidthF(0)  # 0表示线宽为1
        self.pen_DashLine_lightBlue = QPen(self.color_lightBlue)  # 虚线,浅蓝色
        self.pen_DashLine_lightBlue.setStyle(QtCore.Qt.PenStyle.DashLine)

    def init_functional_flag(self):
        self.hasScreenShot = False  # 是否已通过拖动鼠标左键划定截图区域
        self.isCapturing = False  # 正在拖动鼠标左键选定截图区域时
        self.isMoving = False  # 在截图区域内拖动时
        self.isAdjusting = False  # 在截图区域的边框按住鼠标左键调整大小时
        self.setCursor(QtCore.Qt.CursorShape.CrossCursor)  # 设置鼠标样式 十字

    def init_threshold_input(self):
        self.img_threshold.setRange(0.0, 1.0)
        self.img_threshold.setValue(0.70)
        self.img_threshold.setDecimals(2)
        self.img_threshold.setSingleStep(0.01)
        self.img_threshold.setMaximumSize(75, 38)
        self.img_threshold.hide()

    def paintEvent(self, event):
        center_rect_f = self.screenArea.center_logical_rect_f()
        screen_size_f = self.screenArea.screen_logical_size_f()
        canvas_pixmap = self.screenArea.screen_physical_pixmap_copy()
        # canvas_pixmap = QPixmap(screen_size_f.toSize())
        # canvas_pixmap.fill(self.color_transparent)
        # 在屏幕截图的副本上绘制已选定的截图区域
        self.painter.begin(canvas_pixmap)
        if self.hasScreenShot:
            if not self.toolbar.set_debug.isChecked():
                self.paint_mask_layer(screen_size_f, full_screen=False)  # 绘制截图区域的周边区域遮罩层
            self.paint_center_area(center_rect_f)  # 绘制中央截图区域
        else:
            self.paint_mask_layer(screen_size_f)
        self.paint_magnifying_glass(screen_size_f)  # 在鼠标光标右下角显示放大镜
        self.paint_toolbar(center_rect_f, screen_size_f)  # 在截图区域右下角显示工具条
        self.paint_threshold_input()
        self.painter.end()
        # 把绘制结果显示到窗口上
        self.painter.begin(self)
        self.painter.drawPixmap(0, 0, canvas_pixmap)  # 从坐标(0, 0)开始绘制
        self.painter.end()

    def paint_center_area(self, center_rect_f):
        """绘制已选定的截图区域"""
        self.painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)  # 反走样
        # 在截图区域左上角显示截图区域宽高
        if center_rect_f.topLeft().y() > 20:
            label_pos = center_rect_f.topLeft() + QPointF(5, -5)
        else:  # 拖拽截图区域到贴近屏幕上边缘时“宽x高”移动到截图区域左上角的下侧
            label_pos = center_rect_f.topLeft() + QPointF(5, 15)
        center_physical_rect = self.screenArea.center_physical_rect_f().toRect()
        self.painter.setPen(self.pen_white)
        self.painter.setFont(self.font_normal)
        if not self.toolbar.set_debug.isChecked():  # 非debug模式,显示选取区域外轮廓,显示十字线和取点位置
            self.painter.drawText(label_pos, '%s x %s' % (center_physical_rect.width(), center_physical_rect.height()))
            # 1.绘制矩形线框
            self.painter.setPen(self.pen_DashLine_lightBlue)
            self.painter.drawRect(center_rect_f)
            # 2.绘制十字线
            half_width = center_rect_f.width() / 2
            half_height = center_rect_f.height() / 2
            self.painter.setPen(self.pen_SolidLine_lightBlue)
            self.painter.drawLine(QPointF(center_rect_f.x(), center_rect_f.y() + half_height),
                                  QPointF(center_rect_f.width() + center_rect_f.x(), center_rect_f.y() + half_height))
            self.painter.drawLine(QPointF(center_rect_f.x() + half_width, center_rect_f.y()),
                                  QPointF(center_rect_f.x() + half_width, center_rect_f.height() + center_rect_f.y()))
            # 3.绘制矩形线框4个端点和4条边框的中间点
            if center_rect_f.width() >= 30 and center_rect_f.height() >= 30:
                dot_radius = QPointF(2.5, 2.5)  # 椭圆点
                if center_rect_f.width() >= 300 and center_rect_f.height() >= 300:
                    dot_radius = QPointF(3, 3)  # 椭圆点
                center_point = QPointF(
                    int(center_rect_f.x() + center_rect_f.width() / 2),
                    int(center_rect_f.y() + center_rect_f.height() / 2))
                points = [  # 点坐标
                    center_rect_f.topLeft(), self.screenArea.center_top_mid(), center_rect_f.topRight(),
                    self.screenArea.center_left_mid(), center_point, self.screenArea.center_right_mid(),
                    center_rect_f.bottomLeft(), self.screenArea.center_bottom_mid(), center_rect_f.bottomRight()
                ]
                count = 0
                for point in points:
                    count += 1
                    if count == self.toolbar.location:
                        self.painter.setBrush(QColor(255, 0, 0))
                    else:
                        self.painter.setBrush(self.color_lightBlue)
                    self.painter.drawEllipse(QRectF(point - dot_radius, point + dot_radius))
        else:  # debug模式,图像识别
            if not self.isCapturing and not self.isAdjusting and not self.isMoving:  # 静止时执行图像识别
                pic = self.screenArea.center_physical_pixmap().toImage()
                pic = self.convert_q_image_to_mat(pic)
                target = MatchPicture(pic, threshold=self.img_threshold.value(), rgb=self.toolbar.set_rgb.isChecked())
                result_pos = label_pos + QPointF(80, 0)
                if not self.toolbar.match_all_mode.isChecked():
                    result = target._cv_match(screenshot(""))
                    if result:
                        p1 = QPointF(result['rectangle'][0][0], result['rectangle'][0][1])
                        p2 = QPointF(result['rectangle'][2][0], result['rectangle'][2][1])
                        result_pos = p2 + QPointF(5, 0)
                        self.painter.setPen(QPen(QColor(0, 255, 0)))
                        self.painter.drawRect(self.screenArea.normalize_rect_f(p1, p2))
                        self.painter.drawText(result_pos, str(result['confidence'])[:4])
                    else:
                        self.painter.drawText(result_pos, "未找到目标")
                else:
                    result = target._find_all_template(target._imread(), screenshot(""))
                    if result:
                        for num in range(len(result)):
                            if num == 0:
                                self.painter.setPen(QPen(QColor(0, 255, 0)))
                            else:
                                self.painter.setPen(QPen(QColor(0, 145, 198)))
                            p1 = QPointF(result[num]['rectangle'][0][0], result[num]['rectangle'][0][1])
                            p2 = QPointF(result[num]['rectangle'][2][0], result[num]['rectangle'][2][1])
                            self.painter.drawRect(self.screenArea.normalize_rect_f(p1, p2))
                            result_pos = p2 + QPointF(5, 0)
                            self.painter.drawText(result_pos, str(result[num]['confidence'])[:4])
                    else:
                        self.painter.drawText(result_pos, "未找到目标")

    @staticmethod
    def convert_q_image_to_mat(q_image):
        """  Converts a QImage into an opencv MAT format  """
        # Format_RGB32 = 4,存入格式为B,G,R,A 对应 0,1,2,3
        # RGB32图像每个像素用32比特位表示,占4个字节,
        # R,G,B分量分别用8个bit表示,存储顺序为B,G,R,最后8个字节保留
        q_image = q_image.convertToFormat(4)
        width = q_image.width()
        height = q_image.height()

        ptr = q_image.bits()
        ptr.setsize(q_image.byteCount())
        arr = np.array(ptr).reshape(height, width, 4)
        return arr

    def paint_mask_layer(self, screen_size_f, full_screen=True):
        if full_screen:  # 全屏遮罩层
            mask_pixmap = QPixmap(screen_size_f.toSize())
            mask_pixmap.fill(self.color_black)
            self.painter.drawPixmap(0, 0, mask_pixmap)
        else:  # 绘制截图区域的周边区域遮罩层,以凸显截图区域
            # 方法一:截图区域以外的8个方向区域
            # for area in self.screenArea.around_area_in_8_direction():
            #     area = area.normalized()
            #     mask_pixmap = QPixmap(area.size().toSize())  # 由于float转int的精度问题,可能会存在黑线条缝隙
            #     mask_pixmap.fill(self.color_black)
            #     self.painter.drawPixmap(area.topLeft(), mask_pixmap)
            # 方法二:截图区域以外的上下左右区域(有交集,交集部分颜色加深,有明显的纵横效果)
            # for area in self.screenArea.around_area_in_4_direction():
            #     mask_pixmap = QPixmap(area.size().toSize())
            #     mask_pixmap.fill(self.color_black)
            #     self.painter.drawPixmap(area.topLeft(), mask_pixmap)
            # 方法三:截图区域以外的上下左右区域(无交集)
            for area in self.screenArea.around_area_without_intersection():
                mask_pixmap = QPixmap(area.size().toSize())
                mask_pixmap.fill(self.color_black)
                self.painter.drawPixmap(area.topLeft(), mask_pixmap)

    def paint_magnifying_glass(self, screen_size_f, glass_size=150, offset=30, label_height=30):
        """未划定截图区域模式时、正在划定截取区域时、调整截取区域大小时在鼠标光标右下角显示放大镜
        glass_size:放大镜正方形边长
        offset:放大镜任意一个端点距离鼠标光标位置的最近距离
        label_height:pos和rgb两行文字的高度"""
        if self.hasScreenShot and (not self.isCapturing) and (not self.isAdjusting):
            return
        pos = QtGui.QCursor.pos()
        glass_pixmap = self.screenArea.paint_magnifying_glass_pixmap(pos, glass_size)  # 画好纵横十字线后的放大镜内QPixmap
        # 限制放大镜显示不超出屏幕外
        glass_rect = glass_pixmap.rect()
        if (pos.x() + glass_size + offset) < screen_size_f.width():
            if (pos.y() + offset + glass_size + label_height) < screen_size_f.height():
                glass_rect.moveTo(pos + QPoint(offset, offset))
            else:
                glass_rect.moveBottomLeft(pos + QPoint(offset, -offset))
        else:
            if (pos.y() + offset + glass_size + label_height) < screen_size_f.height():
                glass_rect.moveTopRight(pos + QPoint(-offset, offset))
            else:
                glass_rect.moveBottomRight(pos + QPoint(-offset, -offset))
        self.painter.drawPixmap(glass_rect.topLeft(), glass_pixmap)
        # 显示pos:(x, y)、rgb:(255,255,255)
        q_rgb = QtGui.QRgba64.fromArgb32(glass_pixmap.toImage().pixel(glass_pixmap.rect().center()))
        label_rect_f = QRectF(glass_rect.bottomLeft().x(), glass_rect.bottomLeft().y(), glass_size, label_height)
        self.painter.setPen(self.pen_transparent)
        self.painter.setBrush(self.color_black)  # 黑底
        self.painter.drawRect(label_rect_f)
        self.painter.setPen(self.pen_white)
        self.painter.setFont(self.font_normal)
        self.painter.drawText(label_rect_f,
                              QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter,
                              'POS:(%s, %s)\nRGB:(%s, %s, %s)' % (
                                  pos.x(), pos.y(), q_rgb.red8(), q_rgb.green8(), q_rgb.blue8()))

    def paint_toolbar(self, center_rect_f, screen_size_f):
        """在截图区域右下角显示工具条"""
        if self.hasScreenShot:
            if self.isCapturing or self.isAdjusting or self.toolbar.set_debug.isChecked():
                self.toolbar.hide()  # 正在划定截取区域时、调整截图区域大小、图像识别debug时不显示工具条
            else:
                self.toolbar.adjustSize()
                self.toolbar_rect_f = QRectF(self.toolbar.rect())
                # 工具条位置优先顺序:右下角下侧,右上角上侧,右下角上侧
                if (screen_size_f.height() - center_rect_f.bottomRight().y()) > self.toolbar_rect_f.height():
                    self.toolbar_rect_f.moveTopRight(center_rect_f.bottomRight() + QPointF(-5, 5))
                elif center_rect_f.topRight().y() > self.toolbar_rect_f.height():
                    self.toolbar_rect_f.moveBottomRight(center_rect_f.topRight() + QPointF(-5, -5))
                else:
                    self.toolbar_rect_f.moveBottomRight(center_rect_f.bottomRight() + QPointF(-5, -5))
                # 限制工具条的x坐标不为负数,不能移出屏幕外
                if self.toolbar_rect_f.x() < 0:
                    pos = self.toolbar_rect_f.topLeft()
                    pos.setX(center_rect_f.x() + 5)
                    self.toolbar_rect_f.moveTo(pos)
                self.toolbar.move(self.toolbar_rect_f.topLeft().toPoint())
                self.toolbar.show()
        else:
            self.toolbar.hide()

    def paint_threshold_input(self):
        """
        threshold输入框
        """
        if self.hasScreenShot:
            if self.isCapturing or self.isAdjusting or self.toolbar.set_debug.isChecked():
                self.img_threshold.hide()
            else:
                if self.toolbar.set_threshold.isChecked():
                    screen_size_f = self.screenArea.screen_logical_size_f()
                    set_threshold_action_f = QRectF(self.toolbar.actionGeometry(self.toolbar.set_threshold))
                    if self.toolbar_rect_f.bottomRight().y() + self.img_threshold.height() < screen_size_f.height():
                        self.img_threshold.move(
                            self.toolbar.mapToGlobal(
                                set_threshold_action_f.bottomLeft().toPoint()))
                    else:
                        self.img_threshold.move(
                            self.toolbar.mapToGlobal(set_threshold_action_f.topLeft().toPoint()) - QPoint(0,
                                                                                                          self.img_threshold.height()))
                    self.img_threshold.show()
                else:
                    self.img_threshold.hide()
        else:
            self.img_threshold.hide()

    def clear_screen_shot_area(self):
        """清空已划定的截取区域"""
        self.hasScreenShot = False
        self.isCapturing = False
        pos = QPointF()
        self.screenArea.set_center_area(pos, pos)
        self.update()
        self.setCursor(QtCore.Qt.CursorShape.CrossCursor)  # 设置鼠标样式 十字

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MouseButton.LeftButton:
            pos = event.pos()
            if self.hasScreenShot:
                if self.screenArea.is_mouse_pos_in_center_rect_f(pos):
                    self.isMoving = True  # 进入拖拽移动模式
                    self.screenArea.set_begin_drag_point(pos)
                else:
                    self.isAdjusting = True  # 进入调整大小模式
                    self.screenArea.set_begin_adjust_point(pos)
            else:
                self.screenArea.set_center_area(pos, pos)
                self.isCapturing = True  # 进入划定截图区域模式
        if event.button() == QtCore.Qt.MouseButton.RightButton:
            if self.toolbar.set_debug.isChecked():  # 处于图像识别debug模式时退出debug模式
                self.toolbar.set_debug.setChecked(False)
                self.update()
            else:
                if self.hasScreenShot or self.isCapturing:  # 清空已划定的的截图区域
                    self.clear_screen_shot_area()
                else:
                    self.close()

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.MouseButton.LeftButton:
            self.isCapturing = False
            self.isMoving = False
            self.isAdjusting = False
            self.toolbar.show()

    def mouseMoveEvent(self, event):
        pos = event.pos()
        if self.isCapturing:
            self.hasScreenShot = True
            self.screenArea.set_end_point(pos, remake=True)
        elif self.isMoving:
            self.screenArea.move_center_area_to(pos)
        elif self.isAdjusting:
            self.screenArea.adjust_center_area_by(pos)
        self.update()
        if self.hasScreenShot:
            self.setCursor(self.screenArea.get_mouse_shape_by(pos))
        else:
            self.setCursor(QtCore.Qt.CursorShape.CrossCursor)  # 设置鼠标样式 十字

    def mouseDoubleClickEvent(self, event):  # 区域内双击直接关闭
        if event.button() == QtCore.Qt.MouseButton.LeftButton:
            if self.screenArea.is_mouse_pos_in_center_rect_f(event.pos()):
                self.close()

    def keyPressEvent(self, QKeyEvent):
        if QKeyEvent.key() == QtCore.Qt.Key.Key_Escape:
            self.close()
        else:
            key = {QtCore.Qt.Key.Key_Return: None, QtCore.Qt.Key_Enter: None, QtCore.Qt.Key_1: "touch",
                   QtCore.Qt.Key_2: "wait", QtCore.Qt.Key_3: "exists", QtCore.Qt.Key_4: "assert_exists",
                   QtCore.Qt.Key_5: "assert_not_exists", QtCore.Qt.Key_6: "touch_scroll", QtCore.Qt.Key_7: "FindAll"}
            if QKeyEvent.key() in key:
                self.toolbar.mode = key.get(QKeyEvent.key())
                if self.toolbar.parent is not None:
                    self.toolbar.before_save()

    def save_to_local(self, pic_path):
        self.screenArea.center_physical_pixmap().save(pic_path, quality=100)
        self.close()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    snapshot_win = CaptureScreen()
    snapshot_win.start()
    sys.exit(app.exec())

结果展示

截图交互
识别结果反馈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值