Qt Scene graph画线

前言

想要实现在QML中画线,有几种方式:
第一种,用在QML中用Canvas来实现画线功能,经过实践,效率比较低,折线非常严重,特别是在Android手机上运行。
第二种,通过QPainter来绘制,在C++中继承QQuickItem,然后实现paintEvent事件去绘图,然后在QML中显示绘图层,该方式在桌面端应用效果勉强能接受,但是在Android端效果也很差。
第三种方式就是通过Scene graph来画线,这是基于OpenGL来渲染图形,同样是在C++中继承QQuickItem,然后实现updatePaintNode函数,再到QML中显示绘图层。这种方式在桌面和移动端相对来说表现良好。
本文就来介绍第三种绘图方式。
先来看看效果图

正文

这里要实现的效果是,可以进行画线和擦除,这两种模式下的实现方式不太一样,如果用QPainter绘制的话,只需要将线的宽度变大,然后改变绘制颜色和绘制方式,即可实现擦除效果,但是这里不行,因为用scane graph画线的时候线条宽度不能加太粗,所以无法通过QPainter的模式来实现擦除效果。

来看源码

头文件

#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 & operator=(const ElementGroup &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);//设置画线模式,0为画线,1为擦除
    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 &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 lastlenth;
    QSGNode *nodeP;
    bool m_bFlag;
    int m_nPenWidth; //笔宽
    QColor m_penColor; //笔颜色
    int m_currentMode;  //0->画线   1->擦除
};

源文件

#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);
}

(部分代码摘自网络)
代码中还用到了QSGSmoothColorMaterialShader这个类,这是实现类用于实现阴影效果的,主要针对擦除的模式。
来看看代码
头文件

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 &state, QSGMaterial *newEffect, QSGMaterial *oldEffect);
    virtual char const *const *attributeNames() const;
private:
    void initialize();
    int m_matrixLoc;
    int m_opacityLoc;
    int m_pixelSizeLoc;
};

源文件

#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 &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函数实现

#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 app.exec();
}

main.qml实现

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

Item {
    visible: true
    width: 800
    height: 600

    OpenGLDrawLine{
        id:drawLine
        anchors.fill: parent
    }

    Column{
        anchors.top: parent.top
        anchors.horizontalCenter: parent.horizontalCenter
        spacing: 15
        Button{
            width: 100
            height: 50
            text:"画线"
            onClicked: {
                drawLine.setDrawMode(0)
            }
        }
        Button{
            width: 100
            height: 50
            text:"擦除"
            onClicked: {
                drawLine.setDrawMode(1)
            }
        }
    }
}

完整工程在这里,点击下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luoyayun361

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值