jon_bovi
jon_bovi

Reputation: 71

How to crop QPixmap that is set on a Scene with a rotated QGraphicsRectItem?

I cannot figure out how to crop a QPixmap that is set on a scene with a rotated QGraphicsRectItem also placed in the same scene.

Here is the code for my QGraphicsScene and QPixmap.

class crop_pattern(QGraphicsView):
    img_is_cropped = QtCore.Signal(object)
    def __init__(self, path, scale):
        super().__init__()
        # Connect graphics scene with graphics view
        self.setFixedSize(500, 500)
        self.scene = QGraphicsScene(self)
        self.setScene(self.scene)
        self.roi_scale = scale
      
        # Display image
        self.set_image(path)

    def set_image(self, path):
        pixmap = QtGui.QPixmap(path)

        if pixmap:
            pixmap = pixmap.scaledToHeight(self.roi_scale * pixmap.height())

        self.scene.clear()
        self.scene.addPixmap(pixmap)
        
        self.setAlignment(QtCore.Qt.AlignCenter)

    def wheelEvent(self, event):
        zoomInFactor = 1.05
        zoomOutFactor = 1 / zoomInFactor

        oldPos = self.mapToScene(event.pos())

        if event.angleDelta().y() > 0:
            zoomFactor = zoomInFactor
        else:
            zoomFactor = zoomOutFactor
            
        self.scale(zoomFactor, zoomFactor)

        newPos = self.mapToScene(event.pos())

        delta = newPos - oldPos
        self.translate(delta.x(), delta.y())

(Credits to QGraphicsView Zooming in and out under mouse position using mouse wheel for the wheelEvent function)

Here is the QGraphicsItem that is generated when the user clicks a certain button.

    QtCore.Slot(bool)
    def create_shape(self):
        sender = self.sender()
        if sender.text().lower() == "circle":
            self.shape = ellipse_shape(0, 0, 100, 100)

        elif sender.text().lower() == "rectangle":
            self.shape = rect_shape(0, 0, 100, 100)
            
        self.shape.setZValue(1)
        self.shape.setTransformOriginPoint(50, 50)

        self.crop_pattern.scene.addItem(self.shape) # added item to the same scene, which is crop_pattern.

Here is the GUI as the question suggested. (QGraphicsRectItem has been resized)

a very sad cat

How can I crop the pixels inside the rectangle? Thanks!

Upvotes: 0

Views: 637

Answers (1)

eyllanesc
eyllanesc

Reputation: 243983

One possible solution is to create a hidden QGraphicsView and use the render() method to save the image section. The objective of the hidden QGraphicsView is not to modify the existing view since the image must be rotated in addition to not being affected by the scaling.

from PyQt5 import QtCore, QtGui, QtWidgets


def crop_rect(rect_item, scene):
    is_visible = rect_item.isVisible()

    rect_item.hide()

    hide_view = QtWidgets.QGraphicsView(scene)
    hide_view.rotate(-rect_item.rotation())

    polygon = rect_item.mapToScene(rect_item.rect())
    pixmap = QtGui.QPixmap(rect_item.rect().size().toSize())
    pixmap.fill(QtCore.Qt.transparent)
    source_rect = hide_view.mapFromScene(polygon).boundingRect()

    painter = QtGui.QPainter(pixmap)
    hide_view.render(
        painter,
        target=QtCore.QRectF(pixmap.rect()),
        source=source_rect,
    )
    painter.end()

    rect_item.setVisible(is_visible)

    return pixmap


def main():
    app = QtWidgets.QApplication([])

    scene = QtWidgets.QGraphicsScene()
    view = QtWidgets.QGraphicsView(alignment=QtCore.Qt.AlignCenter)
    view.setScene(scene)
    # emulate wheel
    view.scale(0.8, 0.8)

    # create pixmap
    pixmap = QtGui.QPixmap(500, 500)
    pixmap.fill(QtGui.QColor("green"))
    painter = QtGui.QPainter(pixmap)
    painter.setBrush(QtGui.QColor("salmon"))
    painter.drawEllipse(pixmap.rect().adjusted(100, 90, -80, -100))
    painter.end()

    pixmap_item = scene.addPixmap(pixmap)

    rect = QtCore.QRectF(0, 0, 200, 300)
    rect_item = scene.addRect(rect)
    rect_item.setPen(QtGui.QPen(QtGui.QColor("red"), 4))
    rect_item.setPos(100, 100)
    rect_item.setTransformOriginPoint(50, 50)
    rect_item.setRotation(10)

    view.resize(640, 480)
    view.show()

    qpixmap = crop_rect(rect_item, scene)
    label = QtWidgets.QLabel()
    label.setPixmap(qpixmap)
    label.show()

    app.exec_()


if __name__ == "__main__":
    main()

Input:

enter image description here

Output:

enter image description here

Upvotes: 3

Related Questions