如何限制QGraphicsScene仅能在已加载图像上绘制RubberBand选择框?
问题描述
我有一个脚本可以加载图像并在QGraphicsView中显示,同时会将GraphicsView的尺寸设置为与图像的尺寸匹配。
我需要在支持缩放操作的同时,仅允许在图像区域上绘制选择框。目前我已经实现了缩放功能,但可以在GraphicsView的任意位置绘制,这意味着当图像缩小后,我可以在图像外部区域绘制,如下图所示:
请问我要如何调整才能让RubberBand仅能在图像区域上绘制?
现有代码
CustomGraphicsView实现
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem from PyQt5.QtCore import Qt from PyQt5.QtGui import QBrush, QPen from SelectionBand import * from time import sleep class CustomGraphicsView(QGraphicsView): def __init__(self, parent=None): super(CustomGraphicsView, self).__init__() self.setScene(QGraphicsScene(self)) self.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.scene().setBackgroundBrush(Qt.gray) self.brush = QBrush(Qt.green) self.pen = QPen(Qt.blue) def setImage(self, image): self.img = self.scene().addPixmap(image) self.resize(self.img.boundingRect().width(),self.img.boundingRect().height()) self.scene().setSceneRect(self.img.boundingRect()) def wheelEvent(self, event): if event.modifiers() & Qt.ControlModifier: x = event.angleDelta().y() / 120 if x > 0: self.scale(1.05, 1.05) elif x < 0: self.scale(.95, .95) else: super().wheelEvent(event) def mousePressEvent(self, event): pos = self.mapToScene(event.pos()) self.xPos = pos.x() self.yPos = pos.y() self.band = SelectionBand(self) self.band.setGeometry(pos.x(), pos.y(), 0, 0) item = self.scene().addWidget(self.band) item.setFlag(QGraphicsItem.ItemIsMovable) item.setZValue(1) def mouseMoveEvent(self, event): pos = self.mapToScene(event.pos()) self.width = pos.x() - self.xPos self.height = pos.y() - self.yPos if self.width < 0 and self.height < 0: self.band.setGeometry(pos.x(), pos.y(), abs(self.width), abs(self.height)) elif self.width < 0: self.band.setGeometry(pos.x(), self.yPos, abs(self.width), abs(self.height)) elif self.height < 0: self.band.setGeometry(self.xPos, pos.y(), abs(self.width), abs(self.height)) else: self.band.setGeometry(self.xPos, self.yPos, abs(self.width), abs(self.height))
SelectionBand实现
from typing import ItemsView from PyQt5 import QtWidgets from PyQt5.QtWidgets import QApplication, QHBoxLayout, QRubberBand, QWidget,QSizeGrip from PyQt5.QtCore import QEasingCurve, Qt, QEvent from PyQt5.QtGui import QCursor, QHoverEvent, QPixmap import sys # 功能说明: # - 鼠标点击时创建新的RubberBand,按住拖动选择区域直到鼠标释放 # - 选中后支持调整大小 class SelectionBand(QWidget): def __init__(self, parent=None): super(SelectionBand, self).__init__() # self.setMouseTracking=True self.draggable = True self.dragging_threshold = 5 self.borderRadius = 5 self.setWindowFlags(Qt.SubWindow) layout = QHBoxLayout(self) layout.setContentsMargins(0,0,0,0) layout.addWidget( QSizeGrip(self), 0, Qt.AlignRight | Qt.AlignBottom) self._band = QtWidgets.QRubberBand( QtWidgets.QRubberBand.Rectangle, self) self._band.show() self.show() def resizeEvent(self, event): self._band.resize(self.size())
解决方案
你只需要在两个关键操作节点做坐标范围限制即可:
- 鼠标按下时先判断点击位置是否在图像范围内,不在的话直接不创建选择框
- 鼠标拖动过程中对实时坐标做裁剪,强制限制在图像的边界内
具体修改代码
第一步:修改mousePressEvent方法
def mousePressEvent(self, event): pos = self.mapToScene(event.pos()) # 获取图像的边界矩形 img_rect = self.img.boundingRect() # 点击位置不在图像范围内就直接返回 if not img_rect.contains(pos): return self.xPos = pos.x() self.yPos = pos.y() self.band = SelectionBand(self) self.band.setGeometry(pos.x(), pos.y(), 0, 0) item = self.scene().addWidget(self.band) item.setFlag(QGraphicsItem.ItemIsMovable) item.setZValue(1)
第二步:修改mouseMoveEvent方法
def mouseMoveEvent(self, event): pos = self.mapToScene(event.pos()) img_rect = self.img.boundingRect() # 对当前坐标做裁剪,限制在图像范围内 clamped_x = max(img_rect.left(), min(pos.x(), img_rect.right())) clamped_y = max(img_rect.top(), min(pos.y(), img_rect.bottom())) self.width = clamped_x - self.xPos self.height = clamped_y - self.yPos if self.width < 0 and self.height < 0: self.band.setGeometry(clamped_x, clamped_y, abs(self.width), abs(self.height)) elif self.width < 0: self.band.setGeometry(clamped_x, self.yPos, abs(self.width), abs(self.height)) elif self.height < 0: self.band.setGeometry(self.xPos, clamped_y, abs(self.width), abs(self.height)) else: self.band.setGeometry(self.xPos, self.yPos, abs(self.width), abs(self.height))
如果需要兼容SelectionBand调整大小的场景,也可以在SelectionBand类中添加相同的坐标范围校验,逻辑和上面一致,拿到图像边界做坐标裁剪即可。
内容的提问来源于stack exchange,提问作者Justin Wade
相关产品推荐
相关产品推荐

