天天看点

QT5.14.2自带Examples:2D Painting

概要

本示例分别使用native(CPU)方式和OpenGL(GPU)方式绘制同样一个动画。

QPainter类用于将二维图原绘制到QPaintDevice子类(如QWidget和QImage)提供的绘制设备上。由于QGLWidget是QWidget的一个子类,因此可以重新实现其paintEvent()并使用QPainter在设备上绘制,就像使用QWidget一样。唯一的区别是,如果系统的OpenGL驱动程序支持,则绘制操作将在硬件中加速。在本例中,我们对QWidget和QGLWidget执行相同的绘制操作。QWidget启用了抗锯齿,如果系统的OpenGL驱动程序支持所需的扩展,QGLWidget也将使用抗锯齿。我们派生了QWidget和QGLWidget的子类,使用一个单独的Helper类为它们执行相同的绘制操作,并将它们放置在一个顶级widget中,它本身提供了一个窗口类。

OpenGL方式中,展示了QPainter和QGLWidget如何一起使用,在支持的硬件上显示加速的2D图形。

QT5.14.2自带Examples:2D Painting

从上图看不出太大的区别,对于简单的二维图像来说,使用Native方式更加清晰。实际上两种方式,背后的实现方式有着巨大的差异。

下面我们将原实例分成两个独立的程序。我们把需要绘制的圆改为1000个,对两个应用分别进行测试,观察下面两张图,会发现巨大的性能差异:

QT5.14.2自带Examples:2D Painting
QT5.14.2自带Examples:2D Painting

实现步骤

新建工程

QT5.14.2自带Examples:2D Painting
QT5.14.2自带Examples:2D Painting
QT5.14.2自带Examples:2D Painting

Helper

类定义

在本例中,绘制操作由Helper类执行。我们这样做是因为我们希望对QWidget子类和QGLWidget子类执行相同的绘制操作。

#ifndef HELPER_H
#define HELPER_H

#include <QBrush>
#include <QFont>
#include <QPen>
#include <QWidget>

class Helper
{
public:
    Helper();

public:
    //除了构造函数之外,它只提供了一个paint()函数
    //使用QWidget子类提供的painter来进行绘制。
    void paint(QPainter *painter, QPaintEvent *event, int elapsed);

private:
    QBrush background;
    QBrush circleBrush;
    QFont textFont;
    QPen circlePen;
    QPen textPen;
};

#endif
           

类实现

#include "helper.h"

#include <QPainter>
#include <QPaintEvent>
#include <QWidget>

Helper::Helper()
{
	//线性渐变的一种颜色
	//仅仅理解为一种颜色就好
    QLinearGradient gradient(QPointF(50, -20), QPointF(80, 20));
    gradient.setColorAt(0.0, Qt::white);
    gradient.setColorAt(1.0, QColor(0xa6, 0xce, 0x39));

    background = QBrush(QColor(64, 32, 64));
    circleBrush = QBrush(gradient);
    circlePen = QPen(Qt::black);
    circlePen.setWidth(1);
    textPen = QPen(Qt::white);
    textFont.setPixelSize(50);
}
//实际的绘制是在paint函数中执行的。
//painter:已经设置好绘制设备(QWidget或QGLWidget)的QPainter
//event:提供有关要绘制区域的信息的QPaintEvent
//elapsed:自上次更新绘图设备以来,经过的时间(毫秒)
//绘制圆形的螺旋图案,使用指定的经过时间设置动画,使它们围绕坐标系的原向外移动:
void Helper::paint(QPainter *painter, QPaintEvent *event, int elapsed)
{
	//在转换坐标系原点之前,我们先填充“event”事件中包含的区域
    painter->fillRect(event->rect(), background);
    //将原点移到正中心
    painter->translate(100, 100);

	//坐标系被多次旋转,我们预先保存QPainter的状态,然后再还原它。
    painter->save();//保存状态
    painter->setBrush(circleBrush);
    painter->setPen(circlePen);

    //![动画]
    //这两条语句,通过elapsed产生动画效果
    painter->rotate(elapsed * 0.030);
    qreal r = elapsed / 1000.0;
    //![动画]
    int n = 30;
    for (int i = 0; i < n; ++i) {
        //在循环内每次旋转30度,使得○有合适的间隔
        painter->rotate(30);
        qreal factor = (i + r) / n;
        //radiu在循环内,每次增大20%,得到○里原点原来越远
        qreal radius = 0 + 120.0 * factor;
        qreal circleRadius = 1 + factor * 20;
        painter->drawEllipse(QRectF(radius, -circleRadius,
                                    circleRadius * 2, circleRadius * 2));
    }
    painter->restore();//恢复状态

    painter->setPen(textPen);
    painter->setFont(textFont);
    painter->drawText(QRect(-50, -50, 100, 100), Qt::AlignCenter, QStringLiteral("Qt"));
}

           

在构造函数中完成了一些初始值的设置。去除构造函数的内容,观察默认值的效果如下:

QT5.14.2自带Examples:2D Painting

Widget类

类定义

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class Helper;
//提供一个自定义QWidget,显示由Helper类绘制的简单动画
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(Helper *helper, QWidget *parent);

public slots:
	//设置动画
    void animate();

protected:
	//绘制自定义内容
    void paintEvent(QPaintEvent *event) override;

private:
    Helper *helper;
    int elapsed;
};

#endif

           

类实现

#include "widget.h"
#include "helper.h"

#include <QPainter>
#include <QTimer>


Widget::Widget(Helper *helper, QWidget *parent)
    : QWidget(parent), helper(helper)
{
    elapsed = 0;
    setFixedSize(200, 200);
}
//当我们稍后定义的计时器超时时调用
void Widget::animate()
{
	//更新elapse的值,加上自计时器上次超时以来经过的时间间隔
	//每秒循环一次
    elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
    //update会触发paintEvent
    update();
}
//由于Helper类完成了所有实际的绘制,我们只需实现一个绘制事件,
//该事件为widget设置一个QPainter,,并调用Helper的paint()函数:
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    // 设置painter,QPainter::begin(QPaintDevice *device)
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing);
    helper->paint(&painter, event, elapsed);
    painter.end();
}
           

GLWidget类

类定义

GLWidget类定义基本上与Widget类相同,只是它派生自QGLWidget。

#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>

class Helper;

class GLWidget : public QOpenGLWidget
{
    Q_OBJECT

public:
    GLWidget(Helper *helper, QWidget *parent);

public slots:
    void animate();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Helper *helper;
    int elapsed;
};

#endif
           

类实现

#include "glwidget.h"
#include "helper.h"

#include <QPainter>
#include <QTimer>

GLWidget::GLWidget(Helper *helper, QWidget *parent)
    : QOpenGLWidget(parent), helper(helper)
{
    elapsed = 0;
    setFixedSize(200, 200);
    setAutoFillBackground(false);
}

void GLWidget::animate()
{
    elapsed = (elapsed + qobject_cast<QTimer*>(sender())->interval()) % 1000;
    update();
}

void GLWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter;
    painter.begin(this);
    painter.setRenderHint(QPainter::Antialiasing);
    helper->paint(&painter, event, elapsed);
    painter.end();
}


           

Window类

类定义

#ifndef WINDOW_H
#define WINDOW_H

#include "helper.h"

#include <QWidget>

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
	//它包含一个将在所有widget之间共享的Helper对象。
	//widget中包含的是指针
    Helper helper;
};

#endif
           

类实现

#include "glwidget.h"
#include "widget.h"
#include "window.h"

#include <QGridLayout>
#include <QLabel>
#include <QTimer>

Window::Window()
{
    setWindowTitle(tr("2D Painting on Native and OpenGL Widgets"));
	//创建每种类型的widget,并将它们与标签一起插入到布局中
    Widget *native = new Widget(&helper, this);
    GLWidget *openGL = new GLWidget(&helper, this);
    QLabel *nativeLabel = new QLabel(tr("Native"));
    nativeLabel->setAlignment(Qt::AlignHCenter);
    QLabel *openGLLabel = new QLabel(tr("OpenGL"));
    openGLLabel->setAlignment(Qt::AlignHCenter);

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(native, 0, 0);
    layout->addWidget(openGL, 0, 1);
    layout->addWidget(nativeLabel, 1, 0);
    layout->addWidget(openGLLabel, 1, 1);
    setLayout(layout);
	//设置一个周期为50毫秒的定时器,出发widget的动画
	//animate里改变画面的参数,调用update出发widget的重新绘制
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, native, &Widget::animate);
    connect(timer, &QTimer::timeout, openGL, &GLWidget::animate);
    timer->start(50);
}
           

main函数

#include "window.h"

#include <QApplication>
#include <QSurfaceFormat>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    //OpenGL设置
    //格式包括颜色缓冲区、alpha缓冲区、深度和模板缓冲区的大小;
    //以及多个采样设置中每像素的采样数。这里是为了设置采样数。
    QSurfaceFormat fmt;
    //为了抗锯齿,需要采用对每个像素进行多重采样,这里采样数设置为4
    fmt.setSamples(64);
    QSurfaceFormat::setDefaultFormat(fmt);

    Window window;
    window.show();
    return app.exec();
}