Qt: Как объединить 2 QQuickItems в один перед сохранением в png
Из этого обсуждения Stackru я вполне могу сохранить изображение из элемента QML в файл как png/jpeg
,
Как я могу наложить или объединить два разных qml
слои и объединить их в один, чтобы сохранить его в PNG / JPEG?
Примечание: я могу сохранить один QQuickItem
, Просто нужно знать, как наложить 2 QQuickItem
s
2 ответа
Просто пусть два объекта qml будут дочерними для корня Item
а затем захватить этот корневой элемент, он будет захватывать все его содержимое.
Просто убедитесь, что корневой элемент достаточно велик, чтобы охватить дочерние элементы, и что дочерние элементы не находятся в отрицательном пространстве, потому что он будет захватывать только то, что находится внутри отпечатка корневого элемента.
Вы также можете сделать ручную композицию из C++ или даже QML.
Проблема, описанная в вашем комментарии, заключается в том, что вы не можете перемещать вещи, так что вы можете сделать? Вместо того чтобы иметь исходные объекты QML в качестве родителей одного и того же корня, вы можете иметь два Image
элементы, затем вы захватываете элемент A и устанавливаете результат захвата в качестве источника изображения A, затем делаете то же самое для элемента B, и, наконец, вы захватываете корневой элемент, который будет захватывать два изображения вместе.
Хорошо, вот быстрый пример, он выглядит немного сложнее, потому что захваты асинхронны, и вам нужно дождаться завершения отдельных результатов захвата, прежде чем вы сможете захватить "конечный" корневой элемент, таким образом, используя таймер. В этом примере различные элементы располагаются в ряд, но вы можете составить их любым удобным для вас способом:
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() })
}
}
Сначала эти два прямоугольника захватываются и используются в качестве источников для изображений в программе объединения, затем объединяются и отображаются в результирующем изображении, все объекты, кроме конечного результирующего изображения, скрыты.
Еще проще, вы можете использовать этот изящный маленький помощник, чтобы быстро объединить любое количество элементов в одном изображении:
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
}
}
И вы используете это так:
joinHelper.join(function(res) { result.source = res.url }, s1, s2)
Он по-прежнему использует ряд, но вы можете легко настроить его для создания собственного макета. Он работает, передавая последний обратный вызов и все элементы, которые вы хотите захватить, внутренне он создает изображение для каждого элемента, помещает их в контейнер, а затем запускает таймер завершения.
Обратите внимание, что в зависимости от того, насколько быстрым является система, насколько сложны элементы и каково их количество, вам может потребоваться увеличить интервал таймера, поскольку окончательный обратный вызов должен выполняться только после того, как все захваты были завершены, источники изображений были назначены и изображения были изменены, чтобы придать строке правильные размеры.
Я также аннотировал большинство вещей, чтобы было легче понять.
Этот вопрос, кажется, тесно связан с этим
Решение состоит в том, чтобы сделать Item
речь идет о текстуре второго элемента, которую вам не нужно отображать на экране. Вы можете написать это Item
как вы хотите, добавив несколько ShaderEffectSource
как дети, расположите их относительно друг друга так, как вам нравится, и установите их источники на Item
s вы хотите получить изображение.
Затем вы берете Item
изображать.
Общий пример, который предоставляет функцию для получения списка Item
с одного изображения, где каждый Item
накладывается друг на друга с непрозрачностью 0,2 каждый:
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++ }
}
}
}
Использование этого было бы так:
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
}
}
Но если вам нужно более сложное объединение, вы также можете создать этот объект вручную и захватить его, когда захотите.
Без учета его, согласно примечанию из документации
Примечание. Эта функция визуализирует элемент за пределами экрана и копирует эту поверхность из памяти графического процессора в память центрального процессора, что может быть довольно дорогостоящим. Для "живого" предварительного просмотра используйте слои или ShaderEffectSource.
это решение должно быть более эффективным, чем использование Image
с ItemGrabResult
s как источники для этого хранит материал в памяти GPU, пока он не будет захвачен и сохранен.