PYQT Draw selection rectangle over picture
Asked Answered
J

1

8

I'm confused on how to click and draw a rectangle over a picture that the user has loaded in. I found a few examples that I tried to follow but nothing seems to work and I am not sure why or how to go about fixing it.

I have put some breakpoints in and it doesn't seem to be going into the mouseMoveEvent function, but I am not sure why. Any help would be very much appreciated.

What I would like

I would like to be able to click and drag on a picture that I have loaded into QGraphicsand have it draw a box and then in the status bar output the X and Y coordinates of both points of the rectangle. I would also like the rectangle to stay there until the user clicks on the picture a second time.

Examples I have found

What I would like

Simple example

Current Code

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        QMainWindow.__init__(self, *args, **kwargs)
        self.setupUi(self)
        self.setUpMainUiFunction()

    def setUpMainUiFunction(self):
        self.actionOpen.triggered.connect(self.OpenDialog)
        self.Button_LoadPhoto.clicked.connect(self.OpenDialog)

        open = QAction(QIcon("icons/open.bmp"), "open", self)
        save = QAction(QIcon("icons/save.bmp"), "save", self)
        NormalCursor = QAction(QIcon("icons/cursor-normal.png"), "NormalCursor", self)
        CrosshairCursor = QAction(QIcon("icons/crosshair.png"), "CrosshairCursor", self)

        self.TopToolBar.addAction(open)
        self.TopToolBar.addAction(save)
        self.LeftToolBar.addAction(NormalCursor)
        self.LeftToolBar.addAction(CrosshairCursor)

        # self.TopToolBar.actionTriggered[QAction].connect(self.toolbtnpressed)

    def OpenDialog(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        PicturePath = QStandardPaths.standardLocations(QStandardPaths.PicturesLocation)[0]
        filenames, _ = QFileDialog.getOpenFileNames(self, "Open File", PicturePath, "JPEG File (*.png)", options=options)
        for filename in filenames:
            pixmap = QPixmap(filename)
            self.showPicture(pixmap)
            self.statusbar.showMessage("Successfully Loaded: {}".format(filename))

    def showPicture(self, picture):
        sub = QMdiSubWindow(self)
        loadPicture = LoadPicture(picture, sub)
        sub.setWidget(loadPicture)
        sub.setObjectName("Load_Picture_window")
        sub.setWindowTitle("New Photo")
        self.mdiArea.addSubWindow(sub)
        sub.show()
        sub.resize(picture.size())
        loadPicture.log.MousePixmapSignal.connect(self.updatePixel)

    def updatePixel(self, point, color):
        self.UserInput_PixelValue_X.setText("{}".format(point.x()))
        self.UserInput_PixelValue_Y.setText("{}".format(point.y()))

        self.UserInput_PixelValue_R.setText("{}".format(color.red()))
        self.UserInput_PixelValue_G.setText("{}".format(color.green()))
        self.UserInput_PixelValue_B.setText("{}".format(color.blue()))

This is in a seprate file.

class LogObject(QObject):
    MousePixmapSignal = pyqtSignal(QPoint, QColor)


class PictureItem(QGraphicsPixmapItem):
    def __init__(self, log, *args, **kwargs):
        QGraphicsPixmapItem.__init__(self, *args, **kwargs)
        self.setAcceptHoverEvents(True)
        self.log = log

    def hoverMoveEvent(self, event):
        point = event.pos().toPoint()
        color = QColor(self.pixmap().toImage().pixel(point.x(), point.y()))
        self.log.MousePixmapSignal.emit(point, color)
        QGraphicsPixmapItem.hoverMoveEvent(self, event)

    def hoverEnterEvent(self, event):
        QApplication.setOverrideCursor(Qt.CrossCursor)
        QGraphicsPixmapItem.hoverMoveEvent(self, event)

    def hoverLeaveEvent(self, event):
        QApplication.setOverrideCursor(Qt.ArrowCursor)
        QGraphicsPixmapItem.hoverLeaveEvent(self, event)


    def paintEvent(self, event):
        qp = QPainter(self)
        br = QBrush(QColor(100, 10, 10, 40))
        qp.setBrush(br)
        qp.drawRect(QRect(self.begin, self.end))

   def mousePressEvent(self, event):
       self.begin = event.pos()
       self.end = event.pos()
       QGraphicsPixmapItem.mousePressEvent(self, event)
       self.update()

   def mouseMoveEvent(self, event):
       self.end = event.pos()
       QGraphicsPixmapItem.mouseMoveEvent(self, event)
       self.update()

   def mouseReleaseEvent(self, event):
       self.begin = event.pos()
       self.end = event.pos()
       QGraphicsPixmapItem.mouseReleaseEvent(self, event)
       self.update()   

class LoadPicture(QWidget, Ui_GraphicsArea):
    def __init__(self, pixmap, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.log = LogObject(self)
        self.PictureArea.setScene(QGraphicsScene())
        self.item = PictureItem(self.log, pixmap)
        self.PictureArea.scene().addItem(self.item)
        self.resize(pixmap.size())
Japonica answered 3/11, 2017 at 18:19 Comment(3)
See QRuberband.Jackie
I actually found that this afternoon but for some reason, it is just not clicking how to incorporate it. I keep getting TypeErrorsJaponica
TypeError: QRubberBand(QRubberBand.Shape, parent: QWidget = None): argument 2 has unexpected type 'PictureItem' to be exactJaponica
J
8

In order to implement this functionality we must overwrite the methods mouseMoveEvent, mousePressEvent, mouseReleaseEvent of QGraphicsView for it we create the following file:

QGraphicsView.py

class GraphicsView(QGraphicsView):
    rectChanged = pyqtSignal(QRect)

    def __init__(self, *args, **kwargs):
        QGraphicsView.__init__(self, *args, **kwargs)
        self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.setMouseTracking(True)
        self.origin = QPoint()
        self.changeRubberBand = False

    def mousePressEvent(self, event):
        self.origin = event.pos()
        self.rubberBand.setGeometry(QRect(self.origin, QSize()))
        self.rectChanged.emit(self.rubberBand.geometry())
        self.rubberBand.show()
        self.changeRubberBand = True
        QGraphicsView.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        if self.changeRubberBand:
            self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized())
            self.rectChanged.emit(self.rubberBand.geometry())
        QGraphicsView.mouseMoveEvent(self, event)

    def mouseReleaseEvent(self, event):
        self.changeRubberBand = False
        QGraphicsView.mouseReleaseEvent(self, event)

This class must be used in GraphicsArea_GUI.py for it we change the following:

self.PictureArea = QtWidgets.QGraphicsView(self.scrollAreaWidgetContents)

to:

from GraphicsView import GraphicsView
[...]
self.PictureArea = GraphicsView(self.scrollAreaWidgetContents)

The initial class uses a QRubberBand where we update its geometry, as you want the upper left position and the lower right we emit a signal sent by the associated QRect, this is connected to a slot.

    [...]
    loadPicture.log.MousePixmapSignal.connect(self.updatePixel)
    loadPicture.PictureArea.rectChanged.connect(self.onRectChanged)

def onRectChanged(self, r):
    topLeft = r.topLeft()
    bottomRight = r.bottomRight()
    print(topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y())
Jackie answered 4/11, 2017 at 7:24 Comment(1)
That is perfect! Thank you for the response. I have learned a ton from all your help on thisJaponica

© 2022 - 2024 — McMap. All rights reserved.