在线程中创建 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()
我在尝试加载大量 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()