如何在 PyQt5 中正确异步加载图像? [英] How to correctly load images asynchronously in PyQt5?

查看:148
本文介绍了如何在 PyQt5 中正确异步加载图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试弄清楚如何在 PyQt Qlistview 中正确完成异步图像加载.

I'm trying to figure out how to accomplish an async image load correctly, in PyQt Qlistview.

我的主要小部件由一个 Qlistview 和一个 QLineEdit 文本框组成.我有一个演员数据库,我使用 QAbstractListModel 的子类查询它.在文本框中输入文本时,将查询数据库并用结果填充模型.结果随后显示在 Qlistview 中.(每个演员的结果包含演员姓名和图像路径.)

My main widget consists of a Qlistview and a QLineEdit textbox. I have a database of actors which I query using a subclass of QAbstractListModel When text is entered in the textbox, the database is queried and the Model is populated with the results. Results are then displayed in the Qlistview. (A result for each Actor contains the actors name and a path to an image.)

像这样:

当结果集太大(大于 50)时,问题就开始了,从磁盘加载图像会造成损失并挂起 UI.我希望实现的行为是最初为所有结果加载一个占位符图像,然后在不同的线程中从磁盘加载特定图像,并在加载时使用新加载的图像更新 Qlistview 项目.

The problem begins when the result set is too large (Larger than 50), loading the images from disk is taking it's toll and hanging the UI. The behaviour I wish to achieve is to initially load a placeholder image for all the results and then in a different thread load the specific image from disk and when it's loaded update the Qlistview item with the newly loaded image.

为此,我创建了一个自定义 QItemDelegate 类,该类具有需要加载的所有图像的缓存.如果图像不在缓存中,则它会绘制占位符图像并向另一个线程发送信号,该线程加载该图像并将其放入缓存中.

To that end I created a custom QItemDelegate class that has a cache of all images that needs to be loaded. If the image is not in the cache then it draws the placeholder image and sends a signal to a another thread that loads that image and puts it in the cache.

我的委托类:

class MyDelegate(QStyledItemDelegate):
    t1 = pyqtSignal(str, str, dict)

    def __init__(self, image_cache, loader_thread, parent=None):
        super(MyDelegate, self).__init__(parent)
        self.placeholder_image = QPixmap(PLACEHOLDER_IMAGE_PATH).scaled(200, 300)
        self.image_cache = image_cache
        self.loader_thread = loader_thread
        self.t1.connect(self.loader_thread.insert_into_queue)


    def paint(self, QPainter, QStyleOptionViewItem, QModelIndex):
        rect = QStyleOptionViewItem.rect
        actor_name = QModelIndex.data(Qt.DisplayRole)
        actor_thumb = QModelIndex.data(Qt.UserRole)
        pic_rect = QRect(rect.left(), rect.top(), 200, 300)
        text_rect = QRect(rect.left(), rect.top() + 300, 200, 20)
        try:
            cached_thumb = self.image_cache[actor_name]
            print("Got image: {} from cache".format(actor_name)
        except KeyError as e:
            self.t1.emit(actor_name, actor_thumb, self.image_cache)
            cached_thumb = self.placeholder_image
            print("Drawing placeholder image for {}".format(actor_name)

        QPainter.drawPixmap(pic_rect, cached_thumb)
        QPainter.drawText(text_rect, Qt.AlignCenter, actor_name)

        if QStyleOptionViewItem.state & QStyle.State_Selected:
            highlight_color = QStyleOptionViewItem.palette.highlight().color()
            highlight_color.setAlpha(50)
            highlight_brush = QBrush(highlight_color)
            QPainter.fillRect(rect, highlight_brush)

    def sizeHint(self, QStyleOptionViewItem, QModelIndex):
        return QSize(200, 320)

加载线程:

class LoaderThread(QObject):

    def __init__(self):
        super(LoaderThread, self).__init__()

    @pyqtSlot(str, str, dict)
    def insert_into_queue(self, name, thumb_path, image_cache):
        print("Got signal, loading image for {} from disk".format(name))
        pixmap = QPixmap(thumb_path).scaled(200, 300)
        image_cache[name] = pixmap
        print("Image for {} inserted to cache".format(name))

主窗口相关部分__init__方法:

image_cache = {}
lt = loader_tread.LoaderThread()
self.thread = QThread()
lt.moveToThread(self.thread)
self.thread.start()
self.delegate = MyDelegate(image_cache, lt)

虽然这种方法在图像加载正确的情况下似乎有效,但在 MyDelegate 中多次调用 self.t1.emit(actor_name, actor_thumb, self.image_cache) 时 UI 挂起制作.

While this approach seems to work as so far as the images are loading correctly, the UI is hanging when multiple calls to self.t1.emit(actor_name, actor_thumb, self.image_cache) in MyDelegate are made.

实际上,延迟几乎与在同一线程中加载图像时的延迟几乎相同:

In fact, the delay is almost identical to when the images are loaded in the same thread like so:

 try:
            cached_thumb = self.image_cache[actor_name]
            print("Got image: {} from cache".format(QModelIndex.data(Qt.DisplayRole)))
 except KeyError as e:
            # self.t1.emit(actor_name, actor_thumb, self.image_cache)
            pixmap = QPixmap(actor_thumb).scaled(200,300)
            self.image_cache[actor_name] = pixmap
            cached_thumb = self.image_cache[actor_name]

如果有人对我做错了什么或如何实现预期行为有任何指示,他们会很受欢迎.

If someone has any pointers about what I am doing wrong or about how the desired behavior can be achieved they will be well received.

附言我知道我可以限制数据库查询中的结果集,但这不是我想要做的.

P.S I'm aware that I can limit the result set in the database query, however this is not what I wish to do.

推荐答案

我今天刚遇到同样的问题:尝试在单独的 Qthread 上加载缩略图 QPixmaps,并且它挂起了 UI.

I just had the same problem today : Trying to load thumbnails QPixmaps on a separate Qthread, and it was hanging the UI.

我发现出于某种原因...使用 QPixmap 从磁盘加载图像将始终冻结"主 GUI 线程:

I figured out that for some reason... using QPixmap to load image from disk will always "freeze" the main GUI Thread :

pixmap = QPixmap(thumb_path).scaled(200, 300)  # Will hang UI

一个解决方案;使用 QImage 对象加载图像,然后从图像生成 QPixmap,以下代码为我完成这项工作:

One solution ; load the image using a QImage object, then generate the QPixmap from image, The following code do the job for me :

image = QImage(thumb_path)
pixmap = QPixmap.fromImage(image).scaled(200, 300)

滚动流畅,已使用 500 多个缩略图进行测试.

Scrolling is smooth, tested with 500+ thumbnails.

这篇关于如何在 PyQt5 中正确异步加载图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆