在PySide中异步加载图像

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在PySide中异步加载图像相关的知识,希望对你有一定的参考价值。

我正在尝试使用QAbstractListModel将数千张或更多张图像加载到QListView中。我想尽可能地利用它们异步加载。

为了简单起见,现在我的模型中有一个称为LoadImages的方法,可以将其更改为任何图像目录进行测试。

  • QStandardItem(DisplayRole):图像名称
  • QStandardItem(UserRole):图像的完整文件路径

我的目标是,首先为所有列表项加载一个占位符图像,然后在单独的线程中加载磁盘中该特定项的缩略图。加载后,应将GUI更新为加载的图像。原始代码使用QStyledItemDelegate处理所有需要加载的图像的缓存。如果该图像不在高速缓存中,则它将绘制占位符图像并将信号发送到另一个线程,该线程将加载该图像并将其放入高速缓存。

我的代码很大程度上基于此帖子:How to correctly load images asynchronously in PyQt5?,该代码未提供完整的代码。

它似乎正在正确加载占位符图像,但是不会从磁盘加载图像。我不断收到这些错误:

QPixmap::scaled: Pixmap is a null pixmap
QPixmap: It is not safe to use pixmaps outside the GUI thread

Stackoverflow上的原始线程说,有一种解决方案,我认为我已正确实现。我有点迷茫,为什么它不起作用。

1

import os
import sys

from PySide.QtCore import *
from PySide.QtGui import *


PLACEHOLDER_IMAGE_PATH = "C:/Users/jmartini/Desktop/Trash/imageIcon.svg"
IMAGES_PATH = "C:/Users/jmartini/Desktop/Trash/imagesList"

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

    def __init__(self, image_cache, loader_thread, parent=None):
        super(MyDelegate, self).__init__(parent)
        self.placeholder_image = QIcon(PLACEHOLDER_IMAGE_PATH).pixmap(QSize(128,128))
        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
        asset_name = QModelIndex.data(Qt.DisplayRole)
        asset_thumb = QModelIndex.data(Qt.UserRole)
        pic_rect = QRect(rect.left(), rect.top(), 128, 128)
        text_rect = QRect(rect.left(), rect.top() + 128, 128, 22)
        try:
            cached_thumb = self.image_cache[asset_name]
            print("Got image:  from cache".format(asset_name))
        except KeyError as e:
            self.t1.emit(asset_name, asset_thumb, self.image_cache)
            cached_thumb = self.placeholder_image
            print("Drawing placeholder image for ".format(asset_name))

        QPainter.drawPixmap(pic_rect, cached_thumb)
        QPainter.drawText(text_rect, Qt.AlignCenter, asset_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(128, 150)


class MyModel(QAbstractListModel):
    def __init__(self, *args, **kwargs):
        QAbstractListModel.__init__(self, *args, **kwargs)
        self._items = []

    def rowCount(self, index=QModelIndex()):
        return len(self._items)


    def itemByIndex(self, index):
        if (index < 0 or index >= len(self._items)):
            return None
        return self._items[index]


    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None

        item = self.itemByIndex(index.row())
        if not item:
            return None
        elif role == Qt.DisplayRole:
            return item.data(role=Qt.DisplayRole)
        elif role == Qt.DecorationRole:
            return item.data(role=Qt.DecorationRole)
        elif role == Qt.UserRole:
            return item.data(role=Qt.UserRole)
        return None


    # Extra Methods
    def loadImages(self):
        self.image_dir = IMAGES_PATH

        for img in os.listdir(self.image_dir):
            filepath = os.path.join(self.image_dir, img)
            item = QStandardItem(filepath)
            item.setData(os.path.basename(filepath), role=Qt.DisplayRole)
            item.setData(filepath, role=Qt.UserRole)
            self.appendItem(item)


    def appendItem(self, item):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self._items.append(item)
        self.endInsertRows()



class LoaderThread(QObject):

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

    @Slot(str, str, dict)
    def insert_into_queue(self, name, thumb_path, image_cache):
        print("Got signal, loading image for  from disk".format(name))
        image = QImage(thumb_path)
        pixmap = QPixmap.fromImage(image).scaled(128, 128)
        image_cache[name] = pixmap
        print("Image for  inserted to cache".format(name))


class Example(QMainWindow):
    def __init__(self):
        super(Example, self).__init__()
        self.resize(960, 800)
        self.setWindowTitle('Image Viewer')

        image_cache = 
        lt = LoaderThread()
        self.thread = QThread()
        lt.moveToThread(self.thread)
        self.thread.start()

        self.delegate = MyDelegate(image_cache, lt)

        self.model = MyModel()

        self.proxyModel = QSortFilterProxyModel()
        self.proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.proxyModel.setSourceModel(self.model)

        self.list = QListView()
        self.list.setViewMode(QListView.IconMode)
        self.list.setResizeMode(QListView.Adjust)
        self.list.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.list.setModel(self.proxyModel)
        self.list.setItemDelegate(self.delegate)

        self.model.loadImages()

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.list)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)


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


if __name__ == '__main__':
    main()
答案

从您的示例中,您只需要将呼叫方式切换为setData,这是另一种方式:

item.setData(Qt.DisplayRole, os.path.basename(filepath)
item.setData(Qt.UserRole, filepath)

我还将删除您的data()重新实现。

以上是关于在PySide中异步加载图像的主要内容,如果未能解决你的问题,请参考以下文章

异步加载图像不断导致图像闪烁?

uitableview 中的异步图像加载

在 iOS 中淡入异步加载的图像

如何在 PyQt5 中正确异步加载图像?

Swift:在 UITableViewCell 中异步加载图像 - 自动布局问题

异步加载联系人列表中的图像