我怎样才能限制 GraphicsScene 以便只能在加载的图像上绘制?
How can I limit the GraphicsScene in order to only be able to draw on the image loaded?
我有一个脚本正在加载图像并将其显示在 QGraphicsView 中,它还将 GraphicsView 的大小设置为图像的尺寸。
我需要能够放大/缩小,同时只能在图像上绘图。目前我可以放大/缩小但可以在 GraphicsView 上的任何地方绘制,这意味着当图像缩小时我可以在它的外部绘制,如下所示。
如何才能使 RubberBand 只能在图像上绘制?
这是我的 GraphicsView:
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))
这是 RubberBand 代码:
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
# Functionality:
# - On initial mouse click
# Create new RubberBand, select area until mouse is released
# - Allow for resizing when selected
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())
由于您使用场景矩形作为参考,因此您必须检查选区的几何形状是否在场景边缘内,并且您还应该考虑橡皮筋小部件的最小尺寸。
此外,与其进行复杂的计算来检查宽度或高度是否为“负数”,您可以使用两个点(单击位置和从鼠标移动接收到的位置)创建 QRectF,然后使用 normalized()
函数得到相同的矩形,但具有正的宽度和高度。
class CustomGraphicsView(QGraphicsView):
def mousePressEvent(self, event):
self.startPos = self.mapToScene(event.pos())
if not self.band:
self.band = SelectionBand(self)
self.bandItem = self.scene().addWidget(self.band)
self.bandItem.setFlag(QGraphicsItem.ItemIsMovable)
self.bandItem.setZValue(1)
sceneRect = self.sceneRect()
if self.startPos.x() < sceneRect.x():
self.startPos.setX(sceneRect.x())
elif self.startPos.x() > sceneRect.right() - self.band.minimumWidth():
self.startPos.setX(sceneRect.right() - self.band.minimumWidth())
if self.startPos.y() < sceneRect.y():
self.startPos.setY(sceneRect.y())
elif self.startPos.y() > sceneRect.bottom() - self.band.minimumHeight():
self.startPos.setY(sceneRect.bottom() - self.band.minimumHeight())
self.bandItem.setPos(self.startPos)
def mouseMoveEvent(self, event):
mousePos = self.mapToScene(event.pos())
# create a normalized rectangle based on the two points
rect = QRectF(self.startPos, mousePos).normalized()
if rect.width() < self.band.minimumWidth():
rect.setWidth(self.band.minimumWidth())
if rect.x() < self.startPos.x():
rect.moveLeft(self.startPos.x())
if rect.height() < self.band.minimumHeight():
rect.setHeight(self.band.minimumHeight())
if rect.y() < self.startPos.y():
rect.moveBottom(self.startPos.y())
sceneRect = self.sceneRect()
if rect.x() < sceneRect.x():
rect.setX(sceneRect.x())
elif rect.x() > sceneRect.right() - self.band.minimumWidth():
rect.setX(sceneRect.right() - self.band.minimumWidth())
if rect.right() > sceneRect.right():
rect.setRight(sceneRect.right())
if rect.y() < sceneRect.y():
rect.setY(sceneRect.y())
elif rect.y() > sceneRect.bottom() - self.band.minimumHeight():
rect.setY(sceneRect.bottom() - self.band.minimumHeight())
if rect.bottom() > sceneRect.bottom():
rect.setBottom(sceneRect.bottom())
self.bandItem.setGeometry(rect)
请考虑使用 QWidget 进行选择并不是一个好主意(并且您的实现无论如何都有点缺陷):您没有使用 QWidget 的大部分功能(除了大小控制,这是反正没用过),所以用起来有点无意义
您可以改为使用 QGraphicsRectItem 的子class。这有很多优点:不仅它的用法和图形视图框架更加连贯,而且,例如,您可以使用 cosmetic 笔作为边框,它始终是即使缩小也能清晰显示。
class SelectionBandItem(QGraphicsRectItem):
_minimumWidth = _minimumHeight = 1
def __init__(self):
super().__init__()
color = QApplication.palette().highlight().color()
pen = QPen(color, 1)
pen.setCosmetic(True)
self.setPen(pen)
color.setAlpha(128)
self.setBrush(color)
self.setFlag(QGraphicsItem.ItemIsMovable)
def minimumWidth(self):
return self._minimumWidth
def minimumHeight(self):
return self._minimumHeight
def setGeometry(self, rect):
# we cannot use setRect, as it will only set the drawn rectangle but
# not the position of the item, which makes it less intuitive; it's
# better to use the given rectangle to set the position and then
# resize the item's rectangle
self.setPos(rect.topLeft())
current = self.rect()
current.setSize(rect.size())
self.setRect(current)
def geometry(self):
return self.rect().translated(self.pos())
class CustomGraphicsView(QGraphicsView):
# ...
def mousePressEvent(self, event):
self.startPos = self.mapToScene(event.pos())
if not self.band:
self.band = SelectionBandItem()
self.scene().addItem(self.band)
sceneRect = self.sceneRect()
if self.startPos.x() < sceneRect.x():
self.startPos.setX(sceneRect.x())
elif self.startPos.x() > sceneRect.right():
self.startPos.setX(sceneRect.right())
if self.startPos.y() < sceneRect.y():
self.startPos.setY(sceneRect.y())
elif self.startPos.y() > sceneRect.bottom():
self.startPos.setY(sceneRect.bottom())
self.band.setPos(self.startPos)
def mouseMoveEvent(self, event):
# all of the above, except for this:
self.band.setGeometry(rect)
然后,如果你想添加对大小手柄的支持,请在 SelectionBandItem
class 的 mouseMoveEvent
中实现它。
不相关的注释:
self.width
和 self.height
是所有 Qt 小部件的属性,您不应该用其他值覆盖它们,特别是如果这些值与 self
无关;此外,由于您不断更改 mouseMoveEvent
中的这些值,因此将它们设为实例属性真的没有意义:如果您需要从其他函数访问 band 的大小,只需使用 self.band.width()
或self.band.height()
;
- 如果您只是检查角度增量是否大于或小于 0,则将角度增量除以 120 毫无意义;
- 新项目总是添加到 0 z 级别的项目堆栈顶部,因此将其设置为 1 没什么用处;
setMouseTracking
是函数不是变量;
- 向场景添加 new 小部件时,您不应在其
__init__
中调用 show
,因为这会使它们显示为正常 window瞬间;
-
QBoxLayout.addWidget
的 stretch
参数已经默认为零,如果要指定对齐方式,最好显式使用关键字;另外,如果您将小部件添加到布局,则不需要父参数:layout.addWidget(QSizeGrip(), alignment=Qt.AlignRight|Qt.AlignBottom)
;
- 如果您要将图形视图添加到布局中,调用它的
resize()
是完全没用的;布局管理器的范围是通过设置其所有项目的位置和大小来[=44=]管理布局,因此手动请求任何几何更改在概念上是错误的;虽然它 可以 调整小部件的大小,但只要调整了任何父级或兄弟级的大小,布局就会覆盖该请求;
我有一个脚本正在加载图像并将其显示在 QGraphicsView 中,它还将 GraphicsView 的大小设置为图像的尺寸。
我需要能够放大/缩小,同时只能在图像上绘图。目前我可以放大/缩小但可以在 GraphicsView 上的任何地方绘制,这意味着当图像缩小时我可以在它的外部绘制,如下所示。
如何才能使 RubberBand 只能在图像上绘制?
这是我的 GraphicsView:
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))
这是 RubberBand 代码:
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
# Functionality:
# - On initial mouse click
# Create new RubberBand, select area until mouse is released
# - Allow for resizing when selected
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())
由于您使用场景矩形作为参考,因此您必须检查选区的几何形状是否在场景边缘内,并且您还应该考虑橡皮筋小部件的最小尺寸。
此外,与其进行复杂的计算来检查宽度或高度是否为“负数”,您可以使用两个点(单击位置和从鼠标移动接收到的位置)创建 QRectF,然后使用 normalized()
函数得到相同的矩形,但具有正的宽度和高度。
class CustomGraphicsView(QGraphicsView):
def mousePressEvent(self, event):
self.startPos = self.mapToScene(event.pos())
if not self.band:
self.band = SelectionBand(self)
self.bandItem = self.scene().addWidget(self.band)
self.bandItem.setFlag(QGraphicsItem.ItemIsMovable)
self.bandItem.setZValue(1)
sceneRect = self.sceneRect()
if self.startPos.x() < sceneRect.x():
self.startPos.setX(sceneRect.x())
elif self.startPos.x() > sceneRect.right() - self.band.minimumWidth():
self.startPos.setX(sceneRect.right() - self.band.minimumWidth())
if self.startPos.y() < sceneRect.y():
self.startPos.setY(sceneRect.y())
elif self.startPos.y() > sceneRect.bottom() - self.band.minimumHeight():
self.startPos.setY(sceneRect.bottom() - self.band.minimumHeight())
self.bandItem.setPos(self.startPos)
def mouseMoveEvent(self, event):
mousePos = self.mapToScene(event.pos())
# create a normalized rectangle based on the two points
rect = QRectF(self.startPos, mousePos).normalized()
if rect.width() < self.band.minimumWidth():
rect.setWidth(self.band.minimumWidth())
if rect.x() < self.startPos.x():
rect.moveLeft(self.startPos.x())
if rect.height() < self.band.minimumHeight():
rect.setHeight(self.band.minimumHeight())
if rect.y() < self.startPos.y():
rect.moveBottom(self.startPos.y())
sceneRect = self.sceneRect()
if rect.x() < sceneRect.x():
rect.setX(sceneRect.x())
elif rect.x() > sceneRect.right() - self.band.minimumWidth():
rect.setX(sceneRect.right() - self.band.minimumWidth())
if rect.right() > sceneRect.right():
rect.setRight(sceneRect.right())
if rect.y() < sceneRect.y():
rect.setY(sceneRect.y())
elif rect.y() > sceneRect.bottom() - self.band.minimumHeight():
rect.setY(sceneRect.bottom() - self.band.minimumHeight())
if rect.bottom() > sceneRect.bottom():
rect.setBottom(sceneRect.bottom())
self.bandItem.setGeometry(rect)
请考虑使用 QWidget 进行选择并不是一个好主意(并且您的实现无论如何都有点缺陷):您没有使用 QWidget 的大部分功能(除了大小控制,这是反正没用过),所以用起来有点无意义
您可以改为使用 QGraphicsRectItem 的子class。这有很多优点:不仅它的用法和图形视图框架更加连贯,而且,例如,您可以使用 cosmetic 笔作为边框,它始终是即使缩小也能清晰显示。
class SelectionBandItem(QGraphicsRectItem):
_minimumWidth = _minimumHeight = 1
def __init__(self):
super().__init__()
color = QApplication.palette().highlight().color()
pen = QPen(color, 1)
pen.setCosmetic(True)
self.setPen(pen)
color.setAlpha(128)
self.setBrush(color)
self.setFlag(QGraphicsItem.ItemIsMovable)
def minimumWidth(self):
return self._minimumWidth
def minimumHeight(self):
return self._minimumHeight
def setGeometry(self, rect):
# we cannot use setRect, as it will only set the drawn rectangle but
# not the position of the item, which makes it less intuitive; it's
# better to use the given rectangle to set the position and then
# resize the item's rectangle
self.setPos(rect.topLeft())
current = self.rect()
current.setSize(rect.size())
self.setRect(current)
def geometry(self):
return self.rect().translated(self.pos())
class CustomGraphicsView(QGraphicsView):
# ...
def mousePressEvent(self, event):
self.startPos = self.mapToScene(event.pos())
if not self.band:
self.band = SelectionBandItem()
self.scene().addItem(self.band)
sceneRect = self.sceneRect()
if self.startPos.x() < sceneRect.x():
self.startPos.setX(sceneRect.x())
elif self.startPos.x() > sceneRect.right():
self.startPos.setX(sceneRect.right())
if self.startPos.y() < sceneRect.y():
self.startPos.setY(sceneRect.y())
elif self.startPos.y() > sceneRect.bottom():
self.startPos.setY(sceneRect.bottom())
self.band.setPos(self.startPos)
def mouseMoveEvent(self, event):
# all of the above, except for this:
self.band.setGeometry(rect)
然后,如果你想添加对大小手柄的支持,请在 SelectionBandItem
class 的 mouseMoveEvent
中实现它。
不相关的注释:
self.width
和self.height
是所有 Qt 小部件的属性,您不应该用其他值覆盖它们,特别是如果这些值与self
无关;此外,由于您不断更改mouseMoveEvent
中的这些值,因此将它们设为实例属性真的没有意义:如果您需要从其他函数访问 band 的大小,只需使用self.band.width()
或self.band.height()
;- 如果您只是检查角度增量是否大于或小于 0,则将角度增量除以 120 毫无意义;
- 新项目总是添加到 0 z 级别的项目堆栈顶部,因此将其设置为 1 没什么用处;
setMouseTracking
是函数不是变量;- 向场景添加 new 小部件时,您不应在其
__init__
中调用show
,因为这会使它们显示为正常 window瞬间; -
QBoxLayout.addWidget
的stretch
参数已经默认为零,如果要指定对齐方式,最好显式使用关键字;另外,如果您将小部件添加到布局,则不需要父参数:layout.addWidget(QSizeGrip(), alignment=Qt.AlignRight|Qt.AlignBottom)
; - 如果您要将图形视图添加到布局中,调用它的
resize()
是完全没用的;布局管理器的范围是通过设置其所有项目的位置和大小来[=44=]管理布局,因此手动请求任何几何更改在概念上是错误的;虽然它 可以 调整小部件的大小,但只要调整了任何父级或兄弟级的大小,布局就会覆盖该请求;