【问题标题】:Qt: How to merge 2 QQuickItems into one before saving it into a pngQt:如何将 2 个 QQuickItems 合并为一个,然后将其保存为 png
【发布时间】:2017-07-10 07:32:22
【问题描述】:

通过 StackOverflow 上的 this discussion,我可以将 QML 项目中的图像保存到文件中,格式为 png/jpeg

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

注意:我可以保存一个QQuickItem。只需要知道怎么覆盖2个QQuickItems

【问题讨论】:

    标签: qt qml qimage qquickitem


    【解决方案1】:

    只需让两个 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)
    

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

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

    我还对大部分内容进行了注释,以便于理解。

    【讨论】:

    • @HAL9000-Kernel 最终结果会发生什么由您传递的回调定义,将result.source = res.url 替换为您想要对抓取结果执行的任何操作。
    • 但是是的,如果你想限制它只做一件事,你可以修改它以跳过传递回调,而是在计时器处理程序中编写回调。我编写它的方式为您提供了拥有任意抓取结果处理程序的灵活性。如果您删除回调参数,请确保修改代码以从零开始执行参数循环,而不是跳过一。
    • 然后您将删除joiner.grabToImage(joinHelper.cb) 并将joiner 传递给save()。这意味着您可以删除 callback 和拥有它的 cb 属性。您仍然需要使用计时器来让整个事情有时间完成。
    • 只需将行替换为项目
    • 您可以在Item 中使用width: childrenRect.width; height: childrenRect.height 使其自动放大以适应所有孩子。
    【解决方案2】:

    这个问题似乎与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 你需要通过,在我的例子中 rootImageGrabber.qmltest 在我的 main.qml 中。
    猜你喜欢
    • 2015-07-20
    • 1970-01-01
    • 2018-04-26
    • 1970-01-01
    • 2023-02-16
    • 2011-11-08
    • 2019-09-07
    • 2011-11-02
    • 2017-12-11
    相关资源
    最近更新 更多