I searched many times over the internet if QGraphicView have functionality for rotating/scaling image and I had no success.
What I want like every diagram program each image/shape has boundary points, so the user can scale or rotate the shape/image. like below:
And what I have so far is this:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ImagePoint(QGraphicsRectItem):
def __init__(self, x, y, w=15, h=15, parent=None):
super(ImagePoint, self).__init__(x - w / 2, y - w / 2, w, h, parent)
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setAcceptHoverEvents(True)
self.setBrush(QColor(Qt.blue))
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionChange:
self.parentItem().setScale(value)
super(ImagePoint, self).itemChange(change, value)
class Back(QGraphicsPixmapItem):
def __init__(self, file_name, scene):
super(Back, self).__init__()
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setAcceptHoverEvents(True)
pixmap = QPixmap(file_name)
self.scene = scene
self.setPixmap(pixmap)
self.init_boundre_points()
def init_boundre_points(self):
ImagePoint(self.boundingRect().topLeft().x(), self.boundingRect().topLeft().y(), parent=self)
ImagePoint(self.boundingRect().topRight().x(), self.boundingRect().topRight().y(), parent=self)
ImagePoint(self.boundingRect().bottomLeft().x(), self.boundingRect().bottomLeft().y(), parent=self)
ImagePoint(self.boundingRect().bottomRight().x(), self.boundingRect().bottomRight().y(), parent=self)
x = self.boundingRect().topLeft().x() + self.boundingRect().width() / 2
y = self.boundingRect().topLeft().y()
ImagePoint(x, y, parent=self)
x = self.boundingRect().bottomLeft().x() + self.boundingRect().width() / 2
y = self.boundingRect().bottomLeft().y()
ImagePoint(x, y, parent=self)
x = self.boundingRect().topLeft().x()
y = self.boundingRect().topLeft().y() + self.boundingRect().height() / 2
ImagePoint(x, y, parent=self)
x = self.boundingRect().topRight().x()
y = self.boundingRect().topRight().y() + self.boundingRect().height() / 2
ImagePoint(x, y, parent=self)
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setScene(MyGraphicsScene(self))
class MyGraphicsScene(QGraphicsScene):
def __init__(self, parent):
super(MyGraphicsScene, self).__init__()
self.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
back = Back("Path_image", self)
self.addItem(back)
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800, 600)
self.gv = MyGraphicsView()
self.setCentralWidget(self.gv)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
So how can effectively do that? is there any thing I missed on QGraphicView framework?
Well, I've implemented it as the following:
Here is the code:
import sys
from PyQt5.QtGui import QPen, QBrush, QColor, QPixmap
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsRectItem, QMainWindow, \
QVBoxLayout, QWidget, QGraphicsSimpleTextItem, QGraphicsPixmapItem
class ResizableRect(QGraphicsRectItem):
selected_edge = None
def __init__(self, x, y, width, height):
super().__init__(0, 0, width, height)
self.setPos(x, y)
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
self.setAcceptHoverEvents(True)
self.setPen(QPen(QBrush(Qt.blue), 5))
self.fileName = "C:/Users/USER/Desktop/inactive_tap.png"
self.pix = QPixmap(self.fileName)
self.pix = self.pix.scaled(width, height, transformMode=Qt.SmoothTransformation)
self.pixItem = QGraphicsPixmapItem(self.pix, self)
self.init_boundre_points()
def getEdges(self, pos):
edges = Qt.Edges()
rect = self.rect()
border = self.pen().width() / 2
if pos.x() < rect.x() + border:
edges |= Qt.LeftEdge
elif pos.x() > rect.right() - border:
edges |= Qt.RightEdge
if pos.y() < rect.y() + border:
edges |= Qt.TopEdge
elif pos.y() > rect.bottom() - border:
edges |= Qt.BottomEdge
return edges
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.selected_edge = self.getEdges(event.pos())
self.offset = QPointF()
else:
self.selected_edge = Qt.Edges()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.selected_edge:
mouse_delta = event.pos() - event.buttonDownPos(Qt.LeftButton)
rect = self.rect()
pos_delta = QPointF()
border = self.pen().width()
if self.selected_edge & Qt.LeftEdge:
diff = min(mouse_delta.x() - self.offset.x(), rect.width() - border)
if rect.x() < 0:
offset = diff / 2
self.offset.setX(self.offset.x() + offset)
pos_delta.setX(offset)
rect.adjust(offset, 0, -offset, 0)
else:
pos_delta.setX(diff)
rect.setWidth(rect.width() - diff)
elif self.selected_edge & Qt.RightEdge:
if rect.x() < 0:
diff = max(mouse_delta.x() - self.offset.x(), border - rect.width())
offset = diff / 2
self.offset.setX(self.offset.x() + offset)
pos_delta.setX(offset)
rect.adjust(-offset, 0, offset, 0)
else:
rect.setWidth(max(border, event.pos().x() - rect.x()))
if self.selected_edge & Qt.TopEdge:
diff = min(mouse_delta.y() - self.offset.y(), rect.height() - border)
if rect.y() < 0:
offset = diff / 2
self.offset.setY(self.offset.y() + offset)
pos_delta.setY(offset)
rect.adjust(0, offset, 0, -offset)
else:
pos_delta.setY(diff)
rect.setHeight(rect.height() - diff)
elif self.selected_edge & Qt.BottomEdge:
if rect.y() < 0:
diff = max(mouse_delta.y() - self.offset.y(), border - rect.height())
offset = diff / 2
self.offset.setY(self.offset.y() + offset)
pos_delta.setY(offset)
rect.adjust(0, -offset, 0, offset)
else:
rect.setHeight(max(border, event.pos().y() - rect.y()))
# re-init Pixmap so the quality of the image will stay good.
self.pix = QPixmap(self.fileName)
self.pix = self.pix.scaled(self.rect().width(), self.rect().height(), transformMode=Qt.SmoothTransformation)
self.pixItem.setPixmap(self.pix)
# re-draw the adjustments points after scaling
self.init_boundre_points()
if rect != self.rect():
self.setRect(rect)
if pos_delta:
self.setPos(self.pos() + pos_delta)
else:
# use the default implementation for ItemIsMovable
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.selected_edge = Qt.Edges()
super().mouseReleaseEvent(event)
def hoverMoveEvent(self, event):
edges = self.getEdges(event.pos())
if not edges:
self.unsetCursor()
elif edges in (Qt.TopEdge | Qt.LeftEdge, Qt.BottomEdge | Qt.RightEdge):
self.setCursor(Qt.SizeFDiagCursor)
elif edges in (Qt.BottomEdge | Qt.LeftEdge, Qt.TopEdge | Qt.RightEdge):
self.setCursor(Qt.SizeBDiagCursor)
elif edges in (Qt.LeftEdge, Qt.RightEdge):
self.setCursor(Qt.SizeHorCursor)
else:
self.setCursor(Qt.SizeVerCursor)
def init_boundre_points(self):
for item in self.childItems():
if isinstance(item, ImagePoint):
self.scene().removeItem(item)
del item
ImagePoint(self.boundingRect().topLeft().x(), self.boundingRect().topLeft().y(), parent=self)
ImagePoint(self.boundingRect().topRight().x(), self.boundingRect().topRight().y(), parent=self)
ImagePoint(self.boundingRect().bottomLeft().x(), self.boundingRect().bottomLeft().y(), parent=self)
ImagePoint(self.boundingRect().bottomRight().x(), self.boundingRect().bottomRight().y(), parent=self)
x = self.boundingRect().topLeft().x() + self.boundingRect().width() / 2
y = self.boundingRect().topLeft().y()
self.topEdge = ImagePoint(x, y, parent=self)
x = self.boundingRect().bottomLeft().x() + self.boundingRect().width() / 2
y = self.boundingRect().bottomLeft().y()
self.bottomEdge = ImagePoint(x, y, parent=self)
x = self.boundingRect().topLeft().x()
y = self.boundingRect().topLeft().y() + self.boundingRect().height() / 2
self.rightEdge = ImagePoint(x, y, parent=self)
x = self.boundingRect().topRight().x()
y = self.boundingRect().topRight().y() + self.boundingRect().height() / 2
self.leftEdge = ImagePoint(x, y, parent=self)
class ImagePoint(QGraphicsRectItem):
def __init__(self, x, y, w=5, h=5, parent=None):
super(ImagePoint, self).__init__(x - w / 2, y - w / 2, w, h, parent)
self.setBrush(QColor(Qt.white))
self.setPen(QColor("blue"))
self.setAcceptHoverEvents(False)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
scene = QGraphicsScene(0, 0, 300, 300)
self.view = QGraphicsView(scene)
self.rect = ResizableRect(0, 50, 200, 100)
scene.addItem(self.rect)
central = QWidget()
layout = QVBoxLayout(central)
layout.addWidget(self.view)
self.setCentralWidget(central)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()