You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制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())
解决方案

你只需要在两个关键操作节点做坐标范围限制即可:

  1. 鼠标按下时先判断点击位置是否在图像范围内,不在的话直接不创建选择框
  2. 鼠标拖动过程中对实时坐标做裁剪,强制限制在图像的边界内

具体修改代码

第一步:修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 08:48:01