2D-Painting-Beispiel

Das 2D-Painting-Beispiel zeigt, wie QPainter und QOpenGLWidget zusammen verwendet werden können, um beschleunigte 2D-Grafiken auf unterstützter Hardware anzuzeigen.

Die Klasse QPainter wird zum Zeichnen von 2D-Grafik-Primitiven auf Malgeräten verwendet, die von QPaintDevice -Unterklassen wie QWidget und QImage bereitgestellt werden.

Da QOpenGLWidget eine Unterklasse von QWidget ist, ist es möglich, paintEvent() neu zu implementieren und QPainter zum Zeichnen auf dem Gerät zu verwenden, genau wie bei QWidget. Der einzige Unterschied besteht darin, dass die Zeichenoperationen in der Hardware beschleunigt werden, wenn dies von den OpenGL-Treibern Ihres Systems unterstützt wird.

In diesem Beispiel führen wir dieselben Malvorgänge auf einem QWidget und einem QOpenGLWidget aus. QWidget wird mit aktiviertem Anti-Aliasing dargestellt, und QOpenGLWidget verwendet ebenfalls Anti-Aliasing, wenn die erforderlichen Erweiterungen vom OpenGL-Treiber Ihres Systems unterstützt werden.

Überblick

Um die Ergebnisse des Malens in einer QOpenGLWidget Unterklasse mit dem nativen Zeichnen in einer QWidget Unterklasse vergleichen zu können, wollen wir beide Arten von Widgets nebeneinander darstellen. Um dies zu erreichen, leiten wir Unterklassen von QWidget und QOpenGLWidget ab, wobei wir eine separate Klasse Helper verwenden, um die gleichen Maloperationen für beide durchzuführen, und legen sie in einem Widget der obersten Ebene an, das wiederum von der Klasse Window bereitgestellt wird.

Definition der Hilfsklasse

In diesem Beispiel werden die Malvorgänge von einer Hilfsklasse ausgeführt. Wir tun dies, weil wir wollen, dass die gleichen Malvorgänge sowohl für unsere Unterklasse QWidget als auch für die Unterklasse QOpenGLWidget ausgeführt werden.

Die Klasse Helper ist 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;
};

Abgesehen vom Konstruktor bietet sie nur eine paint() Funktion zum Malen unter Verwendung eines Malers, der von einer unserer Widget-Unterklassen bereitgestellt wird.

Implementierung der Helper-Klasse

Der Konstruktor der Klasse richtet die Ressourcen ein, die sie benötigt, um Inhalte auf ein Widget zu malen:

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

Das eigentliche Malen wird in der Funktion paint() durchgeführt. Diese nimmt ein QPainter, das bereits für das Malen auf ein Malgerät (entweder ein QWidget oder ein QOpenGLWidget) eingerichtet wurde, ein QPaintEvent, das Informationen über den zu malenden Bereich liefert, und ein Maß für die verstrichene Zeit (in Millisekunden) seit der letzten Aktualisierung des Malgeräts.

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

Wir beginnen mit dem Malen, indem wir den im Malereignis enthaltenen Bereich ausfüllen, bevor wir den Ursprung des Koordinatensystems verschieben, so dass der Rest der Malvorgänge in Richtung des Zentrums des Malgeräts verschoben wird.

Wir zeichnen ein spiralförmiges Muster von Kreisen und verwenden die angegebene Zeit, um sie so zu animieren, dass sie sich nach außen und um den Ursprung des Koordinatensystems zu bewegen scheinen:

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

Da das Koordinatensystem während dieses Vorgangs mehrmals gedreht wird, wird der Zustand von QPainter vorher auf save() und danach auf restore() gespeichert.

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

Um den Effekt zu vervollständigen, zeichnen wir etwas Text am Ursprung.

Definition der Widget-Klasse

Die Klasse Widget stellt ein einfaches benutzerdefiniertes Widget zur Verfügung, mit dem wir die von der Klasse Helper gezeichnete einfache Animation anzeigen können.

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

Abgesehen vom Konstruktor enthält sie nur eine paintEvent()-Funktion, mit der wir benutzerdefinierte Inhalte zeichnen können, und einen Slot, der für die Animation des Inhalts verwendet wird. Eine Mitgliedsvariable hält die Helper fest, die das Widget verwendet, um seinen Inhalt zu zeichnen, und die andere zeichnet die verstrichene Zeit seit der letzten Aktualisierung auf.

Implementierung der Widget-Klasse

Der Konstruktor initialisiert nur die Mitgliedsvariablen, speichert das mitgelieferte Helper Objekt und ruft den Konstruktor der Basisklasse auf und erzwingt eine feste Größe für das Widget:

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

Der Slot animate() wird immer dann aufgerufen, wenn ein Timer, den wir später definieren, ein Zeitlimit erreicht:

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

Hier bestimmen wir das Intervall, das seit der letzten Zeitüberschreitung verstrichen ist, und addieren es zu einem eventuell vorhandenen Wert, bevor wir das Widget neu malen. Da die in der Klasse Helper verwendete Animation jede Sekunde eine Schleife durchläuft, können wir den Modulo-Operator verwenden, um sicherzustellen, dass die Variable elapsed immer kleiner als 1000 ist.

Da die Klasse Helper die eigentliche Bemalung vornimmt, müssen wir nur ein Bemalungsereignis implementieren, das ein QPainter für das Widget einrichtet und die Funktion paint() des Helfers aufruft:

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

GLWidget-Klassendefinition

Die Definition der Klasse GLWidget ist im Grunde die gleiche wie die der Klasse Widget, mit der Ausnahme, dass sie von QOpenGLWidget abgeleitet ist.

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

Auch hier zeichnen die Mitgliedsvariablen die Helper auf, die zum Zeichnen des Widgets verwendet wird, sowie die seit der letzten Aktualisierung verstrichene Zeit.

Implementierung der GLWidget-Klasse

Der Konstruktor unterscheidet sich ein wenig vom Konstruktor der Klasse Widget:

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

Die Membervariable elapsed wird initialisiert und das Helper Objekt, das zum Zeichnen des Widgets verwendet wird, wird gespeichert.

Der animate() Slot ist genau derselbe wie der von der Widget Klasse:

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

Der paintEvent() ist fast derselbe wie der der Klasse Widget:

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

Da die Kantenglättung aktiviert wird, wenn sie verfügbar ist, müssen wir nur ein QPainter auf dem Widget einrichten und die Funktion paint() der Hilfsklasse aufrufen, um den Inhalt des Widgets anzuzeigen.

Definition der Fensterklasse

Die Klasse Window hat eine grundlegende, minimale Definition:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    Helper helper;
};

Sie enthält ein einziges Helper Objekt, das von allen Widgets gemeinsam genutzt wird.

Implementierung der Fensterklasse

Der Konstruktor erledigt die ganze Arbeit, indem er ein Widget jedes Typs erstellt und sie mit Beschriftungen in ein Layout einfügt:

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

Für Animationszwecke wird ein Timer mit einem Timeout von 50 Millisekunden erstellt und mit den animate() -Slots der Objekte Widget und GLWidget verbunden. Einmal gestartet, sollten die Widgets mit etwa 20 Bildern pro Sekunde aktualisiert werden.

Ausführen des Beispiels

Das Beispiel zeigt dieselben Malvorgänge, die gleichzeitig in einem Widget und einem GLWidget ausgeführt werden. Die Qualität und Geschwindigkeit des Renderings in GLWidget hängt von der Unterstützung für Multisampling und Hardwarebeschleunigung ab, die der OpenGL-Treiber Ihres Systems bietet. Wenn diese Unterstützung fehlt, kann der Treiber auf einen Softwarerenderer zurückgreifen, der möglicherweise Qualität gegen Geschwindigkeit eintauscht.

Beispielprojekt @ 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.