最終更新日:2021/08/31 原本2017-03-06

PyQt5とpython3によるGUIプログラミング[8](編集中)

Painting Examplesの翻訳です。
おかしい箇所があったら指摘してください。

Painting Examples

alt
Qtのペインティングシステムは、アンチエイリアスを使用してサブピクセル精度でベクターグラフィックス、画像、およびフォントベースのテキストを描画し、レンダリング品質を向上させることができます。

これらの例は、単純プリミティブの描画や変換の使用などの基本的な概念から、Qtでペイントするときに使用される最も一般的な手法を示しています。

Examples 概要
Affine Transformations QPainterのアフィン変換がどのように機能するかを示します。
Basic Drawing Example 基本描画の例は、QPainterクラスを使用してさまざまなスタイルの基本グラフィックスプリミティブを表示する方法を示しています。
Composition Modes QPainterで合成モードがどのように機能するかを示します。
Concentric Circles Example アンチエイリアス処理と浮動小数点精度の向上した品質を示します。
Font Sampler Example 「フォントサンプラー」の例は、複数ページの文書をプレビューして印刷する方法を示しています。
Gradients QPainterでグラデーションを使用する方法を示します。
Image Composition Example QPainterで合成モードがどのように機能するかを示します。
Painter Paths Example ペインタパスの例は、レンダリングのために複雑なシェイプを構築するためにペインタパスを使用する方法を示しています。
Path Stroking Path Strokingの例は、QPainterで使用できるさまざまな種類のペンを示しています。
Transformations Example 変換の例は、変換がQPainterがグラフィックスプリミティブをレンダリングする方法にどのように影響するかを示しています。
Vector Deformation QPainterPathの要素を操作する方法を示します。

Affine Transformations

alt

変換は、QPainterを使用して描かれたあらゆるグラフィックスで実行できます。 ベクトルグラフィックス、イメージ、およびテキストを表示するために使用される変換は、次の方法で調整できます。

・各図の中央にある赤い円をドラッグすると、新しい位置に移動します。
・移動した赤い円をドラッグすると、現在の図面が中心円の周りを回転します。 回転は、[回転]スライダでも制御できます。
・スケーリングは、[スケール]スライダで制御します。
・それぞれの図面は、Shearスライダで切断できます。

Files:

painting/affine/xform.cpp
painting/affine/xform.h
painting/affine/main.cpp
painting/affine/affine.pro
painting/affine/affine.qrc

Basic Drawing Example

QPainterは、ウィジェットやその他のペイントデバイスで低レベルペインティングを実行します。 クラスは、単純な線からパイやコードのような複雑な図形に至るまで、すべてを描画できます。 また、整列したテキストとピックスマップを描画することもできます。 通常は、「自然な」座標系を描画しますが、それに加えてビューや世界の変換も可能です。

alt

この例では、現在アクティブなシェイプを表示するレンダリング領域を提供し、QPainterパラメータを使用してレンダリングされたシェイプとその外観を操作することができます。ユーザーはアクティブシェイプ(Shape)を変更し、QPainterのペン(Pen Width、Pen スタイル、ペンキャップ、ペン結合)、ブラシ(ブラシスタイル)、レンダリングヒント(アンチエイリアス)。 さらに、ユーザは形状を回転することができます(変形)。 私たちはQPainterが回転を行うために座標系を操作する能力を使用しています。

基本図の例は、2つのクラスで構成されています。
・RenderAreaは、現在アクティブなシェイプの複数のコピーをレンダリングするカスタムウィジェットです。
・Windowはアプリケーションのメインウィンドウで、いくつかのパラメータウィジェットに加えてRenderAreaウィジェットを表示します。

最初にWindowクラスを見て、次にRenderAreaクラスを見てみましょう。

Window Class Definition

WindowクラスはQWidgetを継承し、いくつかのパラメータウィジェットに加えてRenderAreaウィジェットを表示するアプリケーションのメインウィンドウです。

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private slots:
    void shapeChanged();
    void penChanged();
    void brushChanged();

private:
    RenderArea *renderArea;
    QLabel *shapeLabel;
    QLabel *penWidthLabel;
    QLabel *penStyleLabel;
    QLabel *penCapLabel;
    QLabel *penJoinLabel;
    QLabel *brushStyleLabel;
    QLabel *otherOptionsLabel;
    QComboBox *shapeComboBox;
    QSpinBox *penWidthSpinBox;
    QComboBox *penStyleComboBox;
    QComboBox *penCapComboBox;
    QComboBox *penJoinComboBox;
    QComboBox *brushStyleComboBox;
    QCheckBox *antialiasingCheckBox;
    QCheckBox *transformationsCheckBox;
};

さまざまなウィジェットと、RenderAreaウィジェットを更新する3つのプライベートスロットを宣言します。ユーザが現在アクティブなシェイプを変更すると、shapeChanged()スロットはRenderAreaウィジェットを更新します。 QPainterのペンパラメータのいずれかが変更されたときにpenChanged()スロットを呼び出します。 また、brushChanged()スロットは、ユーザがペインタのブラシスタイルを変更したときにRenderAreaウィジェットを更新します。

Window Class Implementation

コンストラクタでは、メインアプリケーションウィンドウに表示されるさまざまなウィジェットを作成して初期化します。

Window::Window()
{
    renderArea = new RenderArea;

    shapeComboBox = new QComboBox;
    shapeComboBox->addItem(tr("Polygon"), RenderArea::Polygon);
    shapeComboBox->addItem(tr("Rectangle"), RenderArea::Rect);
    shapeComboBox->addItem(tr("Rounded Rectangle"), RenderArea::RoundedRect);
    shapeComboBox->addItem(tr("Ellipse"), RenderArea::Ellipse);
    shapeComboBox->addItem(tr("Pie"), RenderArea::Pie);
    shapeComboBox->addItem(tr("Chord"), RenderArea::Chord);
    shapeComboBox->addItem(tr("Path"), RenderArea::Path);
    shapeComboBox->addItem(tr("Line"), RenderArea::Line);
    shapeComboBox->addItem(tr("Polyline"), RenderArea::Polyline);
    shapeComboBox->addItem(tr("Arc"), RenderArea::Arc);
    shapeComboBox->addItem(tr("Points"), RenderArea::Points);
    shapeComboBox->addItem(tr("Text"), RenderArea::Text);
    shapeComboBox->addItem(tr("Pixmap"), RenderArea::Pixmap);

    shapeLabel = new QLabel(tr("&Shape:"));
    shapeLabel->setBuddy(shapeComboBox);

最初に、現在アクティブなシェイプを描画するRenderAreaウィジェットを作成します。 次に、シェイプのコンボボックスを作成し、関連するアイテム(つまり、QPainterが描画できるさまざまなシェイプ)を追加します。

    penWidthSpinBox = new QSpinBox;
    penWidthSpinBox->setRange(0, 20);
    penWidthSpinBox->setSpecialValueText(tr("0 (cosmetic pen)"));

    penWidthLabel = new QLabel(tr("Pen &Width:"));
    penWidthLabel->setBuddy(penWidthSpinBox);

QPainterのペンはQPenオブジェクトです。 QPenクラスは、画家が線や線の輪郭を描く方法を定義します。 ペンにはいくつかのプロパティがあります:幅、スタイル、キャップ、ジョイン。

ペンの幅はゼロ以上にすることができますが、最も一般的な幅はゼロです。 これは0ピクセルを意味するわけではありませんが、おそらく数学的に正しいとは限りませんが、できるだけ滑らかに描かれていることを意味します。

Pen Widthパラメータ用のQSpinBoxを作成します。

    penStyleComboBox = new QComboBox;
    penStyleComboBox->addItem(tr("Solid"), static_cast<int>(Qt::SolidLine));
    penStyleComboBox->addItem(tr("Dash"), static_cast<int>(Qt::DashLine));
    penStyleComboBox->addItem(tr("Dot"), static_cast<int>(Qt::DotLine));
    penStyleComboBox->addItem(tr("Dash Dot"), static_cast<int>(Qt::DashDotLine));
    penStyleComboBox->addItem(tr("Dash Dot Dot"), static_cast<int>(Qt::DashDotDotLine));
    penStyleComboBox->addItem(tr("None"), static_cast<int>(Qt::NoPen));

    penStyleLabel = new QLabel(tr("&Pen Style:"));
    penStyleLabel->setBuddy(penStyleComboBox);

    penCapComboBox = new QComboBox;
    penCapComboBox->addItem(tr("Flat"), Qt::FlatCap);
    penCapComboBox->addItem(tr("Square"), Qt::SquareCap);
    penCapComboBox->addItem(tr("Round"), Qt::RoundCap);

    penCapLabel = new QLabel(tr("Pen &Cap:"));
    penCapLabel->setBuddy(penCapComboBox);

    penJoinComboBox = new QComboBox;
    penJoinComboBox->addItem(tr("Miter"), Qt::MiterJoin);
    penJoinComboBox->addItem(tr("Bevel"), Qt::BevelJoin);
    penJoinComboBox->addItem(tr("Round"), Qt::RoundJoin);

    penJoinLabel = new QLabel(tr("Pen &Join:"));
    penJoinLabel->setBuddy(penJoinComboBox);

ペンのスタイルは、線の種類を定義します。 デフォルトのスタイルはソリッドです(Qt::SolidLine)。 スタイルをnone(Qt::NoPen)に設定すると、画家は線や輪郭を描かないように指示します。 ペンキャップは、線の終点を描画する方法を定義します。 また、ペン結合は、複数の接続線が描画されたときに2つの線がどのように結合するかを定義します。 キャップと結合は、幅が1ピクセル以上の線にのみ適用されます。

ペンスタイル、ペンキャップ、ペン結合パラメータごとにQComboBoxを作成し、関連する項目(Qt::PenStyle、Qt::PenCapStyle、Qt::PenJoinStyle列挙型の値)を追加します。

    brushStyleComboBox = new QComboBox;
    brushStyleComboBox->addItem(tr("Linear Gradient"),
            static_cast<int>(Qt::LinearGradientPattern));
    brushStyleComboBox->addItem(tr("Radial Gradient"),
            static_cast<int>(Qt::RadialGradientPattern));
    brushStyleComboBox->addItem(tr("Conical Gradient"),
            static_cast<int>(Qt::ConicalGradientPattern));
    brushStyleComboBox->addItem(tr("Texture"), static_cast<int>(Qt::TexturePattern));
    brushStyleComboBox->addItem(tr("Solid"), static_cast<int>(Qt::SolidPattern));
    brushStyleComboBox->addItem(tr("Horizontal"), static_cast<int>(Qt::HorPattern));
    brushStyleComboBox->addItem(tr("Vertical"), static_cast<int>(Qt::VerPattern));
    brushStyleComboBox->addItem(tr("Cross"), static_cast<int>(Qt::CrossPattern));
    brushStyleComboBox->addItem(tr("Backward Diagonal"), static_cast<int>(Qt::BDiagPattern));
    brushStyleComboBox->addItem(tr("Forward Diagonal"), static_cast<int>(Qt::FDiagPattern));
    brushStyleComboBox->addItem(tr("Diagonal Cross"), static_cast<int>(Qt::DiagCrossPattern));
    brushStyleComboBox->addItem(tr("Dense 1"), static_cast<int>(Qt::Dense1Pattern));
    brushStyleComboBox->addItem(tr("Dense 2"), static_cast<int>(Qt::Dense2Pattern));
    brushStyleComboBox->addItem(tr("Dense 3"), static_cast<int>(Qt::Dense3Pattern));
    brushStyleComboBox->addItem(tr("Dense 4"), static_cast<int>(Qt::Dense4Pattern));
    brushStyleComboBox->addItem(tr("Dense 5"), static_cast<int>(Qt::Dense5Pattern));
    brushStyleComboBox->addItem(tr("Dense 6"), static_cast<int>(Qt::Dense6Pattern));
    brushStyleComboBox->addItem(tr("Dense 7"), static_cast<int>(Qt::Dense7Pattern));
    brushStyleComboBox->addItem(tr("None"), static_cast<int>(Qt::NoBrush));

    brushStyleLabel = new QLabel(tr("&Brush:"));
    brushStyleLabel->setBuddy(brushStyleComboBox);

QBrushクラスは、QPainterによって描画された図形の塗りつぶしパターンを定義します。 デフォルトのブラシスタイルはQt::NoBrushです。 このスタイルは、図形を塗り潰さないように画家に指示します。 塗りつぶすための標準スタイルはQt::SolidPatternです。

Brush Styleパラメータ用のQComboBoxを作成し、関連する項目(Qt::BrushStyle列挙型の値)を追加します。

    otherOptionsLabel = new QLabel(tr("Options:"));
    antialiasingCheckBox = new QCheckBox(tr("&Antialiasing"));

アンチエイリアスはピクセルを「平滑化」して、より均一でぎざぎざの線を作成し、QPainterのレンダーヒントを使用して適用できる機能です。 QPainter::RenderHintsは、指定されたエンジンによって尊重される場合とそうでない場合があるQPainterへのフラグを指定するために使用されます。

アンチエイリアスオプションのQCheckBoxを作成するだけです。

    transformationsCheckBox = new QCheckBox(tr("&Transformations"));

[変形]オプションは、レンダリングされた図形が3次元で回転しているかのように見える座標系の操作を意味します。

QPainter::translate()、QPainter::rotate()およびQPainter::scale()関数を使用して、簡単なQCheckBoxによってメインアプリケーションウィンドウに表示されるこの機能を実装します。

    connect(shapeComboBox, SIGNAL(activated(int)),
            this, SLOT(shapeChanged()));
    connect(penWidthSpinBox, SIGNAL(valueChanged(int)),
            this, SLOT(penChanged()));
    connect(penStyleComboBox, SIGNAL(activated(int)),
            this, SLOT(penChanged()));
    connect(penCapComboBox, SIGNAL(activated(int)),
            this, SLOT(penChanged()));
    connect(penJoinComboBox, SIGNAL(activated(int)),
            this, SLOT(penChanged()));
    connect(brushStyleComboBox, SIGNAL(activated(int)),
            this, SLOT(brushChanged()));
    connect(antialiasingCheckBox, SIGNAL(toggled(bool)),
            renderArea, SLOT(setAntialiased(bool)));
    connect(transformationsCheckBox, SIGNAL(toggled(bool)),
            renderArea, SLOT(setTransformed(bool)));

次に、静的なQObject::connect()関数を使用してパラメータウィジェットを関連スロットに接続し、ユーザーがシェイプやその他のパラメータを変更するたびにRenderAreaウィジェットが更新されるようにします。

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->setColumnStretch(0, 1);
    mainLayout->setColumnStretch(3, 1);
    mainLayout->addWidget(renderArea, 0, 0, 1, 4);
    mainLayout->addWidget(shapeLabel, 2, 0, Qt::AlignRight);
    mainLayout->addWidget(shapeComboBox, 2, 1);
    mainLayout->addWidget(penWidthLabel, 3, 0, Qt::AlignRight);
    mainLayout->addWidget(penWidthSpinBox, 3, 1);
    mainLayout->addWidget(penStyleLabel, 4, 0, Qt::AlignRight);
    mainLayout->addWidget(penStyleComboBox, 4, 1);
    mainLayout->addWidget(penCapLabel, 3, 2, Qt::AlignRight);
    mainLayout->addWidget(penCapComboBox, 3, 3);
    mainLayout->addWidget(penJoinLabel, 2, 2, Qt::AlignRight);
    mainLayout->addWidget(penJoinComboBox, 2, 3);
    mainLayout->addWidget(brushStyleLabel, 4, 2, Qt::AlignRight);
    mainLayout->addWidget(brushStyleComboBox, 4, 3);
    mainLayout->addWidget(otherOptionsLabel, 5, 0, Qt::AlignRight);
    mainLayout->addWidget(antialiasingCheckBox, 5, 1, 1, 1, Qt::AlignRight);
    mainLayout->addWidget(transformationsCheckBox, 5, 2, 1, 2, Qt::AlignRight);
    setLayout(mainLayout);

    shapeChanged();
    penChanged();
    brushChanged();
    antialiasingCheckBox->setChecked(true);

    setWindowTitle(tr("Basic Drawing"));
}

最後に、さまざまなウィジェットをレイアウトに追加し、shapeChanged()、penChanged()、およびbrushChanged()スロットを呼び出してアプリケーションを初期化します。 アンチエイリアスも有効にします。

void Window::shapeChanged()
{
    RenderArea::Shape shape = RenderArea::Shape(shapeComboBox->itemData(
            shapeComboBox->currentIndex(), IdRole).toInt());
    renderArea->setShape(shape);
}

shapeChanged()スロットは、ユーザーが現在アクティブな図形を変更するたびに呼び出されます。

最初に、ユーザがQComboBox::itemData()関数を使用して選択した形状を取得します。この関数は、コンボボックス内の指定されたインデックス内の指定されたロールのデータを返します。 QComboBox::currentIndex()を使用して図形のインデックスを取得し、そのロールはQt::ItemDataRole列挙型で定義されます。 IdRoleはQt::UserRoleのエイリアスです。

Qt::UserRoleは、アプリケーション固有の目的に使用できる最初の役割に過ぎないことに注意してください。同じインデックスに異なるデータを格納する必要がある場合は、Qt::UserRoleの値を単にインクリメントするだけで、異なるロールを使用できます。たとえば、 'Qt::UserRole + 1'と 'Qt::UserRole + 2'です。しかし、それぞれの役割に「myFirstRole = Qt::UserRole + 1」と「mySecondRole = Qt::UserRole + 2」という名前を付けるのは良いプログラミング方法です。この特定の例では単一の役割しか必要としませんが、次のコード行をwindow.cppファイルの先頭に追加します。

const int IdRole = Qt::UserRole;

QComboBox::itemData()関数はデータをQVariantとして返します。そのため、データをRenderArea::Shapeにキャストする必要があります。 指定されたロールのデータがない場合、関数はQVariant::Invalidを返します。

最後に、RenderArea::setShape()スロットを呼び出してRenderAreaウィジェットを更新します。

void Window::penChanged()
{
    int width = penWidthSpinBox->value();
    Qt::PenStyle style = Qt::PenStyle(penStyleComboBox->itemData(
            penStyleComboBox->currentIndex(), IdRole).toInt());
    Qt::PenCapStyle cap = Qt::PenCapStyle(penCapComboBox->itemData(
            penCapComboBox->currentIndex(), IdRole).toInt());
    Qt::PenJoinStyle join = Qt::PenJoinStyle(penJoinComboBox->itemData(
            penJoinComboBox->currentIndex(), IdRole).toInt());

    renderArea->setPen(QPen(Qt::blue, width, style, cap, join));
}

ユーザーがペンパラメーターのいずれかを変更するたびに、penChanged()スロットを呼び出します。 再度QComboBox::itemData()関数を使用してパラメータを取得し、次にRenderArea::setPen()スロットを呼び出してRenderAreaウィジェットを更新します。

void Window::brushChanged()
{
    Qt::BrushStyle style = Qt::BrushStyle(brushStyleComboBox->itemData(

brushChanged()スロットは、以前のようにQComboBox::itemData()関数を使用して取得するブラシパラメータをユーザが変更するたびに呼び出されます。

    if (style == Qt::LinearGradientPattern) {
        QLinearGradient linearGradient(0, 0, 100, 100);
        linearGradient.setColorAt(0.0, Qt::white);
        linearGradient.setColorAt(0.2, Qt::green);
        linearGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(linearGradient);

ブラシパラメータがグラデーションの塗りつぶしの場合は、特別な操作が必要です。

QGradientクラスはQBrushと組み合わせて使用​​され、グラデーションの塗りつぶしを指定します。 Qtは現在、線形、放射状、円錐形の3種類の勾配塗りつぶしをサポートしています。これらはそれぞれQGradientのサブクラスQLinearGradient、QRadialGradient、QConicalGradientで表されます。

したがって、ブラシスタイルがQt::LinearGradientPatternの場合、コンストラクタに引数として渡された座標の間の補間領域を持つQLinearGradientオブジェクトを作成します。位置は論理座標を使用して指定されます。次に、QGradient::setColorAt()関数を使ってグラデーションの色を設定します。色は、位置(0と1の間)とQColorで構成される停止点を使用して定義されます。ストップポイントのセットは、どのようにグラデーション領域を塗りつぶすかを記述する。勾配は任意の数の停止点を持つことができます。

最後に、RenderArea::setBrush()スロットを呼び出して、RenderAreaウィジェットのブラシをQLinearGradientオブジェクトで更新します。

    } else if (style == Qt::RadialGradientPattern) {
        QRadialGradient radialGradient(50, 50, 50, 70, 70);
        radialGradient.setColorAt(0.0, Qt::white);
        radialGradient.setColorAt(0.2, Qt::green);
        radialGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(radialGradient);
    } else if (style == Qt::ConicalGradientPattern) {
        QConicalGradient conicalGradient(50, 50, 150);
        conicalGradient.setColorAt(0.0, Qt::white);
        conicalGradient.setColorAt(0.2, Qt::green);
        conicalGradient.setColorAt(1.0, Qt::black);
        renderArea->setBrush(conicalGradient);

Qt::RadialGradientPatternとQt::ConicalGradientPatternの場合は、QLinearGradientで使用されているものと同様の動作パターンが使用されます。

唯一の違いは、コンストラクタに渡される引数です。QRadialGradientコンストラクタに関しては、最初の引数が中心であり、2番目の引数が半径勾配の半径です。 3番目の引数はオプションですが、円内のグラデーションの焦点を定義するために使用できます(デフォルトの焦点は円の中心です)。 QConicalGradientコンストラクタに関して、最初の引数は円錐の中心を指定し、2番目の引数は補間の開始角度を指定します。

    } else if (style == Qt::TexturePattern) {
        renderArea->setBrush(QBrush(QPixmap(":/images/brick.png")));

ブラシスタイルがQt::TexturePatternの場合、QPixmapからQBrushを作成します。 次に、RenderArea::setBrush()slotを呼び出して、新しく作成したブラシでRenderAreaウィジェットを更新します。

    } else {
        renderArea->setBrush(QBrush(Qt::green, style));
    }
}

それ以外の場合は、指定されたスタイルと緑色でブラシを作成し、RenderAreaウィジェットを新しく作成したブラシで更新するためにRenderArea::setBrush()slotを呼び出します。

RenderArea Class Definition

RenderAreaクラスはQWidgetを継承し、QPainterを使用して現在アクティブな図形の複数のコピーをレンダリングします。

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    enum Shape { Line, Points, Polyline, Polygon, Rect, RoundedRect, Ellipse, Arc,
                 Chord, Pie, Path, Text, Pixmap };

    RenderArea(QWidget *parent = 0);

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

public slots:
    void setShape(Shape shape);
    void setPen(const QPen &pen);
    void setBrush(const QBrush &brush);
    void setAntialiased(bool antialiased);
    void setTransformed(bool transformed);

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    Shape shape;
    QPen pen;
    QBrush brush;
    bool antialiased;
    bool transformed;
    QPixmap pixmap;
};

最初に、ウィジェットによってレンダリングできるさまざまな図形(つまり、QPainterによってレンダリングできる図形)を保持するためのパブリックShape列挙型を定義します。 次に、コンストラクタとQWidgetのpublic関数の2つ、minimumSizeHint()とsizeHint()を再実装します。

また、QWidget::paintEvent()関数を再実装して、指定されたパラメータに従って現在アクティブな図形を描くことができます。

setShape()スロットはRenderAreaの形状を変更し、setPen()とsetBrush()スロットはウィジェットのペンとブラシを変更し、setAntialiased()とsetTransformed()スロットはウィジェットのそれぞれのプロパティを変更します。

RenderArea Class Implementation

コンストラクタでは、ウィジェットの変数の一部を初期化します。

RenderArea::RenderArea(QWidget *parent)
    : QWidget(parent)
{
    shape = Polygon;
    antialiased = false;
    transformed = false;
    pixmap.load(":/images/qt-logo.png");

    setBackgroundRole(QPalette::Base);
    setAutoFillBackground(true);
}

その形状をポリゴンに設定し、アンチエイリアス処理されたプロパティをfalseにして、イメージをウィジェットのpixmap変数にロードします。 最後に、ウィジェットのバックグラウンドロールを設定し、バックグラウンドのレンダリングに使用されるウィジェットのパレットからブラシを定義します。 QPalette::Baseは通常白です。

QSize RenderArea::sizeHint() const
{
    return QSize(400, 200);
}

RenderAreaは、ウィジェットの推奨サイズを保持するQWidgetのsizeHintプロパティを継承します。 このプロパティの値が無効なサイズの場合、サイズは推奨されません。

QWidget :: sizeHint()関数のデフォルト実装は、ウィジェットのレイアウトがない場合は無効なサイズを返し、それ以外の場合はレイアウトの推奨サイズを返します。

関数を再実装すると、幅400ピクセル、高さ200ピクセルのQSizeが返されます。

QSize RenderArea::minimumSizeHint() const
{
    return QSize(100, 100);
}

RenderAreaは、ウィジェットの推奨最小サイズを保持するQWidgetのminimumSizeHintプロパティも継承します。 このプロパティの値が無効なサイズの場合も、サイズはお勧めしません。

QWidget::minimumSizeHint()のデフォルト実装は、ウィジェットのレイアウトがない場合は無効なサイズを返し、それ以外の場合はレイアウトの最小サイズを返します。

関数を再実装すると、幅100ピクセル、高さ100ピクセルのQSizeが返されます。

void RenderArea::setShape(Shape shape)
{
    this->shape = shape;
    update();
}

void RenderArea::setPen(const QPen &pen)
{
    this->pen = pen;
    update();
}

void RenderArea::setBrush(const QBrush &brush)
{
    this->brush = brush;
    update();
}

public setShape()、setPen()、setBrush()スロットは、RenderAreaウィジェットのシェイプ、ペン、またはブラシを変更するたびに呼び出されます。 シェイプ、ペンまたはブラシをslotパラメーターに従って設定し、QWidget::update()を呼び出してRenderAreaウィジェットで変更を可視にします。

QWidget::update()スロットはすぐに再描画することはありません。 代わりにQtがメインイベントループに戻るときに処理のためのペイントイベントをスケジュールします。

void RenderArea::setAntialiased(bool antialiased)
{
    this->antialiased = antialiased;
    update();
}

void RenderArea::setTransformed(bool transformed)
{
    this->transformed = transformed;
    update();
}

setAntialiased()およびsetTransformed()スロットを使用して、slotパラメータに従ってプロパティの状態を変更し、QWidget::update()スロットを呼び出して、RenderAreaウィジェットで変更を可視にします。

void RenderArea::paintEvent(QPaintEvent * /* event */)
{
    static const QPoint points[4] = {
        QPoint(10, 80),
        QPoint(20, 10),
        QPoint(80, 30),
        QPoint(90, 70)
    };

    QRect rect(10, 20, 80, 60);

    QPainterPath path;
    path.moveTo(20, 80);
    path.lineTo(20, 30);
    path.cubicTo(80, 0, 50, 50, 80, 80);

    int startAngle = 20 * 16;
    int arcLength = 120 * 16;

次に、QWidget::paintEvent()関数を再実装します。 最初に行うことは、さまざまな図形を描くために必要なグラフィカルオブジェクトを作成することです。

4つのQPointからなるベクトルを作成します。 このベクトルを使用して、点、ポリライン、多角形を描画します。 その後、プレーンに矩形を定義するQRectを作成します。これは、PathとPixmapを除くすべてのシェイプの境界矩形として使用します。

また、QPainterPathも作成します。 QPainterPathクラスはペイント操作のコンテナを提供し、グラフィカルな図形を構築して再利用することができます。 ペインタパスは、長方形、楕円、線、および曲線などのいくつかのグラフィカルなビルディングブロックで構成されたオブジェクトです。 QPainterPathクラスの詳細については、ペインタパスの例を参照してください。 この例では、1つの直線とベジェ曲線で構成されるペインタパスを作成します。

さらに、Arc、Chord、Pieの各図形を描画するときに使用する開始角度と弧長を定義します。

    QPainter painter(this);
    painter.setPen(pen);
    painter.setBrush(brush);
    if (antialiased)
        painter.setRenderHint(QPainter::Antialiasing, true);

RenderAreaウィジェット用のQPainterを作成し、RenderAreaのペンとブラシに従ってペインタのペンとブラシを設定します。 アンチエイリアスパラメータオプションがチェックされている場合は、ペインタのレンダーヒントも設定します。 QPainter::Antialiasingは、可能であれば、エンジンがプリミティブのエッジをアンチエイリアスする必要があることを示します。

    for (int x = 0; x < width(); x += 100) {
        for (int y = 0; y < height(); y += 100) {
            painter.save();
            painter.translate(x, y);

最後に、RenderAreaの形状の複数のコピーをレンダリングします。 コピー数はRenderAreaウィジェットのサイズに依存し、2つのforループとウィジェットの高さと幅を使用して位置を計算します。

コピーごとに、まず現在のペインタの状態を保存します(状態をスタックにプッシュします)。 次に、QPainter::translate()関数を使って座標系をforループの変数で決まる位置に変換します。 この座標系の変換を省略すると、シェイプのすべてのコピーが、RenderAreaウィジェットの左上のコーマーで互いに重ねてレンダリングされます。

            if (transformed) {
                painter.translate(50, 50);
                painter.rotate(60.0);
                painter.scale(0.6, 0.9);
                painter.translate(-50, -50);
            }

Transformationsパラメータオプションがチェックされている場合、QPainter::rotate()関数を使用して座標系を時計回りに60度回転させてから、QPainter::scale()関数を使用して座標系をサイズ変更します 。 最後に座標系を元の位置に戻して回転させ、スケーリングします。

今度は、図形をレンダリングすると、3次元で回転したように見えます。

            switch (shape) {
            case Line:
                painter.drawLine(rect.bottomLeft(), rect.topRight());
                break;
            case Points:
                painter.drawPoints(points, 4);
                break;
            case Polyline:
                painter.drawPolyline(points, 4);
                break;
            case Polygon:
                painter.drawPolygon(points, 4);
                break;
            case Rect:
                painter.drawRect(rect);
                break;
            case RoundedRect:
                painter.drawRoundedRect(rect, 25, 25, Qt::RelativeSize);
                break;
            case Ellipse:
                painter.drawEllipse(rect);
                break;
            case Arc:
                painter.drawArc(rect, startAngle, arcLength);
                break;
            case Chord:
                painter.drawChord(rect, startAngle, arcLength);
                break;
            case Pie:
                painter.drawPie(rect, startAngle, arcLength);
                break;
            case Path:
                painter.drawPath(path);
                break;
            case Text:
                painter.drawText(rect,
                                 Qt::AlignCenter,
                                 tr("Qt by\nThe Qt Company"));
                break;
            case Pixmap:
                painter.drawPixmap(10, 10, pixmap);
            }

次に、RenderAreaの形状を特定し、関連するQPainter描画関数を使用してレンダリングします。

QPainter::drawLine(),
QPainter::drawPoints(),
QPainter::drawPolyline(),
QPainter::drawPolygon(),
QPainter::drawRect(),
QPainter::drawRoundedRect(),
QPainter::drawEllipse(),
QPainter::drawArc(),
QPainter::drawChord(),
QPainter::drawPie(),
QPainter::drawPath(),
QPainter::drawText() or
QPainter::drawPixmap()

レンダリングを開始する前に、現在のペインタの状態を保存しました(スタックに状態をプッシュします)。 その根拠は、座標系の同じ点に対する各形状のコピーの位置を計算することです。 座標系を変換するとき、翻訳プロセスを開始する前に現在のペインタの状態を保存しない限り、この点についての知識は失われます。

            painter.restore();
        }
    }

    painter.setRenderHint(QPainter::Antialiasing, false);
    painter.setPen(palette().dark().color());
    painter.setBrush(Qt::NoBrush);
    painter.drawRect(QRect(0, 0, width() - 1, height() - 1));
}

次に、シェイプのコピーのレンダリングが終了したら、QPainter::restore()関数を使用して、元のペインタの状態を関連する座標系に戻すことができます。 このようにして、次の形状のコピーが正しい位置にレンダリングされるようにします。

ペインタの状態を保存するのではなく、QPainter::translate()を使用して座標系を元に戻すことができます。 しかし、(変換パラメータオプションがチェックされている)座標系を変換するだけでなく、座標系を回転させてスケールするので、最も簡単な解決策は現在のペインタの状態を保存することです。

Files:

painting/basicdrawing/renderarea.cpp
painting/basicdrawing/renderarea.h
painting/basicdrawing/window.cpp
painting/basicdrawing/window.h
painting/basicdrawing/main.cpp
painting/basicdrawing/basicdrawing.pro
painting/basicdrawing/basicdrawing.qrc
Images:

painting/basicdrawing/images/brick.png
painting/basicdrawing/images/qt-logo.png

Composition Modes

alt

最も一般的な2つの構成は、SourceとSourceOverです。 ソースは、不透明なオブジェクトをペイントデバイスに描画するために使用されます。 このモードでは、ソース内の各ピクセルが宛先の対応するピクセルを置き換えます。 SourceOver合成モードでは、ソースオブジェクトは透明で、宛先の上に描画されます。

これらの標準モードに加えて、QtはX. PorterとY. Duffによって定義された構成モードの完全なセットを定義します。 詳細については、QPainterのマニュアルを参照してください。

Files:

painting/composition/composition.cpp
painting/composition/composition.h
painting/composition/main.cpp
painting/composition/composition.pro
painting/composition/composition.qrc

Concentric Circles Example

アプリケーションのメインウィンドウには、精度とアンチエイリアスのさまざまな組み合わせを使用して描画されたいくつかのウィジェットが表示されます。

alt

アンチエイリアスは、QPainterのレンダーヒントの1つです。 QPainter::RenderHintsは、任意のエンジンによって尊重されるかどうかにかかわらず、QPainterへのフラグを指定するために使用されます。 QPainter::Antialiasingは、可能であればエンジンがプリミティブのエッジをアンチエイリアスする必要があることを示します。つまり、エッジを滑らかにするために元のピクセルの周りに追加のピクセルを配置します。

浮動小数点精度と整数精度の違いは精度の問題であり、アプリケーションのメインウィンドウに表示されます。円のジオメトリを計算するロジックは同じですが、浮動小数点は各円の間の空白が一方、整数は2つの円を一緒に属するように見せます。その理由は、整数ベースの精度は、非整数計算を四捨五入することに依存するからです。

この例は2つのクラスで構成されています。

CircleWidgetは、いくつかのアニメーション同心円をレンダリングするカスタムウィジェットです。
Windowはアプリケーションのメインウィンドウで、精度とエイリアシングの異なる組み合わせを使用して描画された4つのCircleWidgetを表示します。
最初にCircleWidgetクラスを見直し、Windowクラスを見てみましょう。

CircleWidget Class Definition

CircleWidgetクラスはQWidgetを継承し、いくつかのアニメーション同心円をレンダリングするカスタムウィジェットです。

class CircleWidget : public QWidget
{
    Q_OBJECT

public:
    CircleWidget(QWidget *parent = 0);

    void setFloatBased(bool floatBased);
    void setAntialiased(bool antialiased);

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

public slots:
    void nextAnimationFrame();

protected:
    void paintEvent(QPaintEvent *event) override;

private:
    bool floatBased;
    bool antialiased;
    int frameNo;
};

floatBasedおよびアンチエイリアスされた変数を宣言して、クラスのインスタンスを整数または浮動小数点数の精度でレンダリングするか、レンダリングにアンチエイリアスを適用するかどうかを保持します。 これらの各変数を設定する関数も宣言します。

また、QWidget::paintEvent()関数を再実装して、レンダリング時の精度とアンチエイリアスのさまざまな組み合わせを適用し、アニメーションをサポートします。 私たちはアプリケーション内でウィジェットに妥当なサイズを与えるために、QWidget::minimumSizeHint()とQWidget::sizeHint()関数を再実装します。

プライベートのnextAnimationFrame()スロットと、ウィジェットの「アニメーションフレーム」の数を保持する関連するframeNo変数を宣言して、アニメーションを容易にします。

CircleWidget Class Implementation

コンストラクタでは、デフォルトでウィジェットのレンダリング整数ベースとエイリアスを作成します。

CircleWidget::CircleWidget(QWidget *parent)
    : QWidget(parent)
{
    floatBased = false;
    antialiased = false;
    frameNo = 0;

    setBackgroundRole(QPalette::Base);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

ウィジェットのframeNo変数を初期化し、色の役割を引数とするQWidget::setBackgroundColor()関数を使用してウィジェットの背景色を設定します。 QPalette::Baseの色の役割は通常白です。

次に、QWidget::setSizePolicy()関数を使用してウィジェットのサイズポリシーを設定します。 QSizePolicy::Expandingは、ウィジェットのsizeHint()が適切なサイズであることを意味しますが、ウィジェットは縮小されていても有効です。 ウィジェットは余分なスペースを使用することもできるので、できるだけ多くのスペースを確保する必要があります。

void CircleWidget::setFloatBased(bool floatBased)
{
    this->floatBased = floatBased;
    update();
}

void CircleWidget::setAntialiased(bool antialiased)
{
    this->antialiased = antialiased;
    update();
}

public setFloatBased()およびsetAntialiased()関数は、ウィジェットのレンダリングプリファレンス、すなわちウィジェットを整数または浮動小数点の精度でレンダリングするかどうか、レンダリングにアンチエイリアスを適用するかどうかを更新します。

この関数はまた、QWidget::update()関数を呼び出してペイントイベントを生成し、新しいレンダリング設定でウィジェットのペイントを強制的に再描画します。

QSize CircleWidget::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize CircleWidget::sizeHint() const
{
    return QSize(180, 180);
}

QWidget::minimumSizeHint()とQWidget::sizeHint()関数のデフォルト実装は、ウィジェットのレイアウトがない場合は無効なサイズを返し、そうでない場合はそれぞれレイアウトの最小サイズと推奨サイズを返します。

アプリケーション内で合理的なウィジェットの最小サイズと推奨サイズを与える関数を再実装します。

void CircleWidget::nextAnimationFrame()
{
    ++frameNo;
    update();
}

nextAnimationFrame()スロットは単にframeNo変数の値をインクリメントし、Qtがメインイベントループに戻るときに処理するペイントイベントをスケジュールするQWidget::update()関数を呼び出します。

void CircleWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, antialiased);
    painter.translate(width() / 2, height() / 2);

ペイントイベントは、ウィジェットの全部または一部を再ペイントするリクエストです。 paintEvent()関数は、ウィジェットのペイントイベントを受け取るために再実装できるイベントハンドラです。 イベントハンドラを再実装して、ウィジェットをレンダリングするときの精度とアンチエイリアスのさまざまな組み合わせを適用し、アニメーションをサポートします。

まず、ウィジェットのQPainterを作成し、ウィジェットの優先エイリアシングにアンチエイリアスフラグを設定します。 また、ウィジェットの中心を描画するために、ペインタ座標系を翻訳します。 翻訳は、円の中心がウィジェットの中心と同等であることを保証します。

    for (int diameter = 0; diameter < 256; diameter += 9) {
        int delta = abs((frameNo % 128) - diameter / 2);
        int alpha = 255 - (delta * delta) / 4 - diameter;

円を描くとき、私たちは、円の色のアルファチャンネルを決定するために、「アニメーションフレーム」の番号を使用します。255は完全に不透明な色を表しているアルファチャンネルは、0が完全に透明色を表し、色の透明効果を指定します。

        if (alpha > 0) {
            painter.setPen(QPen(QColor(0, diameter / 2, 127, alpha), 3));

            if (floatBased)
                painter.drawEllipse(QRectF(-diameter / 2.0, -diameter / 2.0, diameter, diameter));
            else
                painter.drawEllipse(QRect(-diameter / 2, -diameter / 2, diameter, diameter));
        }
    }
}

計算されたアルファチャンネルが完全に透明であれば、白い背景に白い円を描くことと同等なので、何も描画しません。 代わりに、次の円にスキップして、まだ空白を作成します。 計算されたアルファチャンネルが完全に不透明な場合は、ペンを設定します(QPenコンストラクタに渡されたQColorはデフォルトで必要なQBrushに変換されます)。 ウィジェットの優先精度がfloatベースの場合、QRectFとdouble値を使用して円の境界矩形を指定します。それ以外の場合は、QRectと整数を使用します。

アニメーションはpublic nextAnimationFrame()スロットによって制御されます。nextAnimationFrame()スロットが呼び出されるたびに、フレーム数がインクリメントされ、ペイントイベントがスケジュールされます。 次に、ウィジェットを再描画すると、円の色のアルファブレンディングが変化し、円がアニメーションとして表示されます。

Window Class Definition

WindowクラスはQWidgetを継承し、精度とエイリアシングの異なる組み合わせを使用して4つのCircleWidgetsをレンダリングするアプリケーションのメインウィンドウです。

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private:
    QLabel *createLabel(const QString &text);

    QLabel *aliasedLabel;
    QLabel *antialiasedLabel;
    QLabel *intLabel;
    QLabel *floatLabel;
    CircleWidget *circleWidgets[2][2];
};

メインウインドウの様々な構成要素、すなわち、4つのサークルウィジェットを参照するテキストラベルおよびダブル配列を宣言する。 さらに、コンストラクタを単純化するprivate createLabel()関数を宣言します。

Window Class Implementation

Window::Window()
{
    aliasedLabel = createLabel(tr("Aliased"));
    antialiasedLabel = createLabel(tr("Antialiased"));
    intLabel = createLabel(tr("Int"));
    floatLabel = createLabel(tr("Float"));

    QGridLayout *layout = new QGridLayout;
    layout->addWidget(aliasedLabel, 0, 1);
    layout->addWidget(antialiasedLabel, 0, 2);
    layout->addWidget(intLabel, 1, 0);
    layout->addWidget(floatLabel, 2, 0);

コンストラクタでは、最初にさまざまなラベルを作成し、それらをQGridLayoutに配置します。

    QTimer *timer = new QTimer(this);

    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            circleWidgets[i][j] = new CircleWidget;
            circleWidgets[i][j]->setAntialiased(j != 0);
            circleWidgets[i][j]->setFloatBased(i != 0);

            connect(timer, SIGNAL(timeout()),
                    circleWidgets[i][j], SLOT(nextAnimationFrame()));

            layout->addWidget(circleWidgets[i][j], i + 1, j + 1);
        }
    }

その後、QTimerを作成します。 QTimerクラスは、タイマーの高水準プログラミングインターフェイスであり、繰り返しタイマーと単発タイマーを提供します。

同心円のアニメーションを容易にするためのタイマーを作成します。 4つのCircleWidgetインスタンスを作成してレイアウトに追加すると、それぞれのウィジェットのnextAnimationFrame()スロットにQTimer::timeout()シグナルが接続されます。

    timer->start(100);
    setLayout(layout);

    setWindowTitle(tr("Concentric Circles"));
}

私たちは、メインウィンドウのレイアウトやウィンドウタイトルを設定する前に、我々は()関数を開始:: QTimerを使って、100ミリ秒のタイムアウト間隔でタイマ起動を行います。つまり、QTimer::タイムアウト()信号が4 CircleWidgetsの再描画を強制的に、放出されることを意味し、アニメーションのように円が表示される理由であるごとに100ミリ秒。

QLabel *Window::createLabel(const QString &text)
{
    QLabel *label = new QLabel(text);
    label->setAlignment(Qt::AlignCenter);
    label->setMargin(2);
    label->setFrameStyle(QFrame::Box | QFrame::Sunken);
    return label;
}

プライベートのcreateLabel()関数は、コンストラクタを単純化するために実装されています。

Files:

painting/concentriccircles/circlewidget.cpp
painting/concentriccircles/circlewidget.h
painting/concentriccircles/window.cpp
painting/concentriccircles/window.h
painting/concentriccircles/main.cpp
painting/concentriccircles/concentriccircles.pro

Font Sampler Example

「フォントサンプラー」の例は、複数ページの文書をプレビューして印刷する方法を示しています。

alt

Files:

painting/fontsampler/mainwindow.cpp
painting/fontsampler/mainwindow.h
painting/fontsampler/mainwindowbase.ui
painting/fontsampler/main.cpp
painting/fontsampler/fontsampler.pro

Gradients

alt

グラデーションには3種類あります。

・Linear(直線グラデーション)は、開始点と終了点の間で色を補間します。
・Radial gradients(放射状のグラデーション)は、焦点とそれを取り巻く円上の点との間の色を補間する。
・Conical gradients(円錐グラデーション)は、中心点の周りの色を補間します。

右側のパネルには、グラデーションの色を定義するカラーテーブルエディタが含まれています。 3つの最上部のコントロールは赤、緑、青のコンポーネントを決定し、最後はグラデーションのアルファを定義します。 マウスの左ボタンでクリックしてポイントを移動し、新しいポイントを追加したり、右ボタンでクリックしてポイントを削除したりすることができます。

ページ下部には、カラーテーブルの設定方法に関する推奨事項として3つのデフォルト設定が用意されています。

Files:

painting/gradients/gradients.cpp
painting/gradients/gradients.h
painting/gradients/main.cpp
painting/gradients/gradients.pro
painting/gradients/gradients.qrc

Image Composition Example

alt

Setting Up The Resource File

Image Compositionの例では、imagecomposition.qrcに埋め込まれたbutterfly.pngとchecker.pngの2つのソースイメージが必要です。 このファイルには、次のコードが含まれています。

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>images/butterfly.png</file>
    <file>images/checker.png</file>
</qresource>
</RCC>

リソースファイルの詳細については、The Qt Resource Systemを参照してください。

ImageComposer Class Definition

ImageComposerクラスは、chooseSource()、chooseDestination()、およびrecalculateResult()の3つのプライベートスロットを実装するQWidgetのサブクラスです。

class ImageComposer : public QWidget
{
    Q_OBJECT

public:
    ImageComposer();

private slots:
    void chooseSource();
    void chooseDestination();
    void recalculateResult();

さらに、ImageComposerは、QToolButton、QComboBox、QLabel、およびQImageのプライベートインスタンスだけでなく、addOp()、chooseImage()、loadImage()、currentMode()、およびimagePos()の5つのプライベート関数から構成されています。

private:
    void addOp(QPainter::CompositionMode mode, const QString &name);
    void chooseImage(const QString &title, QImage *image, QToolButton *button);
    void loadImage(const QString &fileName, QImage *image, QToolButton *button);
    QPainter::CompositionMode currentMode() const;
    QPoint imagePos(const QImage &image) const;

    QToolButton *sourceButton;
    QToolButton *destinationButton;
    QComboBox *operatorComboBox;
    QLabel *equalLabel;
    QLabel *resultLabel;

    QImage sourceImage;
    QImage destinationImage;
    QImage resultImage;
};

ImageComposer Class Implementation

私たちはQSizeオブジェクトresultSizeを幅と高さが200に等しい静的定数として宣言します。

static const QSize resultSize(200, 200);

コンストラクタ内では、QToolButtonオブジェクトsourceButtonをインスタンス化し、そのiconSizeプロパティをresultSizeに設定します。 operatorComboBoxはインスタンス化され、addOp()関数を使用して生成されます。 この関数は、QPainter::CompositionMode、modeおよびQString、nameを受け取り、合成モードの名前を表します。

ImageComposer::ImageComposer()
{
    sourceButton = new QToolButton;
    sourceButton->setIconSize(resultSize);

    operatorComboBox = new QComboBox;
    addOp(QPainter::CompositionMode_SourceOver, tr("SourceOver"));
    addOp(QPainter::CompositionMode_DestinationOver, tr("DestinationOver"));
    addOp(QPainter::CompositionMode_Clear, tr("Clear"));
    addOp(QPainter::CompositionMode_Source, tr("Source"));
    addOp(QPainter::CompositionMode_Destination, tr("Destination"));
    addOp(QPainter::CompositionMode_SourceIn, tr("SourceIn"));
    addOp(QPainter::CompositionMode_DestinationIn, tr("DestinationIn"));
    addOp(QPainter::CompositionMode_SourceOut, tr("SourceOut"));
    addOp(QPainter::CompositionMode_DestinationOut, tr("DestinationOut"));
    addOp(QPainter::CompositionMode_SourceAtop, tr("SourceAtop"));
    addOp(QPainter::CompositionMode_DestinationAtop, tr("DestinationAtop"));
    addOp(QPainter::CompositionMode_Xor, tr("Xor"));
    addOp(QPainter::CompositionMode_Plus, tr("Plus"));
    addOp(QPainter::CompositionMode_Multiply, tr("Multiply"));
    addOp(QPainter::CompositionMode_Screen, tr("Screen"));
    addOp(QPainter::CompositionMode_Overlay, tr("Overlay"));
    addOp(QPainter::CompositionMode_Darken, tr("Darken"));
    addOp(QPainter::CompositionMode_Lighten, tr("Lighten"));
    addOp(QPainter::CompositionMode_ColorDodge, tr("ColorDodge"));
    addOp(QPainter::CompositionMode_ColorBurn, tr("ColorBurn"));
    addOp(QPainter::CompositionMode_HardLight, tr("HardLight"));
    addOp(QPainter::CompositionMode_SoftLight, tr("SoftLight"));
    addOp(QPainter::CompositionMode_Difference, tr("Difference"));
    addOp(QPainter::CompositionMode_Exclusion, tr("Exclusion"));

destinationButtonがインスタンス化され、そのiconSizeプロパティもresultSizeに設定されます。 QLabels equalLabelとresultLabelが作成され、resultLabelのminimumWidthが設定されます。

    destinationButton = new QToolButton;
    destinationButton->setIconSize(resultSize);

    equalLabel = new QLabel(tr("="));

    resultLabel = new QLabel;
    resultLabel->setMinimumWidth(resultSize.width());

次の信号を対応するスロットに接続します。
・sourceButtonのclicked()信号はchooseSource()に接続されています。
・operatorComboBoxのactivated()信号はrecalculateResult()に接続されています。
・destinationButtonのclicked()シグナルはchooseDestination()に接続されています。

    connect(sourceButton, SIGNAL(clicked()), this, SLOT(chooseSource()));
    connect(operatorComboBox, SIGNAL(activated(int)), this, SLOT(recalculateResult()));
    connect(destinationButton, SIGNAL(clicked()), this, SLOT(chooseDestination()));

すべてのウィジェットを配置するには、QGridLayout、mainLayoutが使用されます。 mainLayoutのsizeConstraintプロパティはQLayout::SetFixedSizeに設定されています。つまり、ImageComposerのサイズを変更することはできません。

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->addWidget(sourceButton, 0, 0, 3, 1);
    mainLayout->addWidget(operatorComboBox, 1, 1);
    mainLayout->addWidget(destinationButton, 0, 2, 3, 1);
    mainLayout->addWidget(equalLabel, 1, 3);
    mainLayout->addWidget(resultLabel, 0, 4, 3, 1);
    mainLayout->setSizeConstraint(QLayout::SetFixedSize);
    setLayout(mainLayout);

QImage、resultImageを作成し、loadImage()を2回呼び出して両方のイメージファイルをimagecomposition.qrcファイルにロードします。 次に、windowTitleプロパティを "Image Composition"に設定します。

    resultImage = QImage(resultSize, QImage::Format_ARGB32_Premultiplied);

    loadImage(":/images/butterfly.png", &sourceImage, sourceButton);
    loadImage(":/images/checker.png", &destinationImage, destinationButton);

    setWindowTitle(tr("Image Composition"));
}

chooseSource()およびchooseDestination()関数は、特定のパラメータでchooseImage()を呼び出す便利な関数です。

void ImageComposer::chooseSource()
{
    chooseImage(tr("Choose Source Image"), &sourceImage, sourceButton);
}

void ImageComposer::chooseDestination()
{
    chooseImage(tr("Choose Destination Image"), &destinationImage, destinationButton);
}

chooseImage()関数は、タイトル、イメージ、およびボタンに応じて、ユーザーが選択したイメージを読み込みます。

void ImageComposer::chooseImage(const QString &title, QImage *image,
                                QToolButton *button)
{
    QString fileName = QFileDialog::getOpenFileName(this, title);
    if (!fileName.isEmpty())
        loadImage(fileName, image, button);
}

recalculateResult()関数は、ユーザーが選択した合成モードと共に2つの画像を合成した結果をamdで計算するために使用されます。

void ImageComposer::recalculateResult()
{
    QPainter::CompositionMode mode = currentMode();

    QPainter painter(&resultImage);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(resultImage.rect(), Qt::transparent);
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    painter.drawImage(0, 0, destinationImage);
    painter.setCompositionMode(mode);
    painter.drawImage(0, 0, sourceImage);
    painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
    painter.fillRect(resultImage.rect(), Qt::white);
    painter.end();

    resultLabel->setPixmap(QPixmap::fromImage(resultImage));
}

addOp()関数は、QComboBoxのaddItem関数を使用してoperatorComboBoxに項目を追加します。 この関数は、QPainter::CompositionMode、mode、およびQStringという名前を受け入れます。 四角形はQt::Transparentで塗りつぶされ、sourceImageとdestinationImageの両方が描画されてからresultLabelに表示されます。

void ImageComposer::addOp(QPainter::CompositionMode mode, const QString &name)
{
    operatorComboBox->addItem(name, mode);
}

loadImage()関数は、fillRect()を使用して透明な背景をペイントし、drawImage()を使用してイメージを中央の位置に描画します。 この画像はボタンのアイコンとして設定されます。

void ImageComposer::loadImage(const QString &fileName, QImage *image,
                              QToolButton *button)
{
    image->load(fileName);

    // Scale the image to given size
    *image = image->scaled(resultSize, Qt::KeepAspectRatio);

    QImage fixedImage(resultSize, QImage::Format_ARGB32_Premultiplied);
    QPainter painter(&fixedImage);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(fixedImage.rect(), Qt::transparent);
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    painter.drawImage(imagePos(*image), *image);
    painter.end();
    button->setIcon(QPixmap::fromImage(fixedImage));

    *image = fixedImage;

    recalculateResult();
}

currentMode()関数は、現在operatorComboBoxで選択されている合成モードを返します。

QPainter::CompositionMode ImageComposer::currentMode() const
{
    return (QPainter::CompositionMode)
           operatorComboBox->itemData(operatorComboBox->currentIndex()).toInt();
}

imagePos()関数を使用して、QToolButtonオブジェクト(sourceButtonおよびdestinationButton)にロードされたイメージが確実に一元化されるようにします。

QPoint ImageComposer::imagePos(const QImage &image) const
{
    return QPoint((resultSize.width() - image.width()) / 2,
                  (resultSize.height() - image.height()) / 2);
}

The main() Function

main()関数はQApplicationとImageComposerをインスタンス化し、そのshow()関数を呼び出します。

int main(int argc, char *argv[])
{
    Q_INIT_RESOURCE(imagecomposition);

    QApplication app(argc, argv);
    ImageComposer composer;
    composer.show();
    return app.exec();
}

Files:

painting/imagecomposition/imagecomposer.cpp
painting/imagecomposition/imagecomposer.h
painting/imagecomposition/main.cpp
painting/imagecomposition/imagecomposition.pro
painting/imagecomposition/imagecomposition.qrc
Images:

painting/imagecomposition/images/background.png
painting/imagecomposition/images/blackrectangle.png
painting/imagecomposition/images/butterfly.png
painting/imagecomposition/images/checker.png

Painter Paths Example

alt

QPainterPathクラスはペイント操作のコンテナを提供し、グラフィカルな図形を構築して再利用することができます。

ペインタパスは、四角形、楕円形、線分、曲線などの多数の図形ビルディングブロックで構成され、塗りつぶし、アウトライン化、およびクリッピングに使用できます。 通常の描画操作よりも画家のパスの主な利点は、複雑な図形は一度しか作成する必要がないことですが、QPainter::drawPath()の呼び出しだけを使用して何度も描画できます。

この例は2つのクラスで構成されています。
・単一のペインタパスを表示するカスタムウィジェットであるRenderAreaクラスです。
・いくつかのRenderAreaウィジェットを表示するアプリケーションメインウィンドウであるWindowクラスと、塗りつぶしパスの塗りつぶし、ペン、カラー、回転角度を操作できるようにする。

最初にWindowクラスを見て、次にRenderAreaクラスを見てみましょう。

Window Class Definition

WindowクラスはQWidgetを継承し、いくつかのRenderAreaウィジェットを表示するアプリケーションメインウィンドウであり、ペインタパスの塗りつぶし、ペン、カラー、回転角度を操作することができます。

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

private slots:
    void fillRuleChanged();
    void fillGradientChanged();
    void penColorChanged();

fillとcolor:fillRuleChanged()、fillGradientChanged()、およびpenColorChanged()に関するユーザー入力に応答するための3つのプライベートスロットを宣言します。

ユーザーがペンの幅と回転角度を変更すると、新しい値はQSpinBox::valueChanged()シグナルを使用してRenderAreaウィジェットに直接渡されます。 我々が充填と色を更新するためにスロットを実装しなければならない理由は、QComboBoxは、新しい値を引数として渡す同様のシグナルを提供しないということです。 RenderAreaウィジェットを更新する前に、新しい値または値を取得する必要があります。

private:
    void populateWithColors(QComboBox *comboBox);
    QVariant currentItemData(QComboBox *comboBox);

populateWithColors()は、与えられたQComboBoxに、Qtが知っている色の名前に対応する項目を設定し、currentItemData()は、指定されたQComboBoxの現在の項目を返します。

    QList<RenderArea*> renderAreas;
    QLabel *fillRuleLabel;
    QLabel *fillGradientLabel;
    QLabel *fillToLabel;
    QLabel *penWidthLabel;
    QLabel *penColorLabel;
    QLabel *rotationAngleLabel;
    QComboBox *fillRuleComboBox;
    QComboBox *fillColor1ComboBox;
    QComboBox *fillColor2ComboBox;
    QSpinBox *penWidthSpinBox;
    QComboBox *penColorComboBox;
    QSpinBox *rotationAngleSpinBox;
};

次に、メインウィンドウウィジェットのさまざまなコンポーネントを宣言します。 RenderAreaウィジェットの数を指定する便利な定数も宣言します。

Window Class Implementation

Windowクラスの実装では、まず定数Piを6つの有効数字で宣言します。

const float Pi = 3.14159f;

次に、コンストラクタで、さまざまなpainter pathを定義し、対応するRenderAreaウィジェットを作成します。このウィジェットはグラフィカルshapeをレンダリングします:

Window::Window()
{
    QPainterPath rectPath;
    rectPath.moveTo(20.0, 30.0);
    rectPath.lineTo(80.0, 30.0);
    rectPath.lineTo(80.0, 70.0);
    rectPath.lineTo(20.0, 70.0);
    rectPath.closeSubpath();

私たちは、QPainterPath::moveTo()とQPainterPath::lineTo()関数を使用して、鋭いコーナーの四角形を構築します。

QPainterPath::moveTo()は、現在のポイントを引数として渡されたポイントに移動します。ペインタパスは、多数のグラフィカルビルディングブロック、すなわち、サブパスから構成されるオブジェクトである。現在のポイントを移動すると、新しいサブパスが開始されます(新しいパスが開始されたときに暗黙のうちに現在のパスが閉じられます)。 QPainterPath::lineTo()関数は、現在の点から指定された終点までの直線を追加します。線が描かれた後、現在の点は線の終点に更新されます。

まず、現在のポイントを新しいサブパスを開始して移動させ、3つの矩形の辺を描画します。次に、現在のサブパスの先頭に線を引くQPainterPath::closeSubpath()関数を呼び出します。新しいサブパスは、現在のサブパスが閉じられると自動的に開始されます。新しいパスの現在の点は(0、0)です。また、QPainterPath::lineTo()を呼び出して最後の行を描画してから、QPainterPath::moveTo()関数を使用して新しいサブパスを明示的に開始することもできます。

QPainterPathはQPainterPath::addRect()便利関数を提供します。この関数は、指定された矩形を閉じたサブパスとしてパスに追加します。矩形は時計回りの線として追加されます。 rectが追加された後のペインタパスの現在の位置は、四角形の左上隅にあります。

    QPainterPath roundRectPath;
    roundRectPath.moveTo(80.0, 35.0);
    roundRectPath.arcTo(70.0, 30.0, 10.0, 10.0, 0.0, 90.0);
    roundRectPath.lineTo(25.0, 30.0);
    roundRectPath.arcTo(20.0, 30.0, 10.0, 10.0, 90.0, 90.0);
    roundRectPath.lineTo(20.0, 65.0);
    roundRectPath.arcTo(20.0, 60.0, 10.0, 10.0, 180.0, 90.0);
    roundRectPath.lineTo(75.0, 70.0);
    roundRectPath.arcTo(70.0, 60.0, 10.0, 10.0, 270.0, 90.0);
    roundRectPath.closeSubpath();

次に、角が丸い長方形を作成します。 前と同じように、QPainterPath::moveTo()とQPainterPath::lineTo()関数を使用して矩形の辺を描画します。 丸みのあるコーナーを作成するには、QPainterPath::arcTo()関数を使用します。

QPainterPath::arcTo()は、指定された矩形(QRectまたは矩形の座標によって指定される)を占める弧を作成します。弧は、指定された開始角度から始まり、与えられた度数を反時計回りに延長します。 角度は度で指定します。 時計回りの円弧は、負の角度を使用して指定できます。 この関数は、現在の点がアークの開始点に接続していない場合は、その点をアークの開始点に接続します。

    QPainterPath ellipsePath;
    ellipsePath.moveTo(80.0, 50.0);
    ellipsePath.arcTo(20.0, 30.0, 60.0, 40.0, 0.0, 360.0);

また、QPainterPath::arcTo()関数を使用して楕円パスを構築します。 最初に、現在のポイントを新しいパスから開始します。 次に、最後の引数として開始角度0.0と360.0度のQPainterPath::arcTo()を呼び出し、楕円を作成します。

ここでも、QPainterPathは、指定された境界矩形内に楕円を作成し、それをペインタパスに追加する便利な関数(QPainterPath::addEllipse())を提供します。 現在のサブパスが閉じている場合は、新しいサブパスが開始されます。 楕円は、ゼロ度(3時の位置)で開始および終了する時計回りの曲線で構成されています。

    QPainterPath piePath;
    piePath.moveTo(50.0, 50.0);
    piePath.arcTo(20.0, 30.0, 60.0, 40.0, 60.0, 240.0);
    piePath.closeSubpath();

円グラフのパスを作成するときは、前述の関数の組み合わせを引き続き使用します。まず、現在のポイントを移動し、新しいサブパスを開始します。 次に、チャートの中心から弧までの線と弧そのものを作成します。 サブパスを閉じると、グラフの中央に最後の行を暗黙的に構築します。

    QPainterPath polygonPath;
    polygonPath.moveTo(10.0, 80.0);
    polygonPath.lineTo(20.0, 10.0);
    polygonPath.lineTo(80.0, 30.0);
    polygonPath.lineTo(90.0, 70.0);
    polygonPath.closeSubpath();

ポリゴンの作成は、矩形の作成と同じです。

QPainterPathはQPainterPath::addPolygon()の便利な関数も提供します。この関数は、与えられたポリゴンを新しいサブパスとしてパスに追加します。 ポリゴンが追加された後の現在の位置は、ポリゴンの最後のポイントです。

    QPainterPath groupPath;
    groupPath.moveTo(60.0, 40.0);
    groupPath.arcTo(20.0, 20.0, 40.0, 40.0, 0.0, 360.0);
    groupPath.moveTo(40.0, 40.0);
    groupPath.lineTo(40.0, 80.0);
    groupPath.lineTo(80.0, 80.0);
    groupPath.lineTo(80.0, 40.0);
    groupPath.closeSubpath();

次に、サブパスのグループからなるパスを作成します。まず、現在のポイントを移動し、QPainterPath::arcTo()関数を使用して開始角0.0、最後の引数として360度を使用して円を作成します。 楕円のパスを作成しました。 次に、現在のポイントをもう一度移動し、新しいサブパスを開始し、QPainterPath::lineTo()関数を使用して三角形の3辺を構築します。

さて、QPainterPath::closeSubpath()関数を呼び出すと、最後の面が作成されます。 QPainterPath::closeSubpath()関数は、現在の部分パスの先頭、すなわち正方形に線を引くことを思い出してください。

QPainterPathは便利な関数QPainterPath::addPath()を提供しています。これは関数を呼び出すパスに与えられたパスを追加します。

    QPainterPath textPath;
    QFont timesFont("Times", 50);
    timesFont.setStyleStrategy(QFont::ForceOutline);
    textPath.addText(10, 70, timesFont, tr("Qt"));

テキストパスを作成するときは、最初にフォントを作成します。 次に、フォントマッチングアルゴリズムに、適切なデフォルトファミリを見つけるために使用するフォントの種類を指示するフォントのスタイル戦略を設定します。 QFont::ForceOutlineは、アウトラインフォントの使用を強制します。

テキストを構築するには、QPainterPath::addText()関数を使用します。この関数は、指定されたテキストを、指定されたフォントから作成された閉じたサブパスのセットとしてパスに追加します。 サブパスは、テキストのベースラインの左端が指定された点になるように配置されます。

    QPainterPath bezierPath;
    bezierPath.moveTo(20, 30);
    bezierPath.cubicTo(80, 0, 50, 50, 80, 80);

ベジェパスを作成するには、QPainterPath::cubicTo()関数を使用します。この関数は、現在のポイントと与えられたコントロールポイントを持つ指定されたエンドポイントの間にベジェ曲線を追加します。 カーブが追加されると、現在のポイントがカーブの終点に更新されます。

この場合、単純なカーブしか持たないように、サブパスを閉じるために省略します。 しかし、まだカーブのエンドポイントからサブパスの先頭までの論理的な線があります。 アプリケーションのメインウィンドウに表示されるように、パスを埋めるときに表示されます。

    QPainterPath starPath;
    starPath.moveTo(90, 50);
    for (int i = 1; i < 5; ++i) {
        starPath.lineTo(50 + 40 * std::cos(0.8 * i * Pi),
                        50 + 40 * std::sin(0.8 * i * Pi));
    }
    starPath.closeSubpath();

QPainterPathを使用して、前述のQPainterPath::moveTo()、QPainterPath::lineTo()、およびQPainterPath::closeSubpath()関数のみを使用して、複雑な図形を構築することができます。

    renderAreas.push_back(new RenderArea(rectPath));
    renderAreas.push_back(new RenderArea(roundRectPath));
    renderAreas.push_back(new RenderArea(ellipsePath));
    renderAreas.push_back(new RenderArea(piePath));
    renderAreas.push_back(new RenderArea(polygonPath));
    renderAreas.push_back(new RenderArea(groupPath));
    renderAreas.push_back(new RenderArea(textPath));
    renderAreas.push_back(new RenderArea(bezierPath));
    renderAreas.push_back(new RenderArea(starPath));

これで、必要なすべてのペインタパスが作成されたので、それぞれに対応するRenderAreaウィジェットを作成します。 最後に、Q_ASSERT()マクロを使用してレンダリング領域の数が正しいことを確認します。

    fillRuleComboBox = new QComboBox;
    fillRuleComboBox->addItem(tr("Odd Even"), Qt::OddEvenFill);
    fillRuleComboBox->addItem(tr("Winding"), Qt::WindingFill);

    fillRuleLabel = new QLabel(tr("Fill &Rule:"));
    fillRuleLabel->setBuddy(fillRuleComboBox);

次に、ペインタパスの塗りつぶしルールに関連付けられたウィジェットを作成します。

Qtには2つの塗りつぶしルールがあります。Qt::OddEvenFillルールは、ポイントからシェイプの外にある水平線を引いて、シェイプ内にポイントがあるかどうかを判断し、交差点の数を数えます。 交点の数が奇数の場合、その点は形状の内側にあります。 このルールはデフォルトです。

Qt::WindingFillルールは、ポイントからシェイプの外側にある水平線を引いて、シェイプの内側にポイントがあるかどうかを判断します。 次に、各交点での線の方向が上下であるかどうかを判定する。 巻数は、各交差点の方向を合計することによって決定される。 数値がゼロでない場合、点は形状の内側にあります。

Qt::WindingFillルールは、ほとんどの場合、閉じた形状の交差として考えることができます。

    fillColor1ComboBox = new QComboBox;
    populateWithColors(fillColor1ComboBox);
    fillColor1ComboBox->setCurrentIndex(fillColor1ComboBox->findText("mediumslateblue"));

    fillColor2ComboBox = new QComboBox;
    populateWithColors(fillColor2ComboBox);
    fillColor2ComboBox->setCurrentIndex(fillColor2ComboBox->findText("cornsilk"));

    fillGradientLabel = new QLabel(tr("&Fill Gradient:"));
    fillGradientLabel->setBuddy(fillColor1ComboBox);

    fillToLabel = new QLabel(tr("to"));
    fillToLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    penWidthSpinBox = new QSpinBox;
    penWidthSpinBox->setRange(0, 20);

    penWidthLabel = new QLabel(tr("&Pen Width:"));
    penWidthLabel->setBuddy(penWidthSpinBox);

    penColorComboBox = new QComboBox;
    populateWithColors(penColorComboBox);
    penColorComboBox->setCurrentIndex(penColorComboBox->findText("darkslateblue"));

    penColorLabel = new QLabel(tr("Pen &Color:"));
    penColorLabel->setBuddy(penColorComboBox);

    rotationAngleSpinBox = new QSpinBox;
    rotationAngleSpinBox->setRange(0, 359);
    rotationAngleSpinBox->setWrapping(true);
    rotationAngleSpinBox->setSuffix(QLatin1String("\xB0"));

    rotationAngleLabel = new QLabel(tr("&Rotation Angle:"));
    rotationAngleLabel->setBuddy(rotationAngleSpinBox);

また、塗りつぶし、ペン、回転角度に関連する他のウィジェットも作成します。

    connect(fillRuleComboBox, SIGNAL(activated(int)), this, SLOT(fillRuleChanged()));
    connect(fillColor1ComboBox, SIGNAL(activated(int)), this, SLOT(fillGradientChanged()));
    connect(fillColor2ComboBox, SIGNAL(activated(int)), this, SLOT(fillGradientChanged()));
    connect(penColorComboBox, SIGNAL(activated(int)), this, SLOT(penColorChanged()));

    for(QList<RenderArea*>::iterator it = renderAreas.begin(); it != renderAreas.end(); it++) {
        connect(penWidthSpinBox, SIGNAL(valueChanged(int)), *it, SLOT(setPenWidth(int)));
        connect(rotationAngleSpinBox, SIGNAL(valueChanged(int)), *it, SLOT(setRotationAngle(int)));
    }

comboboxes activated()シグナルをWindowクラスの関連スロットに接続し、スピンボックスvalueChanged()シグナルをRenderAreaウィジェットのそれぞれのスロットに直接接続します。

    QGridLayout *topLayout = new QGridLayout;

    int i=0;
    for(QList<RenderArea*>::iterator it = renderAreas.begin(); it != renderAreas.end(); it++, i++)
        topLayout->addWidget(*it, i / 3, i % 3);

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->addLayout(topLayout, 0, 0, 1, 4);
    mainLayout->addWidget(fillRuleLabel, 1, 0);
    mainLayout->addWidget(fillRuleComboBox, 1, 1, 1, 3);
    mainLayout->addWidget(fillGradientLabel, 2, 0);
    mainLayout->addWidget(fillColor1ComboBox, 2, 1);
    mainLayout->addWidget(fillToLabel, 2, 2);
    mainLayout->addWidget(fillColor2ComboBox, 2, 3);
    mainLayout->addWidget(penWidthLabel, 3, 0);
    mainLayout->addWidget(penWidthSpinBox, 3, 1, 1, 3);
    mainLayout->addWidget(penColorLabel, 4, 0);
    mainLayout->addWidget(penColorComboBox, 4, 1, 1, 3);
    mainLayout->addWidget(rotationAngleLabel, 5, 0);
    mainLayout->addWidget(rotationAngleSpinBox, 5, 1, 1, 3);
    setLayout(mainLayout);

別のレイアウトにRenderAreaウィジェットを追加し、残りのウィジェットと一緒にメインレイアウトに追加します。

    fillRuleChanged();
    fillGradientChanged();
    penColorChanged();
    penWidthSpinBox->setValue(2);

    setWindowTitle(tr("Painter Paths"));
}

最後に、fillRuleChanged()、fillGradientChanged()およびpenColorChanged()スロットを呼び出してRenderAreaウィジェットを初期化し、最初のペンの幅とウィンドウのタイトルを設定します。

void Window::fillRuleChanged()
{
    Qt::FillRule rule = (Qt::FillRule)currentItemData(fillRuleComboBox).toInt();

    for (QList<RenderArea*>::iterator it = renderAreas.begin(); it != renderAreas.end(); ++it)
        (*it)->setFillRule(rule);
}

void Window::fillGradientChanged()
{
    QColor color1 = qvariant_cast<QColor>(currentItemData(fillColor1ComboBox));
    QColor color2 = qvariant_cast<QColor>(currentItemData(fillColor2ComboBox));

    for (QList<RenderArea*>::iterator it = renderAreas.begin(); it != renderAreas.end(); ++it)
        (*it)->setFillGradient(color1, color2);
}

void Window::penColorChanged()
{
    QColor color = qvariant_cast<QColor>(currentItemData(penColorComboBox));

    for (QList<RenderArea*>::iterator it = renderAreas.begin(); it != renderAreas.end(); ++it)
        (*it)->setPenColor(color);
}

プライベートスロットは、関連するコンボボックスから新しい値を取得し、RenderAreaウィジェットを更新するために実装されています。

最初に、プライベートのcurrentItemData()関数とqvariant_cast()テンプレート関数を使用して、新しい値を決定します。 次に、RenderAreaウィジェットのそれぞれに対応するスロットを呼び出して、ペインタのパスを更新します。

void Window::populateWithColors(QComboBox *comboBox)
{
    QStringList colorNames = QColor::colorNames();
    foreach (QString name, colorNames)
        comboBox->addItem(name, QColor(name));
}

populateWithColors()関数は、指定されたコンボボックスに、Qtが静的なQColor::colorNames()関数によって提供されている色名に対応する項目を設定します。

QVariant Window::currentItemData(QComboBox *comboBox)
{
    return comboBox->itemData(comboBox->currentIndex());
}

currentItemData()関数は、指定されたコンボボックスの現在の項目を単純に返します。

Transformations Example

alt

このアプリケーションでは、QPainterの座標系の平行移動、回転、およびスケールを変更することによって、ユーザーがシェイプのレンダリングを操作できます。

この例は、2つのクラスとグローバルenumで構成されています。

・RenderAreaクラスは、指定されたシェイプのレンダリングを制御します。
・Windowクラスは、アプリケーションのメインウィンドウです。
・Operation enumは、アプリケーションで利用可能なさまざまな変換操作を記述します。

最初にOperation enumを簡単に見てから、RenderAreaクラスを見て、シェイプのレンダリング方法を確認します。 最後に、Windowクラスで実装されているTransformationsアプリケーションの機能について見ていきます。

Transformation Operations

通常、QPainterは関連するデバイスの独自の座標系で動作しますが、座標変換も良好にサポートしています。

ペイントデバイスのデフォルトの座標系は、左上隅に原点があります。 x値は右側に増加し、y値は下方に増加する。 QPainter::scale()関数を使用して座標系を一定のオフセットでスケーリングすることができます。QPainter::rotate()関数を使用して時計回りに回転させることができ、QPainter::translate()関数を使用して変換することができます。 また、QPainter::shear()関数を使用して、原点を中心に座標系をねじることもできます(せん断と呼ばれます)。

すべての変換操作は、QPainter::worldTransform()関数を使用して取得できるQPainterの変換マトリックスで動作します。 行列は、平面内の点を別の点に変換します。 変換行列の詳細については、Coordinate SystemおよびQTransformのドキュメントを参照してください。

enum Operation { NoTransformation, Translate, Rotate, Scale };

グローバルOperation enumは、renderarea.hファイルで宣言されており、Transformationsアプリケーションで使用できるさまざまな変換操作について説明しています。

RenderArea Class Definition

RenderAreaクラスはQWidgetを継承し、指定されたシェイプのレンダリングを制御します。

class RenderArea : public QWidget
{
    Q_OBJECT

public:
    RenderArea(QWidget *parent = 0);

    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;

setOperations()とsetShape()の2つのパブリック関数を宣言して、RenderAreaウィジェットのシェイプを指定し、シェイプがレンダリングされる座標系を変換できるようにします。

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 Class Implementation

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"));
}

コンストラクタでは、親クラスを基底クラスに渡し、座標を描画するために使用するフォントをカスタマイズします。 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()関数では、RenderAreaウィジェットを更新するために、新しい値を格納し、Qtがメインイベントループに戻るときに処理するペイントイベントをスケジュールするQWidget::update()スロットを呼び出します。

QSize RenderArea::minimumSizeHint() const
{
    return QSize(182, 182);
}

QSize RenderArea::sizeHint() const
{
    return QSize(232, 232);
}

QWidgetのminimumSizeHint()関数とsizeHint()関数を再実装して、アプリケーション内でRenderAreaウィジェットを適切なサイズにします。 これらの関数のデフォルトの実装では、このウィジェットのレイアウトがない場合は無効なサイズが返され、そうでない場合はレイアウトの最小サイズまたは推奨サイズがそれぞれ返されます。

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ウィジェットのペイントイベントを受け取ります。 ペイントイベントは、ウィジェットの全部または一部を再ペイントするリクエストです。 これは、QWidget::repaint()やQWidget::update()の結果として、またはウィジェットが隠されていて、今や発見されたために、あるいは他の多くの理由で起こることがあります。

最初に、RenderAreaウィジェット用のQPainterを作成します。 QPainter::Antialiasingレンダリングヒントは、可能であれば、エンジンがプリミティブのエッジをアンチエイリアスする必要があることを示します。 次に、QPainter::fillRect()関数を使用して再描画する必要がある領域を消去します。

また、元の形状が適切なマージンでレンダリングされるように、座標系を一定のオフセットで変換します。

    painter.save();
    transformPainter(painter);
    drawShape(painter);
    painter.restore();

シェイプのレンダリングを始める前に、QPainter::save()関数を呼び出します。

QPainter::save()は、現在の座標系を含む現在のペインタの状態を保存する(すなわち、スタックに状態をプッシュする)。 ペインタの状態を保存する根拠は、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の基本的な描画操作と基本的なグラフィックスプリミティブの表示方法の詳細については、「基本的な描画」の例を参照してください。

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 Class Definition

Windowクラスは、変換アプリケーションのメインウィンドウです。

アプリケーションは、4つのRenderAreaウィジェットを表示します。 一番左のウィジェットはQPainterのデフォルトの座標系でシェイプをレンダリングし、他のシェイプはRenderAreaウィジェットに適用されたすべての変換に加えて、選択した変換でシェイプをレンダリングします。

class Window : public QWidget
{
    Q_OBJECT

public:
    Window();

public slots:
    void operationChanged();
    void shapeSelected(int index);

2つのパブリック・スロットを宣言して、アプリケーションがユーザーの対話に応答できるようにし、表示されたRenderAreaウィジェットをユーザーの変換の選択に従って更新します。

operationChanged()スロットは、現在選択されている変換操作を適用する各RenderAreaウィジェットを更新し、ユーザーが選択した操作を変更するたびに呼び出されます。 shapeSelected()スロットは、ユーザが優先シェイプを変更するたびにRenderAreaウィジェットのシェイプを更新します。

private:
    void setupShapes();

    enum { NumTransformedAreas = 3 };
    RenderArea *originalRenderArea;
    RenderArea *transformedRenderAreas[NumTransformedAreas];
    QComboBox *shapeComboBox;
    QComboBox *operationComboBoxes[NumTransformedAreas];
    QList<QPainterPath> shapes;
};

また、Windowウィジェットの構築時に使用されるprivate簡易関数setupShapes()を宣言し、ウィジェットのさまざまなコンポーネントへのポインタを宣言します。 利用可能な図形をQPainterPathのQListに保存することを選択します。 さらに、QPainterのデフォルトの座標系でシェイプをレンダリングするウィジェットを除いて、表示されているRenderAreaウィジェットの数をカウントする専用の列挙型を宣言します。

Window Class Implementation

コンストラクタでは、アプリケーションのコンポーネントを作成して初期化します。

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ウィジェットを作成します。 また、関連するQComboBoxを作成することで、ユーザーはクロック、ハウス、テキスト、トラックの4つの異なる形状から選択することができます。 シェイプ自体は、コンストラクタの最後に、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], SIGNAL(activated(int)),
                this, SLOT(operationChanged()));

        layout->addWidget(transformedRenderAreas[i], 0, i + 1);
        layout->addWidget(operationComboBoxes[i], 1, i + 1);
    }

次に、座標変換の形でレンダリングするRenderAreaウィジェットを作成します。 デフォルトでは、適用される操作は「変形なし」です。つまり、シェイプはデフォルトの座標系内でレンダリングされます。 関連するQComboBoxをグローバルオペレーションenumで記述されたさまざまな変換操作に対応する項目で作成および初期化します。

また、ユーザーが選択した変換操作を変更するたびにアプリケーションを更新するために、QComboBoxesのactivated()信号をoperationChanged()スロットに接続します。

    setLayout(layout);
    setupShapes();
    shapeSelected(0);

    setWindowTitle(tr("Transformations"));
}

最後に、QWidget::setLayout()関数を使用してアプリケーションウィンドウのレイアウトを設定し、専用のsetupShapes()関数を使用して使用可能なシェイプを構築し、起動時にpublic 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, SIGNAL(activated(int)), this, SLOT(shapeSelected(int)));
}

setupShapes()関数はコンストラクターから呼び出され、アプリケーションで使用される図形を表すQPainterPathオブジェクトを作成します。 構築の詳細については、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);
    }
}

public operationChanged()スロットは、ユーザーが選択した操作を変更するたびに呼び出されます。

関連するQComboBoxを照会することによって、変換された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()スロットは、ユーザーが優先シェイプを変更したときに呼び出され、そのpublic setShape()関数を使用してRenderAreaウィジェットを更新します。

Summary

変換の例は、変換がQPainterがグラフィックスプリミティブをレンダリングする方法にどのように影響するかを示しています。 通常、QPainterはデバイス独自の座標系で動作しますが、座標変換も良好にサポートしています。 Transformationsアプリケーションを使用すると、QPainterの座標系をスケール、回転、平行移動できます。 これらの変換が適用される順序は、結果に不可欠です。

すべての変換操作は、QPainterの変換マトリックス上で動作します。 変換行列の詳細については、Coordinate SystemおよびQTransformのドキュメントを参照してください。

Qtリファレンスドキュメントには、いくつかのペイントの例があります。 これらの中には、Qtがペイント操作で変換を実行する能力を示すAffine Transformationsの例があります。 この例では、ユーザーがさまざまな変換操作を試すこともできます。

Files:

painting/transformations/renderarea.cpp
painting/transformations/renderarea.h
painting/transformations/window.cpp
painting/transformations/window.h
painting/transformations/main.cpp
painting/transformations/transformations.pro