Qt:如何将2个QQuickItems合并为一个,然后将其保存到png中

时间:2017-07-10 07:32:23

标签: qt qml qimage qquickitem

从StackOverflow上的this discussion开始,我可以将QML项目中的图像保存为png/jpeg文件。

我如何叠加或合并两个不同的qml图层&将它们合并为一个,将其保存为png / jpeg?

注意:我可以保存一个QQuickItem。只需要知道如何覆盖2 QQuickItem s

2 个答案:

答案 0 :(得分:5)

让两个qml对象成为根Item的子对象,然后获取该根项,它将捕获其所有内容。

确保根项目足够大以封闭子项,并且子项不在负空间中,因为它只会捕获根项目内部的内容。

您也可以使用C ++甚至QML进行手动合成。

您评论中描述的问题是您无法移动内容,那么您可以做些什么?您可以拥有两个Image元素,而不是将原始QML对象作为同一根的父项,然后捕获项目A并将捕获结果设置为图像A的源,然后对项目执行相同操作B,最后,你捕获根项目,它将两个图像一起捕获。

好的,这是一个快速的例子,它看起来有点复杂,因为抓取是异步的,你必须等待个人抓取结果才能完成,然后才能抓住" final" root项,因此计时器的用法。在此示例中,不同的项目连续排列,但您可以按照自己喜欢的方式组合它们:

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)

它仍然使用一行,但您可以轻松调整它以进行自己的布局。它通过传递最终回调和你想要捕获的所有项目来工作,在内部它为每个项目创建一个图像,将它们放入容器中,然后触发完成计时器。

请注意,根据系统的速度以及项目的复杂程度和计数,您可能需要延长计时器间隔,因为最终的回调需要在所有捕获完成后才能执行,图像分配了来源并调整了图像大小以使该行具有适当的尺寸。

我还注释了大多数事情,以便更容易理解。

答案 1 :(得分:2)

这个问题似乎与this one

密切相关

解决方案是将有问题的Item呈现为第二项的纹理,您不需要渲染到屏幕上。您可以根据需要添加多个Item,根据需要将它们相对于彼此放置,并将它们的来源设置为您想要抓取的ShaderEffectSource s,然后根据需要撰写此Item到图像。

然后你抓住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
    }
}

但是如果你需要更复杂的合并,你也可以手动创建这个对象并随时抓取它。

根据文档中的说明

,不进行计量
  

Note:此功能会将项目渲染到屏幕外表面,并将该表面从GPU的内存复制到CPU的内存中,这可能非常昂贵。对于&#34;直播&#34;预览,使用图层或ShaderEffectSource。

这个解决方案应该比使用带有Image的{​​{1}}更有效,因为它可以将内容保存在GPU内存中,直到它被抓取并存储。