2D 绘制示例

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

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

由于QOpenGLWidgetQWidget 的子类,因此可以重新实现它的paintEvent() 并使用QPainter 在设备上绘制,就像使用QWidget 一样。唯一的区别是,如果系统的 OpenGL 驱动程序支持,绘制操作将在硬件上加速。

在本示例中,我们在QWidgetQOpenGLWidget 上执行相同的绘制操作。QWidget 已启用抗锯齿功能,如果系统的 OpenGL 驱动程序支持所需的扩展,QOpenGLWidget 也将使用抗锯齿功能。

概述

为了比较在QOpenGLWidget 子类上绘制与在QWidget 子类中本地绘制的效果,我们希望同时显示这两种 widget。为此,我们派生了QWidgetQOpenGLWidget 的子类,使用单独的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() 函数,使用我们的一个 widget 子类提供的绘制器进行绘制。

辅助类的实现

该类的构造函数设置了将内容绘制到 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);
}

实际绘制是在paint() 函数中进行的。该函数需要一个已经设置好的QPainter ,该 可用于将内容绘制到一个绘制设备(QWidgetQOpenGLWidget )上;一个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 类提供了一个基本的自定义 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();
}

在这里,我们要确定定时器上次超时后的时间间隔,并在重新绘制 widget 之前将其添加到任何现有值中。由于Helper 类中使用的动画每秒循环一次,我们可以使用 modulo 运算符确保elapsed 变量始终小于 1000。

由于Helper 类完成了所有实际绘制工作,因此我们只需实现一个绘制事件,为 widget 设置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;
};

同样,成员变量记录了用于绘制 widget 的Helper 以及自上次更新后的时间。

GLWidget 类的实现

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

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

elapsed 成员变量被初始化,用于绘制 widget 的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();
}

由于抗锯齿会在可用的情况下启用,因此我们只需在 widget 上设置一个QPainter ,然后调用辅助器的paint() 函数来显示 widget 的内容。

窗口类定义

Window 类有一个基本的最小定义:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

它包含一个在所有 widget 之间共享的Helper 对象。

窗口类的实现

构造函数完成所有工作,创建每种类型的窗口部件,并将它们连同标签一起插入布局中:

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 毫秒超时的计时器,并将其连接到WidgetGLWidget 对象的animate() 插槽。一旦启动,小部件将以每秒约 20 帧的速度更新。

运行示例

示例显示了在WidgetGLWidget 中同时执行的相同绘画操作。GLWidget 中的渲染质量和速度取决于系统的 OpenGL 驱动程序对多重采样和硬件加速的支持程度。如果缺乏对这两项功能的支持,驱动程序可能会依赖于软件渲染器,而软件渲染器可能会以质量换速度。

示例项目 @ code.qt.io

© 2025 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.