私は6年以上Qtを開発に使用してきましたが、この3年間でQt QuickでAndroidおよびiOS用のアプリケーションを作成しました。 このフレームワークに対する私のコミットメントは、次の2つの理由によるものです。
- Qtは、ほとんどのアプリケーションを開発するのに十分なコンポーネント、関数、クラスなどの大きなパッケージを提供します。
- 不足しているコンポーネントを作成する必要がある場合、Qtは、このためのいくつかのレベルの抽象化を提供します-シンプルからエンコード、最も生産的で機能的なものまで。
たとえば、Qt Quickには、インターフェイスに画像を配置するImageコンポーネントがあります。 このコンポーネントには、位置、スケーリング方法、スムージングなどの多くのパラメーターがありますが、画像の角を丸めるための半径パラメーターはありません。 同時に、現在のほぼすべてのインターフェイスで丸い画像を見つけることができるため、画像を記述する必要があります。 すべての画像パラメーターと半径をサポート。 この記事では、丸みを帯びた画像を作成するいくつかの方法を説明します。
最初の実装、彼女は素朴です
Qt Quickには、グラフィックエフェクトQtGraphicalEffectsを操作するためのライブラリがあります。 実際、各コンポーネントはシェーダーとOpenGLのラッパーです。 したがって、私はこれがすぐに機能することを提案し、次のようなことをしました:
import QtQuick 2.0 import QtGraphicalEffects 1.0 Item { property alias source: imageOriginal.source property alias radius: mask.radius Image { id: imageOriginal anchors.fill: parent visible: false } Rectangle { id: rectangleMask anchors.fill: parent radius: 0.5*height visible: false } OpacityMask { id: opacityMask anchors.fill: imageOriginal source: imageOriginal maskSource: rectangleMask } }
それがどのように機能するかを見てみましょう: opacityMask
はimageOriginal
画像にrectangleMask
マスクマスクを課し、何が起こったかを表示します。 元の画像と長方形は不可視であるvisible: false
注意してくださいvisible: false
。 これは、重複を避けるために必要です。 opacityMask
は独立したコンポーネントであり、シーン内の他の要素の表示には直接影響しません。
これは可能な限り最も簡単で最も遅い実装です。 画像の長いリストを作成してスクロールすると、表示の遅れがすぐに表示されます(たとえば、Telegramのような連絡先リスト)。 さらに大きな不快感は、画像サイズ変更のブレーキをもたらします。 問題は、元の画像と要素のサイズが変わらない場合でも、 QtGraphicalEffects
ライブラリのすべてのコンポーネントがグラフィックスサブシステムに大きな負荷をかけることです。 この問題は、 grubToImage(...)関数を使用して静的な丸い画像を作成することでわずかに軽減できますが、画像の丸めの別の実装を使用することをお勧めします。
2番目の実装、Canvas
次に思い浮かんだ方法は、 Canvasを使用して背景色で画像の上に画像の角を描くことでした。 この場合、画像のサイズと半径が同じ場合、Canvasは再描画できませんが、新しい要素ごとにコピーされます。 この最適化により、最初の実装と比較して、レンダリング速度が向上します。
このアプローチには2つのマイナス点があります。 まず、サイズと半径の変更にはCanvasの再描画が必要です。場合によっては、OpacityMaskを使用したソリューションよりもパフォーマンスが低下することもあります。 そして2つ目-画像の下の背景は均一でなければなりません。そうでなければ、錯覚が開きます。
import QtQuick 2.0 import QtGraphicalEffects 1.0 Item { property alias source: imageOriginal.source property real radius: 20 property color backgroundColor: "white" Image { id: imageOriginal anchors.fill: parent visible: false } Canvas { id: roundedCorners anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.fillStyle = backgroundColor; ctx.beginPath(); ctx.moveTo(0, radius) ctx.lineTo(0, 0); ctx.lineTo(radius, 0); ctx.arc(radius, radius, radius, 3/2*Math.PI, Math.PI, true); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(width, radius) ctx.lineTo(width, 0); ctx.lineTo(width-radius, 0); ctx.arc(width-radius, radius, radius, 3/2*Math.PI, 2*Math.PI, false); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(0, height-radius) ctx.lineTo(0, height); ctx.lineTo(radius, height); ctx.arc(radius, height-radius, radius, 0.5*Math.PI, Math.PI, false); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(width-radius, height) ctx.lineTo(width, height); ctx.lineTo(width, height-radius); ctx.arc(width-radius, height-radius, radius, 0, 0.5*Math.PI, false); ctx.closePath(); ctx.fill(); } } }
3番目の実装、QPainter
生産性を向上させ、均一な背景への依存を取り除くために、C ++クラスQQuickPaintedItemに基づいてQMLコンポーネントを作成しました。 このクラスは、 QPainterを介してコンポーネントをレンダリングするメカニズムを提供します。 これを行うには、親クラスのvoid paint(QPainter *painter)
メソッドをオーバーライドしvoid paint(QPainter *painter)
。 名前から、コンポーネントをレンダリングするためにメソッドが呼び出されることは明らかです。
void ImageRounded::paint(QPainter *painter) { QPen pen; pen.setStyle(Qt::NoPen); painter->setPen(pen); QImage *image = new QImage("image.png"); // QBrush brush(image); // qreal wi = static_cast<qreal>(image.width()); qreal hi = static_cast<qreal>(image.height()); qreal sw = wi / width(); qreal sh = hi / height(); brush.setTransform(QTransform().scale(1/sw, 1/sh)); painter->setBrush(brush); // qreal radius = 10 painter->drawRoundedRect(QRectF(0, 0, width(), height()), radius, radius); }
上記の例では、元の画像は要素のサイズに引き伸ばされ、角の丸い長方形を描くときのパターンとして使用されます。 コードを簡素化するために、以降、画像のスケーリングオプションPreserveAspectFit
およびPreserveAspectFit
は考慮せず、 Stretch
のみを考慮します。
デフォルトでは、 QPainter
は画像を描画し、それをOpenGLバッファーにコピーします。 FBOで直接描画すると、コンポーネントのレンダリングが数回加速します。 これを行うには、クラスコンストラクターで次の2つの関数を呼び出します。
setRenderTarget(QQuickPaintedItem::FramebufferObject); setPerformanceHint(QQuickPaintedItem::FastFBOResizing, true);
最終実装、Qt Quick Scene Graph
QQuickPaintedItem
の実装は、1番目と2番目よりもはるかに高速です。 ただし、この場合でも、スマートフォンでは、画像サイズを変更するとレンダリングの遅延が顕著になります。 実際には、スケーリングイメージの機能はすべてプロセッサ容量で実行され、少なくとも150ミリ秒かかります(i7およびHTC One M8で測定)。 別のストリームにスケーリングを行って、準備ができたときに画像を描画できます-これにより応答性が向上します(アプリケーションは常にユーザーのアクションに応答します)が、本質的に問題を解決することはありません-スケーリングすると、ぎくしゃくした画像が表示されます。
ボトルネックはプロセッサであるため、ビデオアクセラレータのパワーを使用することが思い浮かびます。 Qt QuickはこのためにQQuickItemクラスを提供します。 継承する場合、 updatePaintNode
メソッドをオーバーライドする必要があります。 このメソッドは、コンポーネントを描画する必要があるたびに呼び出されます。
QSGNode* ImageRounded::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { if (_status != Ready) { return nullptr; } QSGGeometryNode *node; if (!oldNode) { node = new QSGGeometryNode(); // QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), _segmentCount); geometry->setDrawingMode(QSGGeometry::DrawTriangleFan); setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); node->setFlag(QSGNode::OwnsOpaqueMaterial); // auto image = new QImage("image.png"); auto texture = qApp->view()->createTextureFromImage(image); auto material = new QSGOpaqueTextureMaterial; material->setTexture(texture); material->setFiltering(QSGTexture::Linear); material->setMipmapFiltering(QSGTexture::Linear); setMaterial(material); node->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); } else { node = oldNode; node->markDirty(QSGNode::DirtyGeometry); } // QSGGeometry::TexturedPoint2D *vertices = node->geometry()->vertexDataAsTexturedPoint2D(); const int count = 20; // const int segmentCount = 4*count + 3; // Coefficients cf = {0, 0, width(), height() ,0, 0, 1/width(), 1/height()}; const float ox = 0.5f*cf.w + cf.x; const float oy = 0.5f*cf.h + cf.y; const float lx = 0.5f*cf.w + cf.x; const float ly = cf.y; const float ax = 0 + cf.x; const float ay = 0 + cf.y; const float bx = 0 + cf.x; const float by = cf.h + cf.y; const float cx = cf.w + cf.x; const float cy = cf.h + cf.y; const float dx = cf.w + cf.x; const float dy = 0 + cf.y; const float r = 2*_radius <= cf.w && 2*_radius <= cf.h ? _radius : 2*_radius <= cf.w ? 0.5f*cf.w : 0.5f*cf.h; vertices[0].set(ox, oy, ox*cf.tw+cf.tx, oy*cf.th+cf.ty); vertices[1].set(lx, ly, lx*cf.tw+cf.tx, ly*cf.th+cf.ty); // int start = 2; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = ax + r*(1 - qFastSin(angle)); float y = ay + r*(1 - qFastCos(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = bx + r*(1 - qFastCos(angle)); float y = by + r*(-1 + qFastSin(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = cx + r*(-1 + qFastSin(angle)); float y = cy + r*(-1 + qFastCos(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = dx + r*(-1 + qFastCos(angle)); float y = dy + r*(1 - qFastSin(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } vertices[segmentCount-1].set(lx, ly, lx*cf.tw+cf.tx, ly*cf.th+cf.ty); return node; }
ネタバレの下の例では、最初にQSGGeometryNodeクラスのオブジェクトを作成します。このオブジェクトをレンダリングのためにQt Quick Scene Graphエンジンに返します。 次に、オブジェクトのジオメトリ(角の丸い長方形)を示し、元の画像からテクスチャを作成し、テクスチャ座標を転送します(ジオメトリ上でテクスチャがどのように引き伸ばされるかを示します)。 注:この例のジオメトリは、 三角形の扇の方法によって設定されます。 コンポーネントの例を次に示します。
おわりに
この記事では、Qt Quickで丸みを帯びた画像をレンダリングするためのさまざまな方法を収集しようとしました:最も単純なものから最も生産的なものまで。 トピックは私自身の落とし穴がある別の記事であるため、QMLコンポーネントを作成する際に、イメージのロード方法と詳細を意図的に逃しました。 ただし、友人と私がモバイルアプリケーションの作成に使用しているライブラリのソースコードは常に表示できます: こちら