I am using a QML Canvas to draw an image. The image is loaded in the backend using Python, OpenCV and NumPy and provided to QML via a custom image provider, which inherits from QQuickImageProvider. To reload an image in QML, I do the following:
unloadImage('image://<providerName>/<ID>')
loadImage('image://<providerName>/<ID>')
As long as the image is sufficiently big, this approach works well. But as soon as the image size falls under a certain threshold (on my PC roughly 1.57 MB), the unloadImage function seems to stop working. After calling it, I shouldn't be able to paint the image again, but that still works. Also the, requestImage function of my QQuickImageProvider is no longer called, which is also the case if I don't call unloadImage, before calling the loadImage function.
Does anybody know what I am doing wrong here?
Edit:
Here is a minimal code example, which reproduces my error:
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()
}
}
}
I did my best to shrink the example to the absolute minimum, but couldn't get it smaller than that. Sorry.