2D 绘图示例

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

QPainter 类用于将 2D 图形原语绘制到由 QPaintDevice 子类提供的画布设备上,例如 QWidgetQImage

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

在这个示例中,我们在 QWidgetQOpenGLWidget 上执行相同的绘图操作。显示具有抗锯齿功能的 QWidget,而如果您的系统 OpenGL 驱动程序支持所需的扩展,则 QOpenGLWidget 也将使用抗锯齿。

概览

为了能够将绘制到 QOpenGLWidget 子类的结果与在 QWidget 子类中的原生绘图进行比较,我们希望并排放置这两种小部件。为此,我们推导出 QWidgetQOpenGLWidget 的子类,使用一个单独的 Helper 类来执行每个相同的绘图操作,并使用 Window 类将其布局到顶层小部件中。

辅助类定义

在这个示例中,绘图操作由辅助类执行。我们这样做因为我们想要为我们的 QWidget 子类和 QOpenGLWidget 子类执行相同的绘图操作。

辅助类是最小的

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() 函数中执行的。这个函数接受一个已经设置好用于在绘图设备上绘制的 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 以绘制其内容,另一个记录自上次更新以来的时间。

Widget 类实现

构造函数仅初始化成员变量,存储提供的 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类具有基本的最小定义

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

它包含一个单独的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

© 2024 Qt公司。此处包含的文档贡献的版权归其所有者。此处提供的文档受GNU自由文档许可版本1.3的条款约束,由自由软件基金会发布。Qt及其相关标志是芬兰以及世界各地的Qt公司的商标。所有其他商标均为它们各自所有者的财产。