1

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

jon_bovi
  • 71
  • 3
  • 11
  • 1
    All coordinates in the Graphics View framework are based on floating point. Unfortunately you didn't provide a [mre], so it's a bit hard to better understand your problem. Besides that, if you need to set the origin point of the transformation to the center of the rectangle, you can just use `rect.center()`. – musicamante Sep 24 '21 at 12:14
  • Hi @musicamante! Thank you for being ever so patient with me. I will elaborate my question in a moment. – jon_bovi Sep 24 '21 at 13:04
  • 1
    (for future reference, there's no need to tell that you're going to edit the question, just add a comment when it has been updated, as comments should always be kept to the minimum) Now, the problem is a bit complex. The fact is that the rectangle of a QGraphicsRectItem doesn't always match the item's position: you can have a QGraphicsRectItem positioned at 10x10, with a rect(20, 50, 50, 100), and the result is that the rectangle will be drawn at 30x60. Adding a rotation makes it even more complex, as the origin point of the rectangle would be considered in item coordinates, but if you need -> – musicamante Sep 24 '21 at 15:43
  • 1
    -> to know the actual origin point of the rectangle, you have to map it to the scene (which considers any transformation, including rotation and scaling). So, three things must be understood: 1. what coordinates are you interested into? right now you always get the rectangle in *item* coordinates; 2. what is the final purpose of that rectangle (I believe it's cropping, but I'm not sure)? 3. how are those coordinates used, or what do you want to show to the user? – musicamante Sep 24 '21 at 15:46
  • Thank you for your clear explanation. To answer your question, 1. If possible, the coordinates of the rectangle's center; 2. It is indeed for cropping; 3. The coordinates will be used to crop pixels inside the rectangle. Then, the cropped image will be rotated back into a rectangle with 0 degree rotation and saved as jpg to be further used for image processing using openCV. – jon_bovi Sep 24 '21 at 16:07
  • 1
    @musicamante These values are relative to the scene, not the viewport. Do you want the rectangle with respect to which coordinate system: image OR viewport OR self-item OR scene? – eyllanesc Sep 24 '21 at 17:01
  • @eyllanesc Preferrably the image coordinate system. – jon_bovi Sep 25 '21 at 03:39

1 Answers1

3

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)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I understood your explanation. Just one more thing, when I set rect_item.setPos(QtCore.QPointF(0, 0)), the first output of print(point) is (-2, -2). Shouldn't it be (0, 0)? – jon_bovi Sep 25 '21 at 06:38
  • 1
    @husniandre You will always see errors in the conversion, so you can apply corrections – eyllanesc Sep 25 '21 at 06:41
  • Sorry to bother you again, if I set the item's initial rotation to more than 45 degrees, let's say 90 degrees, and then try to drag and move it, the item moves randomly. When I print self.pos on the mouseMoveEvent method, the x and y position became so huge. Do you have any idea why this is happening @eyllanesc ? – jon_bovi Sep 27 '21 at 00:09
  • 1
    @husniandre That problem has nothing to do with my answer, nor with what you require in your post so I will not answer. – eyllanesc Sep 27 '21 at 00:14