Загрузка изображений асинхронно в PySide - PullRequest
0 голосов
/ 20 июня 2019

Я пытаюсь загрузить большой набор изображений, несколько тысяч или более, в QListView, используя QAbstractListModel. Я хотел воспользоваться загрузкой их асинхронно, если это возможно.

Для простоты сейчас в моей модели есть метод LoadImages, который можно заменить на любой каталог изображений для тестирования.

  • QStandardItem ( DisplayRole ): имя изображения
  • QStandardItem ( UserRole ): полный путь к файлу изображения

Моя цель - сначала загрузить изображение заполнителя для всех элементов списка, а затем в отдельном потоке загрузить миниатюру этого конкретного элемента с диска. После загрузки он должен обновить графический интерфейс для загруженного изображения. Оригинальный код использовал QStyledItemDelegate для обработки кэша всех изображений, которые необходимо загрузить. Если изображение отсутствует в кэше, оно рисует изображение-заполнитель и отправляет сигнал другому потоку, который загружает это изображение и помещает его в кэш.

Мой код в значительной степени основан на этом посте здесь: Как правильно загружать изображения асинхронно в 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()
...