diff options
author | Eskil Abrahamsen Blomfeldt <[email protected]> | 2025-04-01 14:24:53 +0200 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2025-04-30 11:21:20 +0200 |
commit | 5f3b613b2ea848dfc66c00a6d91d507703a916b3 (patch) | |
tree | 45571e52ab9fa42a495dbfd9f77dbcda18f6101a | |
parent | f52428e60023828dc20cce188e1689e3dc67c042 (diff) |
Support fill-opacity and stroke-opacity in VectorImage
The alpha value of the fill and stroke colors can be animated
separately in SVG. In order to support this, we introduce a
specialized ColorOpacityAnimation type in a Helpers library
which only overwrites the alpha channel of the target property.
This requires an extra hook in the animation frame work which
allows us to get the current value of the property. It should
have minimal impact on any existing code, but may have
additional use cases later, when we implement support for
additive color animations for instance. Since the interpolator
API in QVariantAnimation is public API, we add a secondary,
private API for this. If we see use for it in the future,
this could mature to a public API as well.
Fixes: QTBUG-135322
Change-Id: I803f4e64c41e9d6dc355f2468233661885aa7f15
Reviewed-by: Eirik Aavitsland <[email protected]>
11 files changed, 277 insertions, 18 deletions
diff --git a/src/quick/util/qquickanimation.cpp b/src/quick/util/qquickanimation.cpp index 12af47fb6b..118e893808 100644 --- a/src/quick/util/qquickanimation.cpp +++ b/src/quick/util/qquickanimation.cpp @@ -2709,7 +2709,7 @@ void QQuickAnimationPropertyUpdater::setValue(qreal v) for (int ii = 0; ii < actions.size(); ++ii) { QQuickStateAction &action = actions[ii]; - if (v == 1.) { + if (v == qreal(1.0) && extendedInterpolator == nullptr) { QQmlPropertyPrivate::write(action.property, action.toValue, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); } else { if (!fromIsSourced && !fromIsDefined) { @@ -2725,8 +2725,21 @@ void QQuickAnimationPropertyUpdater::setValue(qreal v) interpolator = QVariantAnimationPrivate::getInterpolator(prevInterpolatorType); } } - if (interpolator) - QQmlPropertyPrivate::write(action.property, interpolator(action.fromValue.constData(), action.toValue.constData(), v), QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); + + QVariant interpolated; + if (extendedInterpolator) { + QVariant current = action.property.read(); + interpolated = extendedInterpolator(action.fromValue.constData(), + action.toValue.constData(), + current, + v); + + } else if (interpolator) { + interpolated = interpolator(action.fromValue.constData(), action.toValue.constData(), v); + } + QQmlPropertyPrivate::write(action.property, + interpolated, + QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); } if (deleted) return; @@ -2880,6 +2893,7 @@ QAbstractAnimationJob* QQuickPropertyAnimation::transition(QQuickStateActions &a QQuickAnimationPropertyUpdater *data = new QQuickAnimationPropertyUpdater; data->interpolatorType = d->interpolatorType; data->interpolator = d->interpolator; + data->extendedInterpolator = d->extendedInterpolator; data->reverse = direction == Backward ? true : false; data->fromIsSourced = false; data->fromIsDefined = d->fromIsDefined; diff --git a/src/quick/util/qquickanimation_p_p.h b/src/quick/util/qquickanimation_p_p.h index 49f26726ee..84b7066a26 100644 --- a/src/quick/util/qquickanimation_p_p.h +++ b/src/quick/util/qquickanimation_p_p.h @@ -240,7 +240,7 @@ class Q_QUICK_EXPORT QQuickPropertyAnimationPrivate : public QQuickAbstractAnima public: QQuickPropertyAnimationPrivate() : QQuickAbstractAnimationPrivate(), target(nullptr), fromIsDefined(false), toIsDefined(false), ourPropertiesDirty(false), - defaultToInterpolatorType(0), interpolatorType(0), interpolator(nullptr), duration(250), actions(nullptr) {} + defaultToInterpolatorType(0), interpolatorType(0), interpolator(nullptr), extendedInterpolator(nullptr), duration(250), actions(nullptr) {} void animationCurrentLoopChanged(QAbstractAnimationJob *job) override; @@ -260,13 +260,14 @@ public: bool defaultToInterpolatorType:1; int interpolatorType; QVariantAnimation::Interpolator interpolator; + typedef QVariant (*ExtendedInterpolator)(const void *from, const void *to, const QVariant ¤tValue, qreal progress); + ExtendedInterpolator extendedInterpolator; int duration; QEasingCurve easing; // for animations that don't use the QQuickBulkValueAnimator QQuickStateActions *actions; - static QVariant interpolateVariant(const QVariant &from, const QVariant &to, qreal progress); static void convertVariant(QVariant &variant, QMetaType type); }; @@ -282,7 +283,7 @@ public: class Q_AUTOTEST_EXPORT QQuickAnimationPropertyUpdater : public QQuickBulkValueUpdater { public: - QQuickAnimationPropertyUpdater() : interpolatorType(0), interpolator(nullptr), prevInterpolatorType(0), reverse(false), fromIsSourced(false), fromIsDefined(false), wasDeleted(nullptr) {} + QQuickAnimationPropertyUpdater() : interpolatorType(0), interpolator(nullptr), extendedInterpolator(nullptr), prevInterpolatorType(0), reverse(false), fromIsSourced(false), fromIsDefined(false), wasDeleted(nullptr) {} ~QQuickAnimationPropertyUpdater() override; void setValue(qreal v) override; @@ -292,6 +293,7 @@ public: QQuickStateActions actions; int interpolatorType; //for Number/ColorAnimation QVariantAnimation::Interpolator interpolator; + QQuickPropertyAnimationPrivate::ExtendedInterpolator extendedInterpolator; int prevInterpolatorType; //for generic bool reverse; bool fromIsSourced; diff --git a/src/quickvectorimage/CMakeLists.txt b/src/quickvectorimage/CMakeLists.txt index 8f3c5c7620..868dd646ed 100644 --- a/src/quickvectorimage/CMakeLists.txt +++ b/src/quickvectorimage/CMakeLists.txt @@ -28,6 +28,7 @@ qt_internal_add_qml_module(QuickVectorImage VERSION "${PROJECT_VERSION}" PLUGIN_TARGET qquickvectorimageplugin CLASS_NAME QtQuickVectorImagePlugin + IMPORTS QtQuick.VectorImage.Helpers SOURCES qquickvectorimage_p.h qquickvectorimage.cpp qquickvectorimage_p_p.h @@ -36,3 +37,16 @@ qt_internal_add_qml_module(QuickVectorImage Qt::QuickVectorImageGeneratorPrivate Qt::SvgPrivate ) + +qt_internal_add_qml_module(QuickVectorImageHelpers + URI "QtQuick.VectorImage.Helpers" + VERSION "${PROJECT_VERSION}" + PLUGIN_TARGET qquickvectorimagehelpersplugin + NO_PLUGIN_OPTIONAL + CLASS_NAME QtQuickVectorImageHelpersPlugin + SOURCES + helpers/qquickcoloropacityanimation_p.h helpers/qquickcoloropacityanimation.cpp + LIBRARIES + Qt::QuickPrivate + Qt::QuickVectorImageGeneratorPrivate +) diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h index 95dbdc9fef..44226d6a78 100644 --- a/src/quickvectorimage/generator/qquicknodeinfo_p.h +++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h @@ -53,6 +53,7 @@ struct StrokeStyle qreal dashOffset = 0; QList<qreal> dashArray; QQuickAnimatedProperty color = QQuickAnimatedProperty(QVariant::fromValue(QColorConstants::Transparent)); + QQuickAnimatedProperty opacity = QQuickAnimatedProperty(QVariant::fromValue(qreal(1.0))); qreal width = 1.0; static StrokeStyle fromPen(const QPen &p) @@ -74,6 +75,7 @@ struct PathNodeInfo : NodeInfo QPainterPath painterPath; Qt::FillRule fillRule = Qt::FillRule::WindingFill; QQuickAnimatedProperty fillColor = QQuickAnimatedProperty(QVariant::fromValue(QColor{})); + QQuickAnimatedProperty fillOpacity = QQuickAnimatedProperty(QVariant::fromValue(qreal(1.0))); StrokeStyle strokeStyle; QGradient grad; QTransform fillTransform; @@ -89,7 +91,9 @@ struct TextNodeInfo : NodeInfo QFont font; Qt::Alignment alignment; QQuickAnimatedProperty fillColor = QQuickAnimatedProperty(QVariant::fromValue(QColor{})); + QQuickAnimatedProperty fillOpacity = QQuickAnimatedProperty(QVariant::fromValue(qreal(1.0))); QQuickAnimatedProperty strokeColor = QQuickAnimatedProperty(QVariant::fromValue(QColor{})); + QQuickAnimatedProperty strokeOpacity = QQuickAnimatedProperty(QVariant::fromValue(qreal(1.0))); }; struct AnimateColorNodeInfo : NodeInfo diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp index ae77b178f3..72b6b51035 100644 --- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp +++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp @@ -260,7 +260,8 @@ void QQuickQmlGenerator::generateGradient(const QGradient *grad) void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty &property, const QString &targetName, - const QString &propertyName) + const QString &propertyName, + AnimationType animationType) { if (property.animationCount() > 1) { stream() << "ParallelAnimation {"; @@ -295,10 +296,17 @@ void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty const int time = it.key(); const QVariant &value = it.value(); - if (value.typeId() == QMetaType::QColor) - stream() << "ColorAnimation {"; - else - stream() << "PropertyAnimation {"; + switch (animationType) { + case AnimationType::Auto: + if (value.typeId() == QMetaType::QColor) + stream() << "ColorAnimation {"; + else + stream() << "PropertyAnimation {"; + break; + case AnimationType::ColorOpacity: + stream() << "ColorOpacityAnimation {"; + break; + }; m_indentLevel++; stream() << "target: " << targetName; @@ -318,13 +326,23 @@ void QQuickQmlGenerator::generatePropertyAnimation(const QQuickAnimatedProperty if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) { stream() << "ScriptAction {"; m_indentLevel++; - stream() << "script: " << targetName << "." << propertyName << " = "; + stream() << "script: "; + + switch (animationType) { + case AnimationType::Auto: + stream(SameLine) << targetName << "." << propertyName << " = "; + break; + case AnimationType::ColorOpacity: + stream(SameLine) << targetName << "." << propertyName << ".a = "; + break; + }; QVariant value = property.defaultValue(); if (value.typeId() == QMetaType::QColor) stream(SameLine) << "\"" << value.toString() << "\""; else stream(SameLine) << value.toReal(); + m_indentLevel--; stream() << "}"; } @@ -372,14 +390,17 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte static int counter = 0; const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>(); - const bool noPen = strokeColor == QColorConstants::Transparent && !info.strokeStyle.color.isAnimated(); + const bool noPen = strokeColor == QColorConstants::Transparent + && !info.strokeStyle.color.isAnimated() + && !info.strokeStyle.opacity.isAnimated(); if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen) return; const QColor fillColor = info.fillColor.defaultValue().value<QColor>(); const bool noFill = info.grad.type() == QGradient::NoGradient && fillColor == QColorConstants::Transparent - && !info.fillColor.isAnimated(); + && !info.fillColor.isAnimated() + && !info.fillOpacity.isAnimated(); if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill) return; @@ -464,7 +485,9 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte stream() << "}"; generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeColor")); + generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral("strokeColor"), AnimationType::ColorOpacity); generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillColor")); + generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral("fillColor"), AnimationType::ColorOpacity); counter++; } @@ -504,7 +527,9 @@ void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info) stream() << "id: " << textItemId; generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral("color")); + generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral("color"), AnimationType::ColorOpacity); generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral("styleColor")); + generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral("styleColor"), AnimationType::ColorOpacity); if (info.isTextArea) { stream() << "x: " << info.position.x(); @@ -886,6 +911,7 @@ bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info) stream() << "// " << comment; stream() << "import QtQuick"; + stream() << "import QtQuick.VectorImage.Helpers"; stream() << "import QtQuick.Shapes" << Qt::endl; stream() << "Item {"; m_indentLevel++; diff --git a/src/quickvectorimage/generator/qquickqmlgenerator_p.h b/src/quickvectorimage/generator/qquickqmlgenerator_p.h index 334e4cfb31..d65bd4ccc5 100644 --- a/src/quickvectorimage/generator/qquickqmlgenerator_p.h +++ b/src/quickvectorimage/generator/qquickqmlgenerator_p.h @@ -96,9 +96,16 @@ private: void generateTransform(const QTransform &xf); void generatePathContainer(const StructureNodeInfo &info); void generateAnimateTransform(const QString &targetName, const NodeInfo &info); + + enum class AnimationType { + Auto = 0, + ColorOpacity = 1 + }; + void generatePropertyAnimation(const QQuickAnimatedProperty &property, const QString &targetName, - const QString &propertyName); + const QString &propertyName, + AnimationType animationType = AnimationType::Auto); QStringView indent(); enum StreamFlags { NoFlags = 0x0, SameLine = 0x1 }; diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp index f9e9c70de3..5bdbdeae4f 100644 --- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp +++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp @@ -940,11 +940,23 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node) } { + QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity")); + if (!animations.isEmpty()) + applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue); + } + + { QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke")); if (!animations.isEmpty()) applyAnimationsToProperty(animations, &info.strokeColor, calculateInterpolatedValue); } + { + QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity")); + if (!animations.isEmpty()) + applyAnimationsToProperty(animations, &info.strokeOpacity, calculateInterpolatedValue); + } + info.position = node->position(); info.size = node->size(); info.font = font; @@ -1215,10 +1227,22 @@ void QSvgVisitorImpl::fillColorAnimationInfo(const QSvgNode *node, PathNodeInfo } { + QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("fill-opacity")); + if (!animations.isEmpty()) + applyAnimationsToProperty(animations, &info.fillOpacity, calculateInterpolatedValue); + } + + { QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke")); if (!animations.isEmpty()) applyAnimationsToProperty(animations, &info.strokeStyle.color, calculateInterpolatedValue); } + + { + QList<AnimationPair> animations = collectAnimations(node, QStringLiteral("stroke-opacity")); + if (!animations.isEmpty()) + applyAnimationsToProperty(animations, &info.strokeStyle.opacity, calculateInterpolatedValue); + } } void QSvgVisitorImpl::fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info) diff --git a/src/quickvectorimage/helpers/qquickcoloropacityanimation.cpp b/src/quickvectorimage/helpers/qquickcoloropacityanimation.cpp new file mode 100644 index 0000000000..7aa7f8ef6f --- /dev/null +++ b/src/quickvectorimage/helpers/qquickcoloropacityanimation.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickcoloropacityanimation_p.h" + +#include <QtQuick/private/qquickanimation_p_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule QtQuick.VectorImage.Helpers + \title Qt Quick Vector Image Helpers QML Types + \ingroup qmlmodules + \brief Provides QML types used by VectorImage and related tools. + \since 6.10 + + This module contains types used in scenes generated by \l{VectorImage}, \l{svgtoqml} and related + tools. The types are made to replicate specialized behavior defined by the vector graphics file + formats it loads and are not intended to be generally useful. + + To use the types in this module, import the module with the following line: + + \qml + import QtQuick.VectorImage.Helpers + \endqml + + \section1 QML Types +*/ + +static QVariant opacityInterpolator(const QColor &from, + const QColor &to, + const QVariant ¤t, + qreal progress) +{ + QColor color = current.value<QColor>(); + + qreal fromAlpha = from.alphaF(); + qreal toAlpha = to.alphaF(); + + color.setAlphaF(fromAlpha + (toAlpha - fromAlpha) * progress); + return QVariant::fromValue(color); +} + +/*! + \qmltype ColorOpacityAnimation + \inqmlmodule QtQuick.VectorImage.Helpers + \inherits Item + \brief Animates the alpha value of a color without modifying the rest of the color. + + This type will animate the alpha value of a color without modifying the other components. It + is used to support the \c fill-opacity and \c stroke-opacity properties of SVG, which can be + animated separately from the opacity of the shape itself. +*/ +QQuickColorOpacityAnimation::QQuickColorOpacityAnimation(QObject *parent) + : QQuickPropertyAnimation(parent) +{ + Q_D(QQuickPropertyAnimation); + d->defaultToInterpolatorType = true; + d->interpolatorType = QMetaType::QColor; + d->extendedInterpolator = reinterpret_cast<QQuickPropertyAnimationPrivate::ExtendedInterpolator>(reinterpret_cast<void(*)()>(opacityInterpolator)); +} + +/*! + \qmlproperty real QtQuick.VectorImage.Helpers::ColorOpacityAnimation::from + + The value of the color's alpha channel at the start of the animation. +*/ +qreal QQuickColorOpacityAnimation::from() const +{ + Q_D(const QQuickPropertyAnimation); + return d->from.value<QColor>().alphaF(); +} + +void QQuickColorOpacityAnimation::setFrom(qreal from) +{ + QColor color = Qt::red; + color.setAlphaF(from); + + QQuickPropertyAnimation::setFrom(color); +} + +/*! + \qmlproperty real QtQuick.VectorImage.Helpers::ColorOpacityAnimation::to + + The value of the color's alpha channel at the end of the animation. +*/ +qreal QQuickColorOpacityAnimation::to() const +{ + Q_D(const QQuickPropertyAnimation); + return d->to.value<QColor>().alphaF(); +} + +void QQuickColorOpacityAnimation::setTo(qreal to) +{ + QColor color = Qt::yellow; + color.setAlphaF(to); + + QQuickPropertyAnimation::setTo(color); +} + +QT_END_NAMESPACE + +#include <moc_qquickcoloropacityanimation_p.cpp> diff --git a/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h b/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h new file mode 100644 index 0000000000..11f2b53c4d --- /dev/null +++ b/src/quickvectorimage/helpers/qquickcoloropacityanimation_p.h @@ -0,0 +1,45 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKOPACITYANIMATION_P_H +#define QQUICKOPACITYANIMATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickanimation_p.h> +#include <QtQuickVectorImageHelpers/qtquickvectorimagehelpersexports.h> + +QT_BEGIN_NAMESPACE + +class Q_QUICKVECTORIMAGEHELPERS_EXPORT QQuickColorOpacityAnimation : public QQuickPropertyAnimation +{ + Q_OBJECT + + Q_DECLARE_PRIVATE(QQuickPropertyAnimation) + Q_PROPERTY(qreal from READ from WRITE setFrom) + Q_PROPERTY(qreal to READ to WRITE setTo) + QML_NAMED_ELEMENT(ColorOpacityAnimation) + +public: + QQuickColorOpacityAnimation(QObject *parent = nullptr); + + qreal from() const; + void setFrom(qreal from); + + qreal to() const; + void setTo(qreal to); +}; + +QT_END_NAMESPACE + +#endif // QQUICKOPACITYANIMATION_P_H + diff --git a/tests/baseline/scenegraph/data/shared/svg/animateFillStrokeOpacity.svg b/tests/baseline/scenegraph/data/shared/svg/animateFillStrokeOpacity.svg new file mode 100644 index 0000000000..0612f6d7df --- /dev/null +++ b/tests/baseline/scenegraph/data/shared/svg/animateFillStrokeOpacity.svg @@ -0,0 +1,18 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> + <style> + @keyframes opacity { + 0% {fill-opacity: 0; stroke-opacity: 0; } + 1% {fill-opacity: 0.75; stroke-opacity: 0.5; } + 100% {fill-opacity: 0.75; stroke-opacity: 0.5; } + } + + rect { + animation-duration: 50s; + animation-name: opacity; + animation-iteration-count: 1; + } + + </style> + <rect width="100" height="100" fill="green" stroke="red" stroke-width="6"/> + +</svg> diff --git a/tests/baseline/scenegraph/data/vectorimages/opacityAnimation.qml b/tests/baseline/scenegraph/data/vectorimages/opacityAnimation.qml index 4a4af0ffe4..b46cba920e 100644 --- a/tests/baseline/scenegraph/data/vectorimages/opacityAnimation.qml +++ b/tests/baseline/scenegraph/data/vectorimages/opacityAnimation.qml @@ -4,18 +4,20 @@ import QtQuick.VectorImage Rectangle { id: topLevelItem width: 200 - height: 100 + height: 200 ListModel { id: renderers ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateOpacity.svg" } ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateOpacity.svg" } + ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateFillStrokeOpacity.svg" } + ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateFillStrokeOpacity.svg" } } - Row { + Grid { + columns: 2 Repeater { model: renderers - VectorImage { layer.enabled: true layer.samples: 4 |