Qt:如何在将 2 个 QQuickItems 保存为 png 之前将其合并为一个

Qt: How to merge 2 QQuickItems into one before saving it into a png

来自 Whosebug 上的 this discussion,我能够很好地将 QML 项目中的图像保存到文件中 png/jpeg

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

注意:我可以保存一个QQuickItem。只需要知道如何叠加 2 QQuickItems

只要让这两个 qml 对象成为根 Item 的子对象,然后抓取那个根项,它就会抓取它的所有内容。

只需确保根项足够大以包含子项,并且子项不为负数space,因为它只会捕获根项足迹内部的内容。

您也可以从 C++ 甚至 QML 进行手动组合。

您的评论中描述的问题是您无法移动东西,那么您能做什么?您可以有两个 Image 元素,而不是将原始 QML 对象作为同一根的父对象,然后捕获项目 A 并将捕获结果设置为图像 A 的来源,然后对项目执行相同的操作B、最后,你抓取root item,这会把两张图片一起抓取。

OK,举个简单的例子,看起来有点复杂,因为抓取是异步的,你必须等待单个抓取结果完成才能抓取 "final" 根项目,因此定时器的使用。在此示例中,不同的项目排成一行,但您可以按照自己喜欢的方式组合它们:

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)

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

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

我还对大部分内容进行了注释,以使其更易于理解。

这个问题似乎与

密切相关

解决方案是将有问题的 Item 渲染到不需要渲染到屏幕的第二个项目的纹理中。您可以根据需要通过添加多个 ShaderEffectSource 作为子项来组合此 Item,根据需要将它们相对放置,并将它们的来源设置为您想要抓取的 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: 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.

这个解决方案应该比使用 Images 和 ItemGrabResults 作为源更有效,因为它将东西保存在 GPU 内存中,直到它被抓取和存储。