[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, thengrabToImage()
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 viaQPainter
in aQQuickPaintedItem
, expose this asImageViewerQML
and render everything in anItem
. This is exposed to themain.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 aQPainter
and theQQuickPaintedItem
is only used to display the image for the user.QML Items like a
Label
are drawn viaQPainter
onto theQImage
surface viaQImage 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 aQ_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 theQImage
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 theRenderer
to bothExporter
andQML Viewer
, see this thread.