class GrabToolWindow(QWidget): """抓取工具窗口,显示在最前端""" sigDisplay = pyqtSignal() # 创建信号,用于新建贴图窗口 sigScreenShot = pyqtSignal() def __init__(self): super(GrabToolWindow, self).__init__() self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SplashScreen) # 无边框 置顶 不显示任务栏图标 self.setStyleSheet('''background-color:black; ''') self.setWindowOpacity(0.3) self.desktopRect = QDesktopWidget().screenGeometry() self.setGeometry(self.desktopRect) self.setCursor(Qt.CrossCursor) self.blackMask = QBitmap(self.desktopRect.size()) self.blackMask.fill(Qt.black) self.availTopLeftPoint = QDesktopWidget().availableGeometry().topLeft( ) # 有效显示区域左上角点 self.isDrawing = False self.startPoint = QPoint() self.endPoint = QPoint() self.hk1, self.hk2 = SystemHotkey(), SystemHotkey() # 初始化两个热键 # self.hk1.register(('control', '1'), callback=lambda x: self.showGrabWindow()) self.hk2.register(('control', 'shift', 'j'), callback=lambda x: self.showGrabWindow()) self.sigDisplay.connect(self.createDisplayWin) # 信号连接到子窗口创建函数 self.displayWinDict = {} self.displayWinNum = 0 self.sigScreenShot.connect(self.createScreenShotWin) def createDisplayWin(self): # 这里使用字典存储新建的窗口,当窗口关闭时未进行内存释放,可进行优化 self.displayWinDict[self.displayWinNum] = DisplayWindow( self.screenshot, self.startPoint + self.availTopLeftPoint, self.displayWinNum) self.displayWinDict[self.displayWinNum].show() self.displayWinNum += 1 def createScreenShotWin(self): self.screenshotWindow = ScreenshotWindow() self.screenshot_full_screen = self.screenshotWindow.screenShot self.screenshotWindow.show() def showGrabWindow(self): self.sigScreenShot.emit() # 创建并显示全屏截图窗口 self.activateWindow() # 激活窗口以在最顶部显示 self.setWindowState(Qt.WindowActive) # 设置为激活窗口,以便使用快捷键 self.show() # 显示抓取窗口(主窗口) def keyPressEvent(self, event): # Alt+Q键或ESC关闭窗口退出程序, Q键隐藏抓取窗口 if event.key() == Qt.Key_Escape: self.close(), exit(0) elif event.key() == Qt.Key_Q: if QApplication.keyboardModifiers() == Qt.AltModifier: self.close(), exit(0) else: self.hide() self.screenshotWindow.hide() def paintMask(self): self.mask = self.blackMask.copy() pp = QPainter(self.mask) pen = QPen() pen.setStyle(Qt.NoPen) pp.setPen(pen) brush = QBrush(Qt.white) pp.setBrush(brush) pp.drawRect(QRect(self.startPoint, self.endPoint)) self.setMask(QBitmap(self.mask)) self.update() def paintEvent(self, event): if self.isDrawing: self.paintMask() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.isDrawing = True self.startPoint = event.pos() # 相对有效区域左上角点的坐标 self.endPoint = self.startPoint def mouseMoveEvent(self, event): if Qt.LeftButton and self.isDrawing: self.endPoint = event.pos() self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.endPoint = event.pos() self.paintMask() # 确保mask绘制完整 self.isDrawing = False if self.endPoint.x() - self.startPoint.x() > 10 and self.endPoint.y( ) - self.startPoint.y() > 10: # 框选区域太小或从右至左框选不进行截图 self.screenshot = self.screenshot_full_screen.copy( QRect(self.startPoint, self.endPoint)) # 截取框选区域 self.hide() # 隐藏抓取工具窗口 self.screenshotWindow.close() # 关闭全屏截图显示窗口 self.sigDisplay.emit() # 触发信号创建新的子窗口来显示截图 self.setMask(QBitmap((self.blackMask.copy()))) # 框选操作结束, 恢复窗口mask
class WScreenShot(QWidget): def __init__(self, Init, chooseRange, parent=None): super(WScreenShot, self).__init__(parent) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # | Qt.Tool) self.setWindowState(Qt.WindowFullScreen | Qt.WindowActive) self.setStyleSheet('''background-color:black; ''') self.setWindowOpacity(0.6) desktopRect = QDesktopWidget().screenGeometry() self.setGeometry(desktopRect) self.setCursor(Qt.CrossCursor) self.blackMask = QBitmap(desktopRect.size()) self.blackMask.fill(Qt.black) self.mask = self.blackMask.copy() self.isDrawing = False self.startPoint = QPoint() self.endPoint = QPoint() self.Init = Init self.chooseRange = chooseRange def paintEvent(self, event): try: if self.isDrawing: self.mask = self.blackMask.copy() pp = QPainter(self.mask) pen = QPen() pen.setStyle(Qt.NoPen) pp.setPen(pen) brush = QBrush(Qt.white) pp.setBrush(brush) pp.drawRect(QRect(self.startPoint, self.endPoint)) self.setMask(QBitmap(self.mask)) except Exception: write_error(format_exc()) def mousePressEvent(self, event): try: if event.button() == Qt.LeftButton: self.startPoint = event.pos() self.endPoint = self.startPoint self.isDrawing = True except Exception: write_error(format_exc()) def mouseMoveEvent(self, event): try: if self.isDrawing: self.endPoint = event.pos() self.update() except Exception: write_error(format_exc()) def getRange(self): start = findall(r'(\d+), (\d+)', str(self.startPoint))[0] end = findall(r'\d+, \d+', str(self.endPoint))[0] end = end.split(', ') X1 = int(start[0]) Y1 = int(start[1]) X2 = int(end[0]) Y2 = int(end[1]) if X1 > X2: tmp = X1 X1 = X2 X2 = tmp if Y1 > Y2: tmp = Y1 Y1 = Y2 Y2 = tmp with open(folder_path + '/config/settin.json') as file: data = load(file) data["range"]["X1"] = X1 data["range"]["Y1"] = Y1 data["range"]["X2"] = X2 data["range"]["Y2"] = Y2 with open(folder_path + '/config/settin.json', 'w') as file: dump(data, file, indent=2) self.chooseRange.setGeometry(X1, Y1, X2 - X1, Y2 - Y1) self.chooseRange.Label.setGeometry(0, 0, X2 - X1, Y2 - Y1) self.chooseRange.show() # screenshot = QApplication.primaryScreen().grabWindow(QApplication.desktop().winId()) # screenshot.save(folder_path + '/config/full.jpg', format='JPG', quality=100) # outputRegion = screenshot.copy(QRect(int(X1), int(Y1), int(X2 - X1), int(Y2 - Y1))) # outputRegion.save(folder_path + '/config/image.jpg', format='JPG', quality=100) def updata_Init(self): try: if self.Init.mode == False: self.Init.start_login() except Exception: write_error(format_exc()) def mouseReleaseEvent(self, event): try: if event.button() == Qt.LeftButton: self.endPoint = event.pos() self.getRange() self.close() self.updata_Init() except Exception: write_error(format_exc()) def keyPressEvent(self, QKeyEvent): if QKeyEvent.key() == Qt.Key_Escape: self.close()
class WScreenShot(QWidget): storePath = 'screenShot.jpg' imageType = 'JPG' quality = 100 def __init__(self, parent=None): super(WScreenShot, self).__init__(parent) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setStyleSheet('''background-color:black; ''') self.setWindowOpacity(0.6) desktopRect = QDesktopWidget().screenGeometry() self.setGeometry(desktopRect) self.setCursor(Qt.CrossCursor) self.blackMask = QBitmap(desktopRect.size()) self.blackMask.fill(Qt.black) self.mask = self.blackMask.copy() self.isDrawing = False self.startPoint = QPoint() self.endPoint = QPoint() def paintEvent(self, event): if self.isDrawing: self.mask = self.blackMask.copy() pp = QPainter(self.mask) pen = QPen() pen.setStyle(Qt.NoPen) pp.setPen(pen) brush = QBrush(Qt.white) pp.setBrush(brush) pp.drawRect(QRect(self.startPoint, self.endPoint)) self.setMask(QBitmap(self.mask)) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.startPoint = event.pos() self.endPoint = self.startPoint self.isDrawing = True def mouseMoveEvent(self, event): if self.isDrawing: self.endPoint = event.pos() self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.endPoint = event.pos() screenShot = QApplication.primaryScreen().grabWindow( QApplication.desktop().winId()) rect = QRect(self.startPoint, self.endPoint) outputRegion = screenShot.copy(rect) outputRegion.save(self.storePath, format=self.imageType, quality=self.quality) self.close() def handleClick(self): if not self.isVisible(): self.show() def handleClose(self): self.close()
class Ui_ScShot_Logic(QWidget, Ui_ScShot): """ 截屏逻辑: 1、一打开截屏,就先创建屏幕截图pixScreenshot,并设置为主窗口的背景,并设置窗口无标题栏。再创建一个QWidget(wTop), 用来作为全屏的阴影遮盖,设置背景样式rgba的透明,它的大小以及主窗口的大小都是全屏大小(和全屏截图一样大)。功能 按钮容器wFunc,以及其它功能不作介绍,代码里有注释。 2、设置鼠标按下、移动,释放,来画出一个矩形框。在移动的时候设置标记(flagDrawing)为True,并self.update刷新界面,在刷新 界面时就能设置画笔和画刷来画出所要截图的范围 3、这第三步,其实有一部分在第一处就要设置。这对我来说是个难点,研究了两天,才瞎猫碰到死耗子搞成了。原本在网上复制的 截屏代码,竟然矩形选框内被穿透了,直接可以操作内部(此程序下面此范围内),很是让人无语。才研究两天,总算总算解决了。 此程序就是用pyqt5的setMask遮罩效果来完成对我来说主要的功能的。 在初始化处先创建一个QBitmap(blackMask),并填充成黑色,然后设置QWidget(wTop)的遮罩为blackMask。这里简单说一下,如果 在遮罩的地方用白色画刷涂的地方会失去遮罩(也就是wTop的背景),显示出下层窗口(窗口已经设置为全屏截图)的图片。 用黑色画刷涂的话,就是显示出wTop的背景。这里就样photoShop中的那个蒙版效果,原理是一样的。重要处在paintEvent事件中, 已经有注释。 """ def __init__(self, parent): super(Ui_ScShot_Logic, self).__init__() self.setupUi(self) # 加载设计师画的界面 self.p = parent # 主窗口设置 self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # 隐藏标题栏 self.pixPrtSc = QApplication.primaryScreen().grabWindow(QApplication.desktop().winId()) # 截取整个屏幕(QPixmap类型) self.resize(self.pixPrtSc.size()) # 设置主窗口大小 p = QPalette() p.setBrush(self.backgroundRole(), QBrush(self.pixPrtSc)) self.setPalette(p) # 设置主窗口背景 self.flagDrawing = False # 主窗口的鼠标拖动标记,已经为子 self.wid = QLabel(self) self.wid.lower() # self.wid.raise_() # 最上层 self.wid.resize(self.size()) self.wid.move(0, 0) self.wid.setStyleSheet('border:3px solid #00FFFF;') # self.wid.hide() # 阴影容器设置 self.wTop.resize(self.size()) # 设置wTop也为屏幕大小 self.blackMask = QBitmap(self.size()) # 创建位图,全屏大小 self.blackMask.fill(Qt.black) # 填充位图为全黑。显示效果为原本wTop的背影,如果全白,相当于把wTop擦掉了。 self.wTop.setMask(self.blackMask) # 设置self.wTop的遮罩为self.blackMask self.wTop.enterEvent = self.wTop_EnterEvent # 设置wTop的鼠标进入事件。事件内有详细注释。 self.flagWTopEnter = False # wTop的鼠标进入事件的标记 # 其它需要初始化的 self.btnOk.clicked.connect(self.slot_ScShot_btns) self.btnSaveAs.clicked.connect(self.slot_ScShot_btns) self.wFunc.hide() # 先隐藏功能按钮容器 self.wInfo.hide() # 本来可以不用隐藏,再让后面显示,但是那样它会闪一下。因为原来的位置是在Qt设计师中乱放的。 self.strDpi = "0 x 0" self.flag = False # lab = QLabel('aaa', self.wTop) # lab.resize(300, 300) # lab.setStyleSheet('background:red;') def wTop_EnterEvent(self, e): """ 鼠标进入wTop子QWidget内,为了处理一打开截屏界面wInfo初始跟随鼠标状态,只需要一次,因为有self的鼠标 mouseMoveEvent处理后续动作。之所以要做一个标记flagWTopEnter为False才有动作,是因为在截图时,画的矩形 选区内是self的焦点,之外才是wTop的焦点。鼠标在矩形内外来回移动会多次处理此事件动作,关键是此动作和 self的鼠标移动件事有冲突,都在处理wInfo的鼠标跟随。目前只想到这种方法解决。 """ if not self.flagWTopEnter: # 只有self.flagWTopEnter=False时,not self.flagWTopEnter 才为真 self.flagWTopEnter = True self.imgPrtSc = self.pixPrtSc.toImage() self.method_wInfo(e.globalPos().x(), e.globalPos().y()) self.wInfo.show() # rect = QRect(e.globalPos().x() - 13, e.globalPos().y() - 10, 114, 85) # 截取的范围 # copyZoomIn = self.pixPrtSc.copy(rect) # self.labInfoZoomIn.setPixmap(copyZoomIn) def method_wInfo(self, pointX, pointY): """QWidget(wInfo)的移动,以及wInfo内的QLebal(labInfoZoomIn)的图片设置""" self.wInfo.move(pointX + 5, pointY + 20) color = self.imgPrtSc.pixelColor(pointX, pointY) self.strRgb = str(color.getRgb()[:-1]) self.textEdInfoRgb.setText(f"DPI:({self.strDpi})<br/>RGB:{self.strRgb}") # 13 10 # 参数32,24,是矩形的宽高,是要QLabel(labInfoZoomIn)设置的图片,因为在设计师中已经设置setScaledContents为True, # 所以它放比它小的图片会放大。而pointX-14和pointY-10,是矩形的左上角位置,需要减多少,是看原图大小的宽高除以2, # 再减去十字线(宽4px)的一半(32/2-2=14,24/2-2=10。labInfoZoomIn的尺寸是116x84 # 这样,labInfoZoomIn在显示图片的时候,十字交差处才会是鼠标指针点的位置。 rect = QRect(pointX-14, pointY-10, 32, 24) # 截取的范围 copyZoomIn = self.pixPrtSc.copy(rect) self.labInfoZoomIn.setPixmap(copyZoomIn) def slot_ScShot_btns(self): sender = self.sender() filePath = self.p.lineEdFilePath.text() rq = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) self.copyPixPrtSc = self.pixPrtSc.copy(self.rect) # 截取矩形区内的图片 PlaySound('prtSc.wav', flags=1) if sender == self.btnOk: if not os.path.exists(filePath): # 如果此路径没有此文件夹,就创建一个 os.mkdir(filePath) imgPath = f"{filePath}/{rq}.png" self.copyPixPrtSc.save(imgPath, format='png', quality=100) elif sender == self.btnSaveAs: currentPath = filePath + f"/{rq}.png" fileName, _ = QFileDialog.getSaveFileName(self, "另存为", currentPath, f"PNG File(*.png);;All Files(*)") if fileName: self.copyPixPrtSc.save(fileName, format='png', quality=100) self.close() def paintEvent(self, event): if self.flag: pointX = self.endPoint.x() pointY = self.endPoint.y() self.method_wInfo(pointX, pointY) # 调用移动方法 if self.flagDrawing: self.mask = self.blackMask.copy() # 必须要拷贝,其实我也不是很理解这句为何。 pp = QPainter(self.mask) # 参数一定要是拷贝的QPixmap(self.mask) pen = QPen() # 创建画笔 Qt.green, 13, Qt.DashDotLine, Qt.RoundCap, Qt.RoundJoin pen.setStyle(Qt.NoPen) pp.setPen(pen) brush = QBrush(Qt.white) # 创建画刷 pp.setBrush(brush) rect = QRect(self.startPoint, self.endPoint) pp.drawRect(rect) # 画矩形 self.wTop.setMask(QBitmap(self.mask)) # 是wTop设置遮罩,不是主窗口。不然会穿透的。 self.strDpi = f"{abs(rect.width())} x {abs(rect.height())}" # self.wid.setStyleSheet('border:2px dashed #00FFFF;') # 改变边宽和样式(虚线) # self.wid.setGeometry(rect.adjusted(-2, -2, 2, 2)) # 上下左右都往外偏移2px,因为上面设置border为2px # self.wid.setGeometry( rect.x()-2, rect.y()-2,abs(rect.width())+2,abs(rect.height())+2) # 上下左右都往外偏移2px,因为上面设置border为2px try: sX, sY, eX, eY = self.startPoint.x(), self.startPoint.y(), self.endPoint.x(), self.endPoint.y() p2 = QPainter(self) pen2 = QPen(QColor('#00FFFF'), 1) p2.setPen(pen2) p2.drawRect(rect) # 下面这四句忘了本来要做什么的了 # p2.fillRect(rect, QColor('background:rgba(0,0,0,100);')) # rect.adjust(50, 50, -50, -50) # p2.drawRect(rect) # p2.fillRect(rect, Qt.red) listPointRect = [(sX, sY), (sX + (eX - sX) / 2, sY), (eX, sY), (eX, sY + (eY - sY) / 2), (eX, eY), (sX + (eX - sX) / 2, eY), (sX, eY), (sX, sY + (eY - sY) / 2)] for x, y in listPointRect: p2.drawRect(x - 3, y - 3, 6, 6) p2.fillRect(x - 3, y - 3, 6, 6, QBrush(QColor('#00FFFF'))) # p2.begin(self) # path = QPainterPath() # path.addRect(QRectF(self.startPoint, self.endPoint)) # self.wid.setGeometry(QRect(*path.controlPointRect().getRect()).adjusted(-20, -20, 20, 20)) # p2.drawRect(QRectF(*(sX, sY), *(sX + (eX - sX) / 2, sY))) # listPointRect = [(sX, sY), (sX+(eX-sX)/2, sY), (eX, sY), (eX, sY+(eY-sY)/2), # (eX, eY), (sX+(eX-sX)/2, eY), (sX, eY), (sX, sY+(eY-sY)/2)] # path2 = QPainterPath() # for x, y in listPointRect: # path2.addRect(x-3, y-3, 6, 6) # p2.fillPath(path2, QBrush(QColor('#00FFFF'))) # path.connectPath(path2) # 连接两个闭合路径 # path.translate(50, 50) # 偏移到某点 # p2.drawPath(path) # self.wid.setStyleSheet('border:none;') # p2.end() except Exception as e: print(e) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: # self.wid.hide() self.wTop.setStyleSheet('background:rgba(0,0,0,100);') # 设置背景透明度,阴影效果。鼠标按下时再出现 self.wInfo.show() self.startPoint = event.pos() self.endPoint = self.startPoint self.wFunc.hide() # 鼠标按下时隐藏功能按钮容器 self.flagDrawing = True elif event.button() == Qt.RightButton: self.close() def mouseMoveEvent(self, event): """ 必须为子控件设置鼠标跟踪setMouseTracking(True),不然鼠标在控件上时,就不再追踪了。 已经在Qt设计师中设置好了 """ self.flag = True self.endPoint = event.pos() self.update() # self.pointX = event.globalPos().x() # event.pos()也行 # self.pointY = event.globalPos().y() # self.flag = True # # self.method_wInfo_move(pointX, pointY) # 调用移动方法 # if self.flagDrawing: # self.endPoint = event.pos() # self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.endPoint = event.pos() self.rect = QRect(self.startPoint, self.endPoint) # 截取的范围 shotX, shotY, shotWidth, shotHeight = self.rect.getRect() wFuncWidth, wFuncHeight = self.wFunc.width(), self.wFunc.height() # 以下if语句是判断鼠标释放后,最后一点到窗口边缘时,功能按钮容器超界问题 if shotWidth < 0: moveX = shotX + shotWidth - (wFuncWidth + shotWidth) # 这里width是负数,这里用+号相当于减去 else: moveX = shotX + shotWidth - wFuncWidth if shotHeight < 0: moveY = shotY else: moveY = shotY + shotHeight if moveX < 0: moveX = 0 if self.pixPrtSc.height() - moveY < wFuncHeight: moveY = moveY - shotHeight - wFuncHeight self.wFunc.move(moveX, moveY) self.wFunc.show() # 鼠标释放时显示功能按钮容器 self.wInfo.hide() # 鼠标释放时隐藏信息显示容器 self.flagDrawing = False self.flag = False # def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.close()