座標系

座標系はQPainter クラスによって制御されます。QPaintDevice およびQPaintEngine クラスとともに、QPainter は Qt のペイントシステム Arthur の基礎となっています。QPainter は描画操作の実行に使用され、QPaintDeviceQPainter を使用して描画できる二次元空間の抽象化で、QPaintEngine はペインターがさまざまな種類のデバイスに描画するためのインターフェイスを提供します。

QPaintDevice クラスは描画可能なオブジェクトの基本クラスです:その描画機能は、QWidgetQImageQPixmapQPictureQOpenGLPaintDevice の各クラスに継承されます。描画デバイスのデフォルト座標系は左上隅を原点とします。x値は右に向かって増加し、y値は下に向かって増加します。デフ ォル ト 単位は、 ピ ク セルベース のデバ イ ス では 1 ピ ク セル、 プ リ ン タ では 1 ポ イ ン ト (1/72 インチ) です。

論理座標QPainter か ら 物理座標QPaintDevice へのマ ッ ピ ン グは、QPainter の変換行列、 ビ ュ ーポー ト 、 お よ び 「ウ ィ ン ド ウ」 で扱われます。論理座標系と物理座標系はデフォルトで一致しています。QPainter は座標変換(回転や拡大縮小など)もサポートしています。

レンダリング

論理表現

グラフィックス・プリミティブのサイズ(幅と高さ)は、それがレンダリングされるペンの幅を無視して、常にその数学的モデルに対応します:

QRect(QPoint(1, 2),QPoint(7, 6))QLine(QPoint(2, 7),QPoint(6, 1))
QLine(2, 7, 6, 1)
QRect(QPoint(1, 2),QSize(6, 4))
QRect(1, 2, 6, 4)

エイリアス描画

描画時、ピクセルレンダリングはQPainter::Antialiasing レンダーヒントによって制御される。

RenderHint 列挙型は、QPainter にフラグを指定するために使用され、任意のエンジンが尊重するかどうかを指定します。QPainter::Antialiasing の値は、エンジンが可能であればプリミティブのエッジをアンチエイリアスする、つまり異なる色の強度を使用してエッジを滑らかにすることを示します。

しかし、デフォルトではペインターはエイリアスをかけられ、他のルールが適用されます:1ピクセル幅のペンでレンダリングする場合、ピクセルは数学的に定義された点の右と下にレンダリングされます。例えば

QPainter painter(this);

painter.setPen(Qt::darkGreen);
// Using the (x  y  w  h) overload
painter.drawRect(1, 2, 6, 4);
QPainter painter(this);

painter.setPen(Qt::darkGreen);
painter.drawLine(2, 7, 6, 1);

偶数ピクセルのペンでレンダリングする場合、ピクセルは数学的に定義された点を中心に左右対称にレンダリングされます。一方、奇数ピクセルのペンでレンダリングする場合、予備のピクセルは1ピクセルの場合と同様に数学的に定義された点の右と下にレンダリングされます。具体例については、以下のQRectF の図を参照してください。

QRectF
論理的表現1ピクセル幅のペン
2ピクセル幅のペン3ピクセル幅のペン

歴史的な理由により、QRect::right ()とQRect::bottom ()関数の戻り値は、矩形の真の右下隅から逸脱していることに注意してください。

QRect right() 関数は () + () - 1 を返し、 () 関数は () + () - 1 を返します。図の右下の緑色の点は、これらの関数の戻り値の座標を示しています。left width bottom top height

代わりにQRectF を使用することをお勧めします:QRectF クラスは、正確を期すために浮動小数点座標を使用して平面上の矩形を定義します(QRect は整数座標を使用します)。また、QRectF::right ()とQRectF::bottom ()関数は真の右下隅を返します。

また、QRect を使って、x() +width() とy() +height() を適用して右下隅を求め、right() とbottom() 関数を使わないようにすることもできます。

アンチエイリアス塗装

QPainteranti-aliasing レンダーヒントを設定すると、ピクセルは数学的に定義された点の両側で対称にレンダリングされます:

QPainter painter(this);
painter.setRenderHint(
    QPainter::Antialiasing);
painter.setPen(Qt::darkGreen);
// Using the (x  y  w  h) overload
painter.drawRect(1, 2, 6, 4);
QPainter painter(this);
painter.setRenderHint(
    QPainter::Antialiasing);
painter.setPen(Qt::darkGreen);
painter.drawLine(2, 7, 6, 1);

変換

デフォルトでは、QPainter は関連するデバイス自身の座標系で動作しますが、アフィン座標変換も完全にサポートしています。

QPainter::scale ()関数を使用すると、座標系を与えられたオフセットだけ拡大縮小できます。QPainter::rotate ()関数を使用すると、座標系を時計回りに回転できます。QPainter::translate ()関数を使用すると、座標系を平行移動(つまり、与えられたオフセットを点に追加)できます。

また、QPainter::shear() 関数を使用して、原点を中心に座標系をねじることもできます。すべての変換操作は、QPainter::worldTransform() 関数を使用して取得できるQPainter の変換行列を操作します。行列は、平面上の点を別の点に変換します。

同じ変換が何度も必要な場合は、QTransform オブジェクトと、QPainter::worldTransform() およびQPainter::setWorldTransform() 関数を使用することもできます。行列を内部スタックに保存するQPainter::save() 関数を呼び出せば、いつでもQPainter の変換行列を保存できます。QPainter::restore ()関数は、それをポップバックします。

変換行列が頻繁に必要になるのは、同じ描画コードをさまざまな描画デバイスで再利用する場合です。変換を行わないと、描画結果は描画デバイスの解像度に厳密に拘束されます。プリンターの解像度は1インチあたり600ドットなど高いが、スクリーンの解像度は1インチあたり72~100ドットであることが多い。

アナログ時計の例
アナログ時計の例では、QPainter の変換行列を使用してカスタムウィジェットの内容を描画する方法を示します。

この先を読む前に、この例をコンパイルして実行することをお勧めします。特に、ウィンドウのサイズを変更してみてください。

void AnalogClock::paintEvent(QPaintEvent *)
{
    static const QPoint hourHand[4] = {
        QPoint(5, 14),
        QPoint(-5, 14),
        QPoint(-4, -71),
        QPoint(4, -71)
    };
    static const QPoint minuteHand[4] = {
        QPoint(4, 14),
        QPoint(-4, 14),
        QPoint(-3, -89),
        QPoint(3, -89)
    };

    static const QPoint secondsHand[4] = {
       QPoint(1, 14),
       QPoint(-1, 14),
       QPoint(-1, -89),
       QPoint(1, -89)
    };

    const QColor hourColor(palette().color(QPalette::Text));
    const QColor minuteColor(palette().color(QPalette::Text));
    const QColor secondsColor(palette().color(QPalette::Accent));

    int side = qMin(width(), height());

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.translate(width() / 2, height() / 2);
    painter.scale(side / 200.0, side / 200.0);

座標系を平行移動して、点 (0, 0) がウィジェットの中心に来るようにします。また、座標系をside / 200で拡大縮小します。ここで、side はウィジェットの幅か高さのどちらか短い方です。デバイスが正方形でなくても、時計は正方形にしたい。

これにより、原点(0, 0)を中心とした200 x 200の正方形の領域が得られ、そこに描画することができます。描画したものは、ウィジェットに収まる最大の正方形に表示されます。

ウィンドウ-ビューポート変換のセクションも参照してください。

    painter.save();
    painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    painter.drawConvexPolygon(hourHand, 4);
    painter.restore();

座標系を回転させてQPainter::drawConvexPolygon() を呼び出すことで、時計の時針を描画します。回転のおかげで、時針は正しい方向に描画されます。

多角形は、(関数の最初に定義されている)スタティック変数hourHand に格納されている交互のx,y値の配列として指定されます。

コードを囲むQPainter::save ()とQPainter::restore ()の呼び出しは、私たちが使用した変換によって後続のコードが妨害されないことを保証します。

    painter.setBrush(minuteColor);

    painter.save();
    painter.rotate(6.0 * time.minute());
    painter.drawConvexPolygon(minuteHand, 4);
    painter.restore();

その後、30度間隔で12本の短い線で構成される時計の文字盤のアワーマーカーを描く。このループが終わったとき、ペインターは1周して元の状態に戻っているので、状態を保存して復元する必要はない。

    painter.save();
    painter.rotate(6.0 * time.second());
    painter.drawConvexPolygon(secondsHand, 4);
    painter.drawEllipse(-3, -3, 6, 6);
    painter.drawEllipse(-5, -68, 10, 10);
    painter.restore();

時計の分針も同様に、(7, 8), (-7, 8), (0, -70)の3点で定義する。これらの座標は、分針よりも細く長い針を指定する。

    for (int j = 0; j < 60; ++j) {
        painter.drawLine(92, 0, 96, 0);
        painter.rotate(6.0);
    }

最後に、時計の文字盤の分マーカーを描く。これは6度間隔で60本の短い線からなる。分マーカーは5本おきにスキップする。それが終わると、ペインターはあまり役に立たない方法で回転させられる。

変換行列の詳細については、QTransform のドキュメントを参照してください。

ウィンドウ-ビューポート変換

QPainter で描画する場合、論理座標を使って点を指定し、それを描画デバイスの物理座標に変換します。

論理座標から物理座標への変換は、QPainter のワールド変換worldTransform変換のセクションで説明)、QPainter のワールド変換viewport ()、window ()によって行われます。ビューポートは、任意の矩形を指定する物理座標を表します。ウィンドウ」は、同じ矩形を論理座標で表します。デフォルトでは、論理座標系と物理座標系は一致しており、ペイントデバイスの矩形と等価です。

ウィンドウ-ビューポート変換を使えば、論理座標系を好みに合わせることができます。この機構は、描画コードをペイントデバイスから独立させるためにも使用できます。例えば、QPainter::setWindow() 関数を呼び出すことで、論理座標を (0, 0) を中心に (-50, -50) から (50, 50) まで拡張させることができます:

QPainter painter(this);
painter.setWindow(QRect(-50, -50, 100, 100));

これで、論理座標(-50,-50)はペイントデバイスの物理座標(0, 0)に対応します。ペイントデバイスに関係なく、ペイントコードは常に指定された論理座標で動作します。

ウィンドウ」またはビューポートの矩形を設定することで、座標の線形変換を行います。ウィンドウ "の各コーナーはビューポートの対応するコーナーに対応し、その逆も同様であることに注意してください。そのため、通常はビューポートと "ウィンドウ "の縦横比を同じにして変形を防ぐのがよいでしょう:

int side = qMin(width(), height());
int x = (width() - side / 2);
int y = (height() - side / 2);

painter.setViewport(x, y, side, side);

論理座標系を正方形にする場合、QPainter::setViewport ()関数を使用してビューポートも正方形にする必要があります。上の例では、ペイントデバイスの矩形に収まる最大の正方形と同じにしています。ウィンドウやビューポートを設定する際にペイントデバイスのサイズを考慮することで、ペイントデバイスに依存しない描画コードを維持することができます。

ウィンドウとビューポートの変換は線形変換のみで、クリッピングは行わないことに注意してください。つまり、現在設定されている "ウィンドウ "の外側にペイントしても、ペイントは同じ線形代数のアプローチを使ってビューポートに変換されます。

ビューポート、"ウィンドウ"、変換行列は、論理的なQPainter 座標がどのようにペイントデバイスの物理座標にマッピングされるかを決定します。デフォルトでは、ワールドの変換行列は恒等行列で、「ウィンドウ」とビューポートの設定はペイントデバイスの設定と等価です。上の図はそのプロセスを説明したものである。

アナログ時計」も参照して ください。

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