QGraphicsScene::addLine() のパフォーマンス改善:大量の線を描画する際の最適化【Qt 日本語】

2025-04-26

QGraphicsScene::addLine() 関数とは

QGraphicsScene::addLine() は、Qtのグラフィックス/ビューフレームワークにおいて、シーン(QGraphicsScene オブジェクト)に直線をグラフィカルアイテムとして追加するための関数です。この関数を呼び出すことで、指定された始点と終点を持つ線がシーン上に描画されます。

関数のオーバーロード

addLine() 関数にはいくつかのオーバーロードがあります。主なものは以下の通りです。

    • line: 描画する直線を QLineF オブジェクトとして指定します。QLineF は浮動小数点数の精度で直線を定義します。
    • pen: 直線の描画に使用するペン(色、太さ、線種など)を QPen オブジェクトとして指定します。省略した場合、デフォルトのペンが使用されます。
    • 戻り値: 作成された直線のグラフィカルアイテム(QGraphicsLineItem オブジェクト)へのポインタを返します。
  1. QGraphicsLineItem *addLine(const QPointF &p1, const QPointF &p2, const QPen &pen = QPen())

    • p1: 直線の始点を QPointF オブジェクトとして指定します。QPointF は浮動小数点数の精度で点を定義します。
    • p2: 直線の終点を QPointF オブジェクトとして指定します。
    • pen: 直線の描画に使用するペンを QPen オブジェクトとして指定します。省略した場合、デフォルトのペンが使用されます。
    • 戻り値: 作成された直線のグラフィカルアイテム(QGraphicsLineItem オブジェクト)へのポインタを返します。
  2. QGraphicsLineItem *addLine(qreal x1, qreal y1, qreal x2, qreal y2, const QPen &pen = QPen())

    • x1: 直線の始点のX座標を qreal 型(通常は double)で指定します。
    • y1: 直線の始点のY座標を qreal 型で指定します。
    • x2: 直線の終点のX座標を qreal 型で指定します。
    • y2: 直線の終点のY座標を qreal 型で指定します。
    • pen: 直線の描画に使用するペンを QPen オブジェクトとして指定します。省略した場合、デフォルトのペンが使用されます。
    • 戻り値: 作成された直線のグラフィカルアイテム(QGraphicsLineItem オブジェクト)へのポインタを返します。

使用例(C++)

以下に、addLine() 関数の使用例をいくつか示します。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QLineF>
#include <QPointF>
#include <QPen>
#include <QColor>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // シーンの作成
    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200); // シーンの範囲を設定

    // ペンの作成
    QPen redPen(Qt::red);
    redPen.setWidth(3);

    QPen blueDashedPen(Qt::blue);
    blueDashedPen.setStyle(Qt::DashLine);

    // QLineF を使って直線を追加
    QLineF line1(QPointF(-50, 0), QPointF(50, 0));
    scene.addLine(line1, redPen);

    // QPointF を使って直線を追加
    QPointF startPoint(0, -50);
    QPointF endPoint(0, 50);
    scene.addLine(startPoint, endPoint, blueDashedPen);

    // 座標を直接指定して直線を追加
    scene.addLine(-80, -80, 80, 80);

    // ビューの作成とシーンの設定
    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

解説

  • 最後に、QGraphicsView オブジェクトを作成し、作成したシーンを設定して表示します。
  • addLine() 関数は、シーンに追加された直線のグラフィカルアイテムである QGraphicsLineItem のポインタを返します。このポインタを使って、後からアイテムのプロパティを変更したり、操作したりすることができます。
  • addLine() 関数を呼び出し、描画したい直線の情報(QLineF オブジェクト、QPointF オブジェクトのペア、または個々の座標)と、使用する QPen オブジェクトを渡します。
  • 次に、直線の描画に使用する QPen オブジェクトを作成し、色や太さ、線種などを設定します。
  • まず、QGraphicsScene オブジェクトを作成します。

重要な点

  • 作成された QGraphicsLineItem は、他のグラフィカルアイテムと同様に、位置、回転、拡大縮小などの変形を適用したり、マウスイベントなどのインタラクションを処理したりすることができます。
  • addLine() 関数は、シーンに直線のグラフィカルアイテムを追加するだけで、実際に画面に表示するためには QGraphicsView を使用してシーンを表示する必要があります。


一般的なエラーとトラブルシューティング

QGraphicsScene::addLine() 自体は比較的単純な関数ですが、その使用方法や周辺のコードに起因する様々な問題が発生する可能性があります。以下に代表的なエラーと、その解決策や確認ポイントを挙げます。

線が表示されない

  • 原因 4: ビューが正しく設定されていない

    • 確認
      QGraphicsView が作成され、描画対象の QGraphicsScene が正しく設定されているか (QGraphicsView::setScene()) を確認します。また、ビュー自体が適切に表示されているか(show() が呼ばれているかなど)も確認してください。
    • 対処
      QGraphicsView の設定を見直し、シーンが正しく表示されるように修正してください。
  • 原因 3: Z値(スタック順)の問題

    • 確認
      他のグラフィカルアイテムが線の上に重なって表示されている可能性があります。各アイテムには Z 値があり、値が大きいほど前面に表示されます。
    • 対処
      QGraphicsItem::setZValue() を使用して、線の Z 値を他のアイテムよりも大きく設定してください。
  • 原因 2: シーンの範囲外に描画されている

    • 確認
      QGraphicsScene::setSceneRect() で設定したシーンの範囲内に線が描画されているか確認します。デフォルトではシーンの範囲は特に設定されていませんが、意図せず狭い範囲に設定している場合があります。
    • 対処
      線の座標がシーンの範囲内になるように調整するか、setSceneRect() で適切な範囲を設定してください。必要であれば、QGraphicsView::fitInView() を使用して、ビューにシーン全体が収まるように調整することもできます。
    • 確認
      QPen オブジェクトが正しく初期化されているか確認してください。特に、ペンの色(QColor)、太さ(setWidth())、スタイル(setStyle())などが意図した設定になっているかを確認します。例えば、色が背景色と同じ場合や、太さが 0 の場合は見えません。
    • 対処
      適切な色、太さ、スタイルを持つ QPen オブジェクトを作成し、addLine() に渡してください。

意図しない線の描画

  • 原因 2: ループ内での誤った処理

    • 確認
      ループの中で addLine() を呼び出している場合、変数の更新が正しく行われているか、意図しない回数だけ呼び出されていないかなどを確認します。
    • 対処
      ループの制御構造や変数の更新処理を見直し、意図通りの線の描画が行われるように修正してください。
  • 原因 1: 座標の誤り

    • 確認
      addLine() に渡している始点と終点の座標 (QPointF または qreal の値) が意図した位置になっているか確認します。特に、X座標とY座標を間違えていないか、計算ミスがないかなどを注意深く確認してください。
    • 対処
      正しい座標値を addLine() に渡すように修正してください。デバッガを使用して座標の値を確認するのも有効です。

パフォーマンスの問題(多数の線を描画する場合)

  • 原因 2: 不要な再描画

    • 確認
      シーンやビューの更新が頻繁に行われ、そのたびに多数の線が再描画されている可能性があります。
    • 対処
      必要最小限の範囲だけを更新するように最適化します。例えば、QGraphicsView::update()QGraphicsItem::update() を適切なタイミングで呼び出すようにします。
  • 原因 2: グラフィックスシステムのドライバの問題

    • 確認
      まれに、使用しているグラフィックスドライバに問題があり、描画が正しく行われない場合があります。
    • 対処
      グラフィックスドライバを最新バージョンに更新してみるか、別の環境で試してみることを検討してください。
  • 原因 1: QLineF オブジェクトの不正な状態

    • 確認
      addLine()QLineF オブジェクトを渡している場合、そのオブジェクトが有効な直線を定義しているか確認します。例えば、始点と終点が同じでないかなどをチェックします。
    • 対処
      QLineF オブジェクトが意図した直線を正しく表現するように初期化してください。

トラブルシューティングのヒント

  • Qt のドキュメントを参照
    Qt の公式ドキュメントには、各クラスや関数の詳細な説明や使用例が記載されています。困った場合は、関連するドキュメントを参照してください。
  • ログ出力
    重要な変数の値をログに出力することで、プログラムの実行状況を把握し、問題の箇所を特定するのに役立ちます。
  • デバッガの活用
    デバッガを使用して、変数の値(特に座標やペンの設定)をステップ実行しながら確認することで、問題の原因を特定しやすくなります。
  • 最小限のコードで再現
    問題が発生するコードをできるだけ小さく切り分け、再現させることで、原因の特定が容易になります。


基本的な直線の描画

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPointF>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // シーンの作成
    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200); // シーンの範囲を設定

    // 始点と終点を指定して直線を追加
    QPointF startPoint(-50, 0);
    QPointF endPoint(50, 0);
    scene.addLine(startPoint, endPoint);

    // ビューの作成とシーンの設定
    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  • 最後に、QGraphicsView を作成し、setScene()` で作成したシーンを設定して表示します。
  • scene.addLine(startPoint, endPoint); を呼び出すことで、定義した始点と終点を持つ直線がシーンに追加されます。デフォルトのペン(黒色の実線、太さ1ピクセル)で描画されます。
  • 次に、QPointF オブジェクトを使って直線の始点 (-50, 0) と終点 (50, 0) を定義しています。
  • この例では、まず QGraphicsScene オブジェクトを作成し、setSceneRect() でシーンの描画範囲を設定しています。

ペンのスタイルを指定した直線の描画

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPointF>
#include <QPen>
#include <QColor>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    // 赤色の太いペンを作成
    QPen redPen(Qt::red);
    redPen.setWidth(5);

    // 青色の破線ペンを作成
    QPen blueDashedPen(Qt::blue);
    blueDashedPen.setStyle(Qt::DashLine);

    // 赤色の直線を追加
    scene.addLine(-50, -30, 50, -30, redPen);

    // 青色の破線を追加
    scene.addLine(-50, 30, 50, 30, blueDashedPen);

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  • scene.addLine() の第三引数に作成した QPen オブジェクトを渡すことで、指定したスタイルで直線が描画されます。
  • QPen blueDashedPen(Qt::blue); で青色のペンを作成し、blueDashedPen.setStyle(Qt::DashLine); で線種を破線に設定しています。
  • QPen redPen(Qt::red); で赤色のペンを作成し、redPen.setWidth(5); で太さを5ピクセルに設定しています。
  • この例では、QPen オブジェクトをカスタマイズして直線のスタイルを変更しています。

QLineF オブジェクトを使用した直線の描画

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QLineF>
#include <QPen>
#include <QColor>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    // QLineF オブジェクトを作成
    QLineF diagonalLine(-70, -70, 70, 70);

    // 緑色の点線ペンを作成
    QPen greenDottedPen(Qt::green);
    greenDottedPen.setStyle(Qt::DotLine);
    greenDottedPen.setWidth(2);

    // QLineF オブジェクトとペンを指定して直線を追加
    scene.addLine(diagonalLine, greenDottedPen);

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  • scene.addLine(diagonalLine, greenDottedPen); のように、第一引数に QLineF オブジェクト、第二引数に QPen オブジェクトを渡すことで、定義した直線が指定したスタイルで描画されます。
  • QLineF diagonalLine(-70, -70, 70, 70); で、始点 $(-70, -70)$、終点 $(70, 70)$ の直線を定義しています。
  • この例では、QLineF オブジェクトを使って直線を定義しています。QLineF は浮動小数点数の精度で直線を表現できます。

複数の直線の描画

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPointF>
#include <QPen>
#include <QColor>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QPen blackPen(Qt::black);
    blackPen.setWidth(2);

    // 複数の直線をループで追加
    for (int i = 0; i < 10; ++i) {
        qreal x = i * 20 - 90;
        scene.addLine(x, -50, x, 50, blackPen);
    }

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}
  • ループの中で、X座標を少しずつずらしながら scene.addLine() を繰り返し呼び出すことで、等間隔に並んだ複数の直線がシーンに追加されます。
  • この例では、for ループを使って複数の垂直線を描画しています。


#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPainterPath>
#include <QPen>
#include <QGraphicsPathItem>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QPainterPath path;
    QPen pen(Qt::blue);
    pen.setWidth(3);

    // 複数の線分をパスに追加
    path.moveTo(-50, -50);
    path.lineTo(50, -50);
    path.lineTo(50, 50);
    path.lineTo(-50, 50);
    path.closeSubpath(); // 閉じた図形にする場合

    // パスからアイテムを作成してシーンに追加
    QGraphicsPathItem *pathItem = scene.addPath(path, pen);

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  • 多数の直線を個別に addLine() するよりも、描画のオーバーヘッドを減らし、パフォーマンスを向上させることができます。
  • 最後に、scene.addPath(path, pen) を呼び出すことで、定義したパスを持つ QGraphicsPathItem がシーンに追加されます。
  • 複数の直線(この例では四角形)を一つのパスとして定義できます。
  • QPainterPath オブジェクトを作成し、moveTo() で開始点を設定した後、lineTo() で線分の終点を順に指定していきます。

カスタム QGraphicsItem を作成して paint() 関数内で描画する

より複雑な形状や、動的な描画が必要な場合は、QGraphicsItem を継承したカスタムアイテムを作成し、その paint() 関数内で QPainter を使用して線を描画する方法があります。

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QPainter>
#include <QRectF>
#include <QPen>

class MyLineItem : public QGraphicsItem {
public:
    MyLineItem(const QPointF &start, const QPointF &end, QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent), startPoint(start), endPoint(end) {}

    QRectF boundingRect() const override {
        qreal minX = qMin(startPoint.x(), endPoint.x());
        qreal minY = qMin(startPoint.y(), endPoint.y());
        qreal maxX = qMax(startPoint.x(), endPoint.x());
        qreal maxY = qMax(startPoint.y(), endPoint.y());
        return QRectF(minX - 1, minY - 1, maxX - minX + 2, maxY - minY + 2); // 少し大きめのバウンディング矩形
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override {
        QPen pen(Qt::green);
        pen.setWidth(2);
        painter->setPen(pen);
        painter->drawLine(startPoint, endPoint);
    }

private:
    QPointF startPoint;
    QPointF endPoint;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    // カスタムアイテムを作成してシーンに追加
    MyLineItem *line1 = new MyLineItem(QPointF(-50, -50), QPointF(50, 50));
    scene.addItem(line1);

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}

説明

  • カスタムアイテムを scene.addItem() でシーンに追加します。
  • MyLineItem のコンストラクタで始点と終点を保存し、paint() 関数内でそれらを使用して線を描画します。
  • paint() 関数内で QPainter を使用して実際の描画を行います。ここでは、指定された始点と終点を持つ緑色の線を描画しています。
  • boundingRect() 関数では、アイテムのバウンディング矩形を定義します。これは、アイテムの再描画が必要かどうかを判断するために使用されます。
  • QGraphicsItem を継承した MyLineItem クラスを作成します。

この方法の利点は、より複雑な描画ロジックを paint() 関数内に記述できることや、アイテムの状態に応じて描画を動的に変更できることです。

OpenGL を使用した描画(高度なケース)

非常に大量のグラフィカルオブジェクトを扱う場合や、高度なレンダリング効果が必要な場合は、QOpenGLWidgetQQuickViewQSGNode を組み合わせて OpenGL を直接使用して描画することも可能です。これはより高度なトピックであり、Qtのグラフィックス/ビューフレームワークの抽象化レイヤーよりも低いレベルでの操作が必要になります。

QGraphicsItemGroup を使用して複数の線アイテムをグループ化する

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsLineItem>
#include <QGraphicsItemGroup>
#include <QPointF>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QGraphicsLineItem *line1 = scene.addLine(-50, 0, 0, 50);
    QGraphicsLineItem *line2 = scene.addLine(0, 50, 50, 0);
    QGraphicsLineItem *line3 = scene.addLine(50, 0, 0, -50);
    QGraphicsLineItem *line4 = scene.addLine(0, -50, -50, 0);

    // グループを作成してアイテムを追加
    QGraphicsItemGroup *group = new QGraphicsItemGroup();
    group->addToGroup(line1);
    group->addToGroup(line2);
    group->addToGroup(line3);
    group->addToGroup(line4);
    scene.addItem(group);

    // グループ全体を移動
    group->moveBy(20, -20);

    QGraphicsView view(&scene);
    view.show();

    return a.exec();
}
  • グループ化されたアイテムは、グループ全体として移動、回転、拡大縮小などの操作を行うことができます。
  • QGraphicsItemGroup オブジェクトを作成し、addToGroup() を使用して作成した線アイテムをグループに追加します。
  • 複数の QGraphicsLineItemscene.addLine() で個別に追加します。