سؤال

I have a program that draws the contents of a QGraphicsScene to a PNG image. I have found that in some circumstances the PNG image is not being created correctly. The image generated appears to be 'offset' by some amount, in that the top-left corner of the QGraphicsScene isn't at (0,0) in the PNG image.

The code below demonstrates the problem. The createImage function creates a QGraphicsScene of a given width and height, draws at coordinates (0, 0) a solid red rectangle of the same width and height as the scene, renders this scene to an image and writes the image out to a file. I believe that this function should always write out an image that is solid red, regardless of the width and height it is given. However, here is one example output image which demonstrates that this is not the case.

The testImage function (which relies on PIL) reads in the image and inspects the rightmost column and bottom row. If the image has been incorrectly 'offset', it will find a white pixel somewhere within the bottom row or rightmost column. If the image has been generated correctly, the bottom row and rightmost column will contain only red pixels. (PIL isn't part of the problem here; I'm just using it to automate the testing of the output images.)

#!/usr/bin/env python

import sys
import Image        # Requires PIL
from PyQt4.QtCore import Qt, QRect, QRectF
from PyQt4.QtGui import QPen, QBrush, QGraphicsScene, QGraphicsView, QGraphicsRectItem, QPixmap, QPainter, QApplication

whitebrush = QBrush(Qt.white)
redbrush = QBrush(Qt.red)

RED = (255, 0, 0)

def createImage(width, height):
    scene = QGraphicsScene(0, 0, width, height)
    scene.setBackgroundBrush(whitebrush)

    rectangle = QGraphicsRectItem(0, 0, width, height)
    rectangle.setPen(QPen(Qt.NoPen))
    rectangle.setBrush(redbrush)
    rectangle.show()
    scene.addItem(rectangle)

    view = QGraphicsView()
    view.setScene(scene)

    outputimg = QPixmap(width, height)
    painter = QPainter(outputimg)
    targetrect = QRectF(0, 0, width, height)
    sourcerect = QRect(0, 0, width, height)
    view.render(painter, targetrect, sourcerect)
    outputimg.save("output.png", "PNG")

    painter.end()

def testImage(width, height):
    image = Image.open("output.png")
    ok = True

    for x in range(width):
        if image.getpixel((x, height - 1)) == RED:
            if x > 0:
                print "First red pixel on bottom row is at x = %d" % x
                ok = False

            break
    else:
        print "No red pixels found on bottom row"
        ok = False

    for y in range(height):
        if image.getpixel((width - 1, y)) == RED:
            if y > 0:
                print "First red pixel in rightmost column is at y = %d" % y
                ok = False

            break
    else:
        print "No red pixels found in rightmost column"
        ok = False

    if ok:
        print "Image OK"

def doTest(width, height):
    createImage(width, height)
    testImage(width, height)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    doTest(int(sys.argv[1]), int(sys.argv[2]))

Here are a few example runs:

$ ./qgraphicssceneimagetest.py 200 200
No red pixels found on bottom row
No red pixels found in rightmost column

In this case the effect of the 'offsetting' is so drastic that the entire output image is white.

$ ./qgraphicssceneimagetest.py 400 400
First red pixel on bottom row is at x = 117
First red pixel in rightmost column is at y = 37

This is the case that generated the sample image I linked to above.

$ ./qgraphicssceneimagetest.py 500 500
First red pixel on bottom row is at x = 55

At this point, the image is now correctly offset vertically, but the red rectangle is still being drawn 55 pixels too far to the right.

$ ./qgraphicssceneimagetest.py 600 600
First red pixel on bottom row is at x = 5

Nearly correct now...

$ ./qgraphicssceneimagetest.py 700 700
Image OK

So it seems that the code can generate an output image correctly if the image is large enough.

As it happens, I have a workaround for this problem. I can get the createImage function to create the images correctly if I replace the line

    sourcerect = QRect(0, 0, width, height)

with

    xoff = 0
    yoff = 0

    if width <= 634 and height <= 474:
        xoff = (634 - width) // 2
        yoff = (474 - height) // 2
    elif width < 610:
        xoff = (610 - width) // 2
    elif height < 450:
        yoff = (450 - height) // 2

    sourcerect = QRect(xoff, yoff, width, height)

I do not know what the significance of the 'magic' numbers in this code is. However, I have run numerous tests with this workaround in place and have verified that they all generated a correct solid red image.

Having this workaround is all right, but I'd like to understand why these images aren't being created correctly. Is there something I've missed or forgotten to do?

هل كانت مفيدة؟

المحلول

What is happening, is that the view is automatically calculating a rect for the scene and then centring itself on the graphics items that have been added. Depending on the dimensions of the items, this may sometimes leave blank areas around the edges.

The solution is to explicitly set a rect for the scene:

view = QGraphicsView()
view.setScene(scene)
view.setSceneRect(QRectF(view.viewport().rect()))
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top