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

Posted

技术标签:

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

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

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

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

【问题讨论】:

【参考方案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 中。

以上是关于Qt:如何将 2 个 QQuickItems 合并为一个,然后将其保存为 png的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Qt 将两个单列 csv 文件合并为一个多列 csv 文件? [关闭]

如何将2个图像合并/合并为1个

如何将 2 个 vuex 商店合并为一个商店

如何将 2 个查询合并为 1 个?

如何将rtf文件合并

Postgres如何将2个单独的选择查询合并为1个