Search code examples
pythonpyqtsplash-screenqpainter

expand the size of QPaintDevice after it is initialzed


A little Context

I'm trying to make a QSplashScreen with a custom animation. I've tried many different approaches, with each failures. Mostly, my main technique was to create a new Class which inherits from QSplashScreen and then plan with the paintEvent(). It worked... ish. The animation is not the problem, It is really the QPaintDevice which seems corrupted.

Because I was calling the super(classname, self).__init__(args) only way after in my init and passing it arguments that I modified in the init, I always had corrupted pixels; The image was in weird tones and had lines of colorful pixels in the background. Sometimes it is patterns sometimes it is completely random.

enter image description here

I have tried changing every line of code and the only thing that removed those lines was calling the super() at the beginning of the __init__. Unfortunately, I was making a frame that I was passing to the init. Now that this isn't possible, I would like to modify the size of the QPaintDevice on which my QSplashScreen initializes, because my animation is displayed beyond that frame. I won't post all the code since the custom animation is quite heavy.

Minimal Working Exemple

from PyQt5.QtWidgets import QApplication, QSplashScreen, QMainWindow
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QPoint
from PyQt5.QtGui import QPixmap, QPainter, QIcon, QBrush
import time, sys


class FakeAnimatedSplash(QSplashScreen):
    def __init__(self, image):
        self.image = image
        self.newFrame = QPixmap(self.image.size()+QSize(0, 20))
        super(FakeAnimatedSplash, self).__init__(self.newFrame, Qt.WindowStaysOnTopHint)

    def showEvent(self, event):
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.fillRect(self.rect(), Qt.transparent)
        painter.setRenderHint(QPainter.Antialiasing, True)
        painter.setPen(Qt.NoPen)
        painter.drawPixmap(self.image.rect(), self.image)
        painter.drawEllipse(QPoint(0, 110), 8, 8)


class App(QApplication):
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main = QMainWindow()
        self.setAttribute(Qt.AA_EnableHighDpiScaling)
        self.newSplash()
        self.main.show()

    def newSplash(self):
        pixmap = QPixmap("yourImage.png")
        smallerPixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        splash = FakeAnimatedSplash(smallerPixmap)
        splash.setEnabled(False)
        splash.show()
        start = time.time()
        while time.time() < start + 10:
            self.processEvents()


def main():
    app = App(sys.argv)
    app.setWindowIcon(QIcon("ABB_icon.png"))
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Potential Solution

Changing the super() to the beginning makes it work but reduces the QPaintDevice window which hides my animation. I would like to expand it, but there are no methods which can do it after the initialization.

    def __init__(self, image):
        super(LoadingDotsSplash, self).__init__(image, QtCore.Qt.WindowStaysOnTopHint)
        self.image = image
        # here a function or method that changes the size of the QPaintDevice

Solution

  • The problem is that newFrame is an uninitialized QPixmap and for efficiency reasons the pixels are not modified so they have random values, and that is because the size of the FakeAnimatedSplash is larger than the QPixmap that is painted. The solution is to set the value of the newFrame pixels to transparent:

    class FakeAnimatedSplash(QSplashScreen):
        def __init__(self, image):
            self.image = image
            pix = QPixmap(self.image.size() + QSize(0, 20))
            pix.fill(Qt.transparent)
            super(FakeAnimatedSplash, self).__init__(pix, Qt.WindowStaysOnTopHint)
    
        def paintEvent(self, event):
            painter = QPainter(self)
            painter.fillRect(self.rect(), Qt.transparent)
            painter.setRenderHint(QPainter.Antialiasing, True)
            painter.setPen(Qt.NoPen)
            painter.drawPixmap(self.image.rect(), self.image)
            painter.drawEllipse(QPoint(0, 110), 8, 8)