PDA

View Full Version : Rotate a QGraphicsPixmapItem with a rotation handle PyQt5



Carolina5
26th October 2020, 18:00
I’m writing an application in PyQt5 that will allow the user to rotate a QGraphicsPixmapItem and also delete it. The rotation needs to be with the mouse via a rotation handle that the user can grab with the mouse and rotate, thus rotating the QGraphicsPixmapItem. I need the handle to have a fixed distance to the item position, precisely to be at the top left corner of the QGraphicsPixmapItem. Also, I need the handle to be a child of the graphic pixmap item so that when the user moves the image or deletes it, the handle will be moved or deleted too.
I have some troubles, when I do not set the handle as a child of the graphic pixmap item, the rotation is fine but not the part of moving the handle when the user moves the image. When I set the handle as a child, the rotation doesn’t work as I want.
Could someone help me please? This is my code:


import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsView
from PyQt5 import QtGui, QtWidgets
import math

class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)

self.scene = Scene()
self.view = QGraphicsView(self)
self.setGeometry(10, 30, 850, 600)
self.view.setGeometry(20, 22, 800, 550)
self.view.setScene(self.scene)

class Scene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(Scene, self).__init__(parent)
# other stuff here
self.set_image()

def set_image(self):
photo = Photo()
self.addItem(photo)
photo.set_pixmap()

class Photo(QtWidgets.QGraphicsPixmapItem):
def __init__(self, parent=None):
super(Photo, self).__init__(parent)

self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable , True)
self.setTransformOriginPoint(self.boundingRect().c enter())

def set_pixmap(self):
pixmap = QtGui.QPixmap("perro.jpg")
self.setPixmap(pixmap)
self.pixmap_controller = PixmapController(self)
self.scene().addItem(self.pixmap_controller)
self.pixmap_controller.set_pixmap_controller()
self.pixmap_controller.setPos(self.boundingRect(). topLeft())
self.pixmap_controller.setFlag(QtWidgets.QGraphics Item.ItemSendsScenePositionChanges, True)

def rotate_item(self, position):
item_position = self.transformOriginPoint()
angle = math.atan2(item_position.y() - position.y(), item_position.x() - position.x()) / math.pi * 180 - 45
print(angle)
self.setRotation(angle)
self.setPos(position)

class PixmapController(QtWidgets.QGraphicsEllipseItem):
def __init__(self, pixmap):
super(PixmapController, self).__init__(parent=pixmap)
self.pixmap = pixmap

self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable , True)
color = QtGui.QColor(0, 0, 0)
brush = QtGui.QBrush(color)
self.setBrush(brush)

def set_pixmap_controller(self):
self.setRect(-5, -5, 10, 10)

def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange:
self.pixmap.rotate_item(value)
return super(PixmapController, self).itemChange(change, value)

if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

d_stranz
31st October 2020, 15:35
I am not a PyQt5 expert, but I see a few questionable things.

Zeroth: You are using the name "pixmap" to refer to two completely different things: a QPixmap (inside the Photo class) and the parent Photo instance (inside the PixmapController class). This is really confusing and when I first read your code I thought you were making a copy of the QPixmap. It would be more clear (and probably less confusing to you also) if you changed the name to "photo" in the PixmapController class to make it clear you are manipulating the parent object.

First, I think you should be creating your PixmapController instance in the Photo constructor. You don't need to create a new one every time your load the pixmap, you just need to move the existing one so it is at the top left corner of the new pixmap.

Second, it doesn't seem to me that you are keeping the handle position at the top left when you rotate. When you process the ItemPositionChange signal, you do rotate the parent, but then you call the superclass method which will move the handle relative to the Photo. I think you should only be calling the superclass method when the change is not an ItemPositionChange.