diff options
| author | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2026-08-31 11:34:53 +0200 |
|---|---|---|
| committer | Eirik Aavitsland <eirik.aavitsland@qt.io> | 2026-09-14 21:22:28 +0000 |
| commit | 71ee6061b73ef9c0312684d3d49f2e4f75fe4f6b (patch) | |
| tree | cd9fada494c2837a9f57c5fd02b873b3e8affa7c | |
| parent | 2fe514c09d656584d8dc3f005e2a8338f4d7853b (diff) | |
Vectorimage generator: handle SVG group opacity
In Qt Quick, semi-opacity on a parent item is implemented by rendering
each child item individually with that opacity setting. In case of
overlapping children, this gives a different result than what SVG expects.
Work around this by using layered (intermediate texture) rendering on
such items. To handle item transforms correctly, a wrapping item is
introduced. As an optimization, layering is only enabled if there is
more than one graphic child item.
Fixes: QTBUG-149247
Pick-to: 6.12 6.11 6.8
Change-Id: Iaebb8cae0f950a68906a45901345cb8c3181b947
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
7 files changed, 124 insertions, 0 deletions
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h index 6e877144c0..008580fbdc 100644 --- a/src/quickvectorimage/generator/qquicknodeinfo_p.h +++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h @@ -178,6 +178,8 @@ struct StructureNodeInfo : NodeInfo QRectF viewBox; QSize size; QRectF clipBox; + QRectF opacityGroupBounds; + QString opacityGroupBoundsReferenceId; bool isPathContainer = false; std::optional<TimelineInfo> timelineInfo; }; diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp index 3b385a80c7..fa3e0f8054 100644 --- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp +++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp @@ -588,6 +588,7 @@ void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info) const QFileInfo assetFileInfo(filePath); + registerGraphicItem(); stream() << "Image {"; m_indentLevel++; @@ -1035,6 +1036,7 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte if (noPen && noFill) return; auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule()); + registerGraphicItem(); stream() << "ShapePath {"; m_indentLevel++; @@ -1267,6 +1269,7 @@ void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info) stream() << "Item {"; m_indentLevel++; generateNodeBase(info); + registerGraphicItem(); if (!info.isTextArea) stream() << "Item { id: textAlignItem_" << m_textNodeCounter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}"; @@ -1755,6 +1758,68 @@ void QQuickQmlGenerator::generateTransformTimeline(const QString &targetName, co bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info) { + const bool isStartStage = (info.stage == StructureNodeStage::Start); + bool doWrap = false; + if (isStartStage) + doWrap = !info.opacityGroupBounds.isNull() || !info.opacityGroupBoundsReferenceId.isEmpty(); + else + doWrap = (!m_opacityWrapperIds.isEmpty() && m_opacityWrapperIds.top() == info.id); + bool res = false; + if (!doWrap) { + res = generateStructureNode_helper(info); + } else { + StructureNodeInfo wrappedInfo = info; + wrappedInfo.isDefaultOpacity = true; + wrappedInfo.opacity = QQuickAnimatedProperty(QVariant::fromValue(1.0)); + const QString wrapperId = info.id + "_opacityWrapper"_L1; + if (isStartStage) { + stream() << "Item { // Opacity group wrapper"; + m_indentLevel++; + stream() << "id: " << wrapperId; + stream() << "layer.sourceRect: "; + if (!info.opacityGroupBoundsReferenceId.isEmpty()) { + stream(SameLine) << info.opacityGroupBoundsReferenceId << ".originalBounds"; + } else { + stream(SameLine) << "Qt.rect(" + << info.opacityGroupBounds.x() << ", " + << info.opacityGroupBounds.y() << ", " + << info.opacityGroupBounds.width() << ", " + << info.opacityGroupBounds.height() << ")"; + } + stream() << "opacity: " << info.opacity.defaultValue().toReal(); + generatePropertyAnimation(info.opacity, wrapperId, "opacity"_L1); + m_opacityWrapperIds.push(info.id); + m_opacityWrapperShapeCounts.push(0); + } + + res = generateStructureNode_helper(wrappedInfo); + + if (!isStartStage) { + m_opacityWrapperIds.pop(); + if (m_opacityWrapperShapeCounts.pop() > 1) { + const QString itemSpyId = wrapperId + "_itemspy"_L1; + stream() << "layer.enabled: true"; + stream() << "x: layer.sourceRect.x"; + stream() << "y: layer.sourceRect.y"; + stream() << "width: layer.sourceRect.width"; + stream() << "height: layer.sourceRect.height"; + stream() << "layer.textureSize: " << itemSpyId << ".requiredTextureSize"; + stream() << "ItemSpy {"; + m_indentLevel++; + stream() << "id: " << itemSpyId; + stream() << "anchors.fill: parent"; + m_indentLevel--; + stream() << "}"; + } + m_indentLevel--; + stream() << "}"; + } + } + return res; +} + +bool QQuickQmlGenerator::generateStructureNode_helper(const StructureNodeInfo &info) +{ if (Q_UNLIKELY(errorState() || !isNodeVisible(info))) return false; diff --git a/src/quickvectorimage/generator/qquickqmlgenerator_p.h b/src/quickvectorimage/generator/qquickqmlgenerator_p.h index 3a78a0fb38..ce01a1ba06 100644 --- a/src/quickvectorimage/generator/qquickqmlgenerator_p.h +++ b/src/quickvectorimage/generator/qquickqmlgenerator_p.h @@ -153,6 +153,13 @@ private: void generateMarkers(const PathNodeInfo &info); qsizetype generateFilterStep(const FilterNodeInfo &info, qsizetype stepIndex); + bool generateStructureNode_helper(const StructureNodeInfo &info); + void registerGraphicItem() + { + if (!m_opacityWrapperShapeCounts.isEmpty()) + m_opacityWrapperShapeCounts.top()++; + } + bool usingTimelineAnimation() const { return m_flags.testFlag(QQuickVectorImageGenerator::TimelineAnimation); @@ -200,6 +207,8 @@ private: QString m_defsSuffix; QString m_indentString; QSet<QString> m_contentRelativeMasks; + QStack<QString> m_opacityWrapperIds; + QStack<int> m_opacityWrapperShapeCounts; }; QT_END_NAMESPACE diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp index f7c0f78222..428efc3a57 100644 --- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp +++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp @@ -1587,6 +1587,8 @@ bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node) fillAnimationInfo(node, info); info.forceSeparatePaths = forceSeparatePaths; info.isPathContainer = isPathContainer(m_doc, node); + if (!info.isDefaultOpacity || info.opacity.isAnimated()) + info.opacityGroupBounds = node->bounds(m_doc); info.stage = StructureNodeStage::Start; return m_generator->generateStructureNode(info); diff --git a/tests/baseline/scenegraph/data/shared/svg/opacity_group_1.svg b/tests/baseline/scenegraph/data/shared/svg/opacity_group_1.svg new file mode 100644 index 0000000000..1aba4e067e --- /dev/null +++ b/tests/baseline/scenegraph/data/shared/svg/opacity_group_1.svg @@ -0,0 +1,7 @@ +<svg viewBox="0 0 380 380" xmlns="http://www.w3.org/2000/svg"> + <rect width="380" height="300" fill="green"/> + <g transform="translate(40, 40)" opacity="0.5"> + <rect width="300" height="200" fill="blue"/> + <text x="10" y="125" style="font-size: 48px" fill="white">Qt Rocks!</text> + </g> +</svg> diff --git a/tests/baseline/scenegraph/data/shared/svg/opacity_group_2.svg b/tests/baseline/scenegraph/data/shared/svg/opacity_group_2.svg new file mode 100644 index 0000000000..8e5c25fdb2 --- /dev/null +++ b/tests/baseline/scenegraph/data/shared/svg/opacity_group_2.svg @@ -0,0 +1,8 @@ +<svg viewBox="0 0 380 380" xmlns="http://www.w3.org/2000/svg"> + <text x="20" y="175" style="font-size: 48px; font-weight: bold;" fill="black">BACKGROUND</text> + <g transform="translate(40, 75)" opacity="0.8"> + <circle cx="150" cy="25" r="75" fill="red" /> + <circle cx="100" cy="100" r="75" fill="green" /> + <circle cx="200" cy="100" r="75" fill="blue" /> + </g> +</svg> diff --git a/tests/baseline/scenegraph/data/vectorimages/groupOpacity.qml b/tests/baseline/scenegraph/data/vectorimages/groupOpacity.qml new file mode 100644 index 0000000000..f254a6a619 --- /dev/null +++ b/tests/baseline/scenegraph/data/vectorimages/groupOpacity.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.VectorImage + +Rectangle{ + id: topLevelItem + width: 800 + height: 400 + + ListModel { + id: files + ListElement { src: "../shared/svg/opacity_group_1.svg" } + ListElement { src: "../shared/svg/opacity_group_2.svg" } + } + + Grid { + spacing: 10 + columns: 2 + anchors.fill: parent + Repeater { + model: files + + VectorImage { + source: src + preferredRendererType: VectorImage.CurveRenderer + width: 400 + height: 400 + fillMode: VectorImage.PreserveAspectFit + } + } + } +} |
