Qt: Как объединить 2 QQuickItems в один перед сохранением в png

Из этого обсуждения Stackru я вполне могу сохранить изображение из элемента QML в файл как png/jpeg,

Как я могу наложить или объединить два разных qml слои и объединить их в один, чтобы сохранить его в PNG / JPEG?

Примечание: я могу сохранить один QQuickItem, Просто нужно знать, как наложить 2 QQuickItems

2 ответа

Решение

Просто пусть два объекта qml будут дочерними для корня Item а затем захватить этот корневой элемент, он будет захватывать все его содержимое.

Просто убедитесь, что корневой элемент достаточно велик, чтобы охватить дочерние элементы, и что дочерние элементы не находятся в отрицательном пространстве, потому что он будет захватывать только то, что находится внутри отпечатка корневого элемента.

Вы также можете сделать ручную композицию из C++ или даже QML.

Проблема, описанная в вашем комментарии, заключается в том, что вы не можете перемещать вещи, так что вы можете сделать? Вместо того чтобы иметь исходные объекты QML в качестве родителей одного и того же корня, вы можете иметь два Image элементы, затем вы захватываете элемент 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() })
  }
}

Сначала эти два прямоугольника захватываются и используются в качестве источников для изображений в программе объединения, затем объединяются и отображаются в результирующем изображении, все объекты, кроме конечного результирующего изображения, скрыты.

Еще проще, вы можете использовать этот изящный маленький помощник, чтобы быстро объединить любое количество элементов в одном изображении:

  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речь идет о текстуре второго элемента, которую вам не нужно отображать на экране. Вы можете написать это Item как вы хотите, добавив несколько ShaderEffectSourceкак дети, расположите их относительно друг друга так, как вам нравится, и установите их источники на Items вы хотите получить изображение.

Затем вы берете 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
    }
}

Но если вам нужно более сложное объединение, вы также можете создать этот объект вручную и захватить его, когда захотите.

Без учета его, согласно примечанию из документации

Примечание. Эта функция визуализирует элемент за пределами экрана и копирует эту поверхность из памяти графического процессора в память центрального процессора, что может быть довольно дорогостоящим. Для "живого" предварительного просмотра используйте слои или ShaderEffectSource.

это решение должно быть более эффективным, чем использование Imageс ItemGrabResults как источники для этого хранит материал в памяти GPU, пока он не будет захвачен и сохранен.

Другие вопросы по тегам