変換の例
「変形」の例では、変形がQPainter によるグラフィックプリミティブの描画にどのような影響を与えるかを示しています。

このアプリケーションでは、QPainter の座標系の並進、回転、拡大縮小を変更することで、図形の描画を操作することができます。
このサンプルは、2つのクラスと1つのグローバル列挙型で構成されています。
RenderAreaクラスは、指定された図形のレンダリングを制御します。Windowクラスは、アプリケーションのメインウィンドウです。Operation列挙型は、アプリケーションで利用可能なさまざまな変換操作を定義しています。
まず、「Operation 」列について簡単に説明し、次に「RenderArea 」クラスを見て、図形がどのように描画されるかを確認します。最後に、「Window 」クラスに実装されている「Transformations」アプリケーションの機能について見ていきます。
変換操作
通常、QPainter は関連付けられたデバイス独自の座標系上で動作しますが、座標変換についても十分にサポートされています。
ペイントデバイスのデフォルトの座標系は、左上隅を原点としています。x 値は右に向かって増加し、y 値は下に向かって増加します。QPainter::scale() 関数を使用して座標系を指定されたオフセット分だけ拡大縮小したり、QPainter::rotate() 関数を使用して時計回りに回転させたり、QPainter::translate() 関数を使用して平行移動(つまり、各座標に指定されたオフセットを加算)したりすることができます。また、QPainter::shear() 関数を使用して、原点を中心に座標系をねじること(シアと呼ばれる)も可能です。
すべての変換操作は、`QPainter`の変換行列に対して行われます。この行列は、`QPainter::worldTransform()`関数を使用して取得できます。行列は、平面上の1つの点を別の点に変換します。変換行列の詳細については、「座標系」および「QTransform 」のドキュメントを参照してください。
enum Operation { NoTransformation, Translate, Rotate, Scale };グローバル列挙型 `Operation ` は `renderarea.h ` ファイルで宣言されており、`Transformations` アプリケーションで利用可能なさまざまな変換操作を定義しています。
RenderAreaクラスの定義
RenderArea クラスはQWidget を継承しており、指定された図形のレンダリングを制御します。
class RenderArea : public QWidget
{
Q_OBJECT
public:
RenderArea(QWidget *parent = nullptr);
void setOperations(const QList<Operation> &operations);
void setShape(const QPainterPath &shape);
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;RenderArea ウィジェットの形状を指定し、その形状がレンダリングされる座標系を変換できるように、2つのパブリック関数 `setOperations() ` および `setShape()` を宣言します。
QWidget のminimumSizeHint()およびsizeHint()関数を再実装して、RenderArea ウィジェットにアプリケーション内で適切なサイズを与え、QWidget::paintEvent()イベントハンドラを再実装して、ユーザーが選択した変換を適用してレンダリング領域の形状を描画します。
private:
void drawCoordinates(QPainter &painter);
void drawOutline(QPainter &painter);
void drawShape(QPainter &painter);
void transformPainter(QPainter &painter);
QList<Operation> operations;
QPainterPath shape;
QRect xBoundingRect;
QRect yBoundingRect;
};また、図形や座標系の輪郭、座標を描画したり、選択された変形に従ってペインターを変形させたりするための、いくつかの便利な関数を宣言します。
さらに、RenderArea ウィジェットは、現在適用されている変換操作のリスト、その形状への参照、および座標の描画時に使用するいくつかの便利変数を保持しています。
RenderArea クラスの実装
RenderArea ウィジェットは、QWidget::paintEvent()イベントハンドラを再実装することで、座標系の変換を含め、指定された図形の描画を制御します。しかし、まずはコンストラクタと、RenderArea ウィジェットへのアクセスを提供する関数について簡単に見ていきましょう。
RenderArea::RenderArea(QWidget *parent)
: QWidget(parent)
{
QFont newFont = font();
newFont.setPixelSize(12);
setFont(newFont);
QFontMetrics fontMetrics(newFont);
xBoundingRect = fontMetrics.boundingRect(tr("x"));
yBoundingRect = fontMetrics.boundingRect(tr("y"));
}コンストラクタでは、parentパラメータを基底クラスに渡すとともに、座標の描画に使用するフォントをカスタマイズします。QWidget::font() 関数は、ウィジェットに現在設定されているフォントを返します。特別なフォントが設定されていない場合、またはQWidget::setFont() が呼び出された後は、これはウィジェットクラス専用のフォント、親のフォント、あるいは(このウィジェットが最上位ウィジェットである場合)アプリケーションのデフォルトフォントのいずれかになります。
フォントサイズが12ポイントであることを確認した後、QFontMetrics クラスを使用して、座標を表す文字「x」と「y」を囲む矩形を抽出します。
QFontMetrics は、フォントやその文字、およびそのフォントでレンダリングされた文字列の個々のメトリクスにアクセスするための関数を提供します。QFontMetrics::boundingRect() 関数は、ベースラインの最左端を基準とした、指定された文字の境界矩形を返します。
void RenderArea::setOperations(const QList<Operation> &operations)
{
this->operations = operations;
update();
}
void RenderArea::setShape(const QPainterPath &shape)
{
this->shape = shape;
update();
}setShape() およびsetOperations() 関数では、新しい値を格納した後、QWidget::update() スロットを呼び出すことで、RenderArea ウィジェットを更新します。これにより、Qt がメインイベントループに戻った際に処理されるペイントイベントがスケジュールされます。
QSize RenderArea::minimumSizeHint() const
{
return QSize(182, 182);
}
QSize RenderArea::sizeHint() const
{
return QSize(232, 232);
}アプリケーション内で `RenderArea ` ウィジェットに適切なサイズを設定するため、QWidget の `minimumSizeHint()` および `sizeHint()` 関数を再実装します。これらの関数のデフォルト実装では、このウィジェットのレイアウトが存在しない場合は無効なサイズを返し、それ以外の場合はそれぞれレイアウトの最小サイズまたは推奨サイズを返します。
void RenderArea::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.translate(66, 66);paintEvent() イベントハンドラは、RenderArea ウィジェットのpaintイベントを受け取ります。paintイベントとは、ウィジェットの全部または一部を再描画する要求のことです。これは、QWidget::repaint()やQWidget::update()の呼び出しの結果として発生することもあれば、ウィジェットが隠れていた状態から再び表示された場合、あるいはその他多くの理由によって発生することもあります。
まず、RenderArea ウィジェット用のQPainter を作成します。QPainter::Antialiasing というレンダリングヒントは、可能であればエンジンがプリミティブのエッジにアンチエイリアス処理を適用すべきであることを示しています。次に、QPainter::fillRect()関数を使用して、再描画が必要な領域を消去します。
また、元の図形が適切な余白を持って描画されるように、座標系を一定のオフセット分平行移動させます。
{
QPainterStateGuard guard(&painter);
transformPainter(painter);
drawShape(painter);
}図形のレンダリングを開始する前に、QPainterStateGuard をインスタンス化し、スコープ内にある間の現在の座標系を含む現在のペインター状態を保存します(つまり、状態をスタックにプッシュします)。 ペインターの状態を保存する理由は、続くtransformPainter() 関数の呼び出しによって、現在選択されている変換操作に応じて座標系が変換されるためであり、輪郭を描画するために元の状態に戻る方法が必要となるからです。
座標系の変換後、RenderArea の図形を描画し、QPainter::restore() 関数を使用してペインターの状態を復元します(つまり、保存された状態をスタックからポップします)。
drawOutline(painter);その後、正方形のアウトラインを描画します。
transformPainter(painter);
drawCoordinates(painter);
}座標を、図形がレンダリングされる座標系に対応させたいので、transformPainter() 関数をもう一度呼び出す必要があります。
共有ピクセルに関しては、描画操作の順序が極めて重要です。座標系がすでに図形の描画用に変換されている段階で座標を描画せず、その描画を最後に先送りするのは、座標を図形とその輪郭線の上に表示させたいからです。
座標の描画が最後の描画操作であるため、今回はQPainter の状態を保存する必要はありません。
void RenderArea::drawCoordinates(QPainter &painter)
{
painter.setPen(Qt::red);
painter.drawLine(0, 0, 50, 0);
painter.drawLine(48, -2, 50, 0);
painter.drawLine(48, 2, 50, 0);
painter.drawText(60 - xBoundingRect.width() / 2,
0 + xBoundingRect.height() / 2, tr("x"));
painter.drawLine(0, 0, 0, 50);
painter.drawLine(-2, 48, 0, 50);
painter.drawLine(2, 48, 0, 50);
painter.drawText(0 - yBoundingRect.width() / 2,
60 + yBoundingRect.height() / 2, tr("y"));
}
void RenderArea::drawOutline(QPainter &painter)
{
painter.setPen(Qt::darkGreen);
painter.setPen(Qt::DashLine);
painter.setBrush(Qt::NoBrush);
painter.drawRect(0, 0, 100, 100);
}
void RenderArea::drawShape(QPainter &painter)
{
painter.fillPath(shape, Qt::blue);
}drawCoordinates() 、drawOutline() 、およびdrawShape() は、paintEvent() イベントハンドラから呼び出される便利関数です。QPainter の基本的な描画操作や、基本的なグラフィックプリミティブの表示方法の詳細については、「Basic Drawing」サンプルを参照してください。
void RenderArea::transformPainter(QPainter &painter)
{
for (int i = 0; i < operations.size(); ++i) {
switch (operations[i]) {
case Translate:
painter.translate(50, 50);
break;
case Scale:
painter.scale(0.75, 0.75);
break;
case Rotate:
painter.rotate(60);
break;
case NoTransformation:
default:
;
}
}
}transformPainter() という便利関数もpaintEvent() イベントハンドラから呼び出され、指定されたQPainter の座標系を、ユーザーが選択した変換設定に従って変換します。
ウィンドウクラスの定義
Window クラスは、Transformations アプリケーションのメインウィンドウです。
このアプリケーションは、4つのRenderArea ウィジェットを表示します。一番左のウィジェットは、QPainter のデフォルト座標系で図形を描画し、他のウィジェットは、左側のRenderArea ウィジェットに適用されたすべての変換に加えて、選択された変換を適用して図形を描画します。
class Window : public QWidget
{
Q_OBJECT
public:
Window();
public slots:
void operationChanged();
void shapeSelected(int index);アプリケーションがユーザーの操作に応答し、ユーザーの変換選択に応じて表示されているRenderArea ウィジェットを更新できるようにするため、2つのパブリックスロットを宣言します。
operationChanged() スロットは、現在選択されている変換操作を適用して各RenderArea ウィジェットを更新するもので、ユーザーが選択した操作を変更するたびに呼び出されます。shapeSelected() スロットは、ユーザーが希望する形状を変更するたびに、RenderArea ウィジェットの形状を更新します。
private:
void setupShapes();
enum { NumTransformedAreas = 3 };
RenderArea *originalRenderArea;
RenderArea *transformedRenderAreas[NumTransformedAreas];
QComboBox *shapeComboBox;
QComboBox *operationComboBoxes[NumTransformedAreas];
QList<QPainterPath> shapes;
};また、Window ウィジェットの構築時に使用されるプライベートな便利関数setupShapes() を宣言し、ウィジェットの各種コンポーネントへのポインタも宣言します。利用可能な形状は、QPainterPathのQList に保持するようにしています。さらに、QPainter のデフォルト座標系で形状を描画するウィジェットを除いた、表示されているRenderArea ウィジェットの数を数えるプライベートな列挙型を宣言します。
Windowクラスの実装
コンストラクタ内で、アプリケーションのコンポーネントを作成および初期化します:
Window::Window()
{
originalRenderArea = new RenderArea;
shapeComboBox = new QComboBox;
shapeComboBox->addItem(tr("Clock"));
shapeComboBox->addItem(tr("House"));
shapeComboBox->addItem(tr("Text"));
shapeComboBox->addItem(tr("Truck"));
QGridLayout *layout = new QGridLayout;
layout->addWidget(originalRenderArea, 0, 0);
layout->addWidget(shapeComboBox, 1, 0);まず、デフォルトの座標系で図形を描画するRenderArea ウィジェットを作成します。また、ユーザーが4つの異なる図形(時計、家、テキスト、トラック)から選択できるようにする、関連するQComboBox も作成します。図形自体は、コンストラクタの最後で、setupShapes() という便利関数を使用して作成されます。
for (int i = 0; i < NumTransformedAreas; ++i) {
transformedRenderAreas[i] = new RenderArea;
operationComboBoxes[i] = new QComboBox;
operationComboBoxes[i]->addItem(tr("No transformation"));
operationComboBoxes[i]->addItem(tr("Rotate by 60\xC2\xB0"));
operationComboBoxes[i]->addItem(tr("Scale to 75%"));
operationComboBoxes[i]->addItem(tr("Translate by (50, 50)"));
connect(operationComboBoxes[i], &QComboBox::activated,
this, &Window::operationChanged);
layout->addWidget(transformedRenderAreas[i], 0, i + 1);
layout->addWidget(operationComboBoxes[i], 1, i + 1);
}次に、座標変換を用いて図形を描画するRenderArea ウィジェットを作成します。デフォルトでは、適用される操作はNo Transformation であり、つまり図形はデフォルトの座標系内で描画されます。グローバルなOperation 列挙型で定義された各種変換操作に対応する項目を用いて、関連するQComboBoxを作成・初期化します。
また、QComboBoxのactivated()シグナルをoperationChanged() のスロットに接続し、ユーザーが選択した変換操作を変更するたびにアプリケーションを更新するようにします。
setLayout(layout);
setupShapes();
shapeSelected(0);
setWindowTitle(tr("Transformations"));
}最後に、QWidget::setLayout() 関数を使用してアプリケーションウィンドウのレイアウトを設定し、プライベートなsetupShapes() 便利関数を使用して利用可能なシェイプを構築し、ウィンドウのタイトルを設定する前に、パブリックなshapeSelected() スロットを使用して、アプリケーションが起動時に時計のシェイプを表示するようにします。
void Window::setupShapes()
{
QPainterPath truck;
QPainterPath clock;
QPainterPath house;
QPainterPath text;
...
shapes.append(clock);
shapes.append(house);
shapes.append(text);
shapes.append(truck);
connect(shapeComboBox, &QComboBox::activated,
this, &Window::shapeSelected);
}setupShapes() 関数はコンストラクタから呼び出され、アプリケーションで使用される図形を表すQPainterPath オブジェクトを作成します。構築の詳細については、painting/transformations/window.cpp サンプルファイルを参照してください。図形はQList に格納されます。QList::append()関数は、指定された図形をリストの末尾に挿入します。
また、関連するQComboBox のactivated()シグナルをshapeSelected() スロットに接続し、ユーザーが好みの図形を変更した際にアプリケーションを更新するようにしています。
void Window::operationChanged()
{
static const Operation operationTable[] = {
NoTransformation, Rotate, Scale, Translate
};
QList<Operation> operations;
for (int i = 0; i < NumTransformedAreas; ++i) {
int index = operationComboBoxes[i]->currentIndex();
operations.append(operationTable[index]);
transformedRenderAreas[i]->setOperations(operations);
}
}ユーザーが選択した操作を変更するたびに、operationChanged() のパブリックスロットが呼び出されます。
関連する `QComboBoxes` を照会することで、変換された各 `RenderArea ` ウィジェットについて、選択された変換操作を取得します。変換された `RenderArea ` ウィジェットは、その左側にある `RenderArea ` ウィジェットに適用されたすべての変換に加えて、関連するコンボボックスで指定された変換を適用して図形をレンダリングすることになっています。 そのため、クエリ対象となる各ウィジェットについて、関連する操作を変換のQList に追加し、次のウィジェットに進む前にそのウィジェットに適用します。
void Window::shapeSelected(int index)
{
QPainterPath shape = shapes[index];
originalRenderArea->setShape(shape);
for (int i = 0; i < NumTransformedAreas; ++i)
transformedRenderAreas[i]->setShape(shape);
}shapeSelected() スロットは、ユーザーが希望する形状を変更するたびに呼び出され、RenderArea ウィジェットを、それらのパブリックなsetShape() 関数を使用して更新します。
まとめ
「Transformations」の例では、変換がQPainter によるグラフィックプリミティブの描画にどのように影響するかを示しています。通常、QPainter はデバイス独自の座標系で動作しますが、座標変換も十分にサポートされています。「Transformations」アプリケーションを使用すると、QPainter の座標系を拡大縮小、回転、平行移動させることができます。これらの変換が適用される順序は、結果にとって極めて重要です。
すべての変換操作は、QPainter の変換行列に対して行われます。変換行列の詳細については、「座標系」および「QTransform 」のドキュメントを参照してください。
Qtリファレンスドキュメントには、いくつかの描画サンプルが掲載されています。その中には、描画操作に対して変換を実行するQtの機能を示す「アフィン変換(Affine Transformations)」のサンプルがあります。このサンプルでは、ユーザーがさまざまな変換操作を試すこともできます。
© 2026 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.