diff options
author | Hatem ElKharashy <hatem.elkharashy@qt.io> | 2024-04-30 10:58:57 +0300 |
---|---|---|
committer | Hatem ElKharashy <hatem.elkharashy@qt.io> | 2024-05-06 09:44:05 +0000 |
commit | 86aee217c9ea05658a53ab46f9f84bb36233f6c1 (patch) | |
tree | 136526ac1c50425e76c8d818c8bcffef82a1f467 | |
parent | 69d4dcf5d4bfab4c3a8a3ea7a90e6694b995f799 (diff) |
Skip rendering filters with unsupported primitives
In Qt 6.7, <filter> and some of the filter primitives were supported.
However, this caused a regression in behavior because Qt Svg used to
skip filters altogether, but starting from Qt 6.7, the filter and the
filter primitives are parsed and the unsupported filter primitives are
skipped which leads to unexpected output.
It is a good idea to start supporting the rest of the filter primitives
in the future, but for now the renderer will skip applying the filter if
it has an unsupported filter primitive.
Fixes: QTBUG-123994
Pick-to: 6.7
Change-Id: I14b91c919ace8a886ee285ff1ed466110d13322a
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r-- | src/svg/qsvgfilter.cpp | 18 | ||||
-rw-r--r-- | src/svg/qsvgfilter_p.h | 10 | ||||
-rw-r--r-- | src/svg/qsvghandler.cpp | 86 | ||||
-rw-r--r-- | src/svg/qsvghandler_p.h | 6 | ||||
-rw-r--r-- | src/svg/qsvgnode.cpp | 8 | ||||
-rw-r--r-- | src/svg/qsvgnode_p.h | 3 | ||||
-rw-r--r-- | src/svg/qsvgstructure.cpp | 11 | ||||
-rw-r--r-- | src/svg/qsvgstructure_p.h | 3 | ||||
-rw-r--r-- | src/svg/qsvgvisitor.cpp | 1 |
9 files changed, 121 insertions, 25 deletions
diff --git a/src/svg/qsvgfilter.cpp b/src/svg/qsvgfilter.cpp index f773ec6..b38a0fe 100644 --- a/src/svg/qsvgfilter.cpp +++ b/src/svg/qsvgfilter.cpp @@ -674,5 +674,23 @@ QImage QSvgFeFlood::apply(QSvgNode *item, const QMap<QString, QImage> &, return result; } +QSvgFeUnsupported::QSvgFeUnsupported(QSvgNode *parent, QString input, QString result, + const QSvgRectF &rect) + : QSvgFeFilterPrimitive(parent, input, result, rect) +{ +} + +QSvgNode::Type QSvgFeUnsupported::type() const +{ + return QSvgNode::FeUnsupported; +} + +QImage QSvgFeUnsupported::apply(QSvgNode *, const QMap<QString, QImage> &, + QPainter *, const QRectF &, const QRectF &, + QtSvg::UnitTypes, QtSvg::UnitTypes) const +{ + qCDebug(lcSvgDraw) <<"Unsupported filter primitive should not be applied."; + return QImage(); +} QT_END_NAMESPACE diff --git a/src/svg/qsvgfilter_p.h b/src/svg/qsvgfilter_p.h index 70c50de..071ee40 100644 --- a/src/svg/qsvgfilter_p.h +++ b/src/svg/qsvgfilter_p.h @@ -178,7 +178,15 @@ private: QColor m_color; }; - +class Q_SVG_EXPORT QSvgFeUnsupported : public QSvgFeFilterPrimitive +{ +public: + QSvgFeUnsupported(QSvgNode *parent, QString input, QString result, const QSvgRectF &rect); + Type type() const override; + QImage apply(QSvgNode *item, const QMap<QString, QImage> &sources, + QPainter *p, const QRectF &itemBounds, const QRectF &filterBounds, + QtSvg::UnitTypes primitiveUnits, QtSvg::UnitTypes filterUnits) const override; +}; QT_END_NAMESPACE diff --git a/src/svg/qsvghandler.cpp b/src/svg/qsvghandler.cpp index b6b94d3..c49d3a7 100644 --- a/src/svg/qsvghandler.cpp +++ b/src/svg/qsvghandler.cpp @@ -3483,6 +3483,21 @@ static QSvgNode *createFeMergeNodeNode(QSvgNode *parent, return filter; } +static QSvgNode *createFeUnsupportedNode(QSvgNode *parent, + const QXmlStreamAttributes &attributes, + QSvgHandler *handler) +{ + QString inputString; + QString outputString; + QSvgRectF rect; + + parseFilterAttributes(parent, attributes, handler, + &inputString, &outputString, &rect); + + QSvgNode *filter = new QSvgFeUnsupported(parent, inputString, outputString, rect); + return filter; +} + static bool parseSymbolLikeAttributes(const QXmlStreamAttributes &attributes, QSvgHandler *handler, QRectF *rect, QRectF *viewBox, QPointF *refPoint, QSvgSymbolLike::PreserveAspectRatios *aspect, @@ -4313,7 +4328,7 @@ static FactoryMethod findGraphicsFactory(const QString &name, QtSvg::Options opt return 0; } -static FactoryMethod findFilterFtory(const QString &name, QtSvg::Options options) +static FactoryMethod findFilterFactory(const QString &name, QtSvg::Options options) { if (options.testFlag(QtSvg::Tiny12FeaturesOnly)) return 0; @@ -4332,6 +4347,27 @@ static FactoryMethod findFilterFtory(const QString &name, QtSvg::Options options if (name == QLatin1String("feComposite")) return createFeCompositeNode; if (name == QLatin1String("feFlood")) return createFeFloodNode; + static const QStringList unsupportedFilters = { + QStringLiteral("feBlend"), + QStringLiteral("feComponentTransfer"), + QStringLiteral("feConvolveMatrix"), + QStringLiteral("feDiffuseLighting"), + QStringLiteral("feDisplacementMap"), + QStringLiteral("feDropShadow"), + QStringLiteral("feFuncA"), + QStringLiteral("feFuncB"), + QStringLiteral("feFuncG"), + QStringLiteral("feFuncR"), + QStringLiteral("feImage"), + QStringLiteral("feMorphology"), + QStringLiteral("feSpecularLighting"), + QStringLiteral("feTile"), + QStringLiteral("feTurbulence") + }; + + if (unsupportedFilters.contains(name)) + return createFeUnsupportedNode; + return 0; } @@ -4686,6 +4722,8 @@ bool QSvgHandler::startElement(const QString &localName, cssStyleLookup(node, this, m_selector); #endif parseStyle(node, attributes, this); + if (node->type() == QSvgNode::Filter) + m_toBeResolved.append(node); } } } else if (FactoryMethod method = findGraphicsFactory(localName, options())) { @@ -4751,7 +4789,7 @@ bool QSvgHandler::startElement(const QString &localName, } } } - } else if (FactoryMethod method = findFilterFtory(localName, options())) { + } else if (FactoryMethod method = findFilterFactory(localName, options())) { //filter nodes to be aded to be filtercontainer Q_ASSERT(!m_nodes.isEmpty()); node = method(m_nodes.top(), attributes, this); @@ -4874,26 +4912,38 @@ void QSvgHandler::resolvePaintServers(QSvgNode *node, int nestedDepth) void QSvgHandler::resolveNodes() { - for (QSvgUse *useNode : std::as_const(m_toBeResolved)) { - const auto parent = useNode->parent(); - if (!parent) - continue; + for (QSvgNode *node : std::as_const(m_toBeResolved)) { + if (node->type() == QSvgNode::Use) { + QSvgUse *useNode = static_cast<QSvgUse *>(node); + const auto parent = useNode->parent(); + if (!parent) + continue; - QSvgNode::Type t = parent->type(); - if (t != QSvgNode::Doc && t != QSvgNode::Defs && t != QSvgNode::Group && t != QSvgNode::Switch) - continue; + QSvgNode::Type t = parent->type(); + if (t != QSvgNode::Doc && t != QSvgNode::Defs && t != QSvgNode::Group && t != QSvgNode::Switch) + continue; - QSvgStructureNode *group = static_cast<QSvgStructureNode *>(parent); - QSvgNode *link = group->scopeNode(useNode->linkId()); - if (!link) { - qCWarning(lcSvgHandler, "link #%s is undefined!", qPrintable(useNode->linkId())); - continue; - } + QSvgStructureNode *group = static_cast<QSvgStructureNode *>(parent); + QSvgNode *link = group->scopeNode(useNode->linkId()); + if (!link) { + qCWarning(lcSvgHandler, "link #%s is undefined!", qPrintable(useNode->linkId())); + continue; + } - if (useNode->parent()->isDescendantOf(link)) - qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(useNode->linkId())); + if (useNode->parent()->isDescendantOf(link)) + qCWarning(lcSvgHandler, "link #%s is recursive!", qPrintable(useNode->linkId())); - useNode->setLink(link); + useNode->setLink(link); + } else if (node->type() == QSvgNode::Filter) { + QSvgFilterContainer *filter = static_cast<QSvgFilterContainer *>(node); + for (const QSvgNode *renderer : filter->renderers()) { + const QSvgFeFilterPrimitive *primitive = QSvgFeFilterPrimitive::castToFilterPrimitive(renderer); + if (!primitive || primitive->type() == QSvgNode::FeUnsupported) { + filter->setSupported(false); + break; + } + } + } } m_toBeResolved.clear(); } diff --git a/src/svg/qsvghandler_p.h b/src/svg/qsvghandler_p.h index 2857ac0..1627223 100644 --- a/src/svg/qsvghandler_p.h +++ b/src/svg/qsvghandler_p.h @@ -114,8 +114,10 @@ private: QSvgTinyDocument *m_doc; QStack<QSvgNode *> m_nodes; // TODO: This is only needed during parsing, so it unnecessarily takes up space after that. - // Temporary container for <use> nodes which haven't been resolved yet. - QList<QSvgUse *> m_toBeResolved; + // Temporary container for : + // - <use> nodes which haven't been resolved yet. + // - <filter> nodes to be checked for unsupported filter primitives. + QList<QSvgNode *> m_toBeResolved; enum CurrentNode { diff --git a/src/svg/qsvgnode.cpp b/src/svg/qsvgnode.cpp index 8ad2240..dbf71fd 100644 --- a/src/svg/qsvgnode.cpp +++ b/src/svg/qsvgnode.cpp @@ -44,15 +44,16 @@ void QSvgNode::draw(QPainter *p, QSvgExtraStates &states) if (shouldDrawNode(p, states)) { applyStyle(p, states); QSvgNode *maskNode = this->hasMask() ? document()->namedNode(this->maskId()) : nullptr; - QSvgNode *filterNode = this->hasFilter() ? document()->namedNode(this->filterId()) : nullptr; - if (filterNode && filterNode->type() == QSvgNode::Filter) { + QSvgFilterContainer *filterNode = this->hasFilter() ? static_cast<QSvgFilterContainer*>(document()->namedNode(this->filterId())) + : nullptr; + if (filterNode && filterNode->supported()) { QTransform xf = p->transform(); p->resetTransform(); QRectF localRect = bounds(p, states); QRectF boundsRect = xf.mapRect(localRect); p->setTransform(xf); QImage proxy = drawIntoBuffer(p, states, boundsRect.toRect()); - proxy = static_cast<QSvgFilterContainer*>(filterNode)->applyFilter(this, proxy, p, localRect); + proxy = filterNode->applyFilter(this, proxy, p, localRect); boundsRect = QRectF(proxy.offset(), proxy.size()); localRect = p->transform().inverted().mapRect(boundsRect); @@ -413,6 +414,7 @@ QString QSvgNode::typeName() const case FeOffset: return QStringLiteral("feOffset"); case FeComposite: return QStringLiteral("feComposite"); case FeFlood: return QStringLiteral("feFlood"); + case FeUnsupported: return QStringLiteral("feUnsupported"); } return QStringLiteral("unknown"); } diff --git a/src/svg/qsvgnode_p.h b/src/svg/qsvgnode_p.h index f616a45..2fa837b 100644 --- a/src/svg/qsvgnode_p.h +++ b/src/svg/qsvgnode_p.h @@ -61,7 +61,8 @@ public: FeGaussianblur, FeOffset, FeComposite, - FeFlood + FeFlood, + FeUnsupported }; enum DisplayMode { InlineMode, diff --git a/src/svg/qsvgstructure.cpp b/src/svg/qsvgstructure.cpp index 22f1b0d..b729fa8 100644 --- a/src/svg/qsvgstructure.cpp +++ b/src/svg/qsvgstructure.cpp @@ -211,6 +211,7 @@ QSvgFilterContainer::QSvgFilterContainer(QSvgNode *parent, const QSvgRectF &boun , m_rect(bounds) , m_filterUnits(filterUnits) , m_primitiveUnits(primitiveUnits) + , m_supported(true) { } @@ -432,6 +433,16 @@ QImage QSvgFilterContainer::applyFilter(QSvgNode *item, const QImage &buffer, QP return result; } +void QSvgFilterContainer::setSupported(bool supported) +{ + m_supported = supported; +} + +bool QSvgFilterContainer::supported() const +{ + return m_supported; +} + QSvgNode::Type QSvgFilterContainer::type() const { return Filter; diff --git a/src/svg/qsvgstructure_p.h b/src/svg/qsvgstructure_p.h index 9a3c2a0..d2cef9d 100644 --- a/src/svg/qsvgstructure_p.h +++ b/src/svg/qsvgstructure_p.h @@ -165,10 +165,13 @@ public: void drawCommand(QPainter *, QSvgExtraStates &) override {}; Type type() const override; QImage applyFilter(QSvgNode *referenceNode, const QImage &buffer, QPainter *p, QRectF bounds) const; + void setSupported(bool supported); + bool supported() const; private: QSvgRectF m_rect; QtSvg::UnitTypes m_filterUnits; QtSvg::UnitTypes m_primitiveUnits; + bool m_supported; }; diff --git a/src/svg/qsvgvisitor.cpp b/src/svg/qsvgvisitor.cpp index 018ace0..8c1d3f5 100644 --- a/src/svg/qsvgvisitor.cpp +++ b/src/svg/qsvgvisitor.cpp @@ -119,6 +119,7 @@ void QSvgVisitor::traverse(const QSvgNode *node) case QSvgNode::FeOffset: case QSvgNode::FeComposite: case QSvgNode::FeFlood: + case QSvgNode::FeUnsupported: qDebug() << "Unhandled type in switch" << node->type(); break; } |