在线程中创建 QPixmaps

Creating QPixmaps in a thread

我在尝试加载大量 png 图像并随后使用 PyQt 显示主题时遇到问题。我当前的工作流程是使用多处理器池来映射一个函数,该函数以 'rb' 值打开每个文件,然后将每个文件的字节读取到一个统一列表中。最后,父进程通过调用 QPixmap 对象的 fromImageData 方法来显示图像。这种方法似乎工作正常,但每次在图像(8K 分辨率)之间切换时重新绘制新像素图的速度非常慢。

我希望为每个图像创建一个像素图并循环遍历该像素图可能会更快,而不是在每一步都用新图像重新创建相同的像素图。为此,我尝试在多进程函数中创建一个像素图,但是这是不允许的,因为线程中没有父 QApp。

我的问题是是否有正确的方法来做到这一点?我也曾想过用 celery/reddis 来做,但我看不出有什么不同的结果。为每个图像创建一个新的像素图并使用 setPixmap 切换它们是一个可行的选择还是有更合适的方法来实现这一点?

您应该能够使用 QThreadPool 和一些 QRunnable 来完成此操作,它们包装了加载像素图的代码。类似于:

from PyQt5 import QtCore, QtGui

class PixmapLoader(QtCore.QRunnable):
    def __init__(self, filename):
        super().__init__()
        self.filename = filename
    def run(self):
        # Load pixmap at filename 
        # ...
        # then emit in a signal
        loaded.emit(pixmap)

    loaded = QtCore.pyqtSignal(QtGui.QPixmap)

然后在主应用程序的某处创建一个线程池,运行 加载对象,并处理它们的信号。

pool = QtCore.QThreadPool()
loaders = [PixmapLoader(filename) for filename in filenames]
for loader in loaders:
    loader.loaded.connect(handle_new_pixmap)
    pool.start(loader)

def handle_new_pixmap(QtGui.QPixmap):
   # do stuff with pixmap

我没试过这个,但是因为 Qt 正在处理线程,这应该能够很好地利用多线程。

编辑

如评论中所述,这行不通。我忘记了 QRunnable 不会继承 QObject,而且 QPixmaps 不能在主线程之外创建。但是使用图像加载器对象,将其移动到一个或多个后台线程,在其中加载 QImage,然后将其发送到主线程以供以后使用是非常简单的。这是经过测试的代码,可以完成基本功能,加载当前目录中的所有 PNG 文件。

#!/usr/bin/env python3

import os

from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5.QtGui import QImage
from PyQt5.QtWidgets import QApplication

class ImageLoader(QObject):
    loaded = pyqtSignal(str, QImage)

    def __init__(self, filename):
        super().__init__()
        self.filename = filename

    def on_load_signal(self):
        img = QImage(self.filename)
        self.loaded.emit(self.filename, img)   


class LoaderManager(QObject):
    request_img_load = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.loaders = list(map(ImageLoader, 
                filter(lambda f: f.endswith('.png'), os.listdir())))
        self.bg_thread = QThread()

        for loader in self.loaders:
            self.request_img_load.connect(loader.on_load_signal)
            loader.loaded.connect(self.handle_img_loaded)
            loader.moveToThread(self.bg_thread)

        self.bg_thread.start()

    def __del__(self):
        self.bg_thread.quit()

    def load_all(self):
        self.request_img_load.emit()

    def handle_img_loaded(self, name, img):
        print('File {} of size {} loaded'.format(name, img.byteCount()))

if __name__ == '__main__':

    app = QApplication([])
    manager = LoaderManager()
    manager.load_all()