diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2017-07-28 13:44:09 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@qt.io> | 2017-08-08 08:00:30 +0000 |
commit | 003e24d72a9647e2dc397906234059fee19103ec (patch) | |
tree | bbe749e8d665dda882cd7445f77e9d4c813b1e35 | |
parent | 432e27ae092397cb2154f48103e729852c38cf2d (diff) |
shapes: Add support for radial gradients
Task-number: QTBUG-61857
Change-Id: I580e503d8266a9dca69bb542c22228df4ff4bf94
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
24 files changed, 837 insertions, 96 deletions
diff --git a/examples/quick/shapes/content/item3.qml b/examples/quick/shapes/content/item3.qml new file mode 100644 index 0000000000..4274f260f3 --- /dev/null +++ b/examples/quick/shapes/content/item3.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.9 +import QtQuick.Shapes 1.0 + +Rectangle { + color: "lightGray" + Shape { + width: 200 + height: 150 + anchors.centerIn: parent + ShapePath { + strokeWidth: 4 + strokeColor: "red" + fillGradient: RadialGradient { + centerX: 100; centerY: 100; centerRadius: 100 + SequentialAnimation on focalRadius { + loops: Animation.Infinite + NumberAnimation { from: 1; to: 20; duration: 2000 } + NumberAnimation { from: 20; to: 1; duration: 2000 } + } + SequentialAnimation on focalX { + loops: Animation.Infinite + NumberAnimation { from: 50; to: 150; duration: 3000 } + NumberAnimation { from: 150; to: 50; duration: 3000 } + } + SequentialAnimation on focalY { + loops: Animation.Infinite + NumberAnimation { from: 50; to: 150; duration: 1000 } + NumberAnimation { from: 150; to: 50; duration: 1000 } + } + GradientStop { position: 0; color: "#ffffff" } + GradientStop { position: 0.11; color: "#f9ffa0" } + GradientStop { position: 0.13; color: "#f9ff99" } + GradientStop { position: 0.14; color: "#f3ff86" } + GradientStop { position: 0.49; color: "#93b353" } + GradientStop { position: 0.87; color: "#264619" } + GradientStop { position: 0.96; color: "#0c1306" } + GradientStop { position: 1; color: "#000000" } + } + fillColor: "blue" // ignored with the gradient set + strokeStyle: ShapePath.DashLine + dashPattern: [ 1, 4 ] + startX: 20; startY: 20 + PathLine { x: 180; y: 130 } + PathLine { x: 20; y: 130 } + PathLine { x: 20; y: 20 } + } + } +} diff --git a/examples/quick/shapes/content/pathitem.qml b/examples/quick/shapes/content/pathitem.qml index 0933aa79b2..e9c7ce4547 100644 --- a/examples/quick/shapes/content/pathitem.qml +++ b/examples/quick/shapes/content/pathitem.qml @@ -51,8 +51,8 @@ import QtQuick 2.0 Item { - width: 1024 - height: 768 + width: 1280 + height: 720 LauncherList { anchors.fill: parent Component.onCompleted: { diff --git a/examples/quick/shapes/content/pathitemgallery.qml b/examples/quick/shapes/content/pathitemgallery.qml index e871de4c52..4096743833 100644 --- a/examples/quick/shapes/content/pathitemgallery.qml +++ b/examples/quick/shapes/content/pathitemgallery.qml @@ -83,6 +83,10 @@ Rectangle { shapeUrl: "item5.qml" } ListElement { + name: "Radial gradient" + shapeUrl: "item3.qml" + } + ListElement { name: "Fill rules" shapeUrl: "item6.qml" } diff --git a/examples/quick/shapes/shapes.pro b/examples/quick/shapes/shapes.pro index 1b953f56ca..d884c0e8eb 100644 --- a/examples/quick/shapes/shapes.pro +++ b/examples/quick/shapes/shapes.pro @@ -11,6 +11,7 @@ OTHER_FILES += content/pathitem.qml \ content/tiger.qml \ content/item1.qml \ content/item2.qml \ + content/item3.qml \ content/item4.qml \ content/item5.qml \ content/item6.qml \ diff --git a/examples/quick/shapes/shapes.qrc b/examples/quick/shapes/shapes.qrc index 533ba090bc..388fb867dd 100644 --- a/examples/quick/shapes/shapes.qrc +++ b/examples/quick/shapes/shapes.qrc @@ -14,6 +14,7 @@ <file alias="tiger.qml">content/tiger.qml</file> <file alias="item1.qml">content/item1.qml</file> <file alias="item2.qml">content/item2.qml</file> + <file alias="item3.qml">content/item3.qml</file> <file alias="item4.qml">content/item4.qml</file> <file alias="item5.qml">content/item5.qml</file> <file alias="item6.qml">content/item6.qml</file> diff --git a/src/imports/shapes/plugin.cpp b/src/imports/shapes/plugin.cpp index 1729fc88b4..186e182192 100644 --- a/src/imports/shapes/plugin.cpp +++ b/src/imports/shapes/plugin.cpp @@ -66,6 +66,7 @@ public: qmlRegisterType<QQuickShapePath>(uri, 1, 0, "ShapePath"); qmlRegisterUncreatableType<QQuickShapeGradient>(uri, 1, 0, "ShapeGradient", QQuickShapeGradient::tr("ShapeGradient is an abstract base class")); qmlRegisterType<QQuickShapeLinearGradient>(uri, 1, 0, "LinearGradient"); + qmlRegisterType<QQuickShapeRadialGradient>(uri, 1, 0, "RadialGradient"); } }; diff --git a/src/imports/shapes/qquickshape.cpp b/src/imports/shapes/qquickshape.cpp index b8a138507a..b6174fb177 100644 --- a/src/imports/shapes/qquickshape.cpp +++ b/src/imports/shapes/qquickshape.cpp @@ -1061,12 +1061,12 @@ void QQuickShapeGradient::setSpread(SpreadMode mode) \brief Linear gradient \since 5.10 - Linear gradients interpolate colors between start and end points. Outside - these points the gradient is either padded, reflected or repeated depending - on the spread type. + Linear gradients interpolate colors between start and end points in Shape + items. Outside these points the gradient is either padded, reflected or + repeated depending on the spread type. - \note LinearGradient is not compatible with Rectangle items that only - support Gradient. This type is to be used with Shape. + \note LinearGradient is only supported in combination with Shape items. It + is not compatible with \l Rectangle, as that only supports \l Gradient. \sa QLinearGradient */ @@ -1143,6 +1143,159 @@ void QQuickShapeLinearGradient::setY2(qreal v) } } +/*! + \qmltype RadialGradient + \instantiates QQuickShapeRadialGradient + \inqmlmodule QtQuick.Shapes + \ingroup qtquick-paths + \ingroup qtquick-views + \inherits ShapeGradient + \brief Radial gradient + \since 5.10 + + Radial gradients interpolate colors between a focal circle and a center + circle in Shape items. Points outside the cone defined by the two circles + will be transparent. + + Outside the end points the gradient is either padded, reflected or repeated + depending on the spread type. + + Below is an example of a simple radial gradient. Here the colors are + interpolated between the specified point and the end points on a circle + specified by the radius: + + \code + fillGradient: RadialGradient { + centerX: 50; centerY: 50 + centerRadius: 100 + focalX: centerX; focalY: centerY + GradientStop { position: 0; color: "blue" } + GradientStop { position: 0.2; color: "green" } + GradientStop { position: 0.4; color: "red" } + GradientStop { position: 0.6; color: "yellow" } + GradientStop { position: 1; color: "cyan" } + } + \endcode + + \image shape-radial-gradient.png + + Extended radial gradients, where a separate focal circle is specified, are + also supported. + + \note RadialGradient is only supported in combination with Shape items. It + is not compatible with \l Rectangle, as that only supports \l Gradient. + + \sa QRadialGradient + */ + +QQuickShapeRadialGradient::QQuickShapeRadialGradient(QObject *parent) + : QQuickShapeGradient(parent) +{ +} + +/*! + \qmlproperty real QtQuick.Shapes::RadialGradient::centerX + \qmlproperty real QtQuick.Shapes::RadialGradient::centerY + \qmlproperty real QtQuick.Shapes::RadialGradient::focalX + \qmlproperty real QtQuick.Shapes::RadialGradient::focalY + + These properties define the center and focal points. To specify a simple + radial gradient, set focalX and focalY to the value of centerX and centerY, + respectively. + */ + +qreal QQuickShapeRadialGradient::centerX() const +{ + return m_centerPoint.x(); +} + +void QQuickShapeRadialGradient::setCenterX(qreal v) +{ + if (m_centerPoint.x() != v) { + m_centerPoint.setX(v); + emit centerXChanged(); + emit updated(); + } +} + +qreal QQuickShapeRadialGradient::centerY() const +{ + return m_centerPoint.y(); +} + +void QQuickShapeRadialGradient::setCenterY(qreal v) +{ + if (m_centerPoint.y() != v) { + m_centerPoint.setY(v); + emit centerYChanged(); + emit updated(); + } +} + +/*! + \qmlproperty real QtQuick.Shapes::RadialGradient::centerRadius + \qmlproperty real QtQuick.Shapes::RadialGradient::focalRadius + + These properties define the center and focal radius. For simple radial + gradients, focalRadius should be set to \c 0 (the default value). + */ + +qreal QQuickShapeRadialGradient::centerRadius() const +{ + return m_centerRadius; +} + +void QQuickShapeRadialGradient::setCenterRadius(qreal v) +{ + if (m_centerRadius != v) { + m_centerRadius = v; + emit centerRadiusChanged(); + emit updated(); + } +} + +qreal QQuickShapeRadialGradient::focalX() const +{ + return m_focalPoint.x(); +} + +void QQuickShapeRadialGradient::setFocalX(qreal v) +{ + if (m_focalPoint.x() != v) { + m_focalPoint.setX(v); + emit focalXChanged(); + emit updated(); + } +} + +qreal QQuickShapeRadialGradient::focalY() const +{ + return m_focalPoint.y(); +} + +void QQuickShapeRadialGradient::setFocalY(qreal v) +{ + if (m_focalPoint.y() != v) { + m_focalPoint.setY(v); + emit focalYChanged(); + emit updated(); + } +} + +qreal QQuickShapeRadialGradient::focalRadius() const +{ + return m_focalRadius; +} + +void QQuickShapeRadialGradient::setFocalRadius(qreal v) +{ + if (m_focalRadius != v) { + m_focalRadius = v; + emit focalRadiusChanged(); + emit updated(); + } +} + #if QT_CONFIG(opengl) // contexts sharing with each other get the same cache instance @@ -1181,7 +1334,7 @@ void QQuickShapeGradientCache::freeResource(QOpenGLContext *) m_cache.clear(); } -static void generateGradientColorTable(const QQuickShapeGradientCache::GradientDesc &gradient, +static void generateGradientColorTable(const QQuickShapeGradientCache::Key &gradient, uint *colorTable, int size, float opacity) { int pos = 0; @@ -1232,7 +1385,7 @@ static void generateGradientColorTable(const QQuickShapeGradientCache::GradientD colorTable[size-1] = last_color; } -QSGTexture *QQuickShapeGradientCache::get(const GradientDesc &grad) +QSGTexture *QQuickShapeGradientCache::get(const Key &grad) { QSGPlainTexture *tx = m_cache[grad]; if (!tx) { @@ -1263,6 +1416,7 @@ QSGTexture *QQuickShapeGradientCache::get(const GradientDesc &grad) qWarning("Unknown gradient spread mode %d", grad.spread); break; } + tx->setFiltering(QSGTexture::Linear); m_cache[grad] = tx; } return tx; diff --git a/src/imports/shapes/qquickshape_p.h b/src/imports/shapes/qquickshape_p.h index 3a630aacbc..b6943db37c 100644 --- a/src/imports/shapes/qquickshape_p.h +++ b/src/imports/shapes/qquickshape_p.h @@ -120,6 +120,53 @@ private: QPointF m_end; }; +class QQuickShapeRadialGradient : public QQuickShapeGradient +{ + Q_OBJECT + Q_PROPERTY(qreal centerX READ centerX WRITE setCenterX NOTIFY centerXChanged) + Q_PROPERTY(qreal centerY READ centerY WRITE setCenterY NOTIFY centerYChanged) + Q_PROPERTY(qreal centerRadius READ centerRadius WRITE setCenterRadius NOTIFY centerRadiusChanged) + Q_PROPERTY(qreal focalX READ focalX WRITE setFocalX NOTIFY focalXChanged) + Q_PROPERTY(qreal focalY READ focalY WRITE setFocalY NOTIFY focalYChanged) + Q_PROPERTY(qreal focalRadius READ focalRadius WRITE setFocalRadius NOTIFY focalRadiusChanged) + Q_CLASSINFO("DefaultProperty", "stops") + +public: + QQuickShapeRadialGradient(QObject *parent = nullptr); + + qreal centerX() const; + void setCenterX(qreal v); + + qreal centerY() const; + void setCenterY(qreal v); + + qreal centerRadius() const; + void setCenterRadius(qreal v); + + qreal focalX() const; + void setFocalX(qreal v); + + qreal focalY() const; + void setFocalY(qreal v); + + qreal focalRadius() const; + void setFocalRadius(qreal v); + +signals: + void centerXChanged(); + void centerYChanged(); + void focalXChanged(); + void focalYChanged(); + void centerRadiusChanged(); + void focalRadiusChanged(); + +private: + QPointF m_centerPoint; + QPointF m_focalPoint; + qreal m_centerRadius = 0; + qreal m_focalRadius = 0; +}; + class QQuickShapePath : public QQuickPath { Q_OBJECT diff --git a/src/imports/shapes/qquickshape_p_p.h b/src/imports/shapes/qquickshape_p_p.h index 6ca752de56..bbe9a81d4a 100644 --- a/src/imports/shapes/qquickshape_p_p.h +++ b/src/imports/shapes/qquickshape_p_p.h @@ -70,6 +70,16 @@ public: }; Q_DECLARE_FLAGS(Flags, Flag) + enum FillGradientType { NoGradient = 0, LinearGradient, RadialGradient, ConicalGradient }; + struct GradientDesc { // can fully describe a linear/radial/conical gradient + QGradientStops stops; + QQuickShapeGradient::SpreadMode spread; + QPointF a; // start (L) or center point (R/C) + QPointF b; // end (L) or focal point (R) + qreal v0; // center radius (R) or start angle (C) + qreal v1; // focal radius (R) + }; + virtual ~QQuickAbstractPathRenderer() { } // Gui thread @@ -171,15 +181,15 @@ public: class QQuickShapeGradientCache : public QOpenGLSharedResource { public: - struct GradientDesc { + struct Key { + Key(const QGradientStops &stops, QQuickShapeGradient::SpreadMode spread) + : stops(stops), spread(spread) + { } QGradientStops stops; - QPointF start; - QPointF end; QQuickShapeGradient::SpreadMode spread; - bool operator==(const GradientDesc &other) const + bool operator==(const Key &other) const { - return start == other.start && end == other.end && spread == other.spread - && stops == other.stops; + return spread == other.spread && stops == other.stops; } }; @@ -189,18 +199,17 @@ public: void invalidateResource() override; void freeResource(QOpenGLContext *) override; - QSGTexture *get(const GradientDesc &grad); + QSGTexture *get(const Key &grad); static QQuickShapeGradientCache *currentCache(); private: - QHash<GradientDesc, QSGPlainTexture *> m_cache; + QHash<Key, QSGPlainTexture *> m_cache; }; -inline uint qHash(const QQuickShapeGradientCache::GradientDesc &v, uint seed = 0) +inline uint qHash(const QQuickShapeGradientCache::Key &v, uint seed = 0) { - uint h = seed; - h += v.start.x() + v.end.y() + v.spread; + uint h = seed + v.spread; for (int i = 0; i < 3 && i < v.stops.count(); ++i) h += v.stops[i].second.rgba(); return h; diff --git a/src/imports/shapes/qquickshapegenericrenderer.cpp b/src/imports/shapes/qquickshapegenericrenderer.cpp index 398106af3d..5a2f337b51 100644 --- a/src/imports/shapes/qquickshapegenericrenderer.cpp +++ b/src/imports/shapes/qquickshapegenericrenderer.cpp @@ -97,22 +97,21 @@ void QQuickShapeGenericStrokeFillNode::activateMaterial(Material m) case MatSolidColor: // Use vertexcolor material. Items with different colors remain batchable // this way, at the expense of having to provide per-vertex color values. - if (!m_solidColorMaterial) - m_solidColorMaterial.reset(QQuickShapeGenericMaterialFactory::createVertexColor(m_window)); - m_material = m_solidColorMaterial.data(); + m_material.reset(QQuickShapeGenericMaterialFactory::createVertexColor(m_window)); break; case MatLinearGradient: - if (!m_linearGradientMaterial) - m_linearGradientMaterial.reset(QQuickShapeGenericMaterialFactory::createLinearGradient(m_window, this)); - m_material = m_linearGradientMaterial.data(); + m_material.reset(QQuickShapeGenericMaterialFactory::createLinearGradient(m_window, this)); + break; + case MatRadialGradient: + m_material.reset(QQuickShapeGenericMaterialFactory::createRadialGradient(m_window, this)); break; default: qWarning("Unknown material %d", m); return; } - if (material() != m_material) - setMaterial(m_material); + if (material() != m_material.data()) + setMaterial(m_material.data()); } static bool q_supportsElementIndexUint(QSGRendererInterface::GraphicsApi api) @@ -238,19 +237,25 @@ void QQuickShapeGenericRenderer::setStrokeStyle(int index, QQuickShapePath::Stro void QQuickShapeGenericRenderer::setFillGradient(int index, QQuickShapeGradient *gradient) { ShapePathData &d(m_sp[index]); - d.fillGradientActive = gradient != nullptr; -#if QT_CONFIG(opengl) if (gradient) { d.fillGradient.stops = gradient->gradientStops(); // sorted d.fillGradient.spread = gradient->spread(); if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) { - d.fillGradient.start = QPointF(g->x1(), g->y1()); - d.fillGradient.end = QPointF(g->x2(), g->y2()); + d.fillGradientActive = LinearGradient; + d.fillGradient.a = QPointF(g->x1(), g->y1()); + d.fillGradient.b = QPointF(g->x2(), g->y2()); + } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) { + d.fillGradientActive = RadialGradient; + d.fillGradient.a = QPointF(g->centerX(), g->centerY()); + d.fillGradient.b = QPointF(g->focalX(), g->focalY()); + d.fillGradient.v0 = g->centerRadius(); + d.fillGradient.v1 = g->focalRadius(); } else { Q_UNREACHABLE(); } + } else { + d.fillGradientActive = NoGradient; } -#endif d.syncDirty |= DirtyFillGradient; } @@ -554,12 +559,8 @@ void QQuickShapeGenericRenderer::updateNode() void QQuickShapeGenericRenderer::updateShadowDataInNode(ShapePathData *d, QQuickShapeGenericStrokeFillNode *n) { if (d->fillGradientActive) { -#if QT_CONFIG(opengl) if (d->effectiveDirty & DirtyFillGradient) n->m_fillGradient = d->fillGradient; -#else - Q_UNUSED(n); -#endif } } @@ -585,7 +586,18 @@ void QQuickShapeGenericRenderer::updateFillNode(ShapePathData *d, QQuickShapeGen } if (d->fillGradientActive) { - n->activateMaterial(QQuickShapeGenericStrokeFillNode::MatLinearGradient); + QQuickShapeGenericStrokeFillNode::Material gradMat; + switch (d->fillGradientActive) { + case LinearGradient: + gradMat = QQuickShapeGenericStrokeFillNode::MatLinearGradient; + break; + case RadialGradient: + gradMat = QQuickShapeGenericStrokeFillNode::MatRadialGradient; + break; + default: + Q_UNREACHABLE(); + } + n->activateMaterial(gradMat); if (d->effectiveDirty & DirtyFillGradient) { // Gradients are implemented via a texture-based material. n->markDirty(QSGNode::DirtyMaterial); @@ -665,7 +677,7 @@ QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow * QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi(); #if QT_CONFIG(opengl) - if (api == QSGRendererInterface::OpenGL) // ### so much for "generic"... + if (api == QSGRendererInterface::OpenGL) return new QSGVertexColorMaterial; #endif @@ -674,12 +686,12 @@ QSGMaterial *QQuickShapeGenericMaterialFactory::createVertexColor(QQuickWindow * } QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindow *window, - QQuickShapeGenericStrokeFillNode *node) + QQuickShapeGenericStrokeFillNode *node) { QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi(); #if QT_CONFIG(opengl) - if (api == QSGRendererInterface::OpenGL) // ### so much for "generic"... + if (api == QSGRendererInterface::OpenGL) return new QQuickShapeLinearGradientMaterial(node); #else Q_UNUSED(node); @@ -689,6 +701,22 @@ QSGMaterial *QQuickShapeGenericMaterialFactory::createLinearGradient(QQuickWindo return nullptr; } +QSGMaterial *QQuickShapeGenericMaterialFactory::createRadialGradient(QQuickWindow *window, + QQuickShapeGenericStrokeFillNode *node) +{ + QSGRendererInterface::GraphicsApi api = window->rendererInterface()->graphicsApi(); + +#if QT_CONFIG(opengl) + if (api == QSGRendererInterface::OpenGL) + return new QQuickShapeRadialGradientMaterial(node); +#else + Q_UNUSED(node); +#endif + + qWarning("Radial gradient material: Unsupported graphics API %d", api); + return nullptr; +} + #if QT_CONFIG(opengl) QSGMaterialType QQuickShapeLinearGradientShader::type; @@ -720,10 +748,11 @@ void QQuickShapeLinearGradientShader::updateState(const RenderState &state, QSGM program()->setUniformValue(m_matrixLoc, state.combinedMatrix()); QQuickShapeGenericStrokeFillNode *node = m->node(); - program()->setUniformValue(m_gradStartLoc, QVector2D(node->m_fillGradient.start)); - program()->setUniformValue(m_gradEndLoc, QVector2D(node->m_fillGradient.end)); + program()->setUniformValue(m_gradStartLoc, QVector2D(node->m_fillGradient.a)); + program()->setUniformValue(m_gradEndLoc, QVector2D(node->m_fillGradient.b)); - QSGTexture *tx = QQuickShapeGradientCache::currentCache()->get(node->m_fillGradient); + const QQuickShapeGradientCache::Key cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread); + QSGTexture *tx = QQuickShapeGradientCache::currentCache()->get(cacheKey); tx->bind(); } @@ -744,19 +773,118 @@ int QQuickShapeLinearGradientMaterial::compare(const QSGMaterial *other) const if (a == b) return 0; - const QQuickShapeGradientCache::GradientDesc *ga = &a->m_fillGradient; - const QQuickShapeGradientCache::GradientDesc *gb = &b->m_fillGradient; + const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient; + const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient; if (int d = ga->spread - gb->spread) return d; - if (int d = ga->start.x() - gb->start.x()) + if (int d = ga->a.x() - gb->a.x()) + return d; + if (int d = ga->a.y() - gb->a.y()) return d; - if (int d = ga->start.y() - gb->start.y()) + if (int d = ga->b.x() - gb->b.x()) return d; - if (int d = ga->end.x() - gb->end.x()) + if (int d = ga->b.y() - gb->b.y()) + return d; + + if (int d = ga->stops.count() - gb->stops.count()) + return d; + + for (int i = 0; i < ga->stops.count(); ++i) { + if (int d = ga->stops[i].first - gb->stops[i].first) + return d; + if (int d = ga->stops[i].second.rgba() - gb->stops[i].second.rgba()) + return d; + } + + return 0; +} + +QSGMaterialType QQuickShapeRadialGradientShader::type; + +QQuickShapeRadialGradientShader::QQuickShapeRadialGradientShader() +{ + setShaderSourceFile(QOpenGLShader::Vertex, + QStringLiteral(":/qt-project.org/shapes/shaders/radialgradient.vert")); + setShaderSourceFile(QOpenGLShader::Fragment, + QStringLiteral(":/qt-project.org/shapes/shaders/radialgradient.frag")); +} + +void QQuickShapeRadialGradientShader::initialize() +{ + QOpenGLShaderProgram *prog = program(); + m_opacityLoc = prog->uniformLocation("opacity"); + m_matrixLoc = prog->uniformLocation("matrix"); + m_translationPointLoc = prog->uniformLocation("translationPoint"); + m_focalToCenterLoc = prog->uniformLocation("focalToCenter"); + m_centerRadiusLoc = prog->uniformLocation("centerRadius"); + m_focalRadiusLoc = prog->uniformLocation("focalRadius"); +} + +void QQuickShapeRadialGradientShader::updateState(const RenderState &state, QSGMaterial *mat, QSGMaterial *) +{ + QQuickShapeRadialGradientMaterial *m = static_cast<QQuickShapeRadialGradientMaterial *>(mat); + + if (state.isOpacityDirty()) + program()->setUniformValue(m_opacityLoc, state.opacity()); + + if (state.isMatrixDirty()) + program()->setUniformValue(m_matrixLoc, state.combinedMatrix()); + + QQuickShapeGenericStrokeFillNode *node = m->node(); + + const QPointF centerPoint = node->m_fillGradient.a; + const QPointF focalPoint = node->m_fillGradient.b; + const QPointF focalToCenter = centerPoint - focalPoint; + const GLfloat centerRadius = node->m_fillGradient.v0; + const GLfloat focalRadius = node->m_fillGradient.v1; + + program()->setUniformValue(m_translationPointLoc, focalPoint); + program()->setUniformValue(m_centerRadiusLoc, centerRadius); + program()->setUniformValue(m_focalRadiusLoc, focalRadius); + program()->setUniformValue(m_focalToCenterLoc, focalToCenter); + + const QQuickShapeGradientCache::Key cacheKey(node->m_fillGradient.stops, node->m_fillGradient.spread); + QSGTexture *tx = QQuickShapeGradientCache::currentCache()->get(cacheKey); + tx->bind(); +} + +char const *const *QQuickShapeRadialGradientShader::attributeNames() const +{ + static const char *const attr[] = { "vertexCoord", "vertexColor", nullptr }; + return attr; +} + +int QQuickShapeRadialGradientMaterial::compare(const QSGMaterial *other) const +{ + Q_ASSERT(other && type() == other->type()); + const QQuickShapeRadialGradientMaterial *m = static_cast<const QQuickShapeRadialGradientMaterial *>(other); + + QQuickShapeGenericStrokeFillNode *a = node(); + QQuickShapeGenericStrokeFillNode *b = m->node(); + Q_ASSERT(a && b); + if (a == b) + return 0; + + const QQuickAbstractPathRenderer::GradientDesc *ga = &a->m_fillGradient; + const QQuickAbstractPathRenderer::GradientDesc *gb = &b->m_fillGradient; + + if (int d = ga->spread - gb->spread) + return d; + + if (int d = ga->a.x() - gb->a.x()) + return d; + if (int d = ga->a.y() - gb->a.y()) + return d; + if (int d = ga->b.x() - gb->b.x()) + return d; + if (int d = ga->b.y() - gb->b.y()) + return d; + + if (int d = ga->v0 - gb->v0) return d; - if (int d = ga->end.y() - gb->end.y()) + if (int d = ga->v1 - gb->v1) return d; if (int d = ga->stops.count() - gb->stops.count()) diff --git a/src/imports/shapes/qquickshapegenericrenderer_p.h b/src/imports/shapes/qquickshapegenericrenderer_p.h index dadeba3467..2561116f81 100644 --- a/src/imports/shapes/qquickshapegenericrenderer_p.h +++ b/src/imports/shapes/qquickshapegenericrenderer_p.h @@ -131,10 +131,8 @@ private: Color4ub fillColor; Qt::FillRule fillRule; QPainterPath path; - bool fillGradientActive; -#if QT_CONFIG(opengl) - QQuickShapeGradientCache::GradientDesc fillGradient; -#endif + FillGradientType fillGradientActive; + GradientDesc fillGradient; VertexContainerType fillVertices; IndexContainerType fillIndices; QSGGeometry::Type indexType; @@ -211,7 +209,8 @@ public: enum Material { MatSolidColor, - MatLinearGradient + MatLinearGradient, + MatRadialGradient }; void activateMaterial(Material m); @@ -219,16 +218,12 @@ public: QQuickWindow *window() const { return m_window; } // shadow data for custom materials -#if QT_CONFIG(opengl) - QQuickShapeGradientCache::GradientDesc m_fillGradient; -#endif + QQuickAbstractPathRenderer::GradientDesc m_fillGradient; private: QSGGeometry *m_geometry; QQuickWindow *m_window; - QSGMaterial *m_material; - QScopedPointer<QSGMaterial> m_solidColorMaterial; - QScopedPointer<QSGMaterial> m_linearGradientMaterial; + QScopedPointer<QSGMaterial> m_material; friend class QQuickShapeGenericRenderer; }; @@ -246,6 +241,7 @@ class QQuickShapeGenericMaterialFactory public: static QSGMaterial *createVertexColor(QQuickWindow *window); static QSGMaterial *createLinearGradient(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node); + static QSGMaterial *createRadialGradient(QQuickWindow *window, QQuickShapeGenericStrokeFillNode *node); }; #if QT_CONFIG(opengl) @@ -300,6 +296,53 @@ private: QQuickShapeGenericStrokeFillNode *m_node; }; +class QQuickShapeRadialGradientShader : public QSGMaterialShader +{ +public: + QQuickShapeRadialGradientShader(); + + void initialize() override; + void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override; + char const *const *attributeNames() const override; + + static QSGMaterialType type; + +private: + int m_opacityLoc = -1; + int m_matrixLoc = -1; + int m_translationPointLoc = -1; + int m_focalToCenterLoc = -1; + int m_centerRadiusLoc = -1; + int m_focalRadiusLoc = -1; +}; + +class QQuickShapeRadialGradientMaterial : public QSGMaterial +{ +public: + QQuickShapeRadialGradientMaterial(QQuickShapeGenericStrokeFillNode *node) + : m_node(node) + { + setFlag(Blending | RequiresFullMatrix); + } + + QSGMaterialType *type() const override + { + return &QQuickShapeRadialGradientShader::type; + } + + int compare(const QSGMaterial *other) const override; + + QSGMaterialShader *createShader() const override + { + return new QQuickShapeRadialGradientShader; + } + + QQuickShapeGenericStrokeFillNode *node() const { return m_node; } + +private: + QQuickShapeGenericStrokeFillNode *m_node; +}; + #endif // QT_CONFIG(opengl) QT_END_NAMESPACE diff --git a/src/imports/shapes/qquickshapenvprrenderer.cpp b/src/imports/shapes/qquickshapenvprrenderer.cpp index a859ca45b6..a08da2f3fe 100644 --- a/src/imports/shapes/qquickshapenvprrenderer.cpp +++ b/src/imports/shapes/qquickshapenvprrenderer.cpp @@ -125,16 +125,24 @@ void QQuickShapeNvprRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeS void QQuickShapeNvprRenderer::setFillGradient(int index, QQuickShapeGradient *gradient) { ShapePathGuiData &d(m_sp[index]); - d.fillGradientActive = gradient != nullptr; if (gradient) { d.fillGradient.stops = gradient->gradientStops(); // sorted d.fillGradient.spread = gradient->spread(); if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) { - d.fillGradient.start = QPointF(g->x1(), g->y1()); - d.fillGradient.end = QPointF(g->x2(), g->y2()); + d.fillGradientActive = LinearGradient; + d.fillGradient.a = QPointF(g->x1(), g->y1()); + d.fillGradient.b = QPointF(g->x2(), g->y2()); + } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) { + d.fillGradientActive = RadialGradient; + d.fillGradient.a = QPointF(g->centerX(), g->centerY()); + d.fillGradient.b = QPointF(g->focalX(), g->focalY()); + d.fillGradient.v0 = g->centerRadius(); + d.fillGradient.v1 = g->focalRadius(); } else { Q_UNREACHABLE(); } + } else { + d.fillGradientActive = NoGradient; } d.dirty |= DirtyFillGradient; m_accDirty |= DirtyFillGradient; @@ -485,6 +493,49 @@ QQuickNvprMaterialManager::MaterialDesc *QQuickNvprMaterialManager::activateMate Q_ASSERT(mtl.uniLoc[2] >= 0); mtl.uniLoc[3] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "gradEnd"); Q_ASSERT(mtl.uniLoc[3] >= 0); + } else if (m == MatRadialGradient) { + static const char *fragSrc = + "#version 310 es\n" + "precision highp float;\n" + "uniform sampler2D gradTab;\n" + "uniform float opacity;\n" + "uniform vec2 focalToCenter;\n" + "uniform float centerRadius;\n" + "uniform float focalRadius;\n" + "uniform vec2 translationPoint;\n" + "layout(location = 0) in vec2 uv;\n" + "out vec4 fragColor;\n" + "void main() {\n" + " vec2 coord = uv - translationPoint;\n" + " float rd = centerRadius - focalRadius;\n" + " float b = 2.0 * (rd * focalRadius + dot(coord, focalToCenter));\n" + " float fmp2_m_radius2 = -focalToCenter.x * focalToCenter.x - focalToCenter.y * focalToCenter.y + rd * rd;\n" + " float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2);\n" + " float det = b * b - 4.0 * fmp2_m_radius2 * ((focalRadius * focalRadius) - dot(coord, coord));\n" + " vec4 result = vec4(0.0);\n" + " if (det >= 0.0) {\n" + " float detSqrt = sqrt(det);\n" + " float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2);\n" + " if (focalRadius + w * (centerRadius - focalRadius) >= 0.0)\n" + " result = texture(gradTab, vec2(w, 0.5)) * opacity;\n" + " }\n" + " fragColor = result;\n" + "}\n"; + if (!m_nvpr->createFragmentOnlyPipeline(fragSrc, &mtl.ppl, &mtl.prg)) { + qWarning("NVPR: Failed to create shader pipeline for radial gradient"); + return nullptr; + } + Q_ASSERT(mtl.ppl && mtl.prg); + mtl.uniLoc[1] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "opacity"); + Q_ASSERT(mtl.uniLoc[1] >= 0); + mtl.uniLoc[2] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "focalToCenter"); + Q_ASSERT(mtl.uniLoc[2] >= 0); + mtl.uniLoc[3] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "centerRadius"); + Q_ASSERT(mtl.uniLoc[3] >= 0); + mtl.uniLoc[4] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "focalRadius"); + Q_ASSERT(mtl.uniLoc[4] >= 0); + mtl.uniLoc[5] = f->glGetProgramResourceLocation(mtl.prg, GL_UNIFORM, "translationPoint"); + Q_ASSERT(mtl.uniLoc[5] >= 0); } else { Q_UNREACHABLE(); } @@ -542,17 +593,40 @@ void QQuickShapeNvprRenderNode::renderFill(ShapePathRenderData *d) { QQuickNvprMaterialManager::MaterialDesc *mtl = nullptr; if (d->fillGradientActive) { - mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatLinearGradient); - QSGTexture *tx = QQuickShapeGradientCache::currentCache()->get(d->fillGradient); + const QQuickShapeGradientCache::Key cacheKey(d->fillGradient.stops, d->fillGradient.spread); + QSGTexture *tx = QQuickShapeGradientCache::currentCache()->get(cacheKey); tx->bind(); - // uv = vec2(coeff[0] * x + coeff[1] * y + coeff[2], coeff[3] * x + coeff[4] * y + coeff[5]) - // where x and y are in path coordinate space, which is just what - // we need since the gradient's start and stop are in that space too. - GLfloat coeff[6] = { 1, 0, 0, - 0, 1, 0 }; - nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); - f->glProgramUniform2f(mtl->prg, mtl->uniLoc[2], d->fillGradient.start.x(), d->fillGradient.start.y()); - f->glProgramUniform2f(mtl->prg, mtl->uniLoc[3], d->fillGradient.end.x(), d->fillGradient.end.y()); + + if (d->fillGradientActive == QQuickAbstractPathRenderer::LinearGradient) { + mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatLinearGradient); + // uv = vec2(coeff[0] * x + coeff[1] * y + coeff[2], coeff[3] * x + coeff[4] * y + coeff[5]) + // where x and y are in path coordinate space, which is just what + // we need since the gradient's start and stop are in that space too. + GLfloat coeff[6] = { 1, 0, 0, + 0, 1, 0 }; + nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); + f->glProgramUniform2f(mtl->prg, mtl->uniLoc[2], d->fillGradient.a.x(), d->fillGradient.a.y()); + f->glProgramUniform2f(mtl->prg, mtl->uniLoc[3], d->fillGradient.b.x(), d->fillGradient.b.y()); + } else if (d->fillGradientActive == QQuickAbstractPathRenderer::RadialGradient) { + mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatRadialGradient); + // simply drive uv (location 0) with x and y, just like for the linear gradient + GLfloat coeff[6] = { 1, 0, 0, + 0, 1, 0 }; + nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); + + const QPointF centerPoint = d->fillGradient.a; + const QPointF focalPoint = d->fillGradient.b; + const QPointF focalToCenter = centerPoint - focalPoint; + const GLfloat centerRadius = d->fillGradient.v0; + const GLfloat focalRadius = d->fillGradient.v1; + + f->glProgramUniform2f(mtl->prg, mtl->uniLoc[2], focalToCenter.x(), focalToCenter.y()); + f->glProgramUniform1f(mtl->prg, mtl->uniLoc[3], centerRadius); + f->glProgramUniform1f(mtl->prg, mtl->uniLoc[4], focalRadius); + f->glProgramUniform2f(mtl->prg, mtl->uniLoc[5], focalPoint.x(), focalPoint.y()); + } else { + Q_UNREACHABLE(); + } } else { mtl = mtlmgr.activateMaterial(QQuickNvprMaterialManager::MatSolid); f->glProgramUniform4f(mtl->prg, mtl->uniLoc[0], diff --git a/src/imports/shapes/qquickshapenvprrenderer_p.h b/src/imports/shapes/qquickshapenvprrenderer_p.h index 7eb2924ab7..b3d92dbdbc 100644 --- a/src/imports/shapes/qquickshapenvprrenderer_p.h +++ b/src/imports/shapes/qquickshapenvprrenderer_p.h @@ -116,8 +116,8 @@ private: bool dashActive; qreal dashOffset; QVector<qreal> dashPattern; - bool fillGradientActive; - QQuickShapeGradientCache::GradientDesc fillGradient; + FillGradientType fillGradientActive; + GradientDesc fillGradient; }; void convertPath(const QQuickPath *path, ShapePathGuiData *d); @@ -136,6 +136,7 @@ public: enum Material { MatSolid, MatLinearGradient, + MatRadialGradient, NMaterials }; @@ -143,7 +144,7 @@ public: struct MaterialDesc { GLuint ppl = 0; GLuint prg = 0; - int uniLoc[4]; + int uniLoc[8]; }; void create(QQuickNvprFunctions *nvpr); @@ -199,8 +200,8 @@ private: GLenum fillRule; GLfloat dashOffset; QVector<GLfloat> dashPattern; - bool fillGradientActive; - QQuickShapeGradientCache::GradientDesc fillGradient; + QQuickAbstractPathRenderer::FillGradientType fillGradientActive; + QQuickAbstractPathRenderer::GradientDesc fillGradient; QOpenGLFramebufferObject *fallbackFbo = nullptr; bool fallbackValid = false; QSize fallbackSize; diff --git a/src/imports/shapes/qquickshapesoftwarerenderer.cpp b/src/imports/shapes/qquickshapesoftwarerenderer.cpp index 4e6e758697..15803dcf0a 100644 --- a/src/imports/shapes/qquickshapesoftwarerenderer.cpp +++ b/src/imports/shapes/qquickshapesoftwarerenderer.cpp @@ -130,26 +130,35 @@ void QQuickShapeSoftwareRenderer::setStrokeStyle(int index, QQuickShapePath::Str m_accDirty |= DirtyPen; } +static inline void setupPainterGradient(QGradient *painterGradient, const QQuickShapeGradient &g) +{ + painterGradient->setStops(g.gradientStops()); // sorted + switch (g.spread()) { + case QQuickShapeGradient::PadSpread: + painterGradient->setSpread(QGradient::PadSpread); + break; + case QQuickShapeGradient::RepeatSpread: + painterGradient->setSpread(QGradient::RepeatSpread); + break; + case QQuickShapeGradient::ReflectSpread: + painterGradient->setSpread(QGradient::ReflectSpread); + break; + default: + break; + } +} + void QQuickShapeSoftwareRenderer::setFillGradient(int index, QQuickShapeGradient *gradient) { ShapePathGuiData &d(m_sp[index]); - if (QQuickShapeLinearGradient *linearGradient = qobject_cast<QQuickShapeLinearGradient *>(gradient)) { - QLinearGradient painterGradient(linearGradient->x1(), linearGradient->y1(), - linearGradient->x2(), linearGradient->y2()); - painterGradient.setStops(linearGradient->gradientStops()); // sorted - switch (gradient->spread()) { - case QQuickShapeGradient::PadSpread: - painterGradient.setSpread(QGradient::PadSpread); - break; - case QQuickShapeGradient::RepeatSpread: - painterGradient.setSpread(QGradient::RepeatSpread); - break; - case QQuickShapeGradient::ReflectSpread: - painterGradient.setSpread(QGradient::ReflectSpread); - break; - default: - break; - } + if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(gradient)) { + QLinearGradient painterGradient(g->x1(), g->y1(), g->x2(), g->y2()); + setupPainterGradient(&painterGradient, *g); + d.brush = QBrush(painterGradient); + } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(gradient)) { + QRadialGradient painterGradient(g->centerX(), g->centerY(), g->centerRadius(), + g->focalX(), g->focalY(), g->focalRadius()); + setupPainterGradient(&painterGradient, *g); d.brush = QBrush(painterGradient); } else { d.brush = QBrush(d.fillColor); diff --git a/src/imports/shapes/shaders/radialgradient.frag b/src/imports/shapes/shaders/radialgradient.frag new file mode 100644 index 0000000000..0f503bc0f7 --- /dev/null +++ b/src/imports/shapes/shaders/radialgradient.frag @@ -0,0 +1,25 @@ +uniform sampler2D gradTabTexture; +uniform lowp float opacity; + +uniform highp vec2 focalToCenter; +uniform highp float centerRadius; +uniform highp float focalRadius; + +varying highp vec2 coord; + +void main() +{ + highp float rd = centerRadius - focalRadius; + highp float b = 2.0 * (rd * focalRadius + dot(coord, focalToCenter)); + highp float fmp2_m_radius2 = -focalToCenter.x * focalToCenter.x - focalToCenter.y * focalToCenter.y + rd * rd; + highp float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2); + highp float det = b * b - 4.0 * fmp2_m_radius2 * ((focalRadius * focalRadius) - dot(coord, coord)); + lowp vec4 result = vec4(0.0); + if (det >= 0.0) { + highp float detSqrt = sqrt(det); + highp float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2); + if (focalRadius + w * (centerRadius - focalRadius) >= 0.0) + result = texture2D(gradTabTexture, vec2(w, 0.5)) * opacity; + } + gl_FragColor = result; +} diff --git a/src/imports/shapes/shaders/radialgradient.vert b/src/imports/shapes/shaders/radialgradient.vert new file mode 100644 index 0000000000..3350b0675a --- /dev/null +++ b/src/imports/shapes/shaders/radialgradient.vert @@ -0,0 +1,13 @@ +attribute vec4 vertexCoord; +attribute vec4 vertexColor; + +uniform mat4 matrix; +uniform vec2 translationPoint; + +varying vec2 coord; + +void main() +{ + coord = vertexCoord.xy - translationPoint; + gl_Position = matrix * vertexCoord; +} diff --git a/src/imports/shapes/shaders/radialgradient_core.frag b/src/imports/shapes/shaders/radialgradient_core.frag new file mode 100644 index 0000000000..706ce53e4d --- /dev/null +++ b/src/imports/shapes/shaders/radialgradient_core.frag @@ -0,0 +1,29 @@ +#version 150 core + +uniform sampler2D gradTabTexture; +uniform float opacity; + +uniform vec2 focalToCenter; +uniform float centerRadius; +uniform float focalRadius; + +in vec2 coord; + +out vec4 fragColor; + +void main() +{ + float rd = centerRadius - focalRadius; + float b = 2.0 * (rd * focalRadius + dot(coord, focalToCenter)); + float fmp2_m_radius2 = -focalToCenter.x * focalToCenter.x - focalToCenter.y * focalToCenter.y + rd * rd; + float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2); + float det = b * b - 4.0 * fmp2_m_radius2 * ((focalRadius * focalRadius) - dot(coord, coord)); + vec4 result = vec4(0.0); + if (det >= 0.0) { + float detSqrt = sqrt(det); + float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2); + if (focalRadius + w * (centerRadius - focalRadius) >= 0.0) + result = texture(gradTabTexture, vec2(w, 0.5)) * opacity; + } + fragColor = result; +} diff --git a/src/imports/shapes/shaders/radialgradient_core.vert b/src/imports/shapes/shaders/radialgradient_core.vert new file mode 100644 index 0000000000..f94a56401b --- /dev/null +++ b/src/imports/shapes/shaders/radialgradient_core.vert @@ -0,0 +1,15 @@ +#version 150 core + +in vec4 vertexCoord; +in vec4 vertexColor; + +uniform mat4 matrix; +uniform vec2 translationPoint; + +out vec2 coord; + +void main() +{ + coord = vertexCoord.xy - translationPoint; + gl_Position = matrix * vertexCoord; +} diff --git a/src/imports/shapes/shapes.qrc b/src/imports/shapes/shapes.qrc index 65ee2007f9..92912ede48 100644 --- a/src/imports/shapes/shapes.qrc +++ b/src/imports/shapes/shapes.qrc @@ -8,5 +8,9 @@ <file>shaders/lineargradient.frag</file> <file>shaders/lineargradient_core.vert</file> <file>shaders/lineargradient_core.frag</file> + <file>shaders/radialgradient.vert</file> + <file>shaders/radialgradient.frag</file> + <file>shaders/radialgradient_core.vert</file> + <file>shaders/radialgradient_core.frag</file> </qresource> </RCC> diff --git a/src/quick/doc/images/shape-radial-gradient.png b/src/quick/doc/images/shape-radial-gradient.png Binary files differnew file mode 100644 index 0000000000..bfff2e4b6b --- /dev/null +++ b/src/quick/doc/images/shape-radial-gradient.png diff --git a/tests/auto/quick/qquickshape/data/pathitem5.png b/tests/auto/quick/qquickshape/data/pathitem5.png Binary files differnew file mode 100644 index 0000000000..cb5cfd25dc --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem5.png diff --git a/tests/auto/quick/qquickshape/data/pathitem5.qml b/tests/auto/quick/qquickshape/data/pathitem5.qml new file mode 100644 index 0000000000..1bd465d5c0 --- /dev/null +++ b/tests/auto/quick/qquickshape/data/pathitem5.qml @@ -0,0 +1,37 @@ +import QtQuick 2.9 +import tst_qquickpathitem 1.0 + +Item { + width: 200 + height: 150 + + Shape { + vendorExtensionsEnabled: false + objectName: "pathItem" + anchors.fill: parent + + ShapePath { + strokeWidth: 4 + strokeColor: "red" + fillGradient: RadialGradient { + centerX: 100; centerY: 100; centerRadius: 100 + focalX: 100; focalY: 100; focalRadius: 10 + GradientStop { position: 0; color: "#ffffff" } + GradientStop { position: 0.11; color: "#f9ffa0" } + GradientStop { position: 0.13; color: "#f9ff99" } + GradientStop { position: 0.14; color: "#f3ff86" } + GradientStop { position: 0.49; color: "#93b353" } + GradientStop { position: 0.87; color: "#264619" } + GradientStop { position: 0.96; color: "#0c1306" } + GradientStop { position: 1; color: "#000000" } + } + fillColor: "blue" // ignored with the gradient set + strokeStyle: ShapePath.DashLine + dashPattern: [ 1, 4 ] + startX: 20; startY: 20 + PathLine { x: 180; y: 130 } + PathLine { x: 20; y: 130 } + PathLine { x: 20; y: 20 } + } + } +} diff --git a/tests/auto/quick/qquickshape/tst_qquickshape.cpp b/tests/auto/quick/qquickshape/tst_qquickshape.cpp index dcc79e6599..a779b23abd 100644 --- a/tests/auto/quick/qquickshape/tst_qquickshape.cpp +++ b/tests/auto/quick/qquickshape/tst_qquickshape.cpp @@ -55,6 +55,7 @@ private slots: void changeSignals(); void render(); void renderWithMultipleSp(); + void radialGrad(); }; tst_QQuickShape::tst_QQuickShape() @@ -67,6 +68,7 @@ tst_QQuickShape::tst_QQuickShape() qmlRegisterType<QQuickShapePath>(uri, 1, 0, "ShapePath"); qmlRegisterUncreatableType<QQuickShapeGradient>(uri, 1, 0, "ShapeGradient", QQuickShapeGradient::tr("ShapeGradient is an abstract base class")); qmlRegisterType<QQuickShapeLinearGradient>(uri, 1, 0, "LinearGradient"); + qmlRegisterType<QQuickShapeRadialGradient>(uri, 1, 0, "RadialGradient"); } void tst_QQuickShape::initValues() @@ -245,6 +247,23 @@ void tst_QQuickShape::renderWithMultipleSp() QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg)); } +void tst_QQuickShape::radialGrad() +{ + QScopedPointer<QQuickView> window(createView()); + + window->setSource(testFileUrl("pathitem5.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QImage img = window->grabWindow(); + QVERIFY(!img.isNull()); + + QImage refImg(testFileUrl("pathitem5.png").toLocalFile()); + QVERIFY(!refImg.isNull()); + + QVERIFY(QQuickVisualTestUtil::compareImages(img.convertToFormat(refImg.format()), refImg)); +} + QTEST_MAIN(tst_QQuickShape) #include "tst_qquickshape.moc" diff --git a/tests/manual/shapestest/shapestest.qml b/tests/manual/shapestest/shapestest.qml index df53f088ae..d3f946b227 100644 --- a/tests/manual/shapestest/shapestest.qml +++ b/tests/manual/shapestest/shapestest.qml @@ -372,6 +372,35 @@ Rectangle { } } } + + Rectangle { + border.color: "purple" + color: "transparent" + width: 200 + height: 100 + Shape { + anchors.fill: parent + ShapePath { + strokeWidth: -1 + strokeColor: "red" + fillGradient: RadialGradient { + centerX: 100; centerY: 50 + focalX: centerX; focalY: centerY + centerRadius: 50 + spread: RadialGradient.ReflectSpread + GradientStop { position: 0; color: "blue" } + GradientStop { position: 0.2; color: "green" } + GradientStop { position: 0.4; color: "red" } + GradientStop { position: 0.6; color: "yellow" } + GradientStop { position: 1; color: "cyan" } + } + PathLine { x: 0; y: 100 } + PathLine { x: 200; y: 100 } + PathLine { x: 200; y: 0 } + PathLine { x: 0; y: 0 } + } + } + } } } |