Unsolved Dynamically update QML Image
-
Hello everyone,
So What I am trying to do is fine the most efficient way to feed pre-constructed image data to a QML image.
I'm using PyQt5 (Sorry!) but answers in C/C++ are okay as well since I know both.What this program is doing is creating random images from the Python side and then "feeds" the constructed image elsewhere from memory. So is there a way to take that image data and update a QML image dynamically every second?
I heard QQuickImageProvider was the option to use but I am having difficult reassigning new image data with it. It works if I assign an image once and only once using that 'RequestImage' function (which I overloaded).
Maybe I'm approaching it wrong? Do you have any other suggestions I should try?
So superficially my code looks like this (It's kind of psudocode so bare with me...)
QML side --
ApplicationWindow{ id:win objectName: "win" width: 640 height: 480 Image{ id: img objectName: "img" anchors.fill:parent source: "image://myprovider/test.png" } }
Python side --
class MyImageProvider(QQuickImageProvider): def __init__(self): super(MyImageProvider, self).__init__(QQuickImageProvider.Image) # Here is where the image will be filled def requestImage(self, p_str, size): proc_img = QImage(640, 480, QImage.Format_RGBA8888) proc_img.loadFromBytes(ProcessImage()) return proc_img, proc_img.size() def ProcessImage(): #Image processing here from bytes... ... return byte_output app = QApplication(sys.argv) engine = QmlApplicationEngine() provider = myImageProvider() engine.addImageProvider("myprovider", provider) app.exec_()
Now, this works once. Once I add the image provider, I can never call requestImage again and see an update. Are there different ways to go about updating an image dynamically qith Qt/QML and either C/C++ or Python? any direction would be greatly appreciated.
One option I did hear was to have Python create the image in a temporary directory in the file system such as my_image_001.png, my_image_002.png, my_image_003.png and make it a circular buffer and have the Python side update the QML side with these images. Just seems like a lot of file overhead in my opinion.
Anyway - ANY suggestions would be good! Thanks folks!
-
Create 2 images in your image provider (pointing to the same image data) and cycle between them in the QML Image:Source. Perhaps use a signal to trigger this change in QML when the image is updated. I believe its a caching thing on the QML side that prevents it re-fetching the data.
There may be more appropriate ways of doing this.
-
@fcarney said in Dynamically update QML Image:
Create 2 images in your image provider (pointing to the same image data) and cycle between them in the QML Image:Source. Perhaps use a signal to trigger this change in QML when the image is updated. I believe its a caching thing on the QML side that prevents it re-fetching the data.
There may be more appropriate ways of doing this.
This is kind of what I have been doing now.
What I have been doing is that every time a new image is generated, I have it write to a file and then a signal is called to set the property of the image such as...img.setProperty("source",new_img_src)
and it just cycles through a buffer of 3 images.
Is there any better way to do this without needing to use the filesystem?
I'd like to read in bytes directly from memory and apply it right to the QML image dynamically every second. So far I don't see a good solution to do this? -
Where, when are you writing to file? I did the image provider trick completely in memory. You just have to provide a QImage object. It can be completely in memory. I did this in C++, and unfortunately I don't have the code on hand.
I think this:
Image{ id: img objectName: "img" anchors.fill:parent source: "image://myprovider/test.png" }
Is all in memory. The test.png is just an identifier for the source. Unless you are doing something different somewhere else.
-
@fcarney said in Dynamically update QML Image:
Where, when are you writing to file? I did the image provider trick completely in memory. You just have to provide a QImage object. It can be completely in memory. I did this in C++, and unfortunately I don't have the code on hand.
I think this:
Image{ id: img objectName: "img" anchors.fill:parent source: "image://myprovider/test.png" }
Is all in memory. The test.png is just an identifier for the source. Unless you are doing something different somewhere else.
Thank you for clearing that up. I scrolled back up to your previous reply and re-read it. So if I created say 2 empty QImages (or maybe an array of 3 to create some kind of generated image buffer). My ImageProvider class would need to have these QImages within the requestImage function? I'm not entirely sure how I would implement a QImage to a ImageProvider and have it update. I understand the singals/slots concept though. I use it all the time. Not entirely sure about just how to get this image provider working dynamically.
-
When you are setting the source to: "image://myprovider/test.png" you are just telling the imageprovider to give you the image represented by test.png. So what I did was create 2 images in my image provider that pointed to the same data. When I updated the data in the image provider then a new request would get the data. However, Image will not request the new data unless the source string changes. I then setup a timer to change the source string to a different name that still pointed to the same data in the imageprovider.
I only suggested a messaging scheme to make this maybe more efficient and change only when the image changes. The way I did is definitely hackish and might not be the best way, but I did get it to work.
It looks like your imageprovider is ignoring p_str which is the "test.png" string. So just changing the "source" string in the Image component in QML should make it update whenever that string changes.
-
hi @Bits-n-Flips
maybe you're making that more comlicated than it needs to be.do you emit a signal (from your image provider class) when the image is changed ?
if yes, than you can do the following, inside your qml file (excerpt from a project/challenge of mine)
Image { id: img property bool counter: false asynchronous: false source: "image://live/image" anchors.fill: parent fillMode: Image.PreserveAspectFit cache: false function reload() { counter = !counter source = "image://live/image?id=" + counter } Connections{ target: liveImageProvider onImageChanged: img.reload() } }
-
@J-Hilk said in Dynamically update QML Image:
hi @Bits-n-Flips
maybe you're making that more comlicated than it needs to be.do you emit a signal (from your image provider class) when the image is changed ?
if yes, than you can do the following, inside your qml file (excerpt from a project/challenge of mine)
Image { id: img property bool counter: false asynchronous: false source: "image://live/image" anchors.fill: parent fillMode: Image.PreserveAspectFit cache: false function reload() { counter = !counter source = "image://live/image?id=" + counter } Connections{ target: liveImageProvider onImageChanged: img.reload() } }
@J-Hilk, I think that is missing initialization of
counter
. Also that seems a bit hacky (thought the cleanest solution I have seen so far). Is it not possible to just doonImageChanged: img.sourceChanged();
? -
Hi @Puya and welcome,
I think that is missing initialization of counter
not really QML doesn't really do uninitialized. The best you can have is that type casting fails and you get a
var = undefined
that said this line
property bool counter: false
defines counter to be
false
afterComponent.onCompletion
Is it not possible to just do onImageChanged: img.sourceChanged(); ?
I don't think so. From a quick look into the source code, I would say, you have to call the virtual
load
function of QQuickImage which is protected.
So the only way to do it from the outside is to change the source or derive your own class from it and expose the function. -
This post is deleted! -
@J-Hilk I found this thread by googling. I need to update three images by button or when a Dialog is opened.
the source is calculated by row /col of my tablemodel.The ''bool trick' only works only two times:
- first the url ends with 'false'
- second time the url end with 'true'
I had the idea to change he counter to an int because i already ignore the value in mey requestImage(). But this worked only once when counter is 0.
Here are some snippets:
Column{ leftPadding: 5 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter id: slotB Text{ id: slotBtext text: "Slot B: Detected ID: "+ main_rect.slotid_b } Rectangle{ width: main_rect.width/2 -15 height: 125 radius: 5 border.width: 2 border.color: 'black' Image{ id: slotBImage property int counter: 0 anchors.fill: parent fillMode: Image.PreserveAspectFit cache: false } } }
loadImages()
function loadImages(){ // slotAImage.counter += slotAImage.counter let a_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_A.png" + slotAImage.counter slotAImage.counter += slotBImage.counter let b_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_B.png" + slotBImage.counter slotAImage.counter += palletImage.counter let pallet_string = "image://stockimage/" + main_rect.row + "_" + main_rect.col +"_Pallet.png" + slotBImage.counter slotAImage.source = a_sourcestring slotBImage.source = b_sourcestring palletImage.source = pallet_string }
Any idea how to get this working?
-
hi @LS-KS and welcome
It's been a while since I last dealed with this!
You never increase
slotBImage.counter
but only ever add toslotAImage.counter
is that intended ?If you wan't a working example, I have this old project of mine:
https://github.com/DeiVadder/Sandepile-Challenge-QML.git
as you can see the c++ function takes the counter as String so, there's no reason for it not also working with a real counter.
But, IIRC, you'll need a different ID each time or the QML component doesn't update, but continues to show the cached Image rather than fetching the data anew from qml
-
@J-Hilk Thank you!
You are absolutely right.
I was a bit fast to change the property to int. Here is the refactored and working function:function loadImages(){ slotAImage.counter += 1 let a_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_A.png" + slotAImage.counter slotBImage.counter += 1 let b_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_B.png" + slotBImage.counter palletImage.counter += 1 let pallet_string = "image://stockimage/" + main_rect.row + "_" + main_rect.col +"_Pallet.png" + slotBImage.counter slotAImage.source = a_sourcestring slotBImage.source = b_sourcestring palletImage.source = pallet_string }
First issue was the cpoy/paste error of the counters. second issue was not to increase by 1 or anything else. So the counter would always be 0.
But i can confirm that it wont work if the property is bool, something like that:
function loadImages(){ slotAImage.counter = !slotAImage.counter let a_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_A.png" + slotAImage.counter slotBImage.counter = !slotBImage.counter let b_sourcestring = "image://stockimage/"+ main_rect.row + "_" + main_rect.col +"_B.png" + slotBImage.counter palletImage.counter = !palletImage.counter let pallet_string = "image://stockimage/" + main_rect.row + "_" + main_rect.col +"_Pallet.png" + slotBImage.counter slotAImage.source = a_sourcestring slotBImage.source = b_sourcestring palletImage.source = pallet_string }
As described it works two times. Is it possible that all source strings get stored somehow? Actually i have nt much time to spend on this but i looked for a possibility to purge the Image from the QML Type -without success yet.