2D ペインティングの例

2D ペインティングの例では、QPainterQOpenGLWidget を併用することで、サポートされているハードウェア上にアクセラレーションされた 2D グラフィックスを表示する方法を示します。

QPainter クラスは、QWidgetQImage などのQPaintDevice サブクラスが提供するペイント・デバイスに 2D グラフィックス・プリミティブを描画するために使用されます。

QOpenGLWidgetQWidget のサブクラスなので、paintEvent() を再実装し、QPainter を使用して、QWidget と同じようにデバイスに描画することができます。唯一の違いは、システムの OpenGL ドライバでサポートされている場合、ハードウェアで描画操作が高速化されることです。

この例では、QWidgetQOpenGLWidget で同じ描画操作を行っています。QWidget はアンチエイリアスが有効な状態で表示され、QOpenGLWidget も、必要な拡張がシステムの OpenGL ドライバでサポートされていれば、アンチエイリアスが使用されます。

概要

QOpenGLWidget のサブクラスでのペイントとQWidget のサブクラスでのネイティブ描画の結果を比較できるようにするために、両方の種類のウィジェットを並べて表示したいと思います。そのために、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() 関数を提供し、ウィジェットのサブクラスの 1 つが提供するペインタを使ってペイントするだけです。

ヘルパークラスの実装

クラスのコンストラクタは、ウィジェットにコンテンツをペイントするために必要なリソースをセットアップします:

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

この過程で座標系は何度も回転するので、QPainter の状態を事前にsave ()し、事後に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() 関数と、コンテンツのアニメーションに使用するスロットしか含まれていません。メンバ変数の1つは、ウィジェットがそのコンテンツを描画するために使用するHelper を記録し、もう1つは、最後に更新されてからの経過時間を記録します。

ウィジェットクラスの実装

コンストラクタは、メンバ変数を初期化し、提供された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 クラスで使われているアニメーションは 1 秒ごとにループするので、modulo 演算子を使って、elapsed 変数が常に 1000 未満になるようにします。

実際の描画はすべてHelper クラスが行うので、ウィジェットにQPainter を設定し、ヘルパーのpaint() 関数を呼び出す paint イベントを実装するだけでよい:

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

GLWidgetクラス定義

GLWidget クラス定義は、QOpenGLWidget から派生していることを除けば、Widget クラスと基本的に同じです。

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 オブジェクトが1つ含まれています。

ウィンドウクラスの実装

コンストラクタがすべての作業を行い、各タイプのウィジェットを作成し、ラベルとともにレイアウトに挿入します:

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() スロットに接続されます。一度開始すると、ウィジェットは1秒間に約20フレームで更新されるはずです。

例の実行

この例では、WidgetGLWidget で同時に実行される同じ描画操作を示しています。GLWidget におけるレンダリングの品質と速度は、システムの OpenGL ドライバが提供するマルチサンプリングとハードウェアアクセラレーションのサポートレベルに依存します。これらのサポートが不足している場合、ドライバはソフトウェアレンダラに頼ることになり、品質と速度が引き換えになる可能性があります。

プロジェクト例 @ code.qt.io

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。