Qt Scene graph drawing line

Foreword

There are several ways to draw lines in QML:

The first one is to use Canvas in QML to realize the line drawing function. After practice, the efficiency is relatively low, and the broken line is very serious, especially when running on an Android phone.

The second way is to draw through QPainter, inherit QQuickItem in C++, and then implement the paintEvent event to draw, and then display the drawing layer in QML. This method is barely acceptable on the desktop, but it is also very effective on the Android side. Difference.

The third way is to draw lines through Scene graph, which is based on OpenGL to render graphics. It also inherits QQuickItem in C++, then implements the updatePaintNode function, and then displays the drawing layer in QML. This approach works relatively well on both desktop and mobile.

This article introduces the third drawing method.

Let’s take a look at the renderings first

text

The effect to be achieved here is that you can draw lines and erase. The implementation methods in these two modes are different. If you draw with QPainter, you only need to increase the width of the line, and then change the drawing color and drawing method. The erasing effect can be achieved, but not here, because the line width cannot be increased too thick when drawing lines with scan graph, so the erasing effect cannot be achieved through the QPainter mode.

Look at the source code

head File

#include <QPainter>
#include <QVector>
#include <QPointF>
#include <QLineF>
#include <QPen>
#include <QQuickItemGrabResult>
#include <QSharedPointer>
#include <QSGSimpleRectNode>

class ElementGroup
{
public:
    ElementGroup()
    {
    }

    ElementGroup(const QPen & pen)
        : m_pen(pen)
    {
    }

    ElementGroup(const ElementGroup & e)
    {
        m_lines = e.m_lines;
        m_pen = e.m_pen;
    }

    ElementGroup & amp; operator=(const ElementGroup & amp;e)
    {
        if(this != &e)
        {
            m_lines = e.m_lines;
            m_pen = e.m_pen;
        }
        return *this;
    }

    ~ElementGroup()
    {
    }

    QVector<QLineF> m_lines;
    QPen m_pen;
};

class ALOpenGLDrawLine : public QQuickItem
{
    Q_OBJECT
public:
    explicit ALOpenGLDrawLine(QQuickItem *parent = 0);
    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *);
    Q_INVOKABLE void setDrawMode(int mode);//Set line drawing mode, 0 is line drawing, 1 is erasing
    Q_INVOKABLE int getDrawMode(){return m_currentMode;}
    Q_PROPERTY(QColor penColor READ getPenColor WRITE setPenColor)

    QColor getPenColor() const {return m_penColor;}
    void setPenColor(const QColor & amp;c){m_penColor = c;update();}

    Q_INVOKABLE void clearAll();

protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);

signals:
    void sigMousePress(QPointF curPoint);
    void sigMouseMove(QPointF curPoint);
    void sigMouseRelease(QPointF curPoint);

private:
    QPointF m_lastPoint;
    QVector<ElementGroup*> m_elements;
    ElementGroup * m_element;

    bool m_bPressed;
    QPen m_pen;
    int last length;
    QSGNode *nodeP;
    bool m_bFlag;
    int m_nPenWidth; //pen width
    QColor m_penColor; //pen color
    int m_currentMode; //0->draw line 1->erase

Source File

#include "alopengldrawline.h"
#include <QtQuick/qsgnode.h>
#include <QtQuick/qsgflatcolormaterial.h>
#include <QFileInfo>
#include <QDir>
#include "smoothcolormaterial.h"


namespace
{
struct Color4ub
{
    unsigned char r, g, b, a;
};

inline Color4ub colorToColor4ub(const QColor &c)
{
    Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
                       uchar(c. greenF() * c. alphaF() * 255),
                       uchar(c. blueF() * c. alphaF() * 255),
                       uchar(c.alphaF() * 255)
                     };
    return color;
}

struct SmoothVertex
{
    float x, y;
    Color4ub color;
    float dx, dy;
    void set(float nx, float ny, Color4ub ncolor, float ndx, float ndy)
    {
        x = nx; y = ny; color = ncolor;
        dx = ndx; dy = ndy;
    }
};

const QSGGeometry::AttributeSet & smoothAttributeSet()
{
    static QSGGeometry::Attribute data[] = {
        QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true),
        QSGGeometry::Attribute::create(1, 4, GL_UNSIGNED_BYTE, false),
        QSGGeometry::Attribute::create(2, 2, GL_FLOAT, false)
    };
    static QSGGeometry::AttributeSet attrs = { 3, sizeof(SmoothVertex), data };
    return attrs;
}
}



ALOpenGLDrawLine::ALOpenGLDrawLine(QQuickItem *parent) :
    QQuickItem(parent),
    nodeP(NULL),
    m_bFlag(false),
    m_nPenWidth(5),
    m_penColor(QColor(Qt::red))
{
    setAcceptedMouseButtons(Qt::LeftButton);
    setFlag(ItemHasContents, true);
    lastlenth=0;
    setSmooth(true);
    setAntialiasing(true);
    m_currentMode = 0;
}

QSGNode *ALOpenGLDrawLine::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    if(!oldNode){
        nodeP = new QSGNode;
    }else{
        nodeP = static_cast<QSGNode *>(oldNode);
    }


    QSGGeometryNode *node = 0;
    QSGGeometry *geometry = 0;

    if(m_currentMode == 0)
    {
        node = new QSGGeometryNode;
        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
        geometry->setLineWidth(m_nPenWidth);
        geometry->setDrawingMode(GL_LINE_STRIP);
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);

        QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
        material->setColor(m_penColor);
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);

        QSGGeometry::Point2D *vertices;
        vertices = geometry->vertexDataAsPoint2D();

        int size = m_elements. size();
        ElementGroup *element;
        for(int k = 0; k < size ; + + k)
        {
            m_bFlag = true;
            element = m_elements.at(k);
            int currentIndex = 0;
            if(k == size -1){
                QVector<QLineF>::iterator i;
                for (i = element->m_lines.begin(); i != element->m_lines.end(); + + i) {
                    currentIndex++;
                    if(element->m_lines. size()>1){
                        if(currentIndex == lastlenth){
                            vertices[0].set(i->p1().rx(),i->p1().ry());
                        }
                        if(currentIndex == element->m_lines. size()){
                            vertices[1].set(i->p1().rx(),i->p1().ry());
                        }
                    }else{
                        vertices[0].set(i->p1().rx(),i->p1().ry());
                        vertices[1].set(i->p2().rx(),i->p2().ry());
                    }
                }
                lastlenth = element->m_lines. size();
            }
        }
        node->markDirty(QSGNode::DirtyGeometry);
        if(m_bFlag){
            nodeP->appendChildNode(node);
            m_bFlag = false;
        }
    }
    else
    {
        QSGSmoothColorMaterial *material;

        node = new QSGGeometryNode;
        geometry = new QSGGeometry(smoothAttributeSet(), 0);
        geometry->setDrawingMode(GL_TRIANGLE_STRIP);
        material = new QSGSmoothColorMaterial();
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);

        int vertexStride = geometry->sizeOfVertex();
        int vertexCount = 8;

        geometry->allocate(vertexCount, 0);
        SmoothVertex *smoothVertices = reinterpret_cast<SmoothVertex *>(geometry->vertexData());
        memset(smoothVertices, 0, vertexCount * vertexStride);

        float lineWidth = 100;

        float tlX,tlY,blX,blY,trX,trY,brX,brY;

        float delta = lineWidth * 0.8f;

        Color4ub fillColor = colorToColor4ub(QColor(255,255,255,255));
        Color4ub transparent = { 0, 0, 0, 0 };


        int size = m_elements. size();
        ElementGroup *element;
        for(int k = 0; k < size ; + + k)
        {
            element = m_elements.at(k);
            int currentIndex = 0;
            if(k == size -1){
                QVector<QLineF>::iterator i;
                for (i = element->m_lines.begin(); i != element->m_lines.end(); + + i) {
                    currentIndex++;
                    if(element->m_lines. size()>1){
                        if(currentIndex == lastlenth){
                            tlX = i->p1().rx();
                            tlY = i->p1().ry();
                        }
                        if(currentIndex == element->m_lines. size()){

                            trX = i->p1().rx();
                            trY = i->p1().ry();
                        }
                    }else{
                        tlX = i->p1().rx();
                        tlY = i->p1().ry();
                        trX = i->p2().rx();
                        trY = i->p2().ry();
                    }
                }
                lastlenth = element->m_lines. size();
            }
        }

        blX = tlX;
        blY = tlY + lineWidth;
        brX = trX;
        brY = trY + lineWidth;


        smoothVertices[0].set(trX, trY, transparent, delta, -delta);
        smoothVertices[1].set(tlX, tlY, transparent, -delta, -delta);

        smoothVertices[2].set(trX, trY, fillColor, -delta, delta);
        smoothVertices[3].set(tlX, tlY, fillColor, delta, delta);
        smoothVertices[4].set(brX, brY, fillColor, -delta, -delta);
        smoothVertices[5].set(blX, blY, fillColor, delta, -delta);

        smoothVertices[6].set(brX, brY, transparent, delta, delta);
        smoothVertices[7].set(blX, blY, transparent, -delta, delta);

        node->markDirty(QSGNode::DirtyGeometry);
        nodeP->appendChildNode(node);
    }


    return nodeP;
}

void ALOpenGLDrawLine::setDrawMode(int mode)
{
    m_currentMode = mode;
    if(mode == 0){
        m_penColor = QColor(Qt::red);
        m_nPenWidth = 5;
    }
    else if(mode == 1){
        m_penColor = QColor(Qt::white);
        m_nPenWidth = 100;
    }
    update();
}

void ALOpenGLDrawLine::clearAll()
{
    if(nodeP->childCount() > 0){
        nodeP->removeAllChildNodes();
        m_elements. clear();
        update();
    }
}

void ALOpenGLDrawLine::mousePressEvent(QMouseEvent *event)
{
    m_bPressed = true;

    m_element = new ElementGroup(m_pen);
    m_elements.append(m_element);
    m_lastPoint = event->localPos();
    event->setAccepted(true);
    emit sigMousePress(event->localPos());
}

void ALOpenGLDrawLine::mouseMoveEvent(QMouseEvent *event)
{
    if(m_bPressed){
        m_element->m_lines.append(QLineF(m_lastPoint, event->localPos()));
        m_lastPoint = event->localPos();
        update();
        emit sigMouseMove(event->localPos());
    }
    event->setAccepted(true);
}

void ALOpenGLDrawLine::mouseReleaseEvent(QMouseEvent *event)
{
    m_bPressed = false;
    m_elements. removeFirst();
    emit sigMouseRelease(event->localPos());
    event->setAccepted(true);
}

(Part of the code is taken from the Internet)

The code also uses the QSGSmoothColorMaterialShader class, which is used to implement the shadow effect, mainly for the erasing mode.

Let’s see the code

head File

class QSGSmoothColorMaterial : public QSGMaterial
{
public:
    QSGSmoothColorMaterial();
    int compare(const QSGMaterial *other) const;
protected:
    virtual QSGMaterialType *type() const;
    virtual QSGMaterialShader *createShader() const;
};

//------------------------------------------------ ----------------------

class QSGSmoothColorMaterialShader : public QSGMaterialShader
{
public:
    QSGSmoothColorMaterialShader();
    virtual void updateState(const RenderState & amp;state, QSGMaterial *newEffect, QSGMaterial *oldEffect);
    virtual char const *const *attributeNames() const;
private:
    void initialize();
    int m_matrixLoc;
    int m_opacityLoc;
    int

Source File

#include "smoothcolormaterial.h"

QSGSmoothColorMaterial::QSGSmoothColorMaterial()
{
    setFlag(RequiresFullMatrixExceptTranslate, true);
    setFlag(Blending, true);
}

int QSGSmoothColorMaterial::compare(const QSGMaterial *other) const
{
    Q_UNUSED(other)
    return 0;
}

QSGMaterialType *QSGSmoothColorMaterial::type() const
{
    static QSGMaterialType type;
    return &type;
}

QSGMaterialShader *QSGSmoothColorMaterial::createShader() const
{
    return new QSGSmoothColorMaterialShader();
}

//------------------------------------------------ ----------------------

QSGSmoothColorMaterialShader::QSGSmoothColorMaterialShader()
    : QSGMaterialShader()
{
    setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/shaders/smoothcolor.vert"));
    setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/shaders/smoothcolor.frag"));
}

void QSGSmoothColorMaterialShader::updateState(const QSGMaterialShader::RenderState & amp;state, QSGMaterial *newEffect, QSGMaterial *oldEffect)
{
    Q_UNUSED(newEffect)

    if (state. isOpacityDirty())
        program()->setUniformValue(m_opacityLoc, state.opacity());

    if (state. isMatrixDirty())
        program()->setUniformValue(m_matrixLoc, state.combinedMatrix());

    if (oldEffect == 0) {
        // The viewport is constant, so set the pixel size uniform only once.
        QRect r = state. viewportRect();
        program()->setUniformValue(m_pixelSizeLoc, 2.0f / r.width(), 2.0f / r.height());
    }
}

const char * const *QSGSmoothColorMaterialShader::attributeNames() const
{
    static char const *const attributes[] = {
        "vertex",
        "vertexColor",
        "vertexOffset",
        0
    };
    return attributes;
}

void QSGSmoothColorMaterialShader::initialize()
{
    m_matrixLoc = program()->uniformLocation("matrix");
    m_opacityLoc = program()->uniformLocation("opacity");
    m_pixelSizeLoc = program()->uniformLocation("pixelSize");
}

Main function implementation

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QSurfaceFormat>
#include "alopengldrawline.h"

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

    qmlRegisterType<ALOpenGLDrawLine>("OpenGLDrawLine", 1, 0, "OpenGLDrawLine");

    QQuickView view;
    QSurfaceFormat format = view. format();
    format.setSamples(16);
    view. setFormat(format);
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.setSource(QUrl("qrc:/main.qml"));
    view. show();


    return

main.qml implementation

import QtQuick 2.6
import QtQuick.Window 2.2
import OpenGLDrawLine 1.0
import QtQuick. Controls 2.1

Item {
    visible: 800
    height: 600}

    Column {
        anchors.top:15
        Button {
            width: 100
            height: 50
            text:"Draw a line"
            onClicked: {
                drawLine.setDrawMode(0)}
        }
        Button {
            width: 100
            height: 50
            text:"Erase"
            onClicked: {
                drawLine.setDrawMode(1)}
        }
    }
}