Spencer
Spencer

Reputation: 2110

PyQt resize QMovie with proper anti-aliasing

I created a QLabel and set it's movie to a QMovie object with an animated gif. Then in the resizeEvent of my app I resize and move the label, so that it is centered/fit to the layout. This works fine but the movie has a lot of fine lines which get totally garbled in the resize operation, it seems there is no anti-aliasing. So either I am using the wrong resize method, or I need to set anti-aliasing somewhere right? There is nothing in the QMovie or QLabel documentation to suggest how to do this. I did read that the QMovie is inherited from a QImageReader, though that too has no property for anti-aliasing that I could find.

EDIT

I did somewhat get this to work, but it's still not quite right. I found that QMovie has a setScaledSize method which actually scales the underlying QImageViewer. Then I just have the label adjust to it's contents, namely the movie. Using the following code I am able to resize the movie with proper anti-aliasing, however it is quite "jumpy" and "flickery" during resize so obviously I'm not doing this quite "right". Also it somehow loses it's aspect ratio sometimes. Still looking for the correct way to do this... Maybe a QLabel is the wrong way to go?

Here's a working example

import sys
from PyQt4 import QtGui

class MovieTest(QtGui.QDialog):
    def __init__(self):
        super(MovieTest, self).__init__()

        layout = QtGui.QVBoxLayout()
        self.setLayout(layout)

        self.loading_lbl = QtGui.QLabel()
        self.loading_lbl.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicyIgnored)
        self.loading_lbl.setScaledContents(True)
        layout.addWidget(self.loading_lbl)
        loading_movie = QtGui.QMovie("loading-radial_loop.gif") # some gif in here
        self.loading_lbl.setMovie(loading_movie)
        loading_movie.start()

        self.setGeometry(50,50,100,100)
        self.setMinimumSize(10,10)

    def resizeEvent(self, event):
        rect = self.geometry()
        size = min(rect.width(), rect.height())
        movie = self.loading_lbl.movie()
        movie.setScaledSize(QtCore.QSize(size, size))
        self.loading_lbl.adjustSize()

def main():
    app = QtGui.QApplication(sys.argv)
    ex = MovieTest()
    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Upvotes: 3

Views: 6785

Answers (1)

Spencer
Spencer

Reputation: 2110

Ok I have this figured out now, with just a few tweaks to the code in my edited post. The secret is in keeping the label the full size of it's parent rect (in this case the size of the whole layout) and then scaling the movie within the label. Essentially you are internally scaling the movie, instead of having it automatically fill the contents of the label. From what I can tell this changes around the order of operations a bit and allows the movie to scale itself on render, instead of rendering the frame and then scaling it to the label size.

Working code:

import sys
from PyQt4 import QtGui, QtCore

class MovieTest(QtGui.QDialog):
    def __init__(self):
        super(MovieTest, self).__init__()

        layout = QtGui.QVBoxLayout()
        self.setLayout(layout)

        self.loading_lbl = QtGui.QLabel()
        self.loading_lbl.setStyleSheet('border: 1px solid red') # just for illustration
        self.loading_lbl.setAlignment(QtCore.Qt.AlignCenter)
        layout.addWidget(self.loading_lbl)
        loading_movie = QtGui.QMovie("loading-radial_loop.gif")
        self.loading_lbl.setMovie(loading_movie)
        loading_movie.start()

        self.setGeometry(50,50,100,100)
        self.setMinimumSize(10,10)

    def resizeEvent(self, event):
        rect = self.geometry()
        size = QtCore.QSize(min(rect.width(), rect.height()), min(rect.width(), rect.height()))

        movie = self.loading_lbl.movie()
        movie.setScaledSize(size)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = MovieTest()
    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Upvotes: 6

Related Questions