ザ・ グラフィックデザインの現在の傾向 あらゆる形の丸い角をたくさん使うことです。この事実は、多くのWebページ、モバイルデバイス、およびデスクトップアプリケーションで確認できます。最も注目すべき例は、クリックされたときに何らかのアクションをトリガーするために使用されるアプリケーションのプッシュボタンです。角が90度の厳密な長方形ではなく、角が丸い形で描かれることがよくあります。丸みを帯びた角は、ユーザーインターフェイスをよりスムーズで快適に感じさせます。私はこれについて完全に確信しているわけではありませんが、私のデザイナーの友人は私にそう言っています。
ユーザーインターフェイスの視覚的要素はデザイナーによって作成され、プログラマーはそれらを適切な場所に配置するだけで済みます。しかし、その場で角が丸い形状を生成する必要があり、それをプリロードできない場合はどうなりますか?一部のプログラミングライブラリは、角が丸い事前定義された形状を作成するための機能が制限されていますが、通常、より複雑なケースでは使用できません。例えば、 Qtフレームワーク クラスがあります QPainter
、ウィジェット、ピックスマップ、画像など、QPaintDevice
から派生したすべてのクラスを描画するために使用されます。 drawRoundedRect
というメソッドがあり、名前が示すように、角が丸い長方形を描画します。しかし、もう少し複雑な形状が必要な場合は、自分で実装する必要があります。直線セグメントのグループで囲まれた平面形状であるポリゴンを使用して、これをどのように行うことができますか?紙に鉛筆でポリゴンを描いた場合、最初のアイデアは、消しゴムを使用して各コーナーの線の小さな部分を削除し、残りのセグメントの端を円弧で接続することです。プロセス全体を次の図に示します。
ユニットテストを行う方法
クラスQPainter
drawArc
という名前のオーバーロードされたメソッドがいくつかあり、円弧を描くことができます。それらはすべて、円弧の中心とサイズ、開始角度、および円弧の長さを定義するパラメータを必要とします。回転していない長方形に必要なこれらのパラメータの値を決定するのは簡単ですが、より複雑なポリゴンを扱う場合はまったく別の問題です。さらに、すべてのポリゴン頂点に対してこの計算を繰り返す必要があります。この計算は時間のかかる面倒な作業であり、人間はその過程であらゆる種類の計算エラーを起こしがちです。ただし、コンピュータを人間のために機能させるのはソフトウェア開発者の仕事であり、その逆ではありません。そこで、ここでは、複雑なポリゴンを角の丸い形に変えることができる単純なクラスを開発する方法を示します。このクラスのユーザーは、ポリゴンの頂点を追加するだけでよく、残りはクラスが行います。このタスクに使用する基本的な数学ツールは、 ベジェ曲線 。
ベジェ曲線の理論を説明する数学の本やインターネットリソースがたくさんあるので、関連するプロパティの概要を簡単に説明します。
定義上、ベジェ曲線は2次元サーフェス上の2点間の曲線であり、その軌道は1つ以上の制御点によって制御されます。厳密に言えば、追加の制御点がない2点間の曲線もベジェ曲線です。ただし、これにより2点が直線になるため、特に興味深いものでも、有用なものでもありません。
二次ベジェ曲線には1つの制御点があります。理論によれば、点間の2次ベジェ曲線 P0 そして P2 コントロールポイント付き P1 次のように定義されます。
B(t)=(1-t)2P0+ 2t(1-t)P1+ t2P2、ここで、0≤t≤1 (1)
そうするとき t に等しい 0 、 B(t) 降伏します P0 、 いつ t に等しい 1 、 B(t) 降伏します P2 、しかし他のすべての場合、の値 B(t) に依存します P1 。式以来 2t(1-t) で最大値を持っています t = 0.5 、それは P1 オン B(t) 最高になります。私たちは考えることができます P1 関数の軌道をそれ自体に引き寄せる架空の重力源として。次の図は、開始点、終了点、および制御点を持つ2次ベジェ曲線のいくつかの例を示しています。
では、ベジェ曲線を使用して問題をどのように解決するのでしょうか?下の図は説明を提供します。
ポリゴンの頂点とその周囲の接続された線分の短い部分を削除することを想像すると、1つの線分の終わりは次のように考えることができます。 P0 、他の線分は現在終了しています P2 および削除された頂点 P1 。この点と出来上がりのセットに2次ベジェ曲線を適用します。目的の丸い角があります。
クラスQPainter
二次ベジェ曲線を描く方法はありません。式(1)に従って最初から実装するのは非常に簡単ですが、Qtライブラリはより優れたソリューションを提供します。 2D描画には別の強力なクラスがあります:QPainterPath
。クラスQPainterPath
は、後で追加してQPainter
で使用できる線と曲線のコレクションです。オブジェクト。現在のコレクションにベジェ曲線を追加するオーバーロードされたメソッドがいくつかあります。特に、メソッドquadTo
二次ベジェ曲線を追加します。曲線は現在のQPainterPath
から始まりますポイント( P0 )、一方 P1 そして P2 quadTo
に渡す必要がありますパラメータとして。
QPainter
の方法drawPath
QPainterPath
から線と曲線のコレクションを描画するために使用されますアクティブなペンとブラシを使用して、パラメータとして指定する必要があるオブジェクト。
それでは、クラス宣言を見てみましょう。
class RoundedPolygon : public QPolygon { public: RoundedPolygon() { SetRadius(10); } void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; } const QPainterPath& GetPath(); private: QPointF GetLineStart(int i) const; QPointF GetLineEnd(int i) const; float GetDistance(QPoint pt1, QPoint pt2) const; private: QPainterPath m_path; unsigned int m_iRadius; };
サブクラス化することにしましたQPolygon
そのため、頂点やその他のものを自分で追加する必要はありません。半径を適切な初期値に設定するコンストラクターの他に、このクラスには2つのパブリックメソッドがあります。
SetRadius
メソッドは、半径を指定された値に設定します。半径は、各頂点の近くの直線の長さ(ピクセル単位)であり、丸みを帯びた角では削除されます(より正確には描画されません)。GetPath
すべての計算が行われる場所です。 QPainterPath
を返しますRoundedPolygon
に追加されたポリゴンポイントから生成されたオブジェクト。プライベート部分のメソッドは、GetPath
で使用される単なる補助メソッドです。
javascriptノードとは
実装を見てみましょう。プライベートメソッドから始めます。
float RoundedPolygon::GetDistance(QPoint pt1, QPoint pt2) const { float fD = (pt1.x() - pt2.x())*(pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y()); return sqrtf(fD); }
ここで説明することはあまりありませんが、このメソッドは、指定された2点間のユークリッド距離を返します。
QPointF RoundedPolygon::GetLineStart(int i) const { QPointF pt; QPoint pt1 = at(i); QPoint pt2 = at((i+1) % count()); float fRat = m_uiRadius / GetDistance(pt1, pt2); if (fRat > 0.5f) fRat = 0.5f; pt.setX((1.0f-fRat)*pt1.x() + fRat*pt2.x()); pt.setY((1.0f-fRat)*pt1.y() + fRat*pt2.y()); return pt; }
メソッドGetLineStart
ポイントの位置を計算します P2 最後の図から、ポイントが時計回りにポリゴンに追加された場合。より正確には、m_uiRadius
であるポイントを返します。 i
番目の頂点から(i+1)
番目の頂点に向かう方向のピクセル。 (i+1)
番目の頂点にアクセスするとき、ポリゴンでは、最後の頂点と最初の頂点の間に線分もあり、閉じた形状になるため、式(i+1)%count()
を覚えておく必要があります。 。これにより、メソッドが範囲外になるのを防ぎ、代わりに最初のポイントにアクセスします。変数fRat
半径とi
番目の線分の長さの比率を保持します。 fRat
を防ぐチェックもあります0.5
を超える値を持つことから。 fRat
の場合の値が0.5
を超えると、2つの連続する丸い角が重なり、視覚的な結果が低下します。
ポイントから旅行するとき P1 に P2 直線で、距離の30%を完了することにより、次の式を使用して位置を特定できます。 0.7•P1+ 0.3•P2 。一般に、全距離の一部を達成した場合、 α= 1 は完全な距離を示し、現在の場所は (1-α)•P1 +α•P2 。
これがGetLineStart
の方法ですメソッドは、m_uiRadius
であるポイントの位置を決定しますi
-th頂点から(i+1)
-thの方向にピクセル離れています。
QPointF RoundedPolygon::GetLineEnd(int i) const { QPointF pt; QPoint pt1 = at(i); QPoint pt2 = at((i+1) % count()); float fRat = m_uiRadius / GetDistance(pt1, pt2); if (fRat > 0.5f) fRat = 0.5f; pt.setX(fRat*pt1.x() + (1.0f - fRat)*pt2.x()); pt.setY(fRat*pt1.y() + (1.0f - fRat)*pt2.y()); return pt; }
この方法はGetLineStart
と非常によく似ています。ポイントの位置を計算します P0 (i+1)
-thではなく、i
-th頂点の場合。言い換えれば、GetLineStart(i)
から線を引くと〜GetLineEnd(i)
i
ごとに0
の間およびn-1
、ここでn
はポリゴン内の頂点の数です。頂点とその周辺が消去されたポリゴンを取得します。
そして今、メインクラスのメソッド:
レスポンシブウェブデザイン画像のベストプラクティス
const QPainterPath& RoundedPolygon::GetPath() { m_path = QPainterPath(); if (count() <3) { qWarning() << 'Polygon should have at least 3 points!'; return m_path; } QPointF pt1; QPointF pt2; for (int i = 0; i < count(); i++) { pt1 = GetLineStart(i); if (i == 0) m_path.moveTo(pt1); else m_path.quadTo(at(i), pt1); pt2 = GetLineEnd(i); m_path.lineTo(pt2); } // close the last corner pt1 = GetLineStart(0); m_path.quadTo(at(0), pt1); return m_path; }
この方法では、QPainterPath
を作成しますオブジェクト。ポリゴンに少なくとも3つの頂点がない場合、2D形状を処理しなくなります。この場合、メソッドは警告を発行し、空のパスを返します。十分なポイントが利用可能になったら、ポリゴンのすべての直線セグメントをループし(もちろん、線セグメントの数は頂点の数と同じです)、丸められた間の各直線セグメントの開始と終了を計算します。コーナー。現在の頂点の位置を制御点として使用して、これら2つの点の間に直線を置き、前の線分の終わりと電流の始まりの間に2次ベジェ曲線を置きます。ループの後、最後の線分と最初の線分の間のベジェ曲線でパスを閉じる必要があります。これは、ループではベジェ曲線よりも1本多く直線を描いたためです。
RoundedPolygon
使用法と結果それでは、このクラスを実際に使用する方法を見てみましょう。
QPixmap pix1(300, 200); QPixmap pix2(300, 200); pix1.fill(Qt::white); pix2.fill(Qt::white); QPainter P1(&pix1); QPainter P2(&pix2); P1.setRenderHints(QPainter::Antialiasing); P2.setRenderHints(QPainter::Antialiasing); P1.setPen(QPen(Qt::blue, 2)); P1.setBrush(Qt::red); P2.setPen(QPen(Qt::blue, 2)); P2.setBrush(Qt::red); RoundedPolygon poly; poly << QPoint(147, 187) << QPoint(95, 187) << QPoint(100, 175) << QPoint(145, 165) << QPoint(140, 95) << QPoint(5, 85) << QPoint(5, 70) << QPoint(140, 70) << QPoint(135, 45) << QPoint(138, 25) << QPoint(145, 5) << QPoint(155, 5) << QPoint(162, 25) << QPoint(165, 45) << QPoint(160, 70) << QPoint(295, 70) << QPoint(295, 85) << QPoint(160, 95) << QPoint(155, 165) << QPoint(200, 175) << QPoint(205, 187) << QPoint(153, 187) << QPoint(150, 199); P1.drawPolygon(poly); P2.drawPath(poly.GetPath()); pix1.save('1.png'); pix2.save('2.png');
このソースコードは非常に単純です。 2つを初期化した後QPixmaps
そしてそれらのQPainters
、RoundedPolygon
を作成しますオブジェクトを作成し、ポイントで埋めます。画家P1
P2
が正多角形を描画している間、 QPainterPath
を描画しますポリゴンから生成された、角が丸い。結果の両方のピックスマップがファイルに保存され、結果は次のようになります。
特にQtなどの優れたプログラミングフレームワークを使用している場合、ポリゴンから角が丸い形状を生成することは、結局のところそれほど難しくないことがわかりました。このプロセスは、概念実証としてこのブログで説明したクラスによって自動化できます。ただし、次のような改善の余地はまだたくさんあります。
RoundedPolygon
を使用するビットマップを生成します。これを背景ウィジェットマスクとして使用して、クレイジーな形のウィジェットを作成できます。RoundedPolygon
クラスは実行速度に対して最適化されていません。コンセプトをわかりやすくするためにそのままにしておきました。最適化には、ポリゴンに新しい頂点を追加するときに多くの中間値を計算することが含まれる場合があります。また、GetPath
の場合生成されたQPainterPath
への参照を返そうとしている場合、オブジェクトが最新であることを示すフラグを設定できます。 GetPath
への次の呼び出し同じQPainterPath
のみを返すことになりますオブジェクト、何も再計算せずに。ただし、開発者は、ポリゴンの頂点が変更されるたびに、また新しい頂点ごとにこのフラグがクリアされるようにする必要があります。これにより、最適化されたクラスは最初から開発され、派生しない方がよいと思います。 QPolygon
から。幸いなことに、これは思ったほど難しくはありません。全体として、RoundedPolygon
クラスは、そのまま、追加したいときにいつでもツールとして使用できます。 デザイナータッチ 事前にピックスマップやシェイプを準備しなくても、その場でGUIにアクセスできます。