重新加载QML画布图像

时间:2019-11-25 12:19:47

标签: python qml pyside2

我正在使用QML画布绘制图像。该图像使用Python,OpenCV和NumPy加载到后端,并通过自定义图像提供程序提供给QML,该图像提供程序继承自QQuickImageProvider。要在QML中重新加载图像,请执行以下操作:

unloadImage('image://<providerName>/<ID>')
loadImage('image://<providerName>/<ID>')

只要图像足够大,此方法就可以很好地工作。但是,一旦图像大小低于某个阈值(在我的PC上大约为1.57 MB), unloadImage 函数似乎就会停止工作。调用它之后,我不应该再次绘制图像,但是仍然可以。而且,不再调用我的 QQuickImageProvider requestImage 函数,如果在调用该函数之前不调用 unloadImage ,情况也是如此。 loadImage 函数。

有人知道我在做什么错吗?

编辑:

这是一个最小的代码示例,它重现了我的错误:

main.py

import sys
import time

from PySide2 import QtGui, QtQml
from PySide2.QtQuick import QQuickImageProvider
from PySide2.QtGui import QImage
from PySide2.QtCore import QObject, Signal, Slot

import numpy as np
import cv2

class ImageProvider(QQuickImageProvider, QObject):
    def __init__(self):
        # Load images:
        # - The first image is scaled large enough
        self.img1 = np.require(np.flip(cv2.imread("messi5.jpg"), 2), np.uint8, "C")
        self.img1 = cv2.resize(self.img1, (0,0), fx=3, fy=3) 
        # - The second image is too small and won't allow a reload
        self.img2 = np.require(np.flip(cv2.imread("lena.jpg"), 2), np.uint8, "C")

        # Shown image
        self.image = None

        # Initialize parent objects
        QObject.__init__(self)
        QQuickImageProvider.__init__(self, QQuickImageProvider.Image)

    # =======
    # Signals
    # =======
    updateImage = Signal()

    # =====
    # Slots
    # =====
    def requestImage(self, ident, size, requestedSize):
        print("ImageProvider: loading image")

        format = QImage.Format_RGB888
        (h, w) = self.image.shape[:2]
        line_len = self.image.strides[0]

        img = QImage(self.image, w, h, line_len, format)

        return img

    @Slot(result="QVariantList")
    def getImageSize(self):
        h, w = self.image.shape[:2]
        return [w, h]

    @Slot()
    def changeImage(self):
        if np.array_equal(self.image, self.img1):
            self.show(self.img2) 
        else:
            self.show(self.img1) 

    # ==============
    # public methods
    # ==============
    def show(self, Image):
        self.image = Image
        self.updateImage.emit()

if __name__ == "__main__":
    # Create app
    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()

    # Create backend
    image_provider = ImageProvider()

    # Register backend
    engine.addImageProvider("CustProv", image_provider)
    engine.rootContext().setContextProperty("CustProv", image_provider)

    # Load window
    engine.load('main.qml')
    win = engine.rootObjects()[0]
    win.show()

    # Show image
    image_provider.show(image_provider.img1)

    sys.exit(app.exec_())

main.qml

import QtQuick 2.0
import QtQuick.Controls 2.0

ApplicationWindow {
    id: root
    visible: true
    width: 800
    height: 480

    // =============
    // Event Handler
    // =============
    onHeightChanged: {
        imagePainter.resize()
    } 

    onWidthChanged: {
        imagePainter.resize()
    } 

    // ========
    // Children
    // ========
    Item {
        anchors {
            top: parent.top
            bottom: button.top
            right: parent.right
            left: parent.left
            bottomMargin: 10
        }

        Canvas {
            id: imagePainter
            anchors {
                horizontalCenter: parent.horizontalCenter
                verticalCenter: parent.verticalCenter
            }

            // =================
            // Custom Properties
            // =================
            property var size: {
                'width': -1,
                'height': -1
            }
            property real scale: 1
            property var image: 'image://CustProv/image'

            // =========
            // Functions
            // =========
            function resize() {
                console.log("Resizing canvas contents")
                var wToH =  size.width/size.height
                if (parent.width/parent.height >= wToH) {
                    // image aspect ratio is narrower than parent 
                    // aspect ratio: Use full height
                    height = parent.height
                    width = wToH * parent.height
                    scale = height/size.height
                }
                else {    
                    // image aspect ratio is wider than parent 
                    // aspect ratio: use full width   
                    width = parent.width          
                    height = parent.width / wToH
                    scale = width/size.width
                }
                // repaint the image
                requestPaint();
            }

            function reload() {
                console.log("Reload triggered")
                // First, get the new image size
                var imSize = CustProv.getImageSize()
                size.width = imSize[0]
                size.height = imSize[1]
                resize()

                // Reload image
                unloadImage(image) <--- Seems to fail for small images
                loadImage(image)
            }

            // =============
            // Event Handler
            // =============
            Component.onCompleted: {
                console.log("connecting external signals")
                CustProv.updateImage.connect(reload)
            }

            onPaint: {
                // Invoked by requestPaint()
                if (!isImageLoaded(image)) {
                    return
                }
                var ctx = getContext('2d')
                ctx.clearRect(0, 0, width, height)
                ctx.scale(scale, scale)
                ctx.drawImage(image, 0, 0)
                ctx.scale(1/scale, 1/scale)
            }

            onImageLoaded: {
                requestPaint()
            }
        }
    }


    Button {
        id: button
        anchors {
            right: parent.right
            bottom: parent.bottom
            bottomMargin: 10
            rightMargin: 10
        }

        width: 200
        height: 25

        text: "Change Image"

        onClicked: {
            CustProv.changeImage()
        }
    }
}

我尽了最大的努力将示例缩小到绝对的最小值,但没有比这更小的示例。抱歉。

1 个答案:

答案 0 :(得分:0)

根据documentation

  

由QQuickImageProvider返回的图像会自动缓存,类似于QML引擎加载的任何图像。当从缓存中加载带有“ image://”前缀的图像时,相关图像提供者将不会调用requestImage()和requestPixmap()。如果应该始终从图像提供者那里获取图像,而根本不应该对其进行缓存,则将相关Image,BorderImage或AnimatedImage对象的cache属性设置为false。

因此,您需要将cache组件的Image属性设置为false

要重新加载图像,只需将源路径修改为null并将其还原为以前的值。