aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2017-07-28 13:44:09 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2017-08-08 08:00:30 +0000
commit003e24d72a9647e2dc397906234059fee19103ec (patch)
treebbe749e8d665dda882cd7445f77e9d4c813b1e35
parent432e27ae092397cb2154f48103e729852c38cf2d (diff)
shapes: Add support for radial gradients
Task-number: QTBUG-61857 Change-Id: I580e503d8266a9dca69bb542c22228df4ff4bf94 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r--examples/quick/shapes/content/item3.qml98
-rw-r--r--examples/quick/shapes/content/pathitem.qml4
-rw-r--r--examples/quick/shapes/content/pathitemgallery.qml4
-rw-r--r--examples/quick/shapes/shapes.pro1
-rw-r--r--examples/quick/shapes/shapes.qrc1
-rw-r--r--src/imports/shapes/plugin.cpp1
-rw-r--r--src/imports/shapes/qquickshape.cpp168
-rw-r--r--src/imports/shapes/qquickshape_p.h47
-rw-r--r--src/imports/shapes/qquickshape_p_p.h31
-rw-r--r--src/imports/shapes/qquickshapegenericrenderer.cpp188
-rw-r--r--src/imports/shapes/qquickshapegenericrenderer_p.h65
-rw-r--r--src/imports/shapes/qquickshapenvprrenderer.cpp100
-rw-r--r--src/imports/shapes/qquickshapenvprrenderer_p.h11
-rw-r--r--src/imports/shapes/qquickshapesoftwarerenderer.cpp43
-rw-r--r--src/imports/shapes/shaders/radialgradient.frag25
-rw-r--r--src/imports/shapes/shaders/radialgradient.vert13
-rw-r--r--src/imports/shapes/shaders/radialgradient_core.frag29
-rw-r--r--src/imports/shapes/shaders/radialgradient_core.vert15
-rw-r--r--src/imports/shapes/shapes.qrc4
-rw-r--r--src/quick/doc/images/shape-radial-gradient.pngbin0 -> 16523 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem5.pngbin0 -> 9377 bytes
-rw-r--r--tests/auto/quick/qquickshape/data/pathitem5.qml37
-rw-r--r--tests/auto/quick/qquickshape/tst_qquickshape.cpp19
-rw-r--r--tests/manual/shapestest/shapestest.qml29
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
new file mode 100644
index 0000000000..bfff2e4b6b
--- /dev/null
+++ b/src/quick/doc/images/shape-radial-gradient.png
Binary files differ
diff --git a/tests/auto/quick/qquickshape/data/pathitem5.png b/tests/auto/quick/qquickshape/data/pathitem5.png
new file mode 100644
index 0000000000..cb5cfd25dc
--- /dev/null
+++ b/tests/auto/quick/qquickshape/data/pathitem5.png
Binary files differ
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 }
+ }
+ }
+ }
}
}