本页内容

2D 绘图示例

该 2D 绘图示例展示了如何将QPainter 与QOpenGLWidget 结合使用,在受支持的硬件上显示加速的 2D 图形。

显示原生模式与OpenGL模式下图像对比的截图

QPainter 类用于将2D图形基元绘制到由QPaintDevice 子类(如QWidget 和QImage )提供的绘图设备上。

由于 `QOpenGLWidget ` 是 `QWidget` 的子类,因此可以重写其 `paintEvent()` 方法,并使用 `QPainter ` 在设备上进行绘制,这与对 `QWidget` 的操作完全相同。唯一的区别在于,如果系统中的 OpenGL 驱动程序支持硬件加速,则绘制操作将由硬件加速完成。

在此示例中,我们对QWidget 和QOpenGLWidget 执行相同的绘制操作。QWidget 的显示效果启用了抗锯齿,而如果系统OpenGL驱动程序支持所需的扩展,QOpenGLWidget 也会使用抗锯齿。

概述

为了能够比较在QOpenGLWidget 子类上绘制与在QWidget 子类中进行原生绘制的结果,我们需要将这两种控件并排显示。为此,我们继承了QWidget 和QOpenGLWidget 的子类,并使用一个独立的Helper 类为每个子类执行相同的绘制操作,然后将它们布局在一个顶级控件中,该控件本身由Window 类提供。

辅助类定义

在此示例中,绘制操作由一个辅助类执行。我们这样做是因为希望对QWidget 子类和QOpenGLWidget 子类都执行相同的绘制操作。

Helper 类非常简短:

class Helper
{
public:
    Helper();

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

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

除了构造函数外,它仅提供了一个paint() 函数,用于使用某个控件子类提供的绘图器进行绘制。

辅助类的实现

该类的构造函数会初始化将内容绘制到小部件上所需的资源:

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() ` 函数中完成。该函数接受一个已配置好用于在绘制设备(QWidget 或QOpenGLWidget )上绘制的 `QPainter `、一个提供待绘制区域信息的 `QPaintEvent `,以及自绘制设备上次更新以来经过的时间(以毫秒为单位)。

void Helper::paint(QPainter *painter, QPaintEvent *event, int elapsed)
{
    painter->fillRect(event->rect(), background);
    painter->translate(100, 100);

我们首先绘制绘制事件中包含的区域,然后平移坐标系的原点,以便后续的绘制操作向绘制设备的中心偏移。

我们绘制一个由圆组成的螺旋图案,利用指定的经过时间对其进行动画处理,使其看起来向外并围绕坐标系原点旋转:

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

由于在此过程中坐标系会经历多次旋转,因此我们会在绘制前使用save()保存QPainter 的状态,并在绘制后使用restore()将其恢复。

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

我们在原点处绘制一些文本以完善该效果。

控件类定义

Widget 类提供了一个基本的自定义控件,用于显示由Helper 类绘制的简单动画。

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

除了构造函数外,它仅包含一个paintEvent()函数(用于绘制自定义内容)和一个用于对内容进行动画处理的插槽。其中一个成员变量用于跟踪小部件用于绘制内容的Helper ,另一个则记录自上次更新以来的经过时间。

控件类实现

构造函数仅初始化成员变量,存储传入的Helper 对象并调用基类的构造函数,同时强制小部件保持固定尺寸:

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

每当定时器(我们稍后定义)超时,就会调用animate() 槽方法:

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

在此,我们确定自计时器上次超时以来已过去的间隔时间,并在重新绘制小部件之前将其加到现有值上。由于Helper 类中使用的动画每秒循环一次,我们可以使用取模运算符来确保elapsed 变量始终小于1000。

由于Helper 类负责所有实际的绘制工作,我们只需实现一个绘制事件,该事件为控件设置QPainter ,并调用辅助类的paint() 函数:

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

GLWidget 类定义

GLWidget 类的定义与Widget 类的定义基本相同,只是它继承自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;
};

同样,成员变量记录了用于绘制小部件的Helper 以及自上次更新以来的经过时间。

GLWidget 类的实现

其构造函数与Widget 类的构造函数略有不同:

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

elapsed 成员变量被初始化,并且存储了用于绘制小部件的Helper 对象。

animate() 的槽函数与Widget 类提供的完全相同:

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

paintEvent() 与Widget 类中的几乎完全相同:

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

由于在可用时会启用抗锯齿,我们只需在小部件上设置一个 `QPainter `,并调用该辅助类的 `paint() ` 函数即可显示小部件的内容。

Window 类定义

Window 类的定义非常基础且简洁:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

它包含一个Helper 对象,该对象将由所有小部件共享。

Window 类的实现

构造函数负责所有工作,它会创建每种类型的控件,并将它们连同标签一起插入到布局中:

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

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

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

为实现动画效果,创建了一个超时时间为 50 毫秒的定时器,并将其连接到 `Widget ` 和 `GLWidget ` 对象的 `animate() ` 槽。启动后,控件应以每秒约 20 帧的速度进行更新。

运行示例

该示例展示了在Widget 和GLWidget 中同时执行相同的绘制操作。GLWidget 中的渲染质量和速度取决于系统OpenGL驱动程序对多采样和硬件加速的支持程度。如果缺乏对上述任一功能的支持,驱动程序可能会回退到软件渲染器,从而以质量换取速度。

示例项目 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.