天天看點

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