programmatically changing contents of Image source
-
Hi all -
My app uses some SVG icons that use multiple colors. Here is one such file:
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M7.375 16.5125C9.68176 14.2616 12.777 13.0017 16 13.0017C19.223 13.0017 22.3182 14.2616 24.625 16.5125" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.13751 12.2751C6.56608 8.89633 11.1864 7.00232 16 7.00232C20.8136 7.00232 25.4339 8.89633 28.8625 12.2751" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11.6125 20.7625C12.7926 19.6303 14.3646 18.9982 16 18.9982C17.6353 18.9982 19.2074 19.6303 20.3875 20.7625" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M16 26.5C16.8284 26.5 17.5 25.8284 17.5 25C17.5 24.1716 16.8284 23.5 16 23.5C15.1716 23.5 14.5 24.1716 14.5 25C14.5 25.8284 15.1716 26.5 16 26.5Z" fill="white"/> </svg>
I need to change one of the colors programmatically (this part should be easy with a couple string operations). The trouble I'm having is feeding the file content back into the Image. After stealing from a few SO articles, I've gotten to this:
import QtQuick import QtQuick.Effects Window { id: root property string xmlString: `data:image/svg+xml;utf8, <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M7.375 16.5125C9.68176 14.2616 12.777 13.0017 16 13.0017C19.223 13.0017 22.3182 14.2616 24.625 16.5125" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.13751 12.2751C6.56608 8.89633 11.1864 7.00232 16 7.00232C20.8136 7.00232 25.4339 8.89633 28.8625 12.2751" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11.6125 20.7625C12.7926 19.6303 14.3646 18.9982 16 18.9982C17.6353 18.9982 19.2074 19.6303 20.3875 20.7625" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M16 26.5C16.8284 26.5 17.5 25.8284 17.5 25C17.5 24.1716 16.8284 23.5 16 23.5C15.1716 23.5 14.5 24.1716 14.5 25C14.5 25.8284 15.1716 26.5 16 26.5Z" fill="white"/> </svg>` idth: 800 height: 480 visible: true Image { source: xmlString width: 64 height: 64 } Component.onCompleted: readFile() function readFile() { const xhr = new XMLHttpRequest(); const method = "GET"; const url = "qrc:/Wifi2bars.svg"; xhr.open(method, url, true); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { const status = xhr.status; if (status === 0 || (status >= 200 && status < 400)) { root.xmlString = xhr.response } } }; xhr.send(); } }
When I run, I get this error:
QML Image: Cannot open: qrc:/qt/qml/colorizing/%3Csvg width=%2232%22 height=%2232%22 viewBox=%220 0 32 32%22 fill=%22none%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%0D%0A%3Cpath d=%22M7.375 16.5125C9.68176 14.2616 12.777 13.0017 16 13.0017C19.223 13.0017 22.3182 14.2616 24.625 16.5125%22 stroke=%22#8e8e93%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22/%3E%0D%0A%3Cpath d=%22M3.13751 12.2751C6.56608 8.89633 11.1864 7.00232 16 7.00232C20.8136 7.00232 25.4339 8.89633 28.8625 12.2751%22 stroke=%22#8e8e93%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22/%3E%0D%0A%3Cpath d=%22M11.6125 20.7625C12.7926 19.6303 14.3646 18.9982 16 18.9982C17.6353 18.9982 19.2074 19.6303 20.3875 20.7625%22 stroke=%22white%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22/%3E%0D%0A%3Cpath d=%22M16 26.5C16.8284 26.5 17.5 25.8284 17.5 25C17.5 24.1716 16.8284 23.5 16 23.5C15.1716 23.5 14.5 24.1716 14.5 25C14.5 25.8284 15.1716 26.5 16 26.5Z%22 fill=%22white%22/%3E%0D%0A%3C/svg%3E%0D%0A
(I don't think all the special character conversions in the error message are actually in my string.)
So...what am I doing wrong here? The xhr.responseText appears to be the same as the initial value in the Image source text.Thanks for any ideas...
-
Hi,
Not a direct answer to your issue but I was wondering whether using a QQuickImageProvider wouldn't fit your use case better ?
The example in the documentation covers a similar need to yours (providing a different colored image).
-
I found the solution to my original issue: one of the lines above needed to be changed to this:
root.xmlString = "data:image/svg+xml;utf8," + xhr.responseText
Unfortunately, my editing the contents and replacing root.xmlString doesn't seem to change the displayed image. Perhaps I need an onSourceChanged() in my Image?
@SGaist It's interesting that you mentioned QQuickImageProvider - I just began using that a couple days ago. I don't see, though, how I can use it to selectively edit the contents of an SVG.
-
I tried your example, but flipping the image using a timer. It seems to work, so in principle what you are trying to do ought to work.
Window { id: root width: 640 height: 480 visible: true title: qsTr("Hello World") property string xmlStringA: `data:image/svg+xml;utf8, <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M7.375 16.5125C9.68176 14.2616 12.777 13.0017 16 13.0017C19.223 13.0017 22.3182 14.2616 24.625 16.5125" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.13751 12.2751C6.56608 8.89633 11.1864 7.00232 16 7.00232C20.8136 7.00232 25.4339 8.89633 28.8625 12.2751" stroke="#8e8e93" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11.6125 20.7625C12.7926 19.6303 14.3646 18.9982 16 18.9982C17.6353 18.9982 19.2074 19.6303 20.3875 20.7625" stroke="blue" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M16 26.5C16.8284 26.5 17.5 25.8284 17.5 25C17.5 24.1716 16.8284 23.5 16 23.5C15.1716 23.5 14.5 24.1716 14.5 25C14.5 25.8284 15.1716 26.5 16 26.5Z" fill="blue"/> </svg>` property string xmlStringB: `data:image/svg+xml;utf8, <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M7.375 16.5125C9.68176 14.2616 12.777 13.0017 16 13.0017C19.223 13.0017 22.3182 14.2616 24.625 16.5125" stroke="#8e8e93" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.13751 12.2751C6.56608 8.89633 11.1864 7.00232 16 7.00232C20.8136 7.00232 25.4339 8.89633 28.8625 12.2751" stroke="#8e8e93" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11.6125 20.7625C12.7926 19.6303 14.3646 18.9982 16 18.9982C17.6353 18.9982 19.2074 19.6303 20.3875 20.7625" stroke="green" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> <path d="M16 26.5C16.8284 26.5 17.5 25.8284 17.5 25C17.5 24.1716 16.8284 23.5 16 23.5C15.1716 23.5 14.5 24.1716 14.5 25C14.5 25.8284 15.1716 26.5 16 26.5Z" fill="green"/> </svg>` property string xmlString: xmlStringA Image { source: xmlString width: 64 height: 64 } Timer { interval: 1000 repeat: true running: true property bool isA: true onTriggered: { if (isA) { xmlString = xmlStringB; } else { xmlString = xmlStringA; } isA = !isA; } } }
-
Oops.
After looking at @Bob64 's example, I revisited my own. It turns out that in the course of testing this, I'd changed this:
Image { source: xmlString; Layout.preferredHeight: 64 Layout.preferredWidth: 64 Component.onCompleted: { readFile(true); } }
to this:
Image { source: "qrc:/Wifi2bars.svg" Layout.preferredHeight: 64 Layout.preferredWidth: 64 Component.onCompleted: { readFile(true); } }
Oops.
Thanks to all who looked.
-
M mzimmers has marked this topic as solved
-
After further experimentation, I realized I could greatly simplify this solution:
import QtQuick import QtQuick.Effects Window { width: 800 height: 480 visible: true Image { source: "qrc:/Wifi3bars.svg" Component.onCompleted: { source = makeDark(source) } } function makeDark(url) { const xhr = new XMLHttpRequest(); const method = "GET"; let newString = "" // the "false" below makes the request synchronous. xhr.open(method, url, false) xhr.send() const status = xhr.status if (status === 0 || (status >= 200 && status < 400)) { newString = "data:image/svg+xml;utf8," + xhr.responseText while (newString.includes('#ffffff')) { newString = newString.replace('#ffffff', '#000000'); } } return newString } }
No need for a url property or a string containing the initial value to be edited. And making the open request synchronous greatly simplifies the code within the function. I can do this safely because all my images to be edited will be contained within my binary; if someone needs to use this with web-derived files (which is of course actually the real intent of XMLHttpRequest), they'll have to use something closer to the original function.
-
@mzimmers said in programmatically changing contents of Image source:
@SGaist It's interesting that you mentioned QQuickImageProvider - I just began using that a couple days ago. I don't see, though, how I can use it to selectively edit the contents of an SVG.
You would pass the color you want as parameter to the image provider, and depending on what you need the image size. There you'll modify the svg text as needed and generate the pixmap to return to the QML side.