11

I need to grab each QML (QtQuick 2) drawing frame and sent it over the network. At the moment I have used method listed below, but this method has two big disadvantage

1) Due to Qt5 documentation grabWindow() function has performance issues

2) It can't work with hidden QML window

Is it possible to get OpenGL render buffer right after QQuickWindow::afterRendering ? Using FBOs ? Shared opengl context ?

class Grab: public QObject
{
 public:
 Grab( QQuickWindow * wnd ) : wnd_(wnd) {}

 public slots:

    void Grabme()
    {
       QImage image = wnd_->grabWindow();
    }

private:

QQuickWindow *wnd_;
};

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);


QtQuick2ApplicationViewer viewer;
viewer.setMainQmlFile(QStringLiteral("qml/grab1/main.qml"));
viewer.showExpanded();

Grab grab( &viewer );
QObject::connect( &viewer, &QtQuick2ApplicationViewer::frameSwapped,
                  &grab, &Grab::Grabme, Qt::DirectConnection );


return app.exec();
}
Dmitry
  • 906
  • 1
  • 13
  • 32

3 Answers3

8

Example bellow can grab any qml content to FBO and then sent it as Image via signal. Only one problem of this approach is visibility, grab window must be visible for successful grabbing. If anybody knows how to prevent this you can help me and provide more advanced approach.

// main.cpp
int main(int argc, char* argv[])
{
  QApplication app(argc, argv);

  GrabWindow grab;
  grab.setResizeMode( QQuickView::SizeViewToRootObject );
  grab.setSource( QUrl::fromLocalFile("qml/main.qml") );
  grab.setFlags( Qt::Popup );

  grab.show();
  return app.exec();
}


// grabwindow.hpp
#pragma once
#include <QOpenGLFramebufferObject>
#include <QScopedPointer>
#include <QQuickView>
#include <QImage>

class GrabWindow: public QQuickView
{
  Q_OBJECT

signals:
     void changeImage( const QImage &image );

public:
    GrabWindow( QWindow * parent = 0 );

private slots:
    void afterRendering();
    void beforeRendering();

private:
    QScopedPointer<QOpenGLFramebufferObject> fbo_;
};

// grabwindow.cpp
#include "grabwindow.hpp"
#include <limits>

GrabWindow::GrabWindow( QWindow * parent ) :
    QQuickView( parent )
{
  setClearBeforeRendering( false );
  setPosition( std::numeric_limits<unsigned short>::max(), std::numeric_limits<unsigned short>::max() );

  connect( this, SIGNAL( afterRendering()  ), SLOT( afterRendering()  ), Qt::DirectConnection );
  connect( this, SIGNAL( beforeRendering() ), SLOT( beforeRendering() ), Qt::DirectConnection );
}

void GrabWindow::afterRendering()
{
  if( !fbo_.isNull() )
  {
    emit changeImage( fbo_->toImage() );
  }
}

void GrabWindow::beforeRendering()
{
  if (!fbo_)
  {
        fbo_.reset(new QOpenGLFramebufferObject( size(), QOpenGLFramebufferObject::NoAttachment) );
        setRenderTarget(fbo_.data());
  }
}
Dmitry
  • 906
  • 1
  • 13
  • 32
  • this code gives me error: error C2027: use of undefined type 'QOpenGLFramebufferObject' 1> C:\Qt\Qt5.1.0\5.1.0\msvc2010\include\QtQuick/qquickwindow.h(57) : see declaration of 'QOpenGLFramebufferObject' 1>grab_window.cpp(16): error C2232: '->QScopedPointer::isNull' : left operand has 'class' type, use '. – otto Jul 23 '13 at 13:26
  • You should check if your Qt version compiled with OpenGL support, if you download it from official site, that it should be. Also, regarding msvc2010 you need to create correct one project file with included library. I'm using both cmake and .pro project files and everything works on Qt 5.0.2 – Dmitry Jul 24 '13 at 18:43
  • yes that's the reason, i'm developing on Windows 7 by Visual Studio 2010, i used QT_NO_OPENGL macro, otherwise it can't find GLES2 headers. – otto Jul 25 '13 at 06:29
  • I have tried above example, unfortunately "beforeRendering" signal not at all calling. Can some one give hind on this? – Ashif Mar 29 '17 at 11:14
  • @Ashif Please provide your Qt version – Dmitry Apr 06 '17 at 09:24
  • does not work, ./app --platforms xcb give me error require x11 and same with minimalegl could not initialize egl display server – user3453753 Dec 30 '17 at 21:42
  • How can I use that class? – Denis Sologub May 03 '18 at 16:49
  • Possible you can improve above example In Qt 5.11 and use renderTarget() function of GrabWindow for getting FBO. – Dmitry Jun 01 '18 at 12:32
4

With later versions of Qt 5.X you can also use the software render backend. The following renders any scene in the background without any visible window or OpenGL tricks:

// main.cpp
#include <QGuiApplication>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQuickRenderControl>

int main(int argc, char *argv[])
{
    const char *source = "qrc:/main.qml";
    if (argc > 1) source = argv[1];

    QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software);

    QGuiApplication app{argc, argv};

    QQuickRenderControl renderControl;

    QQuickWindow window{&renderControl};

    QQmlEngine engine;

    QQmlComponent component{
        &engine,
        QUrl{QString::fromUtf8(source)}
    };

    QQuickItem *rootItem = qobject_cast<QQuickItem *>(component.create());
    window.contentItem()->setSize(rootItem->size());
    rootItem->setParentItem(window.contentItem());

    window.resize(rootItem->size().width(), rootItem->size().height());

    QImage image = renderControl.grab();
    image.save("output.png");

    return 0;
}
  • Is it possible to grab each frame? Because in my example you will get afterRendering signal for each QML frame processed. – Dmitry Mar 15 '22 at 10:58
2

I managed to find a trick to make grabWindow() work when the Window is "not visible". The trick is to set the window's visibility: Window.Minimized and the flags: Qt.Tool. The window is not displayed to the user, but to the Qt's internals it appears to be visible and the grabWindow() method call works as expected. Remember to call that method only once the scene has been initialised.

The only problem with this solution (that I have come across) is that if the window's color property is set to transparent, the captured content has black background.

adrian5632
  • 41
  • 2