Home > Mobile >  drawing with Canvas in realtime (Qt/QML)
drawing with Canvas in realtime (Qt/QML)

Time:06-15

I hope i will be as clear as possible for my problem. I'm currently devlopping an Application using a QML file as GUI.

The goal is simple : i become a large amount of data via QTcpSocket (raw data from a linear camera) and I would like to display the image. It's important to know that each time I receive a frame, it's a 1*2048 array of colors. I'd like to display it as fast as possible after receiving a data.

I've tried to set up a property string containing the color and 2 others properties containing the position (x;y) and then :

Canvas {
                id: camera
                objectName: "cameradrawer"
                x: 1270
                y: 30
                width: 650
                height: 500
                renderStrategy: Canvas.Threaded
                property string iColor
                property int imageX
                property int imageY
                property var line
                property var ctx

                onPaint: {
                    ctx = getContext('2d');
                    ctx.clearRect(0, 0, width, height);
                    // draw here
                    ctx.fillStyle = iColor;
                    ctx.fillRect(imageY,imageX, 1, 1);

                }
            }

When i change the properties and then use ìnvokeMethod("requestPaint"), the application keeps crashing. Am i doing something wrong or is the QML not used for high speed processes?

Thank you in advance for your help!

PS : i can post more code (C part) if necessary.

CodePudding user response:

I had a bit of free time so I researched the issue as I found it quite interesting. So the faster way to implement that I guess is creating an image from the data and so create a texture from it that can be used in a QQuickItem-based item.

The custom item:

CustomItem.h

class CustomItem : public QQuickItem
{
    Q_OBJECT
    QML_ELEMENT
public:
    CustomItem(QQuickItem *parent = nullptr);
    ~CustomItem();
    QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
    void componentComplete() override;

protected:
    void createImage();
    void timerHandler();

private:
    uint32_t m_buffer[BUFFER_SIZE] = {};
    QSGTexture *m_texture = nullptr;
    QSGTexture *m_newTexture = nullptr;
    QTimer m_timer;
};

CustomItem.cpp

CustomItem::CustomItem(QQuickItem *parent):
    QQuickItem(parent)
{
    setFlag( QQuickItem::ItemHasContents, true);
    QObject::connect(&m_timer, &QTimer::timeout, this, &CustomItem::timerHandler);
}

CustomItem::~CustomItem()
{
    if(m_texture != nullptr)
    {
        delete m_texture;
        m_texture = nullptr;
    }
    if(m_newTexture != nullptr)
    {
        delete m_newTexture;
        m_newTexture = nullptr;
    }
}

QSGNode *CustomItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
    QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node);
    if (n == nullptr)
    {
        if(m_newTexture != nullptr)
        {
            n = new QSGSimpleTextureNode();
            n->setRect(boundingRect());
        }
    }

    if(n != nullptr)
    {
        if(m_newTexture != nullptr)
        {
            if(m_texture != nullptr)
            {
                delete m_texture;
                m_texture = nullptr;
            }
            m_texture = m_newTexture;
            m_newTexture = nullptr;
            n->setTexture(m_texture);
        }
    }

    return n;
}

void CustomItem::componentComplete()
{
    createImage();
    m_timer.start(1000);
    QQuickItem::componentComplete();
}

void CustomItem::createImage()
{
    if(m_newTexture == nullptr)
    {
        QRandomGenerator::global()->fillRange(m_buffer, BUFFER_SIZE);
        QImage img(reinterpret_cast<uchar *>(m_buffer), IMAGE_WIDTH, IMAGE_HEIGHT, QImage::Format_ARGB32);

        auto wnd = window();
        if(wnd != nullptr)
        {
            m_newTexture = wnd->createTextureFromImage(img);
        }
    }
}

void CustomItem::timerHandler()
{
    createImage();
    update();
}

Some short clarification:

the class contains a big array of integers. I simulate the periodical data updating with timer so the array is updated once per second with random data. as soon as data changed I create a texture. I use 2 pointers to avoid deleting the old texture while rendering and so I delete the old one inside updatePaintNode when it's safety. I use QSGSimpleTextureNode since that allows use textures but I guess you can use any other classes you want. The texture has alpha channel, you can use Format_RGB32 instead to avoid that. If the item bigger the the texture it will be scaled, you can play with that too.

The main.qml for testing can be like this:

import QtQuick
import QtQuick.Window
import CustomItems 1.0

Window {
    id: window
    visible: true
    height: 400
    width: 400

    Rectangle {
        width: 300
        height: 300
        color: "orange"
        anchors.centerIn: parent
        CustomItem {
            width: 200
            height: 200
            anchors.centerIn: parent
        }
    }
}

To register the custom item in case of CMake the following lines should be added into CMakeFiles.txt:

set(CMAKE_AUTOMOC ON)
qt_add_qml_module(qml_test
    URI CustomItems
    VERSION 1.0
    QML_FILES main.qml
    SOURCES CustomItem.h CustomItem.cpp

The sources could be found here

  • Related