QGraphicsItem::scale() 活用術:Qtアイテムのサイズ変更をマスター
QGraphicsItem::scale() とは
QGraphicsItem::scale()
は、Qtのグラフィックスビューフレームワークにおいて、QGraphicsItem
(例えば、QGraphicsRectItem
やQGraphicsPixmapItem
など)の現在の拡大率を取得するための関数です。これは、アイテムが自身の原点を基準としてどれくらい拡大または縮小されているかを示す値を返します。
拡大率の仕組み
グラフィックスビューフレームワークでは、各QGraphicsItem
は独自のローカル座標系を持っています。scale()
関数が返す値は、このローカル座標系が親アイテムまたはシーンの座標系に対してどれだけ拡大されているかを示します。
- 値の意味
1.0
: アイテムは拡大も縮小もされていません。(オリジナルのサイズ)> 1.0
: アイテムは拡大されています。(例:2.0
なら2倍の大きさ)< 1.0
(ただし> 0.0
): アイテムは縮小されています。(例:0.5
なら半分の大きさ)
通常、scale()
関数は、アイテムの現在の視覚的なサイズを計算したり、アイテムにさらに拡大/縮小を適用する前の状態を確認したりするのに使用されます。
Qt のグラフィックスビューフレームワークにおけるアイテムのスケーリングは強力ですが、意図しない挙動を引き起こすこともあります。
アイテムの見た目のサイズが変わらない/おかしい
問題
setScale()
を呼び出したのに、アイテムの見た目のサイズが変わらない、または期待通りに変化しない。
考えられる原因と解決策
- ビューの変換の影響
QGraphicsView
自体もズームやパンなどの変換を持つことができます(例:QGraphicsView::setTransform()
やscale()
メソッド)。アイテムのスケールとビューのスケールが組み合わさって、最終的な表示サイズが決まります。- トラブルシューティング
ビューの現在の変換状態を確認してください。ビューのスケールをリセットしたり、アイテムのスケールとビューのスケールの相互作用を考慮して計算を行う必要があるかもしれません。
- トラブルシューティング
- 親アイテムの影響
子アイテムのscale()
は、その親アイテムのスケールと組み合わされて最終的な表示サイズが決まります。親アイテムがすでにスケールされている場合、子アイテムのscale()
値が小さくても、実際には大きく表示されることがあります。- トラブルシューティング
アイテム階層を考慮し、各アイテムのスケールがどのように累積されるかを理解してください。場合によっては、親アイテムのスケールを操作するか、ItemIgnoresTransformations
フラグを使用して、親の変換(スケールを含む)を無視するように子アイテムを設定することを検討してください。
- トラブルシューティング
- boundingRect() の問題
QGraphicsItem
を継承してカスタムアイテムを作成している場合、boundingRect()
関数が正しくアイテムの境界を返していない可能性があります。boundingRect()
はアイテムのローカル座標系における境界を定義し、これがレンダリングや衝突検出に使われます。scale()
はこのboundingRect()
を基準に拡大縮小するため、boundingRect()
が不正確だと見た目もおかしくなります。- トラブルシューティング
boundingRect()
がアイテムの実際の描画内容を正確に囲むように実装されているか確認してください。特に、アイテムのサイズを変更する際にprepareGeometryChange()
を呼び出すのを忘れていないか確認してください。これにより、グラフィックスビューフレームワークはアイテムのジオメトリが変更されたことを認識し、再描画をトリクエストします。
- トラブルシューティング
アイテムが不鮮明に表示される(アンチエイリアシングの問題)
問題
アイテムを拡大すると、境界線がギザギザになったり、全体的に不鮮明になったりする。
考えられる原因と解決策
- アンチエイリアシングの不足
スケーリングされたグラフィックスを滑らかに描画するためには、アンチエイリアシングが必要です。- トラブルシューティング
QGraphicsView
またはQPainter
に適切なレンダリングヒントを設定してください。// QGraphicsView でアンチエイリアシングを有効にする view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); // paint() 関数内で QPainter にアンチエイリアシングを設定する void MyGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setRenderHint(QPainter::Antialiasing); // ... 描画コード ... }
QPainter::SmoothPixmapTransform
は、ピクスマップがスケールされたときに品質を向上させます。
- トラブルシューティング
イベント座標とアイテム座標のずれ
問題
アイテムがスケールされている状態でマウスイベント(クリック、ドラッグなど)を処理すると、クリックした位置とアイテムの内部座標がずれる。
考えられる原因と解決策
- 座標系の変換不足
QGraphicsItem
のマウスイベントハンドラ(例:mousePressEvent
)で取得できるevent->pos()
は、デフォルトではアイテムのローカル座標系におけるマウスの位置を返します。アイテムがスケールされている場合、このローカル座標はシーン座標やビューポート座標とは異なります。- トラブルシューティング
- シーン座標で処理する場合
event->scenePos()
を使用して、シーン座標におけるマウスの位置を取得します。 - ビューポート座標で処理する場合
event->screenPos()
を使用します。 - 明示的な座標変換
必要に応じて、mapToItem()
,mapFromItem()
,mapToScene()
,mapFromScene()
,mapToParent()
,mapFromParent()
などの変換関数を使用して、異なる座標系間の変換を明示的に行います。
スケールが適用されている場合、// mousePressEvent(QGraphicsSceneMouseEvent *event) の中で QPointF itemLocalPos = event->pos(); // アイテムのローカル座標 QPointF scenePos = event->scenePos(); // シーン座標 QPointF mappedBackToItem = mapFromScene(scenePos); // シーン座標からアイテムのローカル座標に戻す qDebug() << "アイテムローカル座標:" << itemLocalPos; qDebug() << "シーン座標:" << scenePos; qDebug() << "マップし直したアイテムローカル座標:" << mappedBackToItem;
itemLocalPos
とmappedBackToItem
は一致しないことに注意してください。これは、event->pos()
が既にスケール変換を反映したローカル座標を返すためです。アイテムのオリジナルサイズでのローカル座標が必要な場合は、mapFromScene(event->scenePos())
のように、いったんシーン座標に変換してからアイテムのローカル座標に変換し直すのが正確です。 - シーン座標で処理する場合
- トラブルシューティング
アイテムのペン幅がスケーリングされる問題
問題
アイテムを拡大すると、その境界線や描画に使っているペンの太さも一緒に拡大されてしまい、見た目が太くなる。線幅を一定に保ちたい。
考えられる原因と解決策
- デフォルトの描画動作
QPainter
はデフォルトで現在の変換(スケールを含む)に従って描画します。- トラブルシューティング
- QPen の cosmetic プロパティを使用する
QPen::setCosmetic(true)
を設定すると、ペンの幅は常に1ピクセルとして描画され、変換の影響を受けません。
QPen pen; pen.setWidth(1); // 任意の幅 pen.setCosmetic(true); // スケールに影響されないようにする painter->setPen(pen);
- スケールを考慮してペン幅を設定する
アイテムのスケールを取得し、その逆数でペン幅を調整することで、見た目の太さを一定に保つことができます。
この方法は、void MyGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { qreal currentScale = painter->transform().m11(); // X軸方向のスケールを取得 (単純な均一スケールの場合) if (currentScale == 0) currentScale = 1.0; // ゼロ除算回避 qreal penWidth = 1.0 / currentScale; // 例: 1ピクセルの線を維持したい場合 QPen pen(Qt::black); pen.setWidthF(penWidth); // float値で幅を設定 painter->setPen(pen); // ... 描画コード ... }
QGraphicsView
のズームも考慮に入れたい場合に特に有効です。painter->transform()
を使用することで、アイテム自身のスケールだけでなく、ビューのスケールも考慮した現在の変換行列からスケール値を取得できます。 - QPen の cosmetic プロパティを使用する
- トラブルシューティング
描画位置のずれ(特にアイテムの中心を基準にスケールしたい場合)
問題
setScale()
を呼び出すと、アイテムが画面の左上を基準に拡大縮小されてしまい、望む中心位置からずれてしまう。
- 変換原点
QGraphicsItem
の変換(スケール、回転など)は、デフォルトではアイテムのローカル座標の(0,0)
を原点として適用されます。- トラブルシューティング
- setTransformOriginPoint() を使用する
スケールや回転の原点をアイテムの任意の位置に設定できます。アイテムの中心を基準に拡大縮小したい場合は、そのアイテムのboundingRect().center()
を原点に設定します。
// アイテムの中心を原点として設定 item->setTransformOriginPoint(item->boundingRect().center()); item->setScale(newScale);
- 手動で位置を調整する
setTransformOriginPoint()
を使用せずに、setScale()
を適用する前にアイテムを移動させ、適用後に再び移動させて中心を合わせることも可能ですが、より複雑になります。通常はsetTransformOriginPoint()
を使用するのが推奨されます。
- setTransformOriginPoint() を使用する
- トラブルシューティング
基本的なアイテムの拡大・縮小
最も基本的な例として、QGraphicsRectItem
を作成し、そのスケールを変更する例です。
// main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// シーンとビューを作成
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing); // アンチエイリアシングを有効にする
// 四角形アイテムを作成
QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
rectItem->setBrush(Qt::blue);
rectItem->setPen(QPen(Qt::black, 2)); // 黒い線で描画
rectItem->setPos(50, 50); // シーン上の位置
scene->addItem(rectItem);
// スケール変更用のボタンを作成
QPushButton *scaleUpButton = new QPushButton("拡大 (x1.2)");
QPushButton *scaleDownButton = new QPushButton("縮小 (x0.8)");
QPushButton *resetScaleButton = new QPushButton("スケールをリセット");
// ボタンのレイアウト
QVBoxLayout *buttonLayout = new QVBoxLayout();
buttonLayout->addWidget(scaleUpButton);
buttonLayout->addWidget(scaleDownButton);
buttonLayout->addWidget(resetScaleButton);
QWidget *controlWidget = new QWidget();
controlWidget->setLayout(buttonLayout);
// メインウィンドウのレイアウト
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->addWidget(view);
mainLayout->addWidget(controlWidget);
QWidget *mainWindow = new QWidget();
mainWindow->setLayout(mainLayout);
mainWindow->setWindowTitle("QGraphicsItem Scale Example");
mainWindow->resize(800, 600);
mainWindow->show();
// スケールアップボタンの接続
QObject::connect(scaleUpButton, &QPushButton::clicked, [&]() {
qreal currentScale = rectItem->scale();
rectItem->setScale(currentScale * 1.2);
qDebug() << "現在のスケール (拡大):" << rectItem->scale();
});
// スケールダウンボタンの接続
QObject::connect(scaleDownButton, &QPushButton::clicked, [&]() {
qreal currentScale = rectItem->scale();
rectItem->setScale(currentScale * 0.8);
qDebug() << "現在のスケール (縮小):" << rectItem->scale();
});
// スケールリセットボタンの接続
QObject::connect(resetScaleButton, &QPushButton::clicked, [&]() {
rectItem->setScale(1.0);
qDebug() << "スケールをリセット:" << rectItem->scale();
});
return a.exec();
}
解説
view->setRenderHint(QPainter::Antialiasing);
は、拡大時に描画が滑らかになるようにアンチエイリアシングを有効にしています。rectItem->scale();
で現在の拡大率を取得し、それに基づいて新たな拡大率を計算しています。rectItem->setScale(factor);
でアイテムの拡大率を設定します。rectItem->setPos(50, 50);
でアイテムをシーンの (50, 50) の位置に配置します。QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
で幅100、高さ50の長方形を作成します。このサイズが「オリジナル」のサイズとなります。
アイテムの中心を基準にスケールする
デフォルトでは、QGraphicsItem
のスケールはアイテムのローカル座標系の (0,0)
(通常は左上隅) を基準に行われます。アイテムの中心を基準にスケールしたい場合は、setTransformOriginPoint()
を使用します。
// main.cpp (上記コードに以下を追加または修正)
// 四角形アイテムを作成 (変更なし)
QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
rectItem->setBrush(Qt::blue);
rectItem->setPen(QPen(Qt::black, 2));
rectItem->setPos(50, 50);
scene->addItem(rectItem);
// アイテムの中心を変換の原点に設定
// QRectF::center() は boundingRect() の中心点を返します
rectItem->setTransformOriginPoint(rectItem->boundingRect().center());
qDebug() << "変換原点:" << rectItem->transformOriginPoint();
// 後は上記コードのボタン接続と同じロジックで setScale() を呼び出す
解説
setTransformOriginPoint()
にこの中心点を設定することで、今後のsetScale()
やsetRotation()
などの変換がその点を基準に行われるようになります。これにより、アイテムがその場にとどまったままサイズが変化する(拡大・縮小される)ように見えます。rectItem->boundingRect().center()
は、アイテムの境界矩形の中心点を返します。
ペン幅をスケールに依存させない
アイテムを拡大しても、その描画に使われている線の太さを一定に保ちたい場合があります。
// カスタムアイテムのヘッダーファイル (myrectitem.h)
#ifndef MYRECTITEM_H
#define MYRECTITEM_H
#include <QGraphicsRectItem>
#include <QPainter>
class MyRectItem : public QGraphicsRectItem
{
public:
MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent = nullptr);
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
};
#endif // MYRECTITEM_H
// カスタムアイテムの実装ファイル (myrectitem.cpp)
#include "myrectitem.h"
MyRectItem::MyRectItem(qreal x, qreal y, qreal width, qreal height, QGraphicsItem *parent)
: QGraphicsRectItem(x, y, width, height, parent)
{
setBrush(Qt::blue);
}
void MyRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
// 方法1: コスメティックペンを使用する (推奨)
// このペンの幅は常に1ピクセルとして扱われ、変換の影響を受けない
QPen cosmeticPen(Qt::black, 1); // 1ピクセル幅のペン
cosmeticPen.setCosmetic(true);
painter->setPen(cosmeticPen);
painter->setBrush(brush()); // 設定されているブラシを使用
painter->drawRect(rect()); // QGraphicsRectItem の rect() を描画
// 方法2: 現在の変換行列に基づいてペン幅を調整する (より細かい制御が必要な場合)
/*
qreal currentViewScale = painter->transform().m11(); // X軸方向のスケールを取得 (単純な均一スケールの場合)
if (currentViewScale == 0) currentViewScale = 1.0;
qreal desiredPenWidth = 2.0; // 常に2ピクセルの線幅を維持したい場合
QPen dynamicPen(Qt::black);
dynamicPen.setWidthF(desiredPenWidth / currentViewScale); // スケールの逆数で調整
painter->setPen(dynamicPen);
painter->setBrush(brush());
painter->drawRect(rect());
*/
}
// main.cpp (MyRectItem を使用するように修正)
#include "myrectitem.h"
// ... (他の include と main 関数開始まで同じ)
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
// MyRectItem を使用
MyRectItem *rectItem = new MyRectItem(0, 0, 100, 50);
rectItem->setPos(50, 50);
rectItem->setTransformOriginPoint(rectItem->boundingRect().center()); // 中心を原点に
scene->addItem(rectItem);
// ... (ボタンの作成と接続は上記と同じ)
return a.exec();
}
解説
painter->transform().m11()
:paint()
関数内でQPainter
オブジェクトの現在の変換行列からスケール成分を取得し、それに基づいてペン幅を計算する例も示しています。これは、ビューのズームとアイテムのスケールの両方がペンの見た目に影響を与える場合に、より柔軟な制御を可能にします。QPen::setCosmetic(true)
: これが最も簡単な方法です。このフラグを設定すると、ペンの幅は常に1ピクセルとして描画され、アイテムやビューのスケールの影響を受けません。
アイテムが親アイテムやビューの変換(スケール、回転など)を無視して、常に同じサイズと向きで表示されるようにしたい場合があります。これは、例えば地図上のアイコンや、ユーザーインターフェース要素などで役立ちます。
// main.cpp (上記コードに以下を追加または修正)
// ... (他の include と main 関数開始まで同じ)
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
// 通常の四角形アイテム (ビューのスケールの影響を受ける)
QGraphicsRectItem *scaledRect = new QGraphicsRectItem(0, 0, 100, 50);
scaledRect->setBrush(Qt::green);
scaledRect->setPen(QPen(Qt::black, 2));
scaledRect->setPos(50, 50);
scaledRect->setTransformOriginPoint(scaledRect->boundingRect().center());
scene->addItem(scaledRect);
// スケールを無視する四角形アイテム
QGraphicsRectItem *fixedSizeRect = new QGraphicsRectItem(0, 0, 80, 40);
fixedSizeRect->setBrush(Qt::red);
fixedSizeRect->setPen(QPen(Qt::black, 2));
fixedSizeRect->setPos(200, 50); // 別の位置に配置
// ここが重要: 親やビューの変換を無視するフラグを設定
fixedSizeRect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(fixedSizeRect);
// ビューのズームを制御するスライダー
QSlider *zoomSlider = new QSlider(Qt::Horizontal);
zoomSlider->setRange(10, 200); // 10% から 200%
zoomSlider->setValue(100);
zoomSlider->setTickInterval(10);
zoomSlider->setTickPosition(QSlider::TicksBelow);
// スライダーのレイアウト
QVBoxLayout *sliderLayout = new QVBoxLayout();
sliderLayout->addWidget(new QLabel("ビューのズーム:"));
sliderLayout->addWidget(zoomSlider);
QWidget *controlWidget = new QWidget();
controlWidget->setLayout(sliderLayout);
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->addWidget(view);
mainLayout->addWidget(controlWidget);
QWidget *mainWindow = new QWidget();
mainWindow->setLayout(mainLayout);
mainWindow->setWindowTitle("ItemIgnoresTransformations Example");
mainWindow->resize(800, 600);
mainWindow->show();
// スライダーの変更にビューのスケールを接続
QObject::connect(zoomSlider, &QSlider::valueChanged, [&](int value) {
qreal scaleFactor = value / 100.0;
QTransform transform;
transform.scale(scaleFactor, scaleFactor);
view->setTransform(transform);
qDebug() << "ビューのスケール:" << scaleFactor;
});
return a.exec();
}
- この例では、スライダーで
QGraphicsView
のズームを変更していますが、scaledRect
はズームに合わせてサイズが変わるのに対し、fixedSizeRect
は常に同じ見た目のサイズを保ちます。 fixedSizeRect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
がポイントです。このフラグを設定したアイテムは、親アイテムやQGraphicsView
のsetTransform()
によって適用されるスケール、回転、シアーなどの変換を無視します。
QTransform を直接操作する
QGraphicsItem::setTransform()
を使用して、変換行列 QTransform
を直接設定することで、スケーリングだけでなく、回転、平行移動、シアー(傾斜)などの複数の変換を一度に適用できます。setScale()
は、内部的にはこの QTransform
を簡略化して設定しているに過ぎません。
利点
- 複雑な変換
より高度な変換(例えば、非対称なシアー)を実現できます。 - 複数の変換を結合
スケール、回転、平行移動などを一度に適用でき、変換の順序を厳密に制御できます。
欠点
- 既存の変換を維持しつつスケールを変更する場合、現在の
QTransform
を取得し、それにスケールを適用し、再度設定する必要があります。 setScale()
よりも理解と計算が複雑になる可能性があります。
コード例
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTransform>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
rectItem->setBrush(Qt::green);
rectItem->setPen(QPen(Qt::black, 2));
rectItem->setPos(50, 50);
rectItem->setTransformOriginPoint(rectItem->boundingRect().center()); // 中心を原点に
scene->addItem(rectItem);
QPushButton *transformScaleButton = new QPushButton("Transformで拡大 (x1.5)");
QPushButton *transformResetButton = new QPushButton("Transformをリセット");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(transformScaleButton);
layout->addWidget(transformResetButton);
QWidget *controlWidget = new QWidget();
controlWidget->setLayout(layout);
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->addWidget(view);
mainLayout->addWidget(controlWidget);
QWidget *mainWindow = new QWidget();
mainWindow->setLayout(mainLayout);
mainWindow->setWindowTitle("QTransform Scale Example");
mainWindow->resize(800, 600);
mainWindow->show();
// Transformで拡大する
QObject::connect(transformScaleButton, &QPushButton::clicked, [&]() {
QTransform currentTransform = rectItem->transform();
// 現在のスケール値を取得 (X軸とY軸が均一スケールの場合)
qreal currentScaleX = currentTransform.m11();
qreal currentScaleY = currentTransform.m22();
// 新しいスケールを適用
currentTransform.scale(1.5, 1.5);
rectItem->setTransform(currentTransform);
qDebug() << "Transformで拡大後のスケール行列:\n" << rectItem->transform();
});
// Transformをリセットする
QObject::connect(transformResetButton, &QPushButton::clicked, [&]() {
rectItem->setTransform(QTransform()); // デフォルトの単位行列に戻す
qDebug() << "Transformをリセット後のスケール行列:\n" << rectItem->transform();
});
return a.exec();
}
setTransform(const QTransform &matrix, bool combine = false) の combine 引数について
setTransform()
には combine
というブール引数があります。
true
: 新しいmatrix
を既存の変換に乗算します。例えば、アイテムがすでに2倍にスケールされている状態でsetTransform(QTransform().scale(1.5, 1.5), true)
を呼び出すと、最終的なスケールは2 * 1.5 = 3
倍になります。これはsetScale(currentScale * 1.5)
と似た挙動になりますが、より一般的な変換行列に対して適用されます。false
(デフォルト): 既存の変換を破棄し、新しいmatrix
で置き換えます。上記の例ではこれを使用しています。
QGraphicsTransform クラスを使用する (Qt 4.6 以降)
Qt 4.6 から導入された QGraphicsTransform
は、QGraphicsItem
の変換をよりモジュール的に管理するための抽象ベースクラスです。これを使用すると、複数の変換(スケール、回転など)をそれぞれ独立したオブジェクトとして定義し、それらをアイテムに適用できます。
利点
- アニメーションとの親和性
QPropertyAnimation
などと組み合わせて、スムーズな変換アニメーションを実装しやすくなります。 - モジュール性
各変換が独立したオブジェクトとして管理されるため、複数の変換を簡単に組み合わせたり、個別に変更したりできます。
欠点
- シンプルなスケーリングには過剰な場合もあります。
setScale()
に比べてコード量が増え、オーバーヘッドが若干発生する可能性があります。
コード例
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsScale> // QGraphicsScale をインクルード
#include <QGraphicsRotation>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
rectItem->setBrush(Qt::magenta);
rectItem->setPen(QPen(Qt::black, 2));
rectItem->setPos(100, 100);
rectItem->setTransformOriginPoint(rectItem->boundingRect().center()); // 中心を原点に
scene->addItem(rectItem);
// QGraphicsScale オブジェクトを作成
QGraphicsScale *scaleTransform = new QGraphicsScale();
scaleTransform->setXScale(1.0);
scaleTransform->setYScale(1.0);
scaleTransform->setZScale(1.0); // 2DではZは通常1.0
// アイテムに変換を設定
QList<QGraphicsTransform *> transforms;
transforms.append(scaleTransform);
rectItem->setTransformations(transforms);
QPushButton *scaleUpTransformButton = new QPushButton("QGraphicsScaleで拡大 (x1.2)");
QPushButton *scaleDownTransformButton = new QPushButton("QGraphicsScaleで縮小 (x0.8)");
QPushButton *resetTransformButton = new QPushButton("QGraphicsScaleをリセット");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(scaleUpTransformButton);
layout->addWidget(scaleDownTransformButton);
layout->addWidget(resetTransformButton);
QWidget *controlWidget = new QWidget();
controlWidget->setLayout(layout);
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->addWidget(view);
mainLayout->addWidget(controlWidget);
QWidget *mainWindow = new QWidget();
mainWindow->setLayout(mainLayout);
mainWindow->setWindowTitle("QGraphicsTransform Scale Example");
mainWindow->resize(800, 600);
mainWindow->show();
// 拡大
QObject::connect(scaleUpTransformButton, &QPushButton::clicked, [&]() {
scaleTransform->setScale(scaleTransform->scale() * 1.2);
qDebug() << "QGraphicsScaleで拡大後のスケール:" << scaleTransform->scale();
});
// 縮小
QObject::connect(scaleDownTransformButton, &QPushButton::clicked, [&]() {
scaleTransform->setScale(scaleTransform->scale() * 0.8);
qDebug() << "QGraphicsScaleで縮小後のスケール:" << scaleTransform->scale();
});
// リセット
QObject::connect(resetTransformButton, &QPushButton::clicked, [&]() {
scaleTransform->setScale(1.0);
qDebug() << "QGraphicsScaleをリセット:" << scaleTransform->scale();
});
return a.exec();
}
解説
QPropertyAnimation
を使用してQGraphicsScale::scale
プロパティをアニメーションさせることも可能です。QGraphicsItem::setTransformations()
にQGraphicsTransform
のリストを渡すことで、アイテムにこれらの変換を適用します。リスト内の変換は定義された順序で適用されます。QGraphicsScale
クラスのインスタンスを作成し、setXScale()
,setYScale()
,setZScale()
またはsetScale()
でスケール値を設定します。
アイテムのサイズを直接変更する (QGraphicsRectItem などのプリミティブアイテムの場合)
QGraphicsRectItem
や QGraphicsEllipseItem
など、Qt が提供するプリミティブなグラフィックスアイテムの場合、setRect()
や setEllipse()
のように、そのアイテムが持つ形状のプロパティを直接変更することで、見た目のサイズをスケーリングするのと同じ効果を得ることができます。
利点
- 正確なサイズ設定
拡大率ではなく、直接ピクセル単位でサイズを指定できます。 - シンプル
特定のアイテムタイプに特化しており、理解しやすいです。
欠点
- アイテムの原点が常に左上隅になりがちで、中央を基準としたスケーリングが複雑になります。
QGraphicsPathItem
のような複雑なパスを持つアイテムには適用できません。- 回転やシアーなどの他の変換と組み合わせるのが難しい場合があります。
コード例
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 50);
rectItem->setBrush(Qt::cyan);
rectItem->setPen(QPen(Qt::black, 2));
rectItem->setPos(150, 150); // シーン上の位置
scene->addItem(rectItem);
// QGraphicsRectItem の現在の矩形を取得
QRectF currentRect = rectItem->rect();
qDebug() << "初期矩形:" << currentRect;
QPushButton *resizeButton = new QPushButton("サイズ変更 (x1.2)");
QPushButton *resetSizeButton = new QPushButton("サイズをリセット");
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(resizeButton);
layout->addWidget(resetSizeButton);
QWidget *controlWidget = new QWidget();
controlWidget->setLayout(layout);
QHBoxLayout *mainLayout = new QHBoxLayout();
mainLayout->addWidget(view);
mainLayout->addWidget(controlWidget);
QWidget *mainWindow = new QWidget();
mainWindow->setLayout(mainLayout);
mainWindow->setWindowTitle("Direct Size Change Example");
mainWindow->resize(800, 600);
mainWindow->show();
// サイズ変更
QObject::connect(resizeButton, &QPushButton::clicked, [&]() {
QRectF r = rectItem->rect();
// 矩形のサイズを変更(原点を維持しない単純な拡大)
// アイテムの中心を維持したい場合は、setRect() の前に位置も調整する必要がある
rectItem->setRect(r.x(), r.y(), r.width() * 1.2, r.height() * 1.2);
qDebug() << "サイズ変更後の矩形:" << rectItem->rect();
});
// サイズをリセット
QObject::connect(resetSizeButton, &QPushButton::clicked, [&]() {
rectItem->setRect(0, 0, 100, 50); // 元のサイズに戻す
qDebug() << "サイズリセット後の矩形:" << rectItem->rect();
});
return a.exec();
}
- この方法では、アイテムの
pos()
はそのアイテムの左上隅 (0,0) を基準としたシーン上の位置なので、アイテムの中心を基準に拡大縮小したい場合は、setRect()
を呼び出す前にアイテムのpos()
も適切に調整する必要があります。- 例えば、アイテムの現在の中心を取得し、新しいサイズでの中心も計算し、その差分だけ
setPos()
で移動させる、といったロジックが必要です。QGraphicsItem::setTransformOriginPoint()
はこの面倒な計算を自動的に行ってくれるため、通常はそちらが推奨されます。
- 例えば、アイテムの現在の中心を取得し、新しいサイズでの中心も計算し、その差分だけ
rectItem->setRect(x, y, width, height);
を使用して、アイテムの矩形を直接変更しています。
- プリミティブアイテムのサイズを直接変更したい場合
setRect()
,setEllipse()
などのアイテム固有の関数も使えますが、アイテムの原点を意識した位置調整が必要です。 - 複雑な変換の結合、アニメーション、または変換を個別に管理したい場合
QGraphicsItem::setTransform()
を直接使用するか、QGraphicsTransform
クラス(QGraphicsScale
,QGraphicsRotation
など)を使用するのが適しています。 - 中心を基準としたスケール、または他の変換(回転、移動)と組み合わせる場合
QGraphicsItem::setTransformOriginPoint()
とsetScale()
の組み合わせが強力で使いやすいです。 - 簡単な均一スケール
QGraphicsItem::setScale()
が最もシンプルで直接的です。