Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

[SOLVED] Render hidden QQuickPaintedItem



  • Is it possible to create a canvas (e.g pixmap?) in form of a QQuickPaintedItem and render normal QML Items like Label onto it and extract everything as QImage while being hidden from the user?

    Performace is not a priority.

    My use case:

    I mux multiple videosources (via OpenCV) together and want to use QML Widgets like Label as overlay. Then I want to extract the "view" as some sort of image and encode it again as video. The problem is when the view exceeds the size of my window, then grabToImage() only clips the visible part.

    Preferably I would like to create the pixmap with the composition (muxed videos and Label) at the "to be exported size" hidden from the user and then create a resized view if I would want to render on the screen, otherwise it would be exported at the usual size.

    My current solution:

    I read the videos into QImages, mux them together via QPainter in a QQuickPaintedItem , expose this as ImageViewerQML and render everything in an Item. This is exposed to the main.qml through the filename.

    main.qml

    ApplicationWindow
    {
        id: rootWindow
        width: Screen.width
        height: Screen.height
        visible: true
    
        Content {
            visible: true
        }
    

    content.qml

    Item {
        id: renderer
        anchors.centerIn: parent
        anchors.fill: parent
    
        ImageViewerQML {
            id: imageviewer
            anchors.centerIn: parent
        }
    
        GridView {
            id: fpsTextExportView
            cellHeight: fpsTextExportView.height / 5
            cellWidth:  fpsTextExportView.width
            anchors { fill: imageviewer; }
            model: DelegateModel {
                model: fpsOptionsModel
                delegate: fpsTextExportDelegate
            }
        }
    
        Component {
            id: fpsTextExportDelegate
            Item {
                Label {
                    x: 20
                    y: 15
                    text: model.displayedText
                    font: displayedTextFont
                    color: model.color
                    visible: { model.displayedTextEnabled
                            && model.fpsOptionsEnabled }
                }
            }
        }
    


  • I found a the following solution.

    I simply use the QImage as my rendering space and export that image. Drawing is still done with a QPainter and the QQuickPaintedItem is only used to display the image for the user.

    QML Items like a Label are drawn via QPainter onto the QImage surface via

    
    QImage image;
    QPainter painter(&image);
    
    painter.setPen("#FFFFFF");
    painter.setFont(QFont("Helvetica", 15));
    int x = 20;
    int y = 20;
    painter.drawText(x,y, "Hello World");
    painter.done();
    

    For more generic use I provide a paint(QPainter & painter, int x, int y, QSize resolution) interface in C++ for each "rendered object" as alternative compared to a Q_OBJECT where the rendering is provided by Qt.

    This looks like this:

    class Renderer : public QObject
    {
    ...
        Q_SLOT void processImage(const QImage image)
        {
            _image = image.copy();
            QPainter painter;
           painter.begin(&_image);
    
           _draw_fps_text(painter);
           ...
           painter.end();
       }
       
       Q_SLOT void setFPSOptions(const QList<FPSOptions> fpsOptionsList)
       {
          _fps_options_list  = fpsOptionsList;
       }
      
       void _draw_fps_text(QPainter & painter)
       {
           int video_count  = _get_video_count();
           int image_width  = _image.size().width();
           int image_height = _image.size().height();
           for(int i = 0; i < _fps_options_list.size(); ++i)
           {
               int x_padding = static_cast<int>(image_width / 28);
               int x_step = static_cast<int>(image_width / video_count);
               int y_step = static_cast<int>(image_height / 15);
               int x = x_padding + x_step * i; // width
               int y = y_step; // height
               _fps_options_list[i].paint(painter, x, y, image.size());    
           }
       }
    

    My rendering pipeline changed from

    VideoCapture -> (frame) -> Processing -> (QImage, data) -> QML Viewer -> (Grab QImage from View) -> Exporter -> (IO)
    

    to

    VideoCapture -> (frame) -> Processing -> (QImage, data) -> Renderer -> (QImage) -> Exporter -> (QImage) + (IO) -> QML Viewer
    

    The Exporter currently pipes the QImage to the QML Viewer as it's currently impossible to let a QML Object run in another thread. The optimal solution would be to duplicate the output of the Renderer to both Exporter and QML Viewer, see this thread.



  • I found a the following solution.

    I simply use the QImage as my rendering space and export that image. Drawing is still done with a QPainter and the QQuickPaintedItem is only used to display the image for the user.

    QML Items like a Label are drawn via QPainter onto the QImage surface via

    
    QImage image;
    QPainter painter(&image);
    
    painter.setPen("#FFFFFF");
    painter.setFont(QFont("Helvetica", 15));
    int x = 20;
    int y = 20;
    painter.drawText(x,y, "Hello World");
    painter.done();
    

    For more generic use I provide a paint(QPainter & painter, int x, int y, QSize resolution) interface in C++ for each "rendered object" as alternative compared to a Q_OBJECT where the rendering is provided by Qt.

    This looks like this:

    class Renderer : public QObject
    {
    ...
        Q_SLOT void processImage(const QImage image)
        {
            _image = image.copy();
            QPainter painter;
           painter.begin(&_image);
    
           _draw_fps_text(painter);
           ...
           painter.end();
       }
       
       Q_SLOT void setFPSOptions(const QList<FPSOptions> fpsOptionsList)
       {
          _fps_options_list  = fpsOptionsList;
       }
      
       void _draw_fps_text(QPainter & painter)
       {
           int video_count  = _get_video_count();
           int image_width  = _image.size().width();
           int image_height = _image.size().height();
           for(int i = 0; i < _fps_options_list.size(); ++i)
           {
               int x_padding = static_cast<int>(image_width / 28);
               int x_step = static_cast<int>(image_width / video_count);
               int y_step = static_cast<int>(image_height / 15);
               int x = x_padding + x_step * i; // width
               int y = y_step; // height
               _fps_options_list[i].paint(painter, x, y, image.size());    
           }
       }
    

    My rendering pipeline changed from

    VideoCapture -> (frame) -> Processing -> (QImage, data) -> QML Viewer -> (Grab QImage from View) -> Exporter -> (IO)
    

    to

    VideoCapture -> (frame) -> Processing -> (QImage, data) -> Renderer -> (QImage) -> Exporter -> (QImage) + (IO) -> QML Viewer
    

    The Exporter currently pipes the QImage to the QML Viewer as it's currently impossible to let a QML Object run in another thread. The optimal solution would be to duplicate the output of the Renderer to both Exporter and QML Viewer, see this thread.


Log in to reply