前言
想要实现在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)
}
}
}
}
完整工程在这里,点击下载