QT OpenGL (1) 2D Painting Example

2D Painting Example

For the convenience of reference, this article is translated and organized from the original website documents. If there is any infringement, please contact me.
Official website

Directory

  • 2D Painting Example
  • Overview
  • Helper class definition
  • Helper class implementation
  • Widget class definition
  • Widget class implementation
  • GLWidget class definition
  • GLWidget class implementation
  • Window class definition
  • Window class implementation
  • Run the example

The 2D Painting sample shows how QPainter and QOpenGLWidget can be used together to display accelerated 2D graphics on supported hardware.

The QPainter class is used to draw 2D graphics primitives on drawing devices provided by QPaintDevice subclasses such as QWidget and QImage.
Since QOpenGLWidget is a subclass of QWidget, you can reimplement its paintEvent() and use QPainter to draw on the device, just like you would with QWidget. The only difference is that drawing operations will be accelerated in hardware if the system’s OpenGL driver supports it.
In this example, we perform the same drawing operation on QWidget and QOpenGLWidget. The QWidget appears with anti-aliasing enabled, and if the system’s OpenGL driver supports the required extensions, the QOpenGLWidget will also use anti-aliasing.

Overview

To be able to compare the results of drawing on a QOpenGLWidget subclass with drawing locally in a QWidget subclass, we want to display both widgets side by side. To do this, we derived subclasses of QWidget and QOpenGLWidget, used a separate Helper class to perform the same drawing operations for each subclass, and placed them in a top-level widget that itself provided a Window class.

Helper class definition

In this case, the drawing operations are performed by a helper class. We do this because we want to perform the same drawing operations on QWidget subclasses and QOpenGLWidget subclasses.
The Helper class is minimal:

class Helper
{<!-- -->
public:
    Helper();

public:
    void paint(QPainter *painter, QPaintEvent *event, int elapsed);

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

In addition to the constructor, it only provides a paint() function to draw using the painter provided by one of our widget subclasses.

Helper class implementation

The class’s constructor sets the resources needed to draw content onto the widget:

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

The actual drawing is performed in the paint() function. This requires a QPainter that has been set up to draw on the drawing device (QWidget or QOpenGLWidget), a QPaintEvent that provides information about the area to be drawn, and the time elapsed since the drawing device was last updated (in milliseconds)

void Helper::paint(QPainter *painter, QPaintEvent *event, int elapsed)
{<!-- -->
    painter->fillRect(event->rect(), background);
    painter->translate(100, 100);
/*
* We begin drawing by filling the area contained in the draw event before translating the origin of the coordinate system in order to move the rest of the drawing operation toward the center of the drawing device.
*We draw a circular spiral pattern and animate them using a specified run time so that they appear to move outward and around the origin of the coordinate system:
*/
painter->save();
    painter->setBrush(circleBrush);
    painter->setPen(circlePen);
    painter->rotate(elapsed * 0.030);

    qreal r = elapsed / 1000.0;
    int n = 30;
    for (int i = 0; i < n; + + i) {<!-- -->
        painter->rotate(30);
        qreal factor = (i + r) / n;
        qreal radius = 0 + 120.0 * factor;
        qreal circleRadius = 1 + factor * 20;
        painter->drawEllipse(QRectF(radius, -circleRadius,circleRadius * 2, circleRadius * 2));
    }
    painter->restore();
/*
*Since the coordinate system rotates many times during this process, we save() the QPainter state in advance and then restore() it.
*/
painter->setPen(textPen);
    painter->setFont(textFont);
    painter->drawText(QRect(-50, -50, 100, 100), Qt::AlignCenter, QStringLiteral("Qt"));
/*
*We draw some text at the origin to complete the effect.
*/

Widget class definition

The Widget class provides a basic custom widget for displaying simple animations drawn by the Helper class

class 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;
};

Besides the constructor, it only contains a paintEvent() function that allows us to draw custom content, and a slot for animating its content. One member variable keeps track of the helper used by the widget to draw its content, and another records the time elapsed since the last update.

Widget class implementation

The constructor simply initializes the member variables, stores the provided Helper object and calls the base class’s constructor, and enforces a fixed size for the widget:

Widget::Widget(Helper *helper, QWidget *parent)
    : QWidget(parent), helper(helper)
{<!-- -->
    elapsed = 0;
    setFixedSize(200, 200);
}

Whenever the timer we define later times out, the animate() slot is called:

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

Here we determine the time interval that has elapsed since the timer last timed out and add it to any existing values before redrawing the widget. Since the animation used in the Helper class loops every second, we can use the modulo operator to ensure that the passed variable is always less than 1000.
Since the Helper class does all the actual painting, we only need to implement a paint event that sets up a QPainter for the widget and calls the Helper’s paint() function:

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

GLWidget class definition

The definition of the GLWidget class is basically the same as the Widget class, except that it is derived from 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;
};

Likewise, the member variables record the Helper used to draw the widget and the time that has elapsed since the last update.

GLWidget class implementation

The constructor is slightly different from that of the Widget class:

GLWidget::GLWidget(Helper *helper, QWidget *parent)
    : QOpenGLWidget(parent), helper(helper)
{<!-- -->
    elapsed = 0;
    setFixedSize(200, 200);
    setAutoFillBackground(false);//This setting is added
}

In addition to initializing the passed member variables and storing the Helper object used to draw the widget, the constructor of the base class is also called using the format specifying the QGL::SampleBuffers flag. Anti-aliasing can be enabled if your system’s OpenGL driver supports it.
The animate() slot is exactly the same as the one provided by the Widget class:

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

paintEvent() is almost the same as in the Widget class:

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

Since anti-aliasing will be enabled if available, we just need to set a QPainter on the widget and call the helper’s paint() function to display the widget’s content.

Window class definition

The Window class has a basic, minimal definition:

class Window : public QWidget
{<!-- -->
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

It contains a Helper object that will be shared among all widgets.

Window class implementation

The constructor does all the work, creating each type of widget and inserting them into the layout along with the labels:

Window::Window()
{<!-- -->
    setWindowTitle(tr("2D Painting on Native and OpenGL Widgets"));

    Widget *native = new Widget( & amp;helper, this);
    GLWidget *openGL = new GLWidget( & amp;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);

    QTimer *timer = new QTimer(this);
    connect(timer, & amp;QTimer::timeout, native, & amp;Widget::animate);
    connect(timer, & amp;QTimer::timeout, openGL, & amp;GLWidget::animate);
    timer->start(50);
}

A timer with a timeout of 50 milliseconds is built for animation purposes and is connected to the animate() slot of the Widget and GLWidget objects. Once started, the widget should update at around 20 frames per second.

Run the example

This example shows the same drawing operation performed simultaneously in a Widget and a GLWidget. The quality and speed of rendering in GLWidget depends on how well your system’s OpenGL driver supports multisampling and hardware acceleration. If support for any of these is lacking, the driver may rely on a software renderer that may trade quality for speed.