Search code examples
pythonpyqtpysideqgraphicssceneqgraphicsitem

How to retrieve QGraphicsRectItem coordinate with respect to a QGraphicsPixmapItem on a scene?


What is the best way to retrieve the QGraphicsRectItem coordinates with respect to the QGraphicsPixmapItem on the scene?

Here is the code for my main window class.

import sys
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import *
from PySide2.QtGui import QBrush, QPen

class main_window(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(100, 100, 500, 500)

        self.rect = ResizableRect(100, 100, 100, 100)
        self.rect.setZValue(1)
        self.rect.setRotation(10)

        self.view = QGraphicsView(self)
        self.scene = QGraphicsScene(self.view)
        self.scene.addItem(self.rect)

        pixmap = QtGui.QPixmap("images/sadcat.jpg")
        pixmap_item = self.scene.addPixmap(pixmap)
        pixmap_item.setPixmap(pixmap)

        self.view.setSceneRect(0, 0, 500,500)
        self.view.setScene(self.scene)

        self.slider = QSlider(QtCore.Qt.Horizontal)
        self.slider.setMinimum(0)
        self.slider.setMaximum(90)

        vbox = QVBoxLayout(self)
        vbox.addWidget(self.view)
        vbox.addWidget(self.slider)

        self.setLayout(vbox)

        self.slider.valueChanged.connect(self.rotate)

    def rotate(self, value):
        self.angle = int(value)
        self.rect.setRotation(self.angle)

Here is the code for the custom QGraphicsRectItem (credits to Resize a QGraphicsItem with the mouse)

class ResizableRect(QGraphicsRectItem):
    def __init__(self, *args):
        super().__init__(*args)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
        self.setPen(QPen(QBrush(QtGui.QColor('red')), 5))
        self.selected_edge = None
        self.first_pos = self.click_rect = None

    def mousePressEvent(self, event):
        """ The mouse is pressed, start tracking movement. """
        self.first_pos = event.pos()
 
        self.rect_shape = self.rect()

        if abs(self.rect_shape.left() - self.first_pos.x()) < 5:
            self.selected_edge = 'left'
        elif abs(self.rect_shape.right() - self.first_pos.x()) < 5:
            self.selected_edge = 'right'
        elif abs(self.rect_shape.top() - self.first_pos.y()) < 5:
            self.selected_edge = 'top'
        elif abs(self.rect_shape.bottom() - self.first_pos.y()) < 5:
            self.selected_edge = 'bottom'
        else:
            self.selected_edge = None
        self.first_pos = event.pos()
        self.click_rect = self.rect_shape
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        """ Continue tracking movement while the mouse is pressed. """
        # Calculate how much the mouse has moved since the click.
        self.pos = event.pos()
        x_diff = self.pos.x() - self.first_pos.x()
        y_diff = self.pos.y() - self.first_pos.y()

        # Start with the rectangle as it was when clicked.
        self.rect_shape = QtCore.QRectF(self.click_rect)

        # Then adjust by the distance the mouse moved.
        if self.selected_edge is None:
            self.rect_shape.translate(x_diff, y_diff)
        elif self.selected_edge == 'top':
            self.rect_shape.adjust(0, y_diff, 0, 0)
        elif self.selected_edge == 'left':
            self.rect_shape.adjust(x_diff, 0, 0, 0)
        elif self.selected_edge == 'bottom':
            self.rect_shape.adjust(0, 0, 0, y_diff)
        elif self.selected_edge == 'right':
            self.rect_shape.adjust(0, 0, x_diff, 0)

        self.setRect(self.rect_shape)
        coor = self.rect_shape.getRect()
        self.setTransformOriginPoint(coor[0] + coor[2]/2, coor[1] + coor[3]/2)

        print(coor)

The main window GUI can be seen below.

enter image description here


Solution

  • Qt Graphics Framework handles several coordinate systems:

    • With respect to the scene.
    • With respect to the viewport.
    • With respect to any item.

    And QGraphicsView, QGraphicsScene, and QGraphicsItems have methods that allow conversion between the various types of coordinate systems.

    In general the implementation is to convert any position with respect to X to coordinates of the scene and then convert the coordinates with respect to Y.

    It should also be known that the coordinates with respect to the scene and the items are in floating point: QPointF, QRectF and QPolygonF but can be converted to integer values using the toPoint(), toRect() and toPolygon() methods, respectively.

    So in this case you can convert the boundingRect() of the QGraphicsRectItem that are with respect to the item to coordinates of the scene, and then convert them with respect to the QGraphicsPixmapItem:

    import random
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    def build_pixmap():
        pixmap = QtGui.QPixmap(400, 400)
        pixmap.fill(QtCore.Qt.transparent)
    
        painter = QtGui.QPainter(pixmap)
        painter.setRenderHints(
            QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
        )
        painter.setPen(QtCore.Qt.NoPen)
        for _ in range(100):
            x, y = random.sample(range(-100, 400), 2)
            color = QtGui.QColor(*random.sample(range(255), 3))
            painter.setBrush(color)
            painter.drawEllipse(QtCore.QRect(0, 0, 100, 100).translated(x, y))
    
        painter.end()
        return pixmap
    
    
    def main():
        app = QtWidgets.QApplication()
    
        scene = QtWidgets.QGraphicsScene()
        view = QtWidgets.QGraphicsView(
            scene,
            renderHints=QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform,
        )
    
        rect_item = QtWidgets.QGraphicsRectItem()
        rect_item.setPen(QtGui.QPen(QtGui.QColor("red"), 5))
        rect_item.setBrush(QtGui.QColor("gray"))
        rect_item.setRect(QtCore.QRectF(-30, -40, 100, 200))
        rect_item.setPos(QtCore.QPointF(170, 150))
        rect_item.setTransformOriginPoint(30, 20)
        rect_item.setRotation(30)
    
        pixmap_item = QtWidgets.QGraphicsPixmapItem()
        pixmap_item.setPixmap(build_pixmap())
    
        scene.addItem(pixmap_item)
        scene.addItem(rect_item)
    
        scene_coordinate = rect_item.mapToScene(rect_item.boundingRect())
        # view_coordinate = view.mapFromScene(scene_coordinate)
        pixmap_coordinate = pixmap_item.mapFromScene(scene_coordinate)
        for point in pixmap_coordinate.toPolygon():
            print(point)
    
        view.resize(640, 480)
        view.show()
    
        app.exec_()
    
    
    if __name__ == "__main__":
        main()
    

    Output:

    PySide2.QtCore.QPoint(177, 85)
    PySide2.QtCore.QPoint(268, 137)
    PySide2.QtCore.QPoint(166, 315)
    PySide2.QtCore.QPoint(75, 262)
    PySide2.QtCore.QPoint(177, 85)