【Qtプログラミング】QGraphicsView::scale()の落とし穴と解決策

2025-05-27

簡単に言うと、これはビュー(表示領域)を拡大・縮小する機能です。

QGraphicsView::scale() の機能

QGraphicsView::scale(qreal sx, qreal sy) メソッドは、現在のビューの変換行列(Transformation Matrix)に対して、指定された sxsy の値を乗算することで、水平方向 (sx) と垂直方向 (sy) に拡大・縮小を行います。

  • sy: 垂直方向のスケールファクター(拡大率)。
  • sx: 水平方向のスケールファクター(拡大率)。

例えば:

  • scale(0.5, 0.5) とすると、現在の表示を縦横0.5倍(半分)に縮小します。
  • scale(2.0, 2.0) とすると、現在の表示を縦横2倍に拡大します。

どのように機能するか

QGraphicsView は、内部的に変換行列を持っています。この変換行列は、QGraphicsScene の座標系を QGraphicsView のビューポート(表示領域)の座標系にマッピングするために使用されます。scale() 関数は、この変換行列を操作することで、シーンの内容がビューにどのように描画されるかを制御します。

重要な点として:

  1. ビューの変換: QGraphicsView::scale() は、ビューそのもの(表示領域)の変換を調整します。個々の QGraphicsItem を直接拡大・縮小するわけではありません。
  2. 原点: デフォルトでは、ビューの中心ではなく、ビューポートの左上隅を原点として拡大・縮小が行われるように見えます。しかし、QGraphicsView の変換は中心を基準に行われることが多いため、ズーム時にビューポートのアンカーを設定する setTransformationAnchor() などと組み合わせて使用することが一般的です。
  3. 累積: scale() は現在の変換行列に対して累積的に適用されます。例えば、scale(2.0, 2.0) を呼び出した後、もう一度 scale(2.0, 2.0) を呼び出すと、元のサイズの4倍になります。変換をリセットしたい場合は、QGraphicsView::resetMatrix() を使用してから scale() を呼び出すか、QGraphicsView::setMatrix() を使用して新しい変換行列を直接設定します。

マウスホイールでズームイン・ズームアウトする機能は、QGraphicsView::scale() の典型的な使用例です。

#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QWheelEvent>
#include <cmath> // For std::pow

class MyGraphicsView : public QGraphicsView
{
public:
    MyGraphicsView(QWidget* parent = nullptr) : QGraphicsView(parent)
    {
        QGraphicsScene* scene = new QGraphicsScene(this);
        setScene(scene);
        
        // シーンに何かアイテムを追加する
        scene->addRect(0, 0, 100, 100, QPen(Qt::blue), QBrush(Qt::lightGray));
        scene->addEllipse(150, 150, 80, 80, QPen(Qt::red), QBrush(Qt::yellow));

        // ズームのアンカーをマウスカーソル位置に設定
        setTransformationAnchor(AnchorUnderMouse);
        // ドラッグモードをスクロールハンドに設定
        setDragMode(ScrollHandDrag);
    }

protected:
    void wheelEvent(QWheelEvent* event) override
    {
        // マウスホイールの回転量に応じてスケールファクターを計算
        // 通常、イベントのdelta()は120の倍数で、1ステップあたり15度なので、
        // 120 / 8 = 15
        double numDegrees = event->angleDelta().y() / 8.0;
        double numSteps = numDegrees / 15.0;
        double factor = std::pow(1.125, numSteps); // 1.125倍ずつ拡大縮小

        // ビューをスケール
        scale(factor, factor);
    }
};

// main 関数などでの使用例:
// MyGraphicsView* view = new MyGraphicsView();
// view->show();

上記の例では、wheelEvent をオーバーライドして、マウスホイールの回転量に基づいて scale() を呼び出し、ズームイン・ズームアウトを実現しています。setTransformationAnchor(AnchorUnderMouse) を設定することで、マウスカーソルの下にある点を中心に拡大・縮小が行われるため、より直感的な操作感になります。



スケールが累積的に適用され、意図しない拡大・縮小が起こる

問題
scale() を複数回呼び出すと、ビューが予期以上に拡大または縮小されてしまう。 原因: scale() は現在の変換行列に対して乗算を行うため、連続して呼び出すとスケールファクターが累積されます。例えば、ズームインボタンを押すたびに scale(1.1, 1.1) を呼び出すと、毎回現在のズームレベルから1.1倍されるため、最終的には非常に大きくなります。 トラブルシューティング:

  • resetMatrix() を使用する
    新しいスケールを適用する前に view->resetMatrix() を呼び出して変換行列をリセットし、その後で scale() を呼び出す方法もあります。これは、毎回原点からのスケールを適用したい場合に有効です。
    // 例: スライダーでスケールを制御する場合
    void MyGraphicsView::setZoomLevel(qreal zoomFactor)
    {
        resetMatrix(); // 変換をリセット
        scale(zoomFactor, zoomFactor); // 新しいズームファクターでスケールを設定
    }
    
  • 基準となるスケール値を保持する
    現在の全体のスケール値をメンバー変数などで保持し、それを基準に setTransform()setMatrix() を使用してスケールを設定します。
    // 悪い例 (累積される)
    // view->scale(1.1, 1.1);
    
    // 良い例 (現在のスケール値を保持し、それに基づいて設定)
    qreal currentScale = view->transform().m11(); // 現在の水平方向のスケールファクターを取得
    qreal newScale = currentScale * 1.1; // 新しいスケールファクターを計算
    QTransform transform;
    transform.scale(newScale, newScale);
    view->setTransform(transform); // 全体の変換行列を新しいスケールで設定
    

ズームの原点が意図しない位置になる

問題
ズームイン・ズームアウトした際に、ビューの中心ではなく、左上隅や全く関係のない位置を基準に拡大・縮小されてしまう。 原因: デフォルトの transformationAnchorQGraphicsView::NoAnchor または QGraphicsView::ViewAnchor の可能性があります。これらのアンカー設定では、ユーザーが意図する中心でのズームができません。 トラブルシューティング:

  • setTransformationAnchor() を使用する
    ユーザーの操作に合わせてズームの原点を設定します。
    • QGraphicsView::AnchorUnderMouse: マウスカーソルの位置をズームの原点とします。マウスホイールズームなどで非常に直感的です。
    • QGraphicsView::AnchorViewCenter: ビューの中心をズームの原点とします。
    • QGraphicsView::AnchorSceneCenter: シーンの中心をズームの原点とします。
    // マウスホイールイベントでズームする場合
    void MyGraphicsView::wheelEvent(QWheelEvent* event)
    {
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // これが重要
        // ... (スケール計算と scale() の呼び出し)
    }
    

アイテムの線幅もスケールされてしまう

問題
シーン内のアイテム(QGraphicsRectItem の枠線など)をズームインすると線が太くなり、ズームアウトすると細くなってしまう。 原因: QGraphicsView のスケールはビュー全体に適用されるため、描画されるすべての要素(アイテムの形状、線幅、フォントサイズなど)が影響を受けます。デフォルトでは、線幅もスケールされます。 トラブルシューティング:

  • QGraphicsItem::ItemUsesDeviceCoordinates フラグを使用する
    アイテムの描画にデバイス座標(ピクセル単位)を使用するよう設定することで、ズームの影響を受けないようにできます。
    QGraphicsRectItem* rectItem = new QGraphicsRectItem(0, 0, 100, 100);
    rectItem->setFlag(QGraphicsItem::ItemUsesDeviceCoordinates, true); // これを設定
    // このフラグを設定すると、アイテムの描画はビューの変換行列の影響を受けなくなります。
    // 特に線幅や点のサイズを一定に保ちたい場合に有効です。
    // ただし、アイテムの形状自体もズームの影響を受けなくなるため、注意が必要です。
    // 多くの場合、このフラグはペンの設定と組み合わせて使用されます。
    
    // より一般的な解決策:ペンの設定で線幅をデバイス座標で指定する
    QPen pen(Qt::blue);
    pen.setWidthF(0); // ゼロ幅ペンは、デバイス座標で1ピクセル幅として描画されます。
                      // または QPainter::setRenderHint(QPainter::Antialiasing, true); と組み合わせると
                      // 視覚的にきれいな細線になります。
    rectItem->setPen(pen);
    

スケールするとアイテムがギザギザになる、または描画品質が悪い

問題
拡大・縮小するとアイテムの線がギザギザになったり、画像がぼやけたりする。 原因: デフォルトのレンダリング設定では、アンチエイリアシングやスムーズな変換が行われないため、特に拡大時にピクセルが見えてしまうことがあります。 トラブルシューティング:

  • レンダリングヒントを設定する
    QGraphicsView のコンストラクタなどで、QPainter::AntialiasingQPainter::SmoothPixmapTransform を有効にします。
    MyGraphicsView::MyGraphicsView(QWidget* parent) : QGraphicsView(parent)
    {
        setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
        // ...
    }
    
    • QPainter::Antialiasing: 描画される線や図形のギザギザを軽減します。
    • QPainter::SmoothPixmapTransform: QGraphicsPixmapItem など、ピクセルマップの拡大・縮小時の品質を向上させます。

fitInView() との連携がうまくいかない

問題
fitInView() でビューを初期化した後、scale() を使用すると期待通りに動作しない。 原因: fitInView() はシーンの特定の領域がビューポートに収まるように自動的にスケールを設定します。この後で scale() を呼び出すと、その設定の上にさらにスケールが適用されるため、意図しないズームレベルになることがあります。 トラブルシューティング:

  • resetMatrix() を考慮する
    fitInView() を呼び出す前に resetMatrix() を使用することで、常にクリーンな状態からフィットさせることができます。ただし、通常は fitInView() 自体が適切な変換を設定するため、これはあまり必要ありません。
  • 現在の変換行列を理解する
    fitInView() が設定した変換行列の上に scale() が乗算されることを理解し、必要に応じて view->transform() を取得して現在のスケールファクターを確認します。
  • fitInView() を最初に呼び出す
    アプリケーションの起動時やリサイズイベントなどで、まず fitInView() を呼び出して初期状態を設定します。その後、ユーザーのインタラクション(マウスホイールなど)に応じて scale() を使用します。

問題
多くのアイテムがシーンにある状態でズームすると、動作が重くなる。 原因: QGraphicsView は描画時にすべてのアイテムを再描画するため、アイテム数が多いと負荷が大きくなります。 トラブルシューティング:

  • レベルオブディテール (LOD) の実装
    ズームレベルに応じてアイテムの描画詳細度を変えます。例えば、非常にズームアウトされている場合は簡略化された形状で描画し、ズームインしたときに詳細な描画を行うように QGraphicsItem::paint() メソッドを実装します。QStyleOptionGraphicsItem::levelOfDetailFromTransform() を使用して現在の LOD を取得できます。
  • QGraphicsItem::ItemContainsChildrenInShape または QGraphicsItem::ItemClipsChildrenToShape の使用
    親子関係のあるアイテムの場合、これらのフラグを適切に設定することで、子アイテムの描画範囲を親の範囲に限定し、不要な描画を減らすことができます。
  • setViewportUpdateMode()
    ビューポートの更新モードを調整します。
    • FullViewportUpdate: 毎回ビューポート全体を更新します。(デフォルト)
    • SmartViewportUpdate: 変更があった部分のみを更新しようとしますが、複雑なシーンでは逆に遅くなることもあります。
    • NoViewportUpdate: 更新を全く行いません。手動で viewport()->update() を呼び出す必要があります。
  • setOptimizationFlag(QGraphicsView::DontSavePainterState)
    これは描画時にペインターの状態を保存しないようにし、わずかなパフォーマンス向上をもたらす可能性があります。


例1:マウスホイールによる基本的なズーム(AnchorUnderMouse)

最も一般的で直感的なズーム方法です。マウスカーソルの位置を中心に拡大・縮小します。

mygraphicsview.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QGraphicsView>
#include <QWheelEvent> // QWheelEvent を使うために必要

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicsView(QWidget* parent = nullptr);

protected:
    // マウスホイールイベントをオーバーライド
    void wheelEvent(QWheelEvent* event) override;
};

#endif // MYGRAPHICSVIEW_H

mygraphicsview.cpp

#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>
#include <cmath> // std::pow のために必要

MyGraphicsView::MyGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
    // シーンを作成し、ビューに設定
    QGraphicsScene* scene = new QGraphicsScene(this);
    setScene(scene);

    // テスト用のアイテムを追加
    scene->addRect(0, 0, 100, 100, QPen(Qt::blue), QBrush(Qt::lightGray));
    scene->addEllipse(150, 150, 80, 80, QPen(Qt::red), QBrush(Qt::yellow));
    scene->addLine(0, 0, 200, 200, QPen(Qt::green, 5)); // 太めの線

    // レンダリング品質の向上
    setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);

    // ズームのアンカーをマウスカーソル位置に設定
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);

    // ドラッグによるスクロールを有効にする
    setDragMode(QGraphicsView::ScrollHandDrag);

    // シーン全体がビューに収まるように初期表示を調整 (任意)
    // fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
}

void MyGraphicsView::wheelEvent(QWheelEvent* event)
{
    // マウスホイールの回転量に応じてスケールファクターを計算
    // angleDelta().y() は通常 120 の倍数で、1ステップあたり 15 度
    double numDegrees = event->angleDelta().y() / 8.0;
    double numSteps = numDegrees / 15.0; // 1ステップあたりの回転量
    
    // ズームファクター (例: 1ステップで 1.125 倍ずつ拡大縮小)
    double zoomFactor = std::pow(1.125, numSteps);

    qDebug() << "Zooming by:" << zoomFactor;

    // ビューをスケール
    scale(zoomFactor, zoomFactor);

    // 親クラスのwheelEventを呼び出さない (デフォルトのスクロールなどを抑制)
    // QGraphicsView::wheelEvent(event);
}

main.cpp

#include <QApplication>
#include "mygraphicsview.h"

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

    MyGraphicsView view;
    view.setWindowTitle("QGraphicsView Basic Zoom Example");
    view.resize(600, 400);
    view.show();

    return a.exec();
}

例2:ボタンでズームイン・ズームアウト、およびリセット

スケールが累積される問題に対処し、固定のズームファクターで拡大・縮小し、リセットする機能を追加します。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsView>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGraphicsScene>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void zoomIn();
    void zoomOut();
    void resetZoom();

private:
    QGraphicsView* graphicsView;
    QGraphicsScene* graphicsScene;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QGraphicsRectItem>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // シーンとビューのセットアップ
    graphicsScene = new QGraphicsScene(this);
    graphicsView = new QGraphicsView(graphicsScene);
    
    // テスト用のアイテムを追加
    graphicsScene->addRect(0, 0, 100, 100, QPen(Qt::blue), QBrush(Qt::lightGray));
    graphicsScene->addEllipse(150, 150, 80, 80, QPen(Qt::red), QBrush(Qt::yellow));

    // レンダリング品質の向上
    graphicsView->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    
    // ズームのアンカーをビューの中心に設定 (ボタン操作ではこちらが自然)
    graphicsView->setTransformationAnchor(QGraphicsView::AnchorViewCenter);

    // ボタンの作成
    QPushButton* zoomInButton = new QPushButton("Zoom In");
    QPushButton* zoomOutButton = new QPushButton("Zoom Out");
    QPushButton* resetButton = new QPushButton("Reset Zoom");

    // シグナル・スロットの接続
    connect(zoomInButton, &QPushButton::clicked, this, &MainWindow::zoomIn);
    connect(zoomOutButton, &QPushButton::clicked, this, &MainWindow::zoomOut);
    connect(resetButton, &QPushButton::clicked, this, &MainWindow::resetZoom);

    // レイアウトの作成
    QVBoxLayout* controlLayout = new QVBoxLayout();
    controlLayout->addWidget(zoomInButton);
    controlLayout->addWidget(zoomOutButton);
    controlLayout->addWidget(resetButton);
    controlLayout->addStretch(); // ボタンを上部に寄せる

    QHBoxLayout* mainLayout = new QHBoxLayout();
    mainLayout->addWidget(graphicsView, 1); // graphicsView を拡張
    mainLayout->addLayout(controlLayout);

    QWidget* centralWidget = new QWidget(this);
    centralWidget->setLayout(mainLayout);
    setCentralWidget(centralWidget);

    setWindowTitle("QGraphicsView Button Zoom Example");
    resize(800, 600);
}

MainWindow::~MainWindow()
{
}

void MainWindow::zoomIn()
{
    // 現在のスケールファクターを取得し、そこから 1.1 倍する
    // graphicsView->transform().m11() で現在の水平方向のスケールファクターを取得
    qreal currentScale = graphicsView->transform().m11();
    qreal newScale = currentScale * 1.1;

    // QTransform を使用して新しい変換行列を作成し、ビューに設定
    QTransform transform;
    transform.scale(newScale, newScale);
    graphicsView->setTransform(transform);
    qDebug() << "Zoom In: Current Scale =" << newScale;
}

void MainWindow::zoomOut()
{
    // 現在のスケールファクターを取得し、そこから 0.9 倍する
    qreal currentScale = graphicsView->transform().m11();
    qreal newScale = currentScale * 0.9;
    
    QTransform transform;
    transform.scale(newScale, newScale);
    graphicsView->setTransform(transform);
    qDebug() << "Zoom Out: Current Scale =" << newScale;
}

void MainWindow::resetZoom()
{
    // 変換行列をリセットする
    graphicsView->resetTransform(); // resetMatrix() でも可
    qDebug() << "Zoom Reset: Current Scale =" << graphicsView->transform().m11();
}

main.cpp

#include <QApplication>
#include "mainwindow.h"

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

    MainWindow window;
    window.show();

    return a.exec();
}

QGraphicsItem::ItemUsesDeviceCoordinates フラグと QPen の設定を利用します。

mygraphicsview.h (例1と同じものを使用)

mygraphicsview.cpp (一部変更)

#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>
#include <cmath>

MyGraphicsView::MyGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
    QGraphicsScene* scene = new QGraphicsScene(this);
    setScene(scene);

    // 通常の四角形(線幅もズームされる)
    QGraphicsRectItem* normalRect = scene->addRect(-150, -50, 100, 100, QPen(Qt::darkBlue, 5), QBrush(Qt::cyan));
    normalRect->setPos(0, 0);
    normalRect->setToolTip("Normal Rectangle (line width scales)");

    // 線幅がズームに影響されない四角形
    QGraphicsRectItem* fixedWidthRect = scene->addRect(50, -50, 100, 100, QPen(Qt::darkRed, 0), QBrush(Qt::magenta));
    // ここが重要: ペンの幅を0に設定すると、QPainter::Antialiasing と組み合わせて 1 ピクセル幅になります。
    fixedWidthRect->setPos(0, 0);
    fixedWidthRect->setToolTip("Fixed Width Rectangle (line width is 1 device pixel)");

    // もう一つのアイテム(線幅固定の円)
    QGraphicsEllipseItem* fixedWidthEllipse = scene->addEllipse(-100, 100, 80, 80, QPen(Qt::darkGreen, 0), QBrush(Qt::yellow));
    fixedWidthEllipse->setToolTip("Fixed Width Ellipse (line width is 1 device pixel)");


    setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    setDragMode(QGraphicsView::ScrollHandDrag);

    // シーン全体を表示
    // fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio); // 全てのアイテムを含む矩形にフィット
}

void MyGraphicsView::wheelEvent(QWheelEvent* event)
{
    double numDegrees = event->angleDelta().y() / 8.0;
    double numSteps = numDegrees / 15.0;
    double zoomFactor = std::pow(1.125, numSteps);

    scale(zoomFactor, zoomFactor);
}

// main.cpp は例1と同じものを使用

解説
QPen(Qt::darkRed, 0) のようにペン幅を 0 に設定すると、Qt はこのペン幅をデバイス依存の細線として解釈します。これは通常、ビューポートの1ピクセルに相当します。setRenderHints(QPainter::Antialiasing) と組み合わせることで、拡大・縮小しても線が常に1ピクセル幅で滑らかに描画されます。



QGraphicsView::setTransform() または QGraphicsView::setMatrix() を使用する

scale() は内部的にビューの現在の変換行列に対して乗算を行います。より直接的かつ柔軟にビューの変換を制御したい場合は、QTransform オブジェクトを構築して setTransform() または setMatrix() を使用する方法が適しています。

  • QGraphicsView::setMatrix(const QMatrix &matrix) (旧式)
    QTransform の旧バージョンである QMatrix を使用します。基本的には setTransform(QTransform(matrix)) と同じです。
  • QGraphicsView::setTransform(const QTransform &matrix, bool combine = false)
    • matrix: 新しい変換行列。
    • combine: true の場合、既存の変換行列に matrix を乗算します(scale() と同様の動作)。false の場合、既存の変換を破棄し、matrix を新しい変換として設定します。デフォルトは false です。
  • QTransform クラス
    スケーリング、回転、平行移動(移動)、せん断(歪み)などの幾何学的な変換を表現するための2次元変換行列です。

利点

  • 複雑な変換
    3D的な視点変換など、より複雑なカスタム変換を構築できます(QGraphicsView は2Dですが、数学的な変換は可能です)。
  • 複数変換の結合
    スケーリングと同時に平行移動(パン)や回転を適用できます。
  • 絶対的な制御
    累積的な問題なしに、いつでもビューを特定のズームレベルに設定できます。

使用例(スライダーで絶対ズームレベルを設定)

// mainwindow.h (一部抜粋)
// ...
#include <QSlider>

class MainWindow : public QMainWindow
{
    // ...
private slots:
    void onZoomSliderValueChanged(int value); // スライダーの値に応じてズーム
// ...
private:
    QSlider* zoomSlider;
    qreal currentZoomFactor; // 現在のズームファクターを保持
};
// ...

// mainwindow.cpp (一部抜粋)
// ...
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // ... (QGraphicsView, QGraphicsScene のセットアップ)

    zoomSlider = new QSlider(Qt::Horizontal);
    zoomSlider->setRange(10, 500); // 10% から 500% のズーム範囲
    zoomSlider->setValue(100); // 初期値 100%
    
    connect(zoomSlider, &QSlider::valueChanged, this, &MainWindow::onZoomSliderValueChanged);

    // レイアウトにスライダーを追加
    // ...
    controlLayout->addWidget(zoomSlider);
    // ...
    
    // 初期ズームを設定
    onZoomSliderValueChanged(100);
}

void MainWindow::onZoomSliderValueChanged(int value)
{
    currentZoomFactor = value / 100.0; // 例: 100 -> 1.0, 200 -> 2.0
    
    QTransform transform;
    transform.scale(currentZoomFactor, currentZoomFactor);
    
    graphicsView->setTransform(transform);
    qDebug() << "Absolute Zoom: " << currentZoomFactor * 100 << "%";
}
// ...

QGraphicsView::fitInView() を使用する

特定のアイテム、またはシーン全体の矩形がビューポート内に収まるように自動的にスケールとパンを行うメソッドです。

  • void QGraphicsView::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio)
    指定されたアイテムのバウンディングレクタングルがビューポートに収まるように変換を設定します。
  • void QGraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio)
    指定された矩形がビューポートに収まるように変換を設定します。

利点

  • 初期表示や「全体表示」機能
    アプリケーション起動時や「全体表示」ボタンの実装に最適です。
  • 簡便さ
    特定の領域をビューに収めるための計算を自動で行ってくれます。

使用例(シーン全体をフィット)

// mainwindow.cpp (一部抜粋)
// ...
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // ... (QGraphicsView, QGraphicsScene, アイテムのセットアップ)
    // ... (ボタンなどのセットアップ)

    // シーン全体がビューに収まるように初期表示を設定
    graphicsView->fitInView(graphicsScene->sceneRect(), Qt::KeepAspectRatio);

    QPushButton* fitButton = new QPushButton("Fit to View");
    connect(fitButton, &QPushButton::clicked, this, [this]() {
        graphicsView->fitInView(graphicsScene->sceneRect(), Qt::KeepAspectRatio);
        qDebug() << "Fit to View: Current Scale =" << graphicsView->transform().m11();
    });
    
    // レイアウトにフィットボタンを追加
    // ...
    controlLayout->addWidget(fitButton);
    // ...
}
// ...

QGraphicsItem 自体のスケールを変更する

これは QGraphicsView のスケールとは異なり、個々のアイテムのサイズや位置を直接変更する方法です。ビュー全体のズームではなく、特定のオブジェクトの大きさを変えたい場合に利用します。

  • QGraphicsItem::setTransform(const QTransform &matrix)
    アイテム自身の変換行列を設定します。
  • QGraphicsItem::setScale(qreal scale)
    アイテム自身のスケール係数を設定します。アイテムの元のサイズに対して乗算されます。

利点

  • 永続的なサイズ変更
    ビューのズームは一時的な表示変更ですが、アイテムのスケールはアイテム自体の属性を変更します。
  • アイテム単位の制御
    ビューのズームとは独立して、特定のアイテムの大きさを変更できます。

使用例(アイテムのサイズを動的に変更)

// mygraphicsview.h (例1と同じものを使用)

// mygraphicsview.cpp (一部変更)
#include "mygraphicsview.h"
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QDebug>
#include <cmath>

MyGraphicsView::MyGraphicsView(QWidget* parent) : QGraphicsView(parent)
{
    QGraphicsScene* scene = new QGraphicsScene(this);
    setScene(scene);

    QGraphicsRectItem* item1 = scene->addRect(0, 0, 50, 50, QPen(Qt::green), QBrush(Qt::lightGreen));
    item1->setPos(50, 50);
    item1->setToolTip("Item 1 (fixed size)");

    QGraphicsRectItem* item2 = scene->addRect(0, 0, 50, 50, QPen(Qt::blue), QBrush(Qt::lightBlue));
    item2->setPos(200, 50);
    item2->setToolTip("Item 2 (will be scaled dynamically)");

    // QGraphicsItem 自体のスケールを設定
    item2->setScale(2.0); // item2 は初期状態で2倍の大きさになる

    setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    setDragMode(QGraphicsView::ScrollHandDrag);
}

void MyGraphicsView::wheelEvent(QWheelEvent* event)
{
    // ビューのズームは通常通り
    double numDegrees = event->angleDelta().y() / 8.0;
    double numSteps = numDegrees / 15.0;
    double zoomFactor = std::pow(1.125, numSteps);
    scale(zoomFactor, zoomFactor);

    // オプション: 特定のアイテムのスケールをホイールで変更
    // 例えば、Controlキーを押しながらホイールするとアイテムをスケール
    if (event->modifiers() & Qt::ControlModifier) {
        QList<QGraphicsItem*> items = scene()->items(mapToScene(event->pos()));
        if (!items.isEmpty()) {
            QGraphicsItem* topItem = items.first();
            qreal currentItemScale = topItem->scale();
            qreal newItemScale = currentItemScale * zoomFactor;
            topItem->setScale(newItemScale);
            qDebug() << "Item scale changed to:" << newItemScale;
        }
    }
}
// main.cpp は例1と同じものを使用

パフォーマンスが最優先される場合や、より高度なレンダリング機能(例えば3Dレンダリングとの統合)が必要な場合、QGraphicsView のビューポートをカスタムウィジェットに置き換えることができます。例えば QOpenGLWidget をビューポートとして設定し、OpenGL で直接レンダリングを行うことで、非常に複雑なシーンでも高速なズームやパンを実現できます。

  • QGraphicsView::setViewport(QWidget *widget)
    ビューポートウィジェットを設定します。

利点

  • 高度な描画
    OpenGL などのカスタム描画 API をフルに利用できます。
  • 最高のパフォーマンス
    GPUアクセラレーションを最大限に活用できます。

考慮事項

  • Qt の描画パイプラインのバイパス
    QGraphicsItempaint() メソッドを直接呼び出すのではなく、カスタム描画ロジックを実装する必要があります。
  • 複雑性
    カスタムレンダリングコードを書く必要があるため、実装が複雑になります。

QGraphicsView::scale() は最も簡単で直感的なズーム方法ですが、より詳細な制御や特定の要件がある場合は、以下の代替手段を検討してください。

  1. setTransform()
    絶対的なズームレベルの制御や、複数の変換を組み合わせたい場合に最適。
  2. fitInView()
    シーン全体や特定の領域をビューに収めたい場合に便利。
  3. QGraphicsItem::setScale()
    ビューのズームとは独立して、個々のアイテムのサイズを変更したい場合に利用。
  4. カスタムビューポート
    最高のパフォーマンスや特殊なレンダリングが必要な場合に検討。