Search code examples
pythonpython-3.xpyqt5qgraphicssceneqpainter

PyQt 5: QPainter returns false while rendering QGraphicsScene to a QImage


Currently I am working on a program, to display SIP-Trace log files. It is written in Python 3.7 using the PyQt 5(.11.3) module to load and operate a GUI made in QDesigner. As a main feature it parses the SIP-Trace file and displays it as a sequence diagram to a QGraphicsScene with QGraphicsObjects.

My problem lies in the following: For later reference, the content of the QGraphicsScene should be saved as an image file, like .jpg or .png. In the Qt/PyQt documentation I found the useful sounding command QGraphicsScene.render() which renders the content of the GraphicsScene to a saveable file like QImage using QPainter. In the last days, I tried a couple of ways/sample codes found here and elsewhere, but cannot render the GraphicsScene to the QImage much less to an image file. Since I am rather new to Python and Qt, I think I am missing some basic setting somewhere. Following is a minimal version of my code.

# -*- coding: utf8 -*-
"""Class for getting a sequence diagram of a sip traffic"""

from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys


class VoipGui(QMainWindow):
    """ Class that handles the interaction with the UI """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = uic.loadUi("main_window.ui", self)
        self.showMaximized()

        self.sequence_scene = QGraphicsScene()
        self.ui.graphicsView.setScene(self.sequence_scene)
        # self.sequence_scene.setSceneRect(0, 0, 990, 2048)

        # sets the spacing between nodes
        # For more than three nodes columns should be generated in a more automatic way
        self.left_column = 51
        self.middle_column = 381
        self.right_column = 711
        self.flow_height = 60  # Sets the spacing between the arrows in the flowchart

        # --------------------------------- /class init and var set -------------------------------------------

        self.actionOpenFile.triggered.connect(self.on_open_file)
        self.actionCloseFile.triggered.connect(self.on_close_file)
        self.actionCloseProgram.triggered.connect(self.close)
        self.actionSaveFile.triggered.connect(self.save_seq_image)

        # --------------------------------- /connecting slots and signals ----------------------------

    def on_open_file(self):
        """Dummy version of the open file dialog"""
        self.draw_node(self.left_column, 5, "192.168.2.1", 10)
        self.draw_node(self.middle_column, 5, "192.168.2.22", 10)

    def on_close_file(self):
        self.ui.textBrowser.clear()
        self.sequence_scene.clear()

    def save_seq_image(self):
        """ Here lies the problem: Save the rendered sequence scene to file for later use"""
        rect_f = self.sequence_scene.sceneRect()
        # rect = self.sequence_scene.sceneRect().toRect()
        # img = QPixmap(rect.size())

        img = QImage()
        p = QPainter()

        # p.setPen(QColor(255, 255, 255))
        # p.setViewport(rect)

        painting = p.begin(img)
        self.sequence_scene.render(p, target=QRectF(img.rect()), source=rect_f)
        p.end()

        if painting:
            print("Painter init pass")
        elif not painting:
            print("Painter init fail")

        saving = img.save("save.jpg")
        if saving:
            print("Saving Pass")
        elif not saving:
            print("Saving Not Pass")

    def draw_node(self, x_pos, y_pos, ip_address, y_stops):
        """Participating devices are displayed as these nodes"""
        width = 100.0
        height = 40.0
        pc_box = QGraphicsRectItem(x_pos - 50, y_pos, width, height)
        self.sequence_scene.addItem(pc_box)
        pc_ip = QGraphicsTextItem("%s" % ip_address)
        pc_ip.setPos(x_pos - 50, y_pos)
        self.sequence_scene.addItem(pc_ip)
        node_line = QGraphicsLineItem(x_pos, y_pos + 40, x_pos, y_pos + (y_stops * self.flow_height))
        self.sequence_scene.addItem(node_line)


def show_window():
    app = QApplication(sys.argv)
    dialog = VoipGui()
    dialog.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    show_window()

Solution

  • The problem is simple, in render() you are indicating that the size of the target is equal to that of QImage, and how size is QImage?, how are you using QImage() the size is QSize(0, 0) so it can not be generated the image, the solution is to create a QImage with a size:

    def save_seq_image(self):
        """ Here lies the problem: Save the rendered sequence scene to file for later use"""
        rect_f = self.sequence_scene.sceneRect()
        img = QImage(QSize(640, 480), QImage.Format_RGB888)
        img.fill(Qt.white)
        p = QPainter(img)
        self.sequence_scene.render(p, target=QRectF(img.rect()), source=rect_f)
        p.end()
        saving = img.save("save.jpg")
        print("Saving Pass" if saving else "Saving Not Pass")
    

    Output:

    enter image description here