From this discussion on StackOverflow, I am well able to save an image out of a QML item into a file as png/jpeg
.
How can I overlay or merge two different qml
layers & merge them into one, to save it into a png/jpeg ?
Note: I am able to save a single QQuickItem
. Just need to know how to overlay 2 QQuickItem
s
Just have the two qml objects be children of a root Item
and then grab that root item, it will capture all its content.
Just make sure the root item is big enough to enclose the children, and the children are not in negative space, because it will only capture what's inside of the footprint of the root item.
You can also do manual composition, from C++ or even QML.
The problem described in your comment is you can't move stuff around, so what can you do? Instead of having the original QML objects as parents of the same root, you can have two Image
elements, then you capture item A and set the capture result to serve as a source of image A, then do the same for item B, and finally, you capture the root item, which will capture the two images together.
OK, here is a quick example, it looks a little complicated, because grabs are async, and you have to wait for the individual grab results to be completed before you can grab the "final" root item, thus the usage of the timer. In this example, different items are laid out in a row, but you can compose them any way that you like:
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
Rectangle {
id: s1
visible: false
width: 200
height: 200
color: "red"
}
Rectangle {
id: s2
visible: false
width: 200
height: 200
color: "blue"
}
Row {
id: joiner
visible: false
Image { id: t1 }
Image { id: t2 }
}
Image {
id: result
y: 200
}
Timer {
id: finish
interval: 10
onTriggered: joiner.grabToImage(function(res) {result.source = res.url})
}
Component.onCompleted: {
s1.grabToImage(function(res) {t1.source = res.url})
s2.grabToImage(function(res) {t2.source = res.url; finish.start() })
}
}
First the two rectangles are captured and used as sources for the images in joiner, then joiner is captured and displayed in the result image, all objects except the final result image are hidden.
Even easier, you can use this nifty little helper to quickly join any number of items in a single image:
Item {
id: joinHelper
visible: false
property Component ic: Image { }
property var cb: null
Row { id: joiner }
Timer {
id: finish
interval: 100
onTriggered: joiner.grabToImage(joinHelper.cb)
}
function join(callback) {
if (arguments.length < 2) return // no items were passed
var i
if (joiner.children.length) { // clean previous captures
for (i = 0; i < joiner.children.length; ++i) {
joiner.children[i].destroy()
}
}
cb = callback // set callback for later
for (i = 1; i < arguments.length; ++i) { // for every item passed
var img = ic.createObject(joiner) // create empty image
// need to capture img by "value" because of JS scoping rules
// otherwise you end up with only one image - the final one
arguments[i].grabToImage(function(temp){ return function(res){temp.source = res.url}}(img))
}
finish.start() // trigger the finishing step
}
}
And you use it like this:
joinHelper.join(function(res) { result.source = res.url }, s1, s2)
It still uses a row, but you can easily tweak it to do your own layouting. It works by passing the final callback and all items you want to capture, internally it creates an image for every item, puts them in the container, and then triggers the finishing timer.
Note that depending on how fast the system is and how complex the items are and what their count is, you may need to up the timer interval, because the final callback needs to be executed only after all captures were completed, the image sources were assigned and the images were resized to give the row its proper dimensions.
I also annotated most things to make it easier to understand.
This question seems to be closely related to this one
The solution is to render the Item
s in question into an texture of a second item, that you don't need to render to the screen. You can compose this Item
as you want by adding multiple ShaderEffectSource
s as children, position them relatively to each other as you like, and set their sources to the Item
s you want to grab to Image.
Then you grab the Item
to Image.
A generic example, that exposes a function to grab a list of Item
s to one Image, where each Item
is stacked ontop of each other, with an opacity of 0.2 each:
import QtQuick 2.0
Rectangle {
id: root
visible: false
color: 'white'
function grabMultipleToImage(url, objects) {
imgRep.url = url
width = Math.max.apply(root, objects.map(function(e) { return e.width }))
height = Math.max.apply(root, objects.map(function(e) { return e.height }))
imgRep.ready = 0
imgRep.model = objects
}
Repeater {
id: imgRep
onReadyChanged: {
if (ready > 0 && ready === model.length) {
console.log(root.width, root.height, imgRep.url)
root.grabToImage(function (res) { res.saveToFile(imgRep.url); model = null })
}
}
property int ready: 0
property string url
delegate: ShaderEffectSource {
sourceItem: modelData
width: modelData.width
height: modelData.height
opacity: 0.2
live: false
Component.onCompleted: { imgRep.ready++ }
}
}
}
The usage of this would be like this:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: myWindow
visible: true
width: 600
height: 600
color: 'white'
Button {
text: 'grab'
onClicked: {
test.grabMultipleToImage('testimg.jpg', [rect1, rect2, rect3])
}
}
ImageGrabber {
id: test
}
Rectangle {
x: 100
y: 205
id: rect1
color: 'blue'
width: 10
height: 20
}
Rectangle {
x: 250
y: 12
id: rect2
color: 'green'
width: 20
height: 30
}
Rectangle {
x: 100
y: 100
id: rect3
color: 'red'
width: 100
height: 5
}
}
But if you have the need for more complex merging, you can also create this object manually and grab it when ever you want.
Without metering it, according to the note from the documentation
Note : This function will render the item to an offscreen surface and copy that surface from the GPU's memory into the CPU's memory, which can be quite costly. For "live" preview, use layers or ShaderEffectSource.
this solution should be more efficient than using Image
s with ItemGrabResult
s as sources for it keeps the stuff in the GPU memory until it is grabed and stored.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.