【发布时间】:2017-07-10 07:32:22
【问题描述】:
通过 StackOverflow 上的 this discussion,我可以将 QML 项目中的图像保存到文件中,格式为 png/jpeg。
我如何叠加或合并两个不同的qml 图层并将它们合并为一个,以将其保存为 png/jpeg?
注意:我可以保存一个QQuickItem。只需要知道怎么覆盖2个QQuickItems
【问题讨论】:
标签: qt qml qimage qquickitem
通过 StackOverflow 上的 this discussion,我可以将 QML 项目中的图像保存到文件中,格式为 png/jpeg。
我如何叠加或合并两个不同的qml 图层并将它们合并为一个,以将其保存为 png/jpeg?
注意:我可以保存一个QQuickItem。只需要知道怎么覆盖2个QQuickItems
【问题讨论】:
标签: qt qml qimage qquickitem
只需让两个 qml 对象成为根 Item 的子对象,然后抓取该根项目,它将捕获其所有内容。
只需确保根项足够大以包围子项,并且子项不在负空间中,因为它只会捕获根项足迹内的内容。
您还可以使用 C++ 甚至 QML 进行手动组合。
您评论中描述的问题是您无法移动东西,那么您该怎么办?您可以拥有两个Image元素,而不是将原始QML对象作为同一根的父对象,然后捕获项目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() })
}
}
首先捕获两个矩形并将其用作joiner中图像的源,然后捕获joiner并显示在结果图像中,隐藏除最终结果图像之外的所有对象。
更简单的是,您可以使用这个漂亮的小助手将任意数量的项目快速连接到单个图像中:
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)
它仍然使用一行,但您可以轻松地对其进行调整以进行自己的布局。它通过传递最终回调和您要捕获的所有项目来工作,在内部它为每个项目创建一个图像,将它们放入容器中,然后触发完成计时器。
请注意,根据系统的速度和项目的复杂程度以及它们的计数,您可能需要增加计时器间隔,因为最终的回调需要在所有捕获完成后执行,图像分配源并调整图像大小以使行具有适当的尺寸。
我还对大部分内容进行了注释,以便于理解。
【讨论】:
result.source = res.url 替换为您想要对抓取结果执行的任何操作。
joiner.grabToImage(joinHelper.cb) 并将joiner 传递给save()。这意味着您可以删除 callback 和拥有它的 cb 属性。您仍然需要使用计时器来让整个事情有时间完成。
Item 中使用width: childrenRect.width; height: childrenRect.height 使其自动放大以适应所有孩子。
这个问题似乎与this one密切相关
解决方案是将有问题的Items 渲染到第二个项目的纹理中,您不需要渲染到屏幕上。您可以通过添加多个ShaderEffectSources 作为子代,随意组合此Item,根据需要将它们彼此相对放置,并将它们的来源设置为您想要抓取到图像的Items。
然后你将Item 抓取到Image。
一个通用示例,它公开了一个函数,用于将 Items 列表抓取到一个图像,其中每个 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
}
}
但如果您需要更复杂的合并,您也可以手动创建此对象并随时获取。
根据文档中的说明,不对其进行计量
Note:此函数会将项目渲染到屏幕外表面,并将该表面从 GPU 的内存复制到 CPU 的内存中,这可能会非常昂贵。对于“实时”预览,请使用图层或 ShaderEffectSource。
这个解决方案应该比使用Images 和ItemGrabResults 作为源更有效,因为它将内容保留在GPU 内存中,直到它被抓取和存储。
【讨论】:
res.saveToFile(imgRep.url) 替换为对我的C++ 插槽的调用,该插槽接受QQuickItem,就像ImageCapture.save(res) 一样。
res 不会是QQuickItem。它是一个ItemGrabResult,它继承了QObject,但不是QQuickItem。如果你需要 QQuickItem 你需要通过,在我的例子中 root 在 ImageGrabber.qml 或 test 在我的 main.qml 中。