PyQt5 QsvgWidget preserve aspect ratio

I have a small command line utility that creates an svg image based on user input. I have been trying to put together a gui using PyQt5 with an included image preview.

The problem that I am running into is that the image is stretched to fit the available space rather than preserving the aspect ratio, even with preserveAspectRatio="xMidYMid meet" in the file. I am using QtSvg.QtSvgWidget to load the image.

self.svgPreview = QtSvg.QSvgWidget('/tmp/preview.svg')
self.vbox0.addWidget(self.svgPreview)

Looking through the docs I am not seeing any way to specify that aspect ratio must be retained. Is there a better way to load the image, or am I maybe missing something?

Interestingly, the aspect ratio is preserved when using PyGtk, so I knocked together a gui that does what I want that way. But I would like to be able to solve the problem using Qt, both for my own knowledge and to have a more cross-platform friendly application.

A quick solution if you’re using a recent version of PyQt (5.15 or later) is to access the QSvgRenderer of the QSvgWidget by calling its renderer() method and setting the aspect ratio mode:

self.svgPreview.renderer().setAspectRatioMode(Qt.KeepAspectRatio)

For earlier versions of Qt, it could be possible to use the height-for-width feature of QWidget subclasses to maintain the aspect ratio. Another way is to subclass QSvgWidget and reimplement the paintEvent method:

class SvgWidget(QSvgWidget):

    def __init__(self, *args):
        QSvgWidget.__init__(self, *args)

    def paintEvent(self, event):
        renderer = self.renderer()
        if renderer != None:
            painter = QPainter(self)
            size = renderer.defaultSize()
            ratio = size.height()/size.width()
            length = min(self.width(), self.height())
            renderer.render(painter, QRectF(0, 0, length, ratio * length))
            painter.end()

This could be cleaned up a bit. In particular, the QRectF that is passed to renderer.render should be a viewBox, and it might be too simplistic to fix the x and y coordinates to (0, 0).

1 Like

Thanks dboddie, that first method solved it. However, I may dig a little further, as it is allocating more extra vertical space right now that isn’t needed once the aspect ratio is sorted out. But at least it is displaying the image preview without distortion.

Confirmed, the first solution keeps working in PySide6 :clap: :clap: :clap:

1 Like