From 373d1c9400273f95ea56ce7b88059a28de83de0f Mon Sep 17 00:00:00 2001 From: Leif <4603009@qq.com> Date: Wed, 24 Aug 2022 08:44:43 +0800 Subject: [PATCH] add icons --- PDF2WORD.md | 42 ++ ScreenShotWidget.py | 1108 +++++++++++++++++++++++++++++++++++++++++ icons/chinese.png | Bin 0 -> 2670 bytes icons/english.png | Bin 0 -> 2888 bytes icons/folder-open.png | Bin 0 -> 2491 bytes icons/folder-plus.png | Bin 0 -> 2598 bytes image2doc.py | 349 +++++++++++++ 7 files changed, 1499 insertions(+) create mode 100644 PDF2WORD.md create mode 100644 ScreenShotWidget.py create mode 100644 icons/chinese.png create mode 100644 icons/english.png create mode 100644 icons/folder-open.png create mode 100644 icons/folder-plus.png create mode 100644 image2doc.py diff --git a/PDF2WORD.md b/PDF2WORD.md new file mode 100644 index 00000000..382a0feb --- /dev/null +++ b/PDF2WORD.md @@ -0,0 +1,42 @@ +# PDF2WORD + +PDF2WORD是PaddleOCR社区开发者@whj 基于PP-Structure智能文档分析系统实现的PDF转换word应用程序,提供可直接安装的exe,方便windows用户运行 + +
+ +
+ +## 1.使用 + +### 应用程序 + +1. 下载与安装:针对Windows用户,根据[软件下载]()一节下载软件后,运行 `pdf2word.exe` 。若您下载的是lite版本,安装过程中会在线下载环境依赖、模型等必要资源,安装时间较长,请确保网络畅通。serve版本打包了相关依赖,安装时间较短,可按需下载。 + +2. 转换:由于PP-Structure根据中英文数据分别进行适配,在转换相应文件时可**根据文档语言进行相应选择**。 + +### 脚本运行 + +首次运行需要将 + +``` +python pdf2word.py +``` + + + +## 2.自行打包 + +PDF2WORD应用程序通过[QPT](https://github.com/QPT-Family/QPT)工具打包实现,若您修改了界面代码需要重新打包,请在 `ppstructure` 文件夹下运行下方指令 + +``` +cd ./ +mv ./ppstructure/pdf2word .. -r +python GenEXE.py +``` + + + +## 3.软件下载 + +如需获取已打包程序,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群免费获取20G OCR学习大礼包,内含OCR场景应用集合(包含数码管、液晶屏、车牌、高精度SVTR模型等7个垂类模型)、《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料 + diff --git a/ScreenShotWidget.py b/ScreenShotWidget.py new file mode 100644 index 00000000..87d46198 --- /dev/null +++ b/ScreenShotWidget.py @@ -0,0 +1,1108 @@ +# coding=utf-8 +# author: fardeas +# from: https://blog.csdn.net/u010501845/article/details/124931326 +import os +import sys +from datetime import datetime + +from qtpy import QtCore, QtGui +from qtpy.QtWidgets import * +from qtpy.QtCore import * +from qtpy.QtGui import * + + +class TextInputWidget(QTextEdit): + '''在截图区域内的文本输入框''' + + def __init__(self, god=None): + super().__init__(god) + self.god = god + # 设置背景透明 + # self.setStyleSheet("QTextEdit{background-color: transparent;}") + palette = self.palette() + palette.setBrush(QtGui.QPalette.ColorRole.Base, self.god.color_transparent) + self.setPalette(palette) + self.setTextColor(self.god.toolbar.curColor()) + self.setCurrentFont(self.god.toolbar.curFont()) + self._doc = self.document() # QTextDocument + self.textChanged.connect(self.adjustSizeByContent) + self.adjustSizeByContent() # 初始化调整高度为一行 + self.hide() + + def adjustSizeByContent(self, margin=30): + '''限制宽度不超出截图区域,根据文本内容调整高度,不会出现滚动条''' + self._doc.setTextWidth(self.viewport().width()) + margins = self.contentsMargins() + h = int(self._doc.size().height() + margins.top() + margins.bottom()) + self.setFixedHeight(h) + + def beginNewInput(self, pos, endPointF): + '''开始新的文本输入''' + self._maxRect = self.god.screenArea.normalizeRectF(pos, endPointF) + self.waitForInput() + + def waitForInput(self): + self.setGeometry(self._maxRect.toRect()) + # self.setGeometry(self._maxRect.adjusted(0, 0, -1, 0)) # 宽度-1 + self.setFocus() + self.show() + + def loadTextInputBy(self, action): + '''载入修改旧的文本 + action:(type, color, font, rectf, txt)''' + self.setTextColor(action[1]) + self.setCurrentFont(action[2]) + self._maxRect = action[3] + self.append(action[4]) + self.god.isDrawing = True + self.waitForInput() + + +class LineWidthAction(QAction): + + '''画笔粗细选择器''' + + def __init__(self, text, parent, lineWidth): + super().__init__(text, parent) + self._lineWidth = lineWidth + self.refresh(QtCore.Qt.GlobalColor.red) + self.triggered.connect(self.onTriggered) + self.setVisible(False) + + def refresh(self, color): + painter = self.parent().god.screenArea._painter + dotRadius = QPointF(self._lineWidth, self._lineWidth) + centerPoint = self.parent().iconPixmapCenter() + pixmap = self.parent().iconPixmapCopy() + painter.begin(pixmap) + painter.setPen(self.parent().god.pen_transparent) + painter.setBrush(color) + painter.drawEllipse(QRectF(centerPoint - dotRadius, centerPoint + dotRadius)) + painter.end() + self.setIcon(QIcon(pixmap)) + + def onTriggered(self): + self.parent()._curLineWidth = self._lineWidth + + +class FontAction(QAction): + + '''字体选择器''' + + def __init__(self, text, parent): + super().__init__(text, parent) + self.setIcon(QIcon(r"img/sys/font.png")) + self._curFont = self.parent().god.font_textInput + self.triggered.connect(self.onTriggered) + self.setVisible(False) + + def onTriggered(self): + font, ok = QFontDialog.getFont(self._curFont, self.parent(), caption='选择字体') + if ok: + self._curFont = font + self.parent().god.textInputWg.setCurrentFont(font) + + +class ColorAction(QAction): + + '''颜色选择器''' + + def __init__(self, text, parent): + super().__init__(text, parent) + self._curColor = QtCore.Qt.GlobalColor.red + self._pixmap = QPixmap(32, 32) + self.refresh(self._curColor) + self.triggered.connect(self.onTriggered) + + def refresh(self, color): + self._curColor = color + self._pixmap.fill(color) + self.setIcon(QIcon(self._pixmap)) + self.parent()._at_line_small.refresh(color) + self.parent()._at_line_normal.refresh(color) + self.parent()._at_line_big.refresh(color) + + def onTriggered(self): + col = QColorDialog.getColor(self._curColor, self.parent(), title='选择颜色') + if col.isValid(): + self.refresh(col) + self.parent().god.textInputWg.setTextColor(col) + + +class ScreenShotToolBar(QToolBar): + '''截图区域工具条''' + + def __init__(self, god): + super().__init__(god) + self.god = god + self.setToolButtonStyle(QtCore.Qt.ToolButtonStyle.ToolButtonTextUnderIcon) + self.setStyleSheet("QToolBar {border-radius: 5px;padding: 3px;background-color: #eeeeef;}") + self._style_normal = "QToolBar QToolButton{color: black;}" + self._style_selected = "QToolBar QToolButton{color: #ff7300;border: 1px solid #BEDAF2;background-color: #D6E4F1}" # 与鼠标悬停样式一样 + self._iconPixmap = QPixmap(32, 32) + self._iconPixmap.fill(self.god.color_transparent) + self._iconPixmapCenter = QPointF(self._iconPixmap.rect().center()) + self._curLineWidth = 3 + self._at_line_small = LineWidthAction('细', self, self._curLineWidth - 2) + self._at_line_normal = LineWidthAction('中', self, self._curLineWidth) + self._at_line_big = LineWidthAction('粗', self, self._curLineWidth + 2) + self._at_font = FontAction('字体', self) + self._at_color = ColorAction('颜色', self) + # self._at_rectangle = QAction(QIcon(r"img/sys/rectangle.png"), '矩形', self, triggered=self.beforeDrawRectangle) + # self._at_ellipse = QAction(QIcon(r"img/sys/ellipse.png"), '椭圆', self, triggered=self.beforeDrawEllipse) + # self._at_graffiti = QAction(QIcon(r"img/sys/graffiti.png"), '涂鸦', self, triggered=self.beforeDrawGraffiti) + # self._at_textInput = QAction(QIcon(r"img/sys/write.png"), '文字', self, triggered=self.beforeDrawText) + # self.addAction(self._at_line_small) + # self.addAction(self._at_line_normal) + # self.addAction(self._at_line_big) + # self.addAction(self._at_font) + # self.addAction(self._at_color) + # self.addSeparator() + # self.addAction(self._at_rectangle) + # self.addAction(self._at_ellipse) + # self.addAction(self._at_graffiti) + # self.addAction(self._at_textInput) + # self.addAction(QAction(QIcon(r"img/sys/undo.png"), '撤销', self, triggered=self.undo)) + # self.addSeparator() + self.addAction(QAction(QApplication.style().standardIcon(40), + '退出', self, triggered=self.god.rejectSlot)) + self.addAction(QAction(QApplication.style().standardIcon(45), + '接受', self, triggered=self.god.acceptSlot)) + # self.addAction(QAction(QIcon(r"img/chat/download.png"), '保存', self, triggered=lambda: self.beforeSave('local'))) + # self.addAction(QAction(QIcon(r"img/chat/sendImg.png"), '复制', self, triggered=lambda: self.beforeSave('clipboard'))) + # self.actionTriggered.connect(self.onActionTriggered) + + def curLineWidth(self): + return self._curLineWidth + + def curFont(self): + return self._at_font._curFont + + def curColor(self): + return self._at_color._curColor + # return QColor(self._at_color._curColor.toRgb()) # 颜色的副本 + + def iconPixmapCopy(self): + return self._iconPixmap.copy() + + def iconPixmapCenter(self): + return self._iconPixmapCenter + + # def onActionTriggered(self, action): + # '''突出显示已选中的画笔粗细、编辑模式''' + # for at in [self._at_line_small, self._at_line_normal, self._at_line_big]: + # if at._lineWidth == self._curLineWidth: + # self.widgetForAction(at).setStyleSheet(self._style_selected) + # else: + # self.widgetForAction(at).setStyleSheet(self._style_normal) + # if self.god.isDrawRectangle: + # self.widgetForAction(self._at_rectangle).setStyleSheet(self._style_selected) + # else: + # self.widgetForAction(self._at_rectangle).setStyleSheet(self._style_normal) + # if self.god.isDrawEllipse: + # self.widgetForAction(self._at_ellipse).setStyleSheet(self._style_selected) + # else: + # self.widgetForAction(self._at_ellipse).setStyleSheet(self._style_normal) + # if self.god.isDrawGraffiti: + # self.widgetForAction(self._at_graffiti).setStyleSheet(self._style_selected) + # else: + # self.widgetForAction(self._at_graffiti).setStyleSheet(self._style_normal) + # if self.god.isDrawText: + # self.widgetForAction(self._at_textInput).setStyleSheet(self._style_selected) + # else: + # self.widgetForAction(self._at_textInput).setStyleSheet(self._style_normal) + + def setLineWidthActionVisible(self, flag): + self._at_line_small.setVisible(flag) + self._at_line_normal.setVisible(flag) + self._at_line_big.setVisible(flag) + + def beforeDrawRectangle(self): + self.god.clearEditFlags() + self.god.isDrawRectangle = True + self.setLineWidthActionVisible(True) + self._at_font.setVisible(False) + + def beforeDrawEllipse(self): + self.god.clearEditFlags() + self.god.isDrawEllipse = True + self.setLineWidthActionVisible(True) + self._at_font.setVisible(False) + + def beforeDrawGraffiti(self): + self.god.clearEditFlags() + self.god.isDrawGraffiti = True + self.setLineWidthActionVisible(True) + self._at_font.setVisible(False) + + def beforeDrawText(self): + self.god.clearEditFlags() + self.god.isDrawText = True + self.setLineWidthActionVisible(False) + self._at_font.setVisible(True) + + def undo(self): + '''撤销上次编辑行为''' + if self.god.screenArea.undoEditAction(): + self.god.update() + + def beforeSave(self, target): + # 若正在编辑文本未保存,先完成编辑 + if self.god.isDrawing and self.god.isDrawText: + self.god.screenArea.saveTextInputAction() + if target == 'local': + self.god.save2Local() + elif target == 'clipboard': + self.god.save2Clipboard() + + def enterEvent(self, event): + self.god.setCursor(QtCore.Qt.CursorShape.ArrowCursor) # 工具条上显示标准箭头cursor + + def leaveEvent(self, event): + self.god.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._actions = [] # 在截图区域上的所有编辑行为(矩形、椭圆、涂鸦、文本输入等) + self._pt_startEdit = QPointF() # 在截图区域上绘制矩形、椭圆时鼠标左键按下的位置(topLeft) + self._pt_endEdit = QPointF() # 在截图区域上绘制矩形、椭圆时鼠标左键松开的位置(bottomRight) + self._pointfs = [] # 涂鸦经过的所有点 + self._painter = QPainter() # 独立于ScreenShotWidget之外的画家类 + self._textOption = QtGui.QTextOption(QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignTop) + self._textOption.setWrapMode(QtGui.QTextOption.WrapMode.WrapAnywhere) # 文本在矩形内自动换行 + # self._textOption.setWrapMode(QtGui.QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere) + self.captureScreen() + + def captureScreen(self): + '''抓取整个屏幕的截图''' + # screen = QtGui.QGuiApplication.primaryScreen() + self._screenPixmap = QApplication.primaryScreen().grabWindow(0) + self._pixelRatio = self._screenPixmap.devicePixelRatio() # 设备像素比 + self._rt_screen = self.screenLogicalRectF() + self.remakeNightArea() + + def normalizeRectF(self, topLeftPoint, bottomRightPoint): + '''根据起止点生成宽高非负数的QRectF,通常用于bottomRightPoint比topLeftPoint更左更上的情况 + 入参可以是QPoint或QPointF''' + rectf = QRectF(topLeftPoint, bottomRightPoint) + x = rectf.x() + y = rectf.y() + w = rectf.width() + h = rectf.height() + if w < 0: # bottomRightPoint在topLeftPoint左侧时,topLeftPoint往左移动 + x = x + w + w = -w + if h < 0: # bottomRightPoint在topLeftPoint上侧时,topLeftPoint往上移动 + y = y + h + h = -h + return QRectF(x, y, w, h) + + def physicalRectF(self, rectf): + '''计算划定的截图区域的(缩放倍率1.0的)原始矩形(会变大) + rectf:划定的截图区域的矩形。可为QRect或QRectF''' + return QRectF(rectf.x() * self._pixelRatio, rectf.y() * self._pixelRatio, + rectf.width() * self._pixelRatio, rectf.height() * self._pixelRatio) + + def logicalRectF(self, physicalRectF): + '''根据原始矩形计算缩放后的矩形(会变小) + physicalRectF:缩放倍率1.0的原始矩形。可为QRect或QRectF''' + return QRectF(physicalRectF.x() / self._pixelRatio, physicalRectF.y() / self._pixelRatio, + physicalRectF.width() / self._pixelRatio, physicalRectF.height() / self._pixelRatio) + + def physicalPixmap(self, rectf, editAction=False): + '''根据指定区域获取其原始大小的(缩放倍率1.0的)QPixmap + rectf:指定区域。可为QRect或QRectF + editAction:是否带上编辑结果''' + if editAction: + canvasPixmap = self.screenPhysicalPixmapCopy() + self._painter.begin(canvasPixmap) + self.paintEachEditAction(self._painter, textBorder=False) + self._painter.end() + return canvasPixmap.copy(self.physicalRectF(rectf).toRect()) + else: + return self._screenPixmap.copy(self.physicalRectF(rectf).toRect()) + + def screenPhysicalRectF(self): + return QRectF(self._screenPixmap.rect()) + + def screenLogicalRectF(self): + return QRectF(QPointF(0, 0), self.screenLogicalSizeF()) # 即当前屏幕显示的大小 + + def screenPhysicalSizeF(self): + return QSizeF(self._screenPixmap.size()) + + def screenLogicalSizeF(self): + return QSizeF(self._screenPixmap.width() / self._pixelRatio, self._screenPixmap.height() / self._pixelRatio) + + def screenPhysicalPixmapCopy(self): + return self._screenPixmap.copy() + + def screenLogicalPixmapCopy(self): + return self._screenPixmap.scaled(self.screenLogicalSizeF().toSize()) + + def centerPhysicalRectF(self): + return self.physicalRectF(self._rt_center) + + def centerLogicalRectF(self): + '''根据屏幕上的start、end两个QPointF确定''' + return self._rt_center + + def centerPhysicalPixmap(self, editAction=True): + '''截图区域的QPixmap + editAction:是否带上编辑结果''' + return self.physicalPixmap(self._rt_center + QMarginsF(-1, -1, 1, 1), editAction=editAction) + + def centerTopMid(self): + return self._pt_centerTopMid + + def centerBottomMid(self): + return self._pt_centerBottomMid + + def centerLeftMid(self): + return self._pt_centerLeftMid + + def centerRightMid(self): + return self._pt_centerRightMid + + def setStartPoint(self, pointf, remake=False): + self._pt_start = pointf + if remake: + self.remakeNightArea() + + def setEndPoint(self, pointf, remake=False): + self._pt_end = pointf + if remake: + self.remakeNightArea() + + def setCenterArea(self, start, end): + self._pt_start = start + self._pt_end = end + self.remakeNightArea() + + def remakeNightArea(self): + '''重新划分九宫格区域。根据中央截图区域计算出来的其他8个区域、截图区域四个边框中点坐标等都是logical的''' + self._rt_center = self.normalizeRectF(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.squareAreaByCenter(self._rt_center.topLeft()) + self._square_topRight = self.squareAreaByCenter(self._rt_center.topRight()) + self._square_bottomLeft = self.squareAreaByCenter(self._rt_center.bottomLeft()) + self._square_bottomRight = self.squareAreaByCenter(self._rt_center.bottomRight()) + self._square_topMid = self.squareAreaByCenter(self._pt_centerTopMid) + self._square_bottomMid = self.squareAreaByCenter(self._pt_centerBottomMid) + self._square_leftMid = self.squareAreaByCenter(self._pt_centerLeftMid) + self._square_rightMid = self.squareAreaByCenter(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()) + + def squareAreaByCenter(self, pointf): + '''以QPointF为中心的正方形QRectF''' + rectf = QRectF(0, 0, 15, 15) + rectf.moveCenter(pointf) + return rectf + + def aroundAreaIn8Direction(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 aroundAreaIn4Direction(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''' + screenSizeF = self.screenLogicalSizeF() + pt_topRight = self._rt_center.topRight() + pt_bottomLeft = self._rt_center.bottomLeft() + return [QRectF(0, 0, screenSizeF.width(), pt_topRight.y()), + QRectF(0, pt_bottomLeft.y(), screenSizeF.width(), screenSizeF.height() - pt_bottomLeft.y()), + QRectF(0, 0, pt_bottomLeft.x(), screenSizeF.height()), + QRectF(pt_topRight.x(), 0, screenSizeF.width() - pt_topRight.x(), screenSizeF.height())] + + def aroundAreaWithoutIntersection(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''' + screenSizeF = self.screenLogicalSizeF() + pt_topRight = self._rt_center.topRight() + pt_bottomLeft = self._rt_center.bottomLeft() + centerHeight = pt_bottomLeft.y() - pt_topRight.y() + return [QRectF(0, 0, screenSizeF.width(), pt_topRight.y()), + QRectF(0, pt_bottomLeft.y(), screenSizeF.width(), screenSizeF.height() - pt_bottomLeft.y()), + QRectF(0, pt_topRight.y(), pt_bottomLeft.x() - 1, centerHeight), + QRectF(pt_topRight.x() + 1, pt_topRight.y(), screenSizeF.width() - pt_topRight.x(), centerHeight)] + + def setBeginDragPoint(self, pointf): + '''计算开始拖拽位置距离截图区域左上角的向量''' + self._drag_vector = pointf - self._rt_center.topLeft() + + def getNewPosAfterDrag(self, pointf): + '''计算拖拽后截图区域左上角的新位置''' + return pointf - self._drag_vector + + def moveCenterAreaTo(self, pointf): + '''限制拖拽不能超出屏幕范围''' + self._rt_center.moveTo(self.getNewPosAfterDrag(pointf)) + startPointF = self._rt_center.topLeft() + if startPointF.x() < 0: + self._rt_center.moveTo(0, startPointF.y()) + startPointF = self._rt_center.topLeft() + if startPointF.y() < 0: + self._rt_center.moveTo(startPointF.x(), 0) + screenSizeF = self.screenLogicalSizeF() + endPointF = self._rt_center.bottomRight() + if endPointF.x() > screenSizeF.width(): + self._rt_center.moveBottomRight(QPointF(screenSizeF.width(), endPointF.y())) + endPointF = self._rt_center.bottomRight() + if endPointF.y() > screenSizeF.height(): + self._rt_center.moveBottomRight(QPointF(endPointF.x(), screenSizeF.height())) + self.setCenterArea(self._rt_center.topLeft(), self._rt_center.bottomRight()) + + def setBeginAdjustPoint(self, pointf): + '''判断开始调整截图区域大小时鼠标左键在哪个区(不可能是中央区域),用于判断调整大小的意图方向''' + self._mousePos = self.getMousePosBy(pointf) + + def getMousePosBy(self, pointf): + if self._square_topLeft.contains(pointf): + return 'TL' + elif self._square_topMid.contains(pointf): + return 'T' + elif self._square_topRight.contains(pointf): + return 'TR' + elif self._square_leftMid.contains(pointf): + return 'L' + elif self._rt_center.contains(pointf): + return 'CENTER' + elif self._square_rightMid.contains(pointf): + return 'R' + elif self._square_bottomLeft.contains(pointf): + return 'BL' + elif self._square_bottomMid.contains(pointf): + return 'B' + elif self._square_bottomRight.contains(pointf): + return 'BR' + else: + return 'ERROR' + + def adjustCenterAreaBy(self, pointf): + '''根据开始调整截图区域大小时鼠标左键在哪个区(不可能是中央区域),判断调整大小的意图方向,判定新的开始、结束位置''' + startPointF = self._rt_center.topLeft() + endPointF = self._rt_center.bottomRight() + if self._mousePos == 'TL': + startPointF = pointf + elif self._mousePos == 'T': + startPointF = QPointF(startPointF.x(), pointf.y()) + elif self._mousePos == 'TR': + startPointF = QPointF(startPointF.x(), pointf.y()) + endPointF = QPointF(pointf.x(), endPointF.y()) + elif self._mousePos == 'L': + startPointF = QPointF(pointf.x(), startPointF.y()) + elif self._mousePos == 'R': + endPointF = QPointF(pointf.x(), endPointF.y()) + elif self._mousePos == 'BL': + startPointF = QPointF(pointf.x(), startPointF.y()) + endPointF = QPointF(endPointF.x(), pointf.y()) + elif self._mousePos == 'B': + endPointF = QPointF(endPointF.x(), pointf.y()) + elif self._mousePos == 'BR': + endPointF = pointf + else: # 'ERROR' + return + newRectF = self.normalizeRectF(startPointF, endPointF) + self.setCenterArea(newRectF.topLeft(), newRectF.bottomRight()) + + def getMouseShapeBy(self, pointf): + '''根据鼠标位置返回对应的鼠标样式''' + if self._rt_center.contains(pointf): + if self.god.isDrawRectangle or self.god.isDrawEllipse: + return QtCore.Qt.CursorShape.ArrowCursor + elif self.god.isDrawGraffiti: + return QtCore.Qt.CursorShape.PointingHandCursor # 超链接上的手势 + elif self.god.isDrawText: + return QtCore.Qt.CursorShape.IBeamCursor # 工字 + else: + return QtCore.Qt.CursorShape.SizeAllCursor # 十字有箭头 + # return QtCore.Qt.CursorShape.OpenHandCursor # 打开的手,表示可拖拽 + elif self._square_topLeft.contains(pointf) or self._square_bottomRight.contains(pointf): + return QtCore.Qt.CursorShape.SizeFDiagCursor # ↖↘ + elif self._square_topMid.contains(pointf) or self._square_bottomMid.contains(pointf): + return QtCore.Qt.CursorShape.SizeVerCursor # ↑↓ + elif self._square_topRight.contains(pointf) or self._square_bottomLeft.contains(pointf): + return QtCore.Qt.CursorShape.SizeBDiagCursor # ↙↗ + elif self._square_leftMid.contains(pointf) or self._square_rightMid.contains(pointf): + return QtCore.Qt.CursorShape.SizeHorCursor # ←→ + else: + return QtCore.Qt.CursorShape.CrossCursor # 十字无箭头 + + def isMousePosInCenterRectF(self, pointf): + return self._rt_center.contains(pointf) + + def paintMagnifyingGlassPixmap(self, pos, glassSize): + '''绘制放大镜内的图像(含纵横十字线) + pos:鼠标光标位置 + glassSize:放大镜边框大小''' + pixmapRect = QRect(0, 0, 20, 20) # 以鼠标光标为中心的正方形区域,最好是偶数 + pixmapRect.moveCenter(pos) + glassPixmap = self.physicalPixmap(pixmapRect) + glassPixmap.setDevicePixelRatio(1.0) + glassPixmap = glassPixmap.scaled(glassSize, glassSize, QtCore.Qt.AspectRatioMode.KeepAspectRatio) + # 在放大后的QPixmap上画纵横十字线 + self._painter.begin(glassPixmap) + halfWidth = glassPixmap.width() / 2 + halfHeight = glassPixmap.height() / 2 + self._painter.setPen(self.god.pen_SolidLine_lightBlue) + self._painter.drawLine(QPointF(0, halfHeight), QPointF(glassPixmap.width(), halfHeight)) + self._painter.drawLine(QPointF(halfWidth, 0), QPointF(halfWidth, glassPixmap.height())) + self._painter.end() + return glassPixmap + + def paintEachEditAction(self, painter, textBorder=True): + '''绘制所有已保存的编辑行为。编辑行为超出截图区域也无所谓,保存图像时只截取截图区域内 + textBorder:是否绘制文本边框''' + for action in self.getEditActions(): + if action[0] == 'rectangle': # (type, color, lineWidth, startPoint, endPoint) + self.paintRectangle(painter, action[1], action[2], action[3], action[4]) + elif action[0] == 'ellipse': # (type, color, lineWidth, startPoint, endPoint) + self.paintEllipse(painter, action[1], action[2], action[3], action[4]) + elif action[0] == 'graffiti': # (type, color, lineWidth, points) + self.paintGraffiti(painter, action[1], action[2], action[3]) + elif action[0] == 'text': # (type, color, font, rectf, txt) + self.paintTextInput(painter, action[1], action[2], action[3], action[4], textBorder=textBorder) + + def paintRectangle(self, painter, color, lineWidth, startPoint=None, endPoint=None): + if not startPoint: + startPoint = self._pt_startEdit + if not endPoint: + endPoint = self._pt_endEdit + qrectf = self.normalizeRectF(startPoint, endPoint) + if qrectf.isValid(): + pen = QPen(color) + pen.setWidth(lineWidth) + painter.setPen(pen) + painter.setBrush(self.god.color_transparent) + painter.drawRect(qrectf) + + def paintEllipse(self, painter, color, lineWidth, startPoint=None, endPoint=None): + if not startPoint: + startPoint = self._pt_startEdit + if not endPoint: + endPoint = self._pt_endEdit + qrectf = self.normalizeRectF(startPoint, endPoint) + if qrectf.isValid(): + pen = QPen(color) + pen.setWidth(lineWidth) + painter.setPen(pen) + painter.setBrush(self.god.color_transparent) + painter.drawEllipse(qrectf) + + def paintGraffiti(self, painter, color, lineWidth, pointfs=None): + if not pointfs: + pointfs = self.getGraffitiPointFs() + pen = QPen(color) + pen.setWidth(lineWidth) + painter.setPen(pen) + total = len(pointfs) + if total == 0: + return + elif total == 1: + painter.drawPoint(pointfs[0]) + else: + previousPoint = pointfs[0] + for i in range(1, total): + nextPoint = pointfs[i] + painter.drawLine(previousPoint, nextPoint) + previousPoint = nextPoint + + def paintTextInput(self, painter, color, font, rectf, txt, textBorder=True): + painter.setPen(color) + painter.setFont(font) + painter.drawText(rectf, txt, self._textOption) + if textBorder: + painter.setPen(QtCore.Qt.PenStyle.DotLine) # 点线 + painter.setBrush(self.god.color_transparent) + painter.drawRect(rectf) + + def getEditActions(self): + return self._actions.copy() + + def takeTextInputActionAt(self, pointf): + '''根据鼠标位置查找已保存的文本输入结果,找到后取出''' + for i in range(len(self._actions)): + action = self._actions[i] + if action[0] == 'text' and action[3].contains(pointf): + return self._actions.pop(i) + return None + + def undoEditAction(self): + reply = False + if self._actions: + reply = self._actions.pop() + if not self._actions: # 所有编辑行为都被撤销后退出编辑模式 + self.god.exitEditMode() + else: + self.god.exitEditMode() + return reply + + def clearEditActions(self): + self._actions.clear() + + def setBeginEditPoint(self, pointf): + '''在截图区域上绘制矩形、椭圆时鼠标左键按下的位置(topLeft)''' + self._pt_startEdit = pointf + self.god.isDrawing = True + + def setEndEditPoint(self, pointf): + '''在截图区域上绘制矩形、椭圆时鼠标左键松开的位置(bottomRight)''' + self._pt_endEdit = pointf + + def saveRectangleAction(self): + rectf = self.normalizeRectF(self._pt_startEdit, self._pt_endEdit) + self._actions.append(('rectangle', self.god.toolbar.curColor(), self.god.toolbar.curLineWidth(), + rectf.topLeft(), rectf.bottomRight())) + self._pt_startEdit = QPointF() + self._pt_endEdit = QPointF() + self.god.isDrawing = False + + def saveEllipseleAction(self): + rectf = self.normalizeRectF(self._pt_startEdit, self._pt_endEdit) + self._actions.append(('ellipse', self.god.toolbar.curColor(), self.god.toolbar.curLineWidth(), + rectf.topLeft(), rectf.bottomRight())) + self._pt_startEdit = QPointF() + self._pt_endEdit = QPointF() + self.god.isDrawing = False + + def saveGraffitiPointF(self, pointf, first=False): + self._pointfs.append(pointf) + if first: + self.god.isDrawing = True + + def getGraffitiPointFs(self): + return self._pointfs.copy() + + def saveGraffitiAction(self): + if self._pointfs: + self._actions.append(('graffiti', self.god.toolbar.curColor(), self.god.toolbar.curLineWidth(), self._pointfs.copy())) + self._pointfs.clear() + self.god.isDrawing = False + + def setBeginInputTextPoint(self, pointf): + '''在截图区域上输入文字时鼠标左键按下的位置(topLeft)''' + self.god.isDrawing = True + self.god.textInputWg.beginNewInput(pointf, self._pt_end) + + def saveTextInputAction(self): + txt = self.god.textInputWg.toPlainText() + if txt: + rectf = self.god.textInputWg._maxRect # 取最大矩形的topLeft + rectf.setSize(QRectF(self.god.textInputWg.rect()).size()) # 取实际矩形的宽高 + self._actions.append(('text', self.god.toolbar.curColor(), self.god.toolbar.curFont(), + rectf, txt)) + self.god.textInputWg.clear() + self.god.textInputWg.hide() # 不管保存成功与否都取消编辑 + self.god.isDrawing = False + + def saveNightAreaImg(self): + '''将九宫格区域保存为本地图片,仅用于开发测试''' + screenPixmap = self.screenPhysicalPixmapCopy() + self._painter.begin(screenPixmap) + self._painter.setPen(self.pen_SolidLine_lightBlue) + self._painter.setFont(self.god.font_normal) + self._painter.drawRect(self._rt_center) + for area in self.aroundAreaIn8Direction(): + self._painter.drawRect(area) + for pointf in [self._rt_center.topLeft(), self._rt_center.topRight(), + self._rt_center.bottomLeft(), self._rt_center.bottomRight(), + self._pt_centerTopMid, self._pt_centerBottomMid, + self._pt_centerLeftMid, self._pt_centerRightMid]: + self._painter.drawText(pointf + QPointF(5, -5), '(%s, %s)' % (pointf.x(), pointf.y())) + self._painter.end() + screenPixmap.save('1.jpg', quality=100) + self.centerPhysicalPixmap().save('2.jpg', quality=100) + + +class ScreenShotWidget(QWidget): + + fileType_all = '所有文件 (*);;Excel文件 (*.xls *.xlsx);;图片文件 (*.jpg *.jpeg *.gif *.png *.bmp)' + fileType_img = '图片文件 (*.jpg *.jpeg *.gif *.png *.bmp)' + dir_lastAccess = os.getcwd() # 最后访问目录 + + def __init__(self): + super().__init__() + self.setMouseTracking(True) + self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.WindowStaysOnTopHint) + self.initPainterTool() + self.initFunctionalFlag() + self.screenArea = ScreenArea(self) + self.toolbar = ScreenShotToolBar(self) + self.textInputWg = TextInputWidget(self) + + self.captureImage = None + # 设置 screenPixmap 为窗口背景 + # palette = QtGui.QPalette() + # palette.setBrush(QtGui.QPalette.ColorRole.Window, QtGui.QBrush(self.screenArea.screenPhysicalPixmapCopy())) + # self.setPalette(palette) + + def rejectSlot(self): + self.captureImage = None + self.close() + + def acceptSlot(self): + self.captureImage = self.screenArea.centerPhysicalPixmap().toImage() + self.close() + + def start(self): + self.screenArea.captureScreen() + self.setGeometry(self.screenArea.screenPhysicalRectF().toRect()) + self.clearScreenShotArea() + self.showFullScreen() + + def initPainterTool(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('Times New Roman', 11, QtGui.QFont.Weight.Normal) + self.font_textInput = QtGui.QFont('微软雅黑', 16, 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 initFunctionalFlag(self): + self.hasScreenShot = False # 是否已通过拖动鼠标左键划定截图区域 + self.isCapturing = False # 正在拖动鼠标左键选定截图区域时 + self.isMoving = False # 在截图区域内拖动时 + self.isAdjusting = False # 在截图区域的边框按住鼠标左键调整大小时 + self.isDrawing = False # 是否已在截图区域内开始绘制 + self.isDrawRectangle = False # 正在截图区域内画矩形 + self.isDrawEllipse = False # 正在截图区域内画椭圆 + self.isDrawGraffiti = False # 正在截图区域内进行涂鸦 + self.isDrawText = False # 正在截图区域内画文字 + self.setCursor(QtCore.Qt.CursorShape.CrossCursor) # 设置鼠标样式 十字 + + def paintEvent(self, event): + centerRectF = self.screenArea.centerLogicalRectF() + screenSizeF = self.screenArea.screenLogicalSizeF() + canvasPixmap = self.screenArea.screenPhysicalPixmapCopy() + # canvasPixmap = QPixmap(screenSizeF.toSize()) + # canvasPixmap.fill(self.color_transparent) + # 在屏幕截图的副本上绘制已选定的截图区域 + self.painter.begin(canvasPixmap) + if self.hasScreenShot: + self.paintCenterArea(centerRectF) # 绘制中央截图区域 + self.paintMaskLayer(screenSizeF, fullScreen=False) # 绘制截图区域的周边区域遮罩层 + else: + self.paintMaskLayer(screenSizeF) + self.paintMagnifyingGlass(screenSizeF) # 在鼠标光标右下角显示放大镜 + self.paintToolbar(centerRectF, screenSizeF) # 在截图区域右下角显示工具条 + self.paintEditActions() # 在截图区域绘制编辑行为结果 + self.painter.end() + # 把画好的绘制结果显示到窗口上 + self.painter.begin(self) + self.painter.drawPixmap(0, 0, canvasPixmap) # 从坐标(0, 0)开始绘制 + self.painter.end() + + def paintCenterArea(self, centerRectF): + '''绘制已选定的截图区域''' + self.painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) # 反走样 + # 1.绘制矩形线框 + self.painter.setPen(self.pen_DashLine_lightBlue) + self.painter.drawRect(centerRectF) + # 2.绘制矩形线框4个端点和4条边框的中间点 + if centerRectF.width() >= 100 and centerRectF.height() >= 100: + points = [ # 点坐标 + centerRectF.topLeft(), centerRectF.topRight(), centerRectF.bottomLeft(), centerRectF.bottomRight(), + self.screenArea.centerLeftMid(), self.screenArea.centerRightMid(), + self.screenArea.centerTopMid(), self.screenArea.centerBottomMid() + ] + blueDotRadius = QPointF(2, 2) # 椭圆蓝点 + self.painter.setBrush(self.color_lightBlue) + for point in points: + self.painter.drawEllipse(QRectF(point - blueDotRadius, point + blueDotRadius)) + # 3.在截图区域左上角显示截图区域宽高 + if centerRectF.topLeft().y() > 20: + labelPos = centerRectF.topLeft() + QPointF(5, -5) + else: # 拖拽截图区域到贴近屏幕上边缘时“宽x高”移动到截图区域左上角的下侧 + labelPos = centerRectF.topLeft() + QPointF(5, 15) + centerPhysicalRect = self.screenArea.centerPhysicalRectF().toRect() + self.painter.setPen(self.pen_white) + self.painter.setFont(self.font_normal) + self.painter.drawText(labelPos, '%s x %s' % (centerPhysicalRect.width(), centerPhysicalRect.height())) + # 4.在屏幕左上角预览截图结果 + # self.painter.drawPixmap(0, 0, self.screenArea.centerPhysicalPixmap()) # 从坐标(0, 0)开始绘制 + + def paintMaskLayer(self, screenSizeF, fullScreen=True): + if fullScreen: # 全屏遮罩层 + maskPixmap = QPixmap(screenSizeF.toSize()) + maskPixmap.fill(self.color_black) + self.painter.drawPixmap(0, 0, maskPixmap) + else: # 绘制截图区域的周边区域遮罩层,以凸显截图区域 + # 方法一:截图区域以外的8个方向区域 + # for area in self.screenArea.aroundAreaIn8Direction(): + # area = area.normalized() + # maskPixmap = QPixmap(area.size().toSize()) # 由于float转int的精度问题,可能会存在黑线条缝隙 + # maskPixmap.fill(self.color_black) + # self.painter.drawPixmap(area.topLeft(), maskPixmap) + # 方法二:截图区域以外的上下左右区域(有交集,交集部分颜色加深,有明显的纵横效果) + # for area in self.screenArea.aroundAreaIn4Direction(): + # maskPixmap = QPixmap(area.size().toSize()) + # maskPixmap.fill(self.color_black) + # self.painter.drawPixmap(area.topLeft(), maskPixmap) + # 方法三:截图区域以外的上下左右区域(无交集) + for area in self.screenArea.aroundAreaWithoutIntersection(): + maskPixmap = QPixmap(area.size().toSize()) + maskPixmap.fill(self.color_black) + self.painter.drawPixmap(area.topLeft(), maskPixmap) + + def paintMagnifyingGlass(self, screenSizeF, glassSize=150, offset=30, labelHeight=30): + '''未划定截图区域模式时、正在划定截取区域时、调整截取区域大小时在鼠标光标右下角显示放大镜 + glassSize:放大镜正方形边长 + offset:放大镜任意一个端点距离鼠标光标位置的最近距离 + labelHeight:pos和rgb两行文字的高度''' + if self.hasScreenShot and (not self.isCapturing) and (not self.isAdjusting): + return + pos = QtGui.QCursor.pos() + glassPixmap = self.screenArea.paintMagnifyingGlassPixmap(pos, glassSize) # 画好纵横十字线后的放大镜内QPixmap + # 限制放大镜显示不超出屏幕外 + glassRect = glassPixmap.rect() + if (pos.x() + glassSize + offset) < screenSizeF.width(): + if (pos.y() + offset + glassSize + labelHeight) < screenSizeF.height(): + glassRect.moveTo(pos + QPoint(offset, offset)) + else: + glassRect.moveBottomLeft(pos + QPoint(offset, -offset)) + else: + if (pos.y() + offset + glassSize + labelHeight) < screenSizeF.height(): + glassRect.moveTopRight(pos + QPoint(-offset, offset)) + else: + glassRect.moveBottomRight(pos + QPoint(-offset, -offset)) + self.painter.drawPixmap(glassRect.topLeft(), glassPixmap) + # 显示pos:(x, y)、rgb:(255,255,255) + qrgb = QtGui.QRgba64.fromArgb32(glassPixmap.toImage().pixel(glassPixmap.rect().center())) + labelRectF = QRectF(glassRect.bottomLeft().x(), glassRect.bottomLeft().y(), glassSize, labelHeight) + self.painter.setPen(self.pen_transparent) + self.painter.setBrush(self.color_black) # 黑底 + self.painter.drawRect(labelRectF) + self.painter.setPen(self.pen_white) + self.painter.setFont(self.font_normal) + self.painter.drawText(labelRectF, + QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter, + 'pos:(%s, %s)\nrgb:(%s, %s, %s)' % (pos.x(), pos.y(), qrgb.red8(), qrgb.green8(), qrgb.blue8())) + + def paintToolbar(self, centerRectF, screenSizeF): + '''在截图区域右下角显示工具条''' + if self.hasScreenShot: + if self.isCapturing or self.isAdjusting: + self.toolbar.hide() # 正在划定截取区域时、调整截图区域大小时不显示工具条 + else: + self.toolbar.adjustSize() + toolbarRectF = QRectF(self.toolbar.rect()) + # 工具条位置优先顺序:右下角下侧,右上角上侧,右下角上侧 + if (screenSizeF.height() - centerRectF.bottomRight().y()) > toolbarRectF.height(): + toolbarRectF.moveTopRight(centerRectF.bottomRight() + QPointF(-5, 5)) + elif centerRectF.topRight().y() > toolbarRectF.height(): + toolbarRectF.moveBottomRight(centerRectF.topRight() + QPointF(-5, -5)) + else: + toolbarRectF.moveBottomRight(centerRectF.bottomRight() + QPointF(-5, -5)) + # 限制工具条的x坐标不为负数,不能移出屏幕外 + if toolbarRectF.x() < 0: + pos = toolbarRectF.topLeft() + pos.setX(centerRectF.x() + 5) + toolbarRectF.moveTo(pos) + self.toolbar.move(toolbarRectF.topLeft().toPoint()) + self.toolbar.show() + else: + self.toolbar.hide() + + def paintEditActions(self): + '''在截图区域绘制编辑行为结果。编辑行为超出截图区域也无所谓,保存图像时只截取截图区域内''' + # 1.绘制正在拖拽编辑中的矩形、椭圆、涂鸦 + if self.isDrawRectangle: + self.screenArea.paintRectangle(self.painter, self.toolbar.curColor(), self.toolbar.curLineWidth()) + elif self.isDrawEllipse: + self.screenArea.paintEllipse(self.painter, self.toolbar.curColor(), self.toolbar.curLineWidth()) + elif self.isDrawGraffiti: + self.screenArea.paintGraffiti(self.painter, self.toolbar.curColor(), self.toolbar.curLineWidth()) + # 2.绘制所有已保存的编辑行为 + self.screenArea.paintEachEditAction(self.painter) + + def clearEditFlags(self): + self.isDrawing = False + self.isDrawRectangle = False + self.isDrawEllipse = False + self.isDrawGraffiti = False + self.isDrawText = False + + def exitEditMode(self): + '''退出编辑模式''' + self.clearEditFlags() + # self.toolbar.onActionTriggered(None) # 清空工具条工具按钮选中状态 + self.textInputWg.hide() + + def clearScreenShotArea(self): + '''清空已划定的截取区域''' + self.screenArea.clearEditActions() # 清除已保存的编辑行为 + self.exitEditMode() + self.hasScreenShot = False + self.isCapturing = False + pos = QPointF() + self.screenArea.setCenterArea(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.isDrawRectangle or self.isDrawEllipse: + self.screenArea.setBeginEditPoint(pos) + elif self.isDrawGraffiti: # 保存涂鸦经过的每一个点 + self.screenArea.saveGraffitiPointF(pos, first=True) + elif self.isDrawText: + if self.isDrawing: + if QRectF(self.textInputWg.rect()).contains(pos): + pass # 在输入框内调整光标位置,忽略 + else: # 鼠标点到输入框之外,完成编辑 + self.screenArea.saveTextInputAction() + else: # 未开始编辑时(暂不支持文本拖拽) + action = self.screenArea.takeTextInputActionAt(pos) + if action: # 鼠标点到输入框之内,修改旧的文本输入 + self.textInputWg.loadTextInputBy(action) + else: # 鼠标点到输入框之外,开始新的文本输入 + self.screenArea.setBeginInputTextPoint(pos) + elif self.screenArea.isMousePosInCenterRectF(pos): + self.isMoving = True # 进入拖拽移动模式 + self.screenArea.setBeginDragPoint(pos) + else: + self.isAdjusting = True # 进入调整大小模式 + self.screenArea.setBeginAdjustPoint(pos) + else: + self.screenArea.setCenterArea(pos, pos) + self.isCapturing = True # 进入划定截图区域模式 + if event.button() == QtCore.Qt.MouseButton.RightButton: + if self.hasScreenShot or self.isCapturing: # 清空已划定的的截图区域 + self.clearScreenShotArea() + else: + self.close() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.MouseButton.LeftButton: + if self.isDrawRectangle: + self.screenArea.saveRectangleAction() + elif self.isDrawEllipse: + self.screenArea.saveEllipseleAction() + elif self.isDrawGraffiti: + self.screenArea.saveGraffitiAction() + self.isCapturing = False + self.isMoving = False + self.isAdjusting = False + self.toolbar.show() + + def mouseMoveEvent(self, event): + pos = event.pos() + if self.isDrawing: + if self.isDrawRectangle or self.isDrawEllipse: + self.screenArea.setEndEditPoint(pos) + elif self.isDrawGraffiti: + self.screenArea.saveGraffitiPointF(pos) + elif self.isCapturing: + self.hasScreenShot = True + self.screenArea.setEndPoint(pos, remake=True) + elif self.isMoving: + self.screenArea.moveCenterAreaTo(pos) + elif self.isAdjusting: + self.screenArea.adjustCenterAreaBy(pos) + self.update() + if self.hasScreenShot: + self.setCursor(self.screenArea.getMouseShapeBy(pos)) + else: + self.setCursor(QtCore.Qt.CursorShape.CrossCursor) # 设置鼠标样式 十字 + + def mouseDoubleClickEvent(self, event): + if event.button() == QtCore.Qt.MouseButton.LeftButton: + if self.screenArea.isMousePosInCenterRectF(event.pos()): + self.save2Clipboard() + self.close() + + def keyPressEvent(self, QKeyEvent): + if QKeyEvent.key() == QtCore.Qt.Key.Key_Escape: + self.close() + if QKeyEvent.key() in (QtCore.Qt.Key.Key_Return, QtCore.Qt.Key.Key_Enter): # 大键盘、小键盘回车 + self.save2Clipboard() + self.close() + + def save2Clipboard(self): + '''将截图区域复制到剪贴板''' + if self.hasScreenShot: + mimData = QtCore.QMimeData() + mimData.setImageData(self.screenArea.centerPhysicalPixmap().toImage()) + QApplication.clipboard().setMimeData(mimData) + # self.screenArea.saveNightAreaImg() + self.close() + + def save2Local(self): + fileType = self.fileType_img + filePath, fileFormat = self.sys_selectSaveFilePath(self, fileType=fileType) + if filePath: + self.screenArea.centerPhysicalPixmap().save(filePath, quality=100) + self.close() + + def sys_getCurTime(self, fmt='%Y-%m-%d %H:%M:%S'): + '''获取字符串格式的当前时间''' + # return QtCore.QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss') + return datetime.now().strftime(fmt) + + def sys_selectSaveFilePath(self, widget, title='选择文件保存路径', saveFileDir=None, + saveFileName='', defaultFileFmt='%Y%m%d%H%M%S', fileType=None): + '''选择文件保存路径 + title:选择窗口标题 + saveFileDir:指定保存目录 + saveFileName:默认保存文件名 + defaultFileFmt:不指定saveFileName时,自动以此格式的时间字符串命名文件 + fileType:可以选择的文件类型 + return:(所选的文件保存路径, 文件的类型) + ''' + options = QFileDialog.Option.ReadOnly + if saveFileName == '': + saveFileName = self.sys_getCurTime(defaultFileFmt) + if not saveFileDir: + saveFileDir = self.dir_lastAccess + saveFilePath = os.path.join(saveFileDir, saveFileName) + if not fileType: + fileType = self.fileType_all + filePath, fileFormat = QFileDialog.getSaveFileName(widget, title, saveFilePath, fileType, options=options) + if filePath: + self.dir_lastAccess = os.path.dirname(filePath) + return (filePath, fileFormat) + + diff --git a/icons/chinese.png b/icons/chinese.png new file mode 100644 index 0000000000000000000000000000000000000000..328e2fff73bd75188fa888aae45c8cb4ca844f57 GIT binary patch literal 2670 zcmds3dpMNa8eiXNFq0S>#3n;7<2t4rp_*~ViS0ULCNd%u2`QDzB>8F-2{XBM!5|C` zl1oUb+0`U!C!^sMDxwhWE~MPrU;FI;&mZSG|D5MpYyF=0{XJ{F@AIzTde(c!$7{$Q{jd@;ZttCXazC&H*#mVm%i z!uHbvg{X!HxJZEhj4$F1k3FcM;(>sgX8OAm8W?-q{d3u60IP7;M!Yo#1a1pC5{Cw% zxDvND-VkW-PN(yTfWTYfd5Mb!uEM=(x-Xg;pda<6TqmIjFY`@Sor=)7O^SL9BO%TvXYYOw8EP zwNZ~H%JRW)r|sWV+Kg15Zaj3~?=B9x8B0Tm#$@S7KX{}@(l0H!JZ`QDig{Da&d-5h z31!$t-mM5Np&S*5b8P^M@cHt11P{TD@_Hw20O7dw=>{r*1j6xc!lf`sxm9uW1px-G z-qTi{XbPYOF*iozC}8-qWHP~g@KvwhKLYpS`{tzN_3uv!{e>o8soz-oH}Ii}xO zS6u_>tPtcU2c{`vU=^(EnNO)~*O`}1DFt_E<$Zdob7{>6#y#Y&nFB3tDel%l=Ngab zVNiCK*l?Mba$H`$zQk6aGBkh^#8n3#^Mz9G#%go+EE#mM{7O}+l%5?sZ65AD;$jKf z8*LMcw7a`kH5nizAWyygZ@EfCfZ>EYn&`zPP#@Q1dY(S3)p{$?b=vfK%@h#iSgi9p zzMW*|*IHdsDvCFVSFt;CB?J5m z+v4aI2MnA7(Un^gxzp+=IE9SCa-)pB8Jw<-^OCYrBVhD_FcJR^3cVy6N>B$~3XJ&| zJN!&~;%v+84nxZD+6>9UM`&2g*OqCLgnzVf(z6tBfNXQ4QG{SbB`3WE{hw^XBDm4t z>rFGJH(D)InK)*q8p4I!cHYI64|36f)Rt z7B+?T_c>>z8t+vt{_Ud=WS98sNr#9%`b+I75|C^!65;YR5i*#xNJk#lZ#VC=HDmm$4AYzolL-e zrT$AP#2|F;{K>z=C3rRWFYHUmpl`Me3${2)V8^v?dp<=kao?FAXVb$9C-LjloxYlh z4}4?fqmW$p=d2!WLef*TUTAJWp3jg)*wcN;U8n@i?vGGVhVQdFie8~`K&N5qjZ6^- z&QxYo@1O~bfNPy#DJ!}jxN34}>E_n_;P-DmX5YMd^TnxiP#!WrSp(pHr;pAK@PxP2 z`XpCa=vD69WkL=b7w4&JJRUr-Tw8!Y!WW?7s$0zYp`_otT)ryNMoeI&TCQKwq1$rL zH#IedyWjx>BVfhb9Pe$qJ4$aXPU4yLB#Y_SNBGjX1^A<Gf2T;JWP6G0x{{Ql!fe{u2u@7$@eGH*{ikEY!gr?#e+j{w(m=@C3l zk%H^3w?LyNCvTtDrj@ME{cJlcCyHQgzhL-4gMvTZWTXRE{)UFZNX=Eu73nJ25;#}`rhO>V7zDUD?BMyJDqDr z58+wWDh6>_73Ck+V+cUDoo=}t5gon9_edXtV^=G3Fo-CL@2}j9MljazSw|8g+U)6P z1C0o_SVo39gF42+XZ6yKljG5BMA)tJhP8zk9>p}8G6U~2k6m$7{v2a(#r|z?!lsUN z3ftl1_e89oYI3i<%x+s9F|uPB!K%J4vgi>t^#j>6YZ$v*lOSyfF1Q_7I`i>k%!G`a zIlJ`)@?$?bb?J*!av#7a9tY~`HMmpJMG{X46l?jsc0}VHN6%-7GW}~yuBM)vEP@>= z{=4Ye8F+zJO{qR@Kd0@SBuRD+CH`QDHr|tFBa2~8FK?-ee+XfI2@NhS{56WaJZz={ zrM$gVS~tGxt*%*K*p}KJ4Fxv?bg#dbtW0h_V*v-A4-nVCPybG9b|m_sGc4SzuG^LPa|454UU_Vr+Xz>h`kp|k9F!mpBye-_qFr^~aq>??K8mXV literal 0 HcmV?d00001 diff --git a/icons/english.png b/icons/english.png new file mode 100644 index 0000000000000000000000000000000000000000..536c4a910ae6fc05040f4958477a34aeae891ea0 GIT binary patch literal 2888 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 literal 0 HcmV?d00001 diff --git a/icons/folder-plus.png b/icons/folder-plus.png new file mode 100644 index 0000000000000000000000000000000000000000..01ce6c10b0ed3e3975edbebbc8e886f846fabe8d GIT binary patch literal 2598 zcmd5;TR4=98Xo%bqxg*^q5hdh2d9b_i$Q({GZGeqK}`qelv5g0Y12^6P)y^LLgmm& zh8d?sv7AE*GY(s_gfuxs%*tV9t=TvGe(#ID_Qm(T7w`MseDC`_-^X%wcG#q_O#y{M zZ6Xl~ZW3(&SAnvUUP9sKNkHnHn}ZFC`&LVYLdpL|A|PZsbvE~yr=}TXKcX+cxSit7 z$!NLIrq6n-GRR8X+P6y9%_zk0GoEtF`p4X((DSD7KK%L>6kvrbYmk+OmT#jNW#({1 zf&?t!c=(wV7z_*|2gArmY*5H9`8y$}0f;beFT??mKj(h-LwnSJPQI79K&R8SShI_Z zizf?L_Gi`0lhtsq=T}!(cQy0%kJD;LT%(TOk4>APTUhZLTCuKn?6tME@J@cLwxaOi zkXB~E%3Cg<<)N;gt!Tv4s^4&YK)lx3*;#GqiAty=H8`=O@s-?ZWqQ4;HRpgQg9XoQ z8&GfJ>$|)U6B{`4;2AAb^@b~Q-(SlNef=(~IxVPnZxKZmd)z1s*k#tc_db&jOx#MBj~9_qcIh*3A@Fd}K)xF~F(@_vF}>U)vAddr zP8=@gv!4rf$EUnY6!;gFqMBc^3scGEGGi-Eh7=sn&%0O$cw$HMt0nSP$OWBz86Mr* zCHdpr9Jlt+LC3gZxAB4H3+2LKaroyd7Hqi>Ojc&<)yxNmHk~>fDbD}}p>-|eo`=E4 z2LJ-JS5nC^3$mX?11eHpFN7bJOPVcCXJCj@-nhN-1Ejr-&9WYcIeEwZH)A(>q`r)sRBdb(VAzicMSrx%KIrq2CU|#9A2f{I5N1jK<}cCXB`Pr_kC~Pwd8(Ek*YZ$ zoplW3`XU)-Ht}uHa_y!s_S^!H9KJ1j_q!d@1s_ghn0{fzkKo!puHcx2Fdl!CX*};9 z1NL6KL@TWu>aY*=fPc^q;+bmeH)w3!hRm^|j&mSmt1P=uK;kSNX^35MReH_{RVhSK zi+lSL*4jrgqYATeKXkh>Gw{ie!?V3ybnwp)uAPbu-oZ#rO8=FDE~AIpLY;(Qz0Fd9 zyqa7BOOpD0okqHiiZ$Cca{ujM0K)lTZ|wfx)5}V`uEv?C!?|I6-vM#ZDsA}Aac2>} z>DvdWFy~~9!5OpSn1`iF&(&Z?bah>)Y@J=oIpvNJt$>1Lubc3K(G!z=Ei?8+E{Z|+ zP2p`bl=m?+H}0Pi_oPSYt+2=Z?JSL-@F%8~178VkppY7|SH*XxQv@4T@Ap zptNAIy}tCjOQ{|#IPTkTTc(?(k#&6q0Ac@W#a96k?(Fys9CxTtU0Yv!+Mcvy6CT93 z3x&pu-PVTjS}ASgXr{BYKfmq5JU)?a$~{ffr>ORe=+7s1(>Z(NL4_hX@(hKzF*ae z>#%uTmSZw|2w8gke<0qO=RGOa&Cq7haUO#XBAyMHFBdOKUv)>Ls=LkhFQ24RwVM;7 z*j9pL{{3=Gn>87Lcir|TSDMP??+t#2)XOkaT?GG_AR5(b&A`Z(lJEPgVs)`?mvnt8 zmox7*a?bIr3ck$$>AqZATAC_3Sfr7X>D9%#xw-j2{)mnE{3_WHDonn;)n88<>`k)x zK|@f;3nmMx!p6fT?S0|uz~4zfMB{P|Zz<_~#%joFFd9Uz$)!38qEF;B4#`bXDkDD1 zDBySAp^%6#J=tHHA1B1iNCCY2M9n6&t8=SY40}iI$Xq0a<8+1~-TQi(1Dj2Cq!90z zi5;vjSi6)H*z5ejW@)L(727R|a_2D^M;|mhtTYK2Gbs-Eu#{_SJz|*zkXncwRn>ti z_`cPpoHkv{grm;s_gEFoTq{#FJG`U|IH^9gRV}MVbx%#Dk!qr^!T0x~hz(bIef8WC zH@`=tRZjIzt;&!o(5@o}DKlE17&)2~nZ?xkirBmHLT=>H;RV~(N=*6PvhB{<a2i^f(p9I4MBG$a7bK9SV6y?0w6YJW1`8>~uBBp0Fgm kE!vum@e6SKFNc?9_7=&6MRodPC66D9Wamua+MHng1 0: + self.imagePaths = selectedFiles + self.screenShot = None # discard screenshot temp image + self.updateProgressBar(len(selectedFiles), 0) + + def screenShotSlot(self): + ''' + 选定图像文件和截图的转换过程只能同时进行一个 + 截图只能同时转换一个 + ''' + self.screenShotWg.start() + if self.screenShotWg.captureImage: + self.screenShot = self.screenShotWg.captureImage + self.imagePaths.clear() # discard openfile temp list + self.updateProgressBar(1, 0) + + def startSlot(self, lang): + if self.screenShot: # for screenShot + img_name = 'screenshot_' + time.strftime("%Y%m%d%H%M%S", time.localtime()) + image = QImageToCvMat(self.screenShot) + self.predictAndSave(image, img_name, lang) + # update Progress Bar + self.updateProgressBar(1, 1) + QtWidgets.QMessageBox.information(self, + u'Information', "文档提取完成") + elif len(self.imagePaths) > 0 : # for image file selection + self.output_dir = os.path.join( + os.path.dirname(self.imagePaths[0]), "output") # output_dir shold be same as imagepath + os.makedirs(self.output_dir, exist_ok=True) + for i, image_file in enumerate(self.imagePaths): + if os.path.basename(image_file)[-3:] in ['pdf']: + import fitz + from PIL import Image + imgs = [] + with fitz.open(image_file) as pdf: + for pg in range(0, pdf.pageCount): + page = pdf[pg] + mat = fitz.Matrix(2, 2) + pm = page.getPixmap(matrix=mat, alpha=False) + + # if width or height > 2000 pixels, don't enlarge the image + if pm.width > 2000 or pm.height > 2000: + pm = page.getPixmap(matrix=fitz.Matrix(1, 1), alpha=False) + + img = Image.frombytes("RGB", [pm.width, pm.height], pm.samples) + img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) + imgs.append(img) + + else: + img = cv2.imread(image_file) + if img is None: + print("error in loading image:{}".format(image_file)) + continue + imgs = [img] + + img_name = os.path.basename(image_file).split('.')[0] + os.makedirs(os.path.join(self.output_dir, img_name), exist_ok=True) + self.predictAndSave(imgs, img_name, lang) + + # update Progress Bar + self.updateProgressBar(len(self.imagePaths), i+1) + QtWidgets.QMessageBox.information(self, + u'Information', "文档提取完成") + else: + print('empty input') + + def predictAndSave(self, imgs, img_name, lang): + all_res = [] + for index, img in enumerate(imgs): + if lang == 'EN': + res, time_dict = self.structure_sys_en(img) + elif lang == 'CN': + res, time_dict = self.structure_sys_cn(img) + + # save output + save_structure_res(res, self.output_dir, img_name) + draw_img = draw_structure_result(img, res, self.vis_font_path) + img_save_path = os.path.join(self.output_dir, img_name, 'show_{}.jpg'.format(index)) + if res != []: + cv2.imwrite(img_save_path, draw_img) + + # recovery + h, w, _ = img.shape + res = sorted_layout_boxes(res, w) + all_res += res + + try: + convert_info_docx(img, all_res, self.output_dir, img_name, self.save_pdf) + except Exception as ex: + QtWidgets.QMessageBox.information(self, + u'Information', "error in layout recovery image:{}, err msg: {}".format( + img_name, ex)) + + print('result save to {}'.format(self.output_dir)) + + def showResultSlot(self): + if os.path.exists(self.output_dir): + if platform.system() == 'Windows': + os.startfile(self.output_dir) + else: + os.system('open ' + os.path.normpath(self.lastOpenDir)) + else: + QtWidgets.QMessageBox.information(self, + u'Information', "输出文件不存在") + + def updateProgressBar(self, loaded, finished): + self.pb.setText( + self.pb_text.format(loaded, finished)) + + +def main(): + app = QtWidgets.QApplication(sys.argv) + + window = APP_Image2Doc() # 创建对象 + window.show() # 全屏显示窗口 + + QtWidgets.QApplication.processEvents() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() \ No newline at end of file -- GitLab