aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <[email protected]>2025-12-03 11:28:37 +0100
committerEskil Abrahamsen Blomfeldt <[email protected]>2025-12-04 21:01:18 +0100
commita6d9da57f18779ecbd67df8e3df015cccb94dcbe (patch)
tree75b917116f4abe79b0139faabe2685aee8d232d2 /src
parentfdba000bcd0b34740b919d9d2d62eaf056aa434c (diff)
VectorImage: Post processing effect chaining
This introduces chaining post-processing effects, and the feComposite and feBlend nodes to allow compositing these. Task-number: QTBUG-121541 Change-Id: Ib2aa99139869814a575a2bf9d9e6319b31828285 Reviewed-by: Eirik Aavitsland <[email protected]> Reviewed-by: Hatem ElKharashy <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/quickvectorimage/CMakeLists.txt192
-rw-r--r--src/quickvectorimage/generator/qquicknodeinfo_p.h40
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp201
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator_p.h1
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp161
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl_p.h3
-rw-r--r--src/quickvectorimage/helpers/shaders_ng/feblend.frag58
-rw-r--r--src/quickvectorimage/helpers/shaders_ng/fecomposite.frag63
8 files changed, 661 insertions, 58 deletions
diff --git a/src/quickvectorimage/CMakeLists.txt b/src/quickvectorimage/CMakeLists.txt
index 36e6d703d2..789a9f1898 100644
--- a/src/quickvectorimage/CMakeLists.txt
+++ b/src/quickvectorimage/CMakeLists.txt
@@ -68,3 +68,195 @@ qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders"
"helpers/shaders_ng/genericmask.frag"
"helpers/shaders_ng/fecolormatrix.frag"
)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_over"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_OVER"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositeover.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_in"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_IN"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositein.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_out"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_OUT"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositeout.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_atop"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_ATOP"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositeatop.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_xor"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_XOR"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositexor.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_lighter"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_LIGHTER"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositelighter.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_comp_arithmetic"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "COMPOSITE_ARITHMETIC"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/fecomposite.frag"
+ OUTPUTS
+ "helpers/shaders_ng/fecompositearithmetic.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_blend_normal"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "BLEND_NORMAL"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/feblend.frag"
+ OUTPUTS
+ "helpers/shaders_ng/feblendnormal.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_blend_multiply"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "BLEND_MULTIPLY"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/feblend.frag"
+ OUTPUTS
+ "helpers/shaders_ng/feblendmultiply.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_blend_screen"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "BLEND_SCREEN"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/feblend.frag"
+ OUTPUTS
+ "helpers/shaders_ng/feblendscreen.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_blend_lighten"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "BLEND_LIGHTEN"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/feblend.frag"
+ OUTPUTS
+ "helpers/shaders_ng/feblendlighten.frag.qsb"
+)
+
+qt_internal_add_shaders(QuickVectorImageHelpers "vectorimage_shaders_blend_darken"
+ SILENT
+ BATCHABLE
+ PRECOMPILE
+ OPTIMIZED
+ MULTIVIEW
+ DEFINES
+ "BLEND_DARKEN"
+ PREFIX
+ "/qt-project.org/quickvectorimage/"
+ FILES
+ "helpers/shaders_ng/feblend.frag"
+ OUTPUTS
+ "helpers/shaders_ng/feblenddarken.frag.qsb"
+)
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h
index 2f4f522ab0..484047fc7e 100644
--- a/src/quickvectorimage/generator/qquicknodeinfo_p.h
+++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h
@@ -167,7 +167,19 @@ struct FilterNodeInfo : NodeInfo
GaussianBlur,
ColorMatrix,
Offset,
- Flood
+ Flood,
+ CompositeOver,
+ CompositeIn,
+ CompositeOut,
+ CompositeAtop,
+ CompositeXor,
+ CompositeLighter,
+ CompositeArithmetic,
+ BlendNormal,
+ BlendMultiply,
+ BlendScreen,
+ BlendDarken,
+ BlendLighten
};
enum class CoordinateSystem {
@@ -176,14 +188,32 @@ struct FilterNodeInfo : NodeInfo
MatchFilterRect // Special case
};
+ enum class FilterInput {
+ None,
+ SourceAlpha,
+ SourceColor,
+ Name
+ };
+
QRectF filterRect;
CoordinateSystem csFilterRect = CoordinateSystem::Absolute;
- CoordinateSystem csFilterParameter = CoordinateSystem::Absolute;
QSGTexture::WrapMode wrapMode = QSGTexture::ClampToEdge;
- Type filterType = Type::None;
- QRectF filterPrimitiveRect;
- QVariant filterParameter;
+ struct FilterStep {
+ Type filterType = Type::None;
+ QRectF filterPrimitiveRect;
+ QVariant filterParameter;
+ CoordinateSystem csFilterParameter = CoordinateSystem::Absolute;
+
+ QString outputName;
+
+ FilterInput input1 = FilterInput::SourceColor;
+ FilterInput input2 = FilterInput::None; // Only used by some effects
+
+ QString namedInput1;
+ QString namedInput2;
+ };
+ QList<FilterStep> steps;
};
}
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
index 6faedf08f8..3bc39649d4 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -1585,21 +1585,130 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
generateNodeBase(info);
- QString primitiveId = info.id + QStringLiteral("_primitive");
- QString offsetStr = QStringLiteral("Qt.vector2d(0, 0)");
+ for (qsizetype i = 0; i < info.steps.size(); ++i)
+ generateFilterStep(info, i);
- switch (info.filterType) {
+ generateNodeEnd(info);
+
+ m_indentLevel--;
+ stream() << "}"; // End of Component
+}
+
+void QQuickQmlGenerator::generateFilterStep(const FilterNodeInfo &info,
+ qsizetype stepIndex)
+{
+ const FilterNodeInfo::FilterStep &step = info.steps.at(stepIndex);
+ const QString primitiveId = info.id + QStringLiteral("_primitive") + QString::number(stepIndex);
+
+
+ QString inputId = step.input1 != FilterNodeInfo::FilterInput::SourceColor
+ ? step.namedInput1
+ : QStringLiteral("filterSourceItem");
+
+ bool isComposite = false;
+ switch (step.filterType) {
+ case FilterNodeInfo::Type::CompositeOver:
+ case FilterNodeInfo::Type::CompositeOut:
+ case FilterNodeInfo::Type::CompositeIn:
+ case FilterNodeInfo::Type::CompositeXor:
+ case FilterNodeInfo::Type::CompositeAtop:
+ case FilterNodeInfo::Type::CompositeArithmetic:
+ case FilterNodeInfo::Type::CompositeLighter:
+ isComposite = true;
+ Q_FALLTHROUGH();
+
+ case FilterNodeInfo::Type::BlendNormal:
+ case FilterNodeInfo::Type::BlendMultiply:
+ case FilterNodeInfo::Type::BlendScreen:
+ case FilterNodeInfo::Type::BlendDarken:
+ case FilterNodeInfo::Type::BlendLighten:
+ {
+ stream() << "ShaderEffect {";
+ m_indentLevel++;
+
+ QString input2Id = step.input2 != FilterNodeInfo::FilterInput::SourceColor
+ ? step.namedInput2
+ : QStringLiteral("filterSourceItem");
+
+ stream() << "id: " << primitiveId;
+ stream() << "visible: false";
+
+ QString shader;
+ switch (step.filterType) {
+ case FilterNodeInfo::Type::CompositeOver:
+ shader = QStringLiteral("fecompositeover");
+ break;
+ case FilterNodeInfo::Type::CompositeOut:
+ shader = QStringLiteral("fecompositeout");
+ break;
+ case FilterNodeInfo::Type::CompositeIn:
+ shader = QStringLiteral("fecompositein");
+ break;
+ case FilterNodeInfo::Type::CompositeXor:
+ shader = QStringLiteral("fecompositexor");
+ break;
+ case FilterNodeInfo::Type::CompositeAtop:
+ shader = QStringLiteral("fecompositeatop");
+ break;
+ case FilterNodeInfo::Type::CompositeArithmetic:
+ shader = QStringLiteral("fecompositearithmetic");
+ break;
+ case FilterNodeInfo::Type::CompositeLighter:
+ shader = QStringLiteral("fecompositelighter");
+ break;
+ case FilterNodeInfo::Type::BlendNormal:
+ shader = QStringLiteral("feblendnormal");
+ break;
+ case FilterNodeInfo::Type::BlendMultiply:
+ shader = QStringLiteral("feblendmultiply");
+ break;
+ case FilterNodeInfo::Type::BlendScreen:
+ shader = QStringLiteral("feblendscreen");
+ break;
+ case FilterNodeInfo::Type::BlendDarken:
+ shader = QStringLiteral("feblenddarken");
+ break;
+ case FilterNodeInfo::Type::BlendLighten:
+ shader = QStringLiteral("feblendlighten");
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+
+ stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"
+ << shader << ".frag.qsb\"";
+ stream() << "property var source: " << inputId;
+ stream() << "property var source2: " << input2Id;
+ stream() << "width: source.width";
+ stream() << "height: source.height";
+
+ if (isComposite) {
+ QVector4D k = step.filterParameter.value<QVector4D>();
+ stream() << "property var k: Qt.vector4d("
+ << k.x() << ", "
+ << k.y() << ", "
+ << k.z() << ", "
+ << k.w() << ")";
+ }
+
+ m_indentLevel--;
+ stream() << "}";
+
+ break;
+
+ }
case FilterNodeInfo::Type::Flood:
{
stream() << "Rectangle {";
m_indentLevel++;
stream() << "id: " << primitiveId;
+ stream() << "visible: false";
- stream() << "width: filterSourceItem.sourceRect.width";
- stream() << "height: filterSourceItem.sourceRect.height";
+ stream() << "width: " << inputId << ".width";
+ stream() << "height: " << inputId << ".height";
- QColor floodColor = info.filterParameter.value<QColor>();
+ QColor floodColor = step.filterParameter.value<QColor>();
stream() << "color: \"" << floodColor.name(QColor::HexArgb) << "\"";
m_indentLevel--;
@@ -1613,13 +1722,14 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
m_indentLevel++;
stream() << "id: " << primitiveId;
+ stream() << "visible: false";
stream() << "fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb\"";
- stream() << "property var source: filterSourceItem";
- stream() << "width: filterSourceItem.sourceRect.width";
- stream() << "height: filterSourceItem.sourceRect.height";
+ stream() << "property var source: " << inputId;
+ stream() << "width: source.width";
+ stream() << "height: source.height";
- QGenericMatrix<5, 5, qreal> matrix = info.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
+ QGenericMatrix<5, 5, qreal> matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
for (int row = 0; row < 4; ++row) { // Last row is ignored
// Qt SVG stores rows as columns, so we flip the coordinates
@@ -1635,11 +1745,36 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
case FilterNodeInfo::Type::Offset:
{
- QVector2D offset = info.filterParameter.value<QVector2D>();
- offsetStr = (info.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute
- ? QStringLiteral("Qt.vector2d(%1 / filterSourceItem.width, %2 / filterSourceItem.height)")
- : QStringLiteral("Qt.vector2d(%1, %2)")).arg(offset.x()).arg(offset.y());
- primitiveId = QStringLiteral("filterSourceItem");
+ stream() << "ShaderEffectSource {";
+ m_indentLevel++;
+
+ stream() << "id: " << primitiveId;
+ stream() << "visible: false";
+ stream() << "sourceItem: " << inputId;
+ stream() << "width: sourceItem.width + offset.x";
+ stream() << "height: sourceItem.height + offset.y";
+
+ QVector2D offset = step.filterParameter.value<QVector2D>();
+ stream() << "property vector2d offset: Qt.vector2d(";
+ if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute)
+ stream(SameLine) << offset.x() << " / width, " << offset.y() << " / height)";
+ else
+ stream(SameLine) << offset.x() << ", " << offset.y() << ")";
+
+ stream() << "sourceRect: Qt.rect(-offset.x, -offset.y, width, height)";
+
+ stream() << "ItemSpy {";
+ m_indentLevel++;
+ stream() << "id: " << primitiveId << "_offset_itemspy";
+ stream() << "anchors.fill: parent";
+
+ m_indentLevel--;
+ stream() << "}";
+ stream() << "textureSize: " << primitiveId << "_offset_itemspy.requiredTextureSize";
+
+
+ m_indentLevel--;
+ stream() << "}";
break;
}
@@ -1651,15 +1786,16 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
m_indentLevel++;
stream() << "id: " << primitiveId;
+ stream() << "visible: false";
- stream() << "source: filterSourceItem";
+ stream() << "source: " << inputId;
stream() << "blurEnabled: true";
- stream() << "width: filterSourceItem.sourceRect.width";
- stream() << "height: filterSourceItem.sourceRect.height";
+ stream() << "width: source.width";
+ stream() << "height: source.height";
const qreal maxDeviation(12.0); // Decided experimentally
- const qreal deviation = info.filterParameter.toReal();
- if (info.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
+ const qreal deviation = step.filterParameter.toReal();
+ if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
stream() << "blur: Math.min(1.0, " << deviation << " * filterSourceItem.width / " << maxDeviation << ")";
else
stream() << "blur: " << std::min(qreal(1.0), deviation / maxDeviation);
@@ -1671,7 +1807,7 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
break;
}
default:
- qCWarning(lcQuickVectorImage) << "Unhandled filter type: " << int(info.filterType);
+ qCWarning(lcQuickVectorImage) << "Unhandled filter type: " << int(step.filterType);
// Dummy item to avoid empty component
stream() << "Item { id: " << primitiveId << " }";
break;
@@ -1681,14 +1817,18 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
stream() << "ShaderEffectSource {";
m_indentLevel++;
+ stream() << "id: " << step.outputName;
+ if (stepIndex < info.steps.size() - 1)
+ stream() << "visible: false";
+
qreal x1, x2, y1, y2;
- info.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
- if (info.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
+ step.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
+ if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
stream() << "property real fpx1: " << x1;
stream() << "property real fpy1: " << y1;
stream() << "property real fpx2: " << x2;
stream() << "property real fpy2: " << y2;
- } else if (info.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
+ } else if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
// If they are relative, they are actually in the coordinate system
// of the original bounds of the filtered item. This means we first have to convert
// them to the filter's coordinate system first.
@@ -1704,14 +1844,12 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
}
stream() << "sourceItem: " << primitiveId;
- stream() << "hideSource: true";
- stream() << "property vector2d offset: " << offsetStr;
- stream() << "sourceRect: Qt.rect(fpx1 - filterRect.x - offset.x, fpy1 - filterRect.y - offset.y, width, height)";
+ stream() << "sourceRect: Qt.rect(fpx1 - filterRect.x, fpy1 - filterRect.y, width, height)";
stream() << "x: fpx1";
stream() << "y: fpy1";
- stream() << "width: " << "fpx2 - fpx1 + offset.x";
- stream() << "height: " << "fpy2 - fpy1 + offset.y";
+ stream() << "width: " << "fpx2 - fpx1";
+ stream() << "height: " << "fpy2 - fpy1";
stream() << "ItemSpy {";
m_indentLevel++;
@@ -1724,11 +1862,6 @@ void QQuickQmlGenerator::generateFilterNode(const FilterNodeInfo &info)
m_indentLevel--;
stream() << "}";
-
- generateNodeEnd(info);
-
- m_indentLevel--;
- stream() << "}"; // End of Component
}
bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator_p.h b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
index 09fc099b4e..6aebf03f6f 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator_p.h
+++ b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
@@ -137,6 +137,7 @@ private:
const QString &propertyName,
AnimationType animationType = AnimationType::Auto);
void generateShaderUse(const NodeInfo &info);
+ void generateFilterStep(const FilterNodeInfo &info, qsizetype stepIndex);
QStringView indent();
enum StreamFlags { NoFlags = 0x0, SameLine = 0x1 };
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
index 8b4631395a..367c98e0dd 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
@@ -1193,22 +1193,91 @@ void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
if (m_filterPrimitives.isEmpty())
return;
- if (m_filterPrimitives.size() > 1)
- qCWarning(lcQuickVectorImage) << "Chained filters currently not supported.";
-
handleBaseNodeSetup(node);
- const QSvgFeFilterPrimitive *filterPrimitive = m_filterPrimitives.first();
-
FilterNodeInfo info;
fillCommonNodeInfo(node, info);
info.filterRect = node->rect();
- info.filterPrimitiveRect = filterPrimitive->rect();
if (node->filterUnits() == QtSvg::UnitTypes::objectBoundingBox)
info.csFilterRect = FilterNodeInfo::CoordinateSystem::Relative;
+
+ bool generatedAlpha = false;
+ for (const QSvgFeFilterPrimitive *filterPrimitive : std::as_const(m_filterPrimitives)) {
+ if (filterPrimitive->requiresSourceAlpha() && !generatedAlpha) {
+ FilterNodeInfo::FilterStep alphaStep;
+ alphaStep.filterType = FilterNodeInfo::Type::ColorMatrix;
+ alphaStep.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
+
+ // Isolate alpha
+ qreal values[] = { 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0, 0.0 };
+ QGenericMatrix<5, 5, qreal> matrix(values);
+ alphaStep.filterParameter = QVariant::fromValue(matrix);
+ alphaStep.outputName = info.id + QStringLiteral("_source_alpha");
+ generatedAlpha = true;
+
+ info.steps.append(alphaStep);
+ }
+
+ fillFilterPrimitiveInfo(node, filterPrimitive, info);
+ }
+
+ m_generator->generateFilterNode(info);
+ m_filterPrimitives.clear();
+}
+
+void QSvgVisitorImpl::fillFilterPrimitiveInfo(const QSvgFilterContainer *node,
+ const QSvgFeFilterPrimitive *filterPrimitive,
+ FilterNodeInfo &info)
+{
+ FilterNodeInfo::FilterStep step;
+ step.filterPrimitiveRect = filterPrimitive->rect();
+
+ step.outputName = info.id + QStringLiteral("_")
+ + (filterPrimitive->result().isEmpty()
+ ? QStringLiteral("output_") + QString::number(info.steps.size())
+ : filterPrimitive->result());
+
+ auto findInput = [&info, &filterPrimitive](const QString &input, QString *outName) {
+ const QString alphaSource = info.id + QStringLiteral("_source_alpha");
+ if (input == QStringLiteral("SourceGraphic")) {
+ return FilterNodeInfo::FilterInput::SourceColor;
+ } else if (input == QStringLiteral("SourceAlpha")) {
+ *outName = alphaSource;
+ return FilterNodeInfo::FilterInput::SourceAlpha;
+ } else if (info.steps.isEmpty()) {
+ return FilterNodeInfo::FilterInput::SourceColor;
+ }
+
+ if (!input.isEmpty()) {
+ *outName = info.id + QStringLiteral("_") + input;
+ } else {
+ bool insideMergeNode = filterPrimitive->type() == QSvgNode::FeMergenode;
+ for (int i = info.steps.size() - 1; i >= 0; --i) {
+ const auto &prevStep = info.steps.at(i);
+ if (insideMergeNode && prevStep.filterType == FilterNodeInfo::Type::Merge) {
+ insideMergeNode = false;
+ continue;
+ }
+
+ if (!prevStep.outputName.isEmpty() && prevStep.outputName != alphaSource) {
+ *outName = prevStep.outputName;
+ break;
+ }
+ }
+ }
+
+ return FilterNodeInfo::FilterInput::Name;
+ };
+
+ step.input1 = findInput(filterPrimitive->input(), &step.namedInput1);
+
if (node->primitiveUnits() == QtSvg::UnitTypes::objectBoundingBox)
- info.csFilterParameter = FilterNodeInfo::CoordinateSystem::Relative;
+ step.csFilterParameter = FilterNodeInfo::CoordinateSystem::Relative;
// We special-case the default filter primitive rect as is done in Qt Svg.
// The default is to match the filter's rect and this is represented by making
@@ -1216,15 +1285,70 @@ void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
// this is not generally handled in Qt Svg, we also just special case it here.
if (node->primitiveUnits() == QtSvg::UnitTypes::userSpaceOnUse
&& filterPrimitive->rect().unitW() == QtSvg::UnitTypes::unknown) {
- info.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
+ step.csFilterParameter = FilterNodeInfo::CoordinateSystem::MatchFilterRect;
}
switch (filterPrimitive->type()) {
+ case QSvgNode::FeBlend:
+ {
+ const QSvgFeBlend *blend = static_cast<const QSvgFeBlend *>(filterPrimitive);
+ switch (blend->mode()) {
+ case QSvgFeBlend::Mode::Normal:
+ step.filterType = FilterNodeInfo::Type::BlendNormal;
+ break;
+ case QSvgFeBlend::Mode::Multiply:
+ step.filterType = FilterNodeInfo::Type::BlendMultiply;
+ break;
+ case QSvgFeBlend::Mode::Screen:
+ step.filterType = FilterNodeInfo::Type::BlendScreen;
+ break;
+ case QSvgFeBlend::Mode::Darken:
+ step.filterType = FilterNodeInfo::Type::BlendDarken;
+ break;
+ case QSvgFeBlend::Mode::Lighten:
+ step.filterType = FilterNodeInfo::Type::BlendLighten;
+ break;
+ }
+
+ step.input2 = findInput(blend->input2(), &step.namedInput2);
+ break;
+ }
+ case QSvgNode::FeComposite:
+ {
+ const QSvgFeComposite *composite = static_cast<const QSvgFeComposite *>(filterPrimitive);
+ switch (composite->compositionOperator()) {
+ case QSvgFeComposite::Operator::Over:
+ step.filterType = FilterNodeInfo::Type::CompositeOver;
+ break;
+ case QSvgFeComposite::Operator::In:
+ step.filterType = FilterNodeInfo::Type::CompositeIn;
+ break;
+ case QSvgFeComposite::Operator::Out:
+ step.filterType = FilterNodeInfo::Type::CompositeOut;
+ break;
+ case QSvgFeComposite::Operator::Atop:
+ step.filterType = FilterNodeInfo::Type::CompositeAtop;
+ break;
+ case QSvgFeComposite::Operator::Xor:
+ step.filterType = FilterNodeInfo::Type::CompositeXor;
+ break;
+ case QSvgFeComposite::Operator::Lighter:
+ step.filterType = FilterNodeInfo::Type::CompositeLighter;
+ break;
+ case QSvgFeComposite::Operator::Arithmetic:
+ step.filterType = FilterNodeInfo::Type::CompositeArithmetic;
+ break;
+ };
+
+ step.input2 = findInput(composite->input2(), &step.namedInput2);
+ step.filterParameter = composite->k();
+ break;
+ }
case QSvgNode::FeOffset:
{
const QSvgFeOffset *offset = static_cast<const QSvgFeOffset *>(filterPrimitive);
- info.filterType = FilterNodeInfo::Type::Offset;
- info.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
+ step.filterType = FilterNodeInfo::Type::Offset;
+ step.filterParameter = QVariant::fromValue(QVector2D(offset->dx(), offset->dy()));
break;
}
@@ -1232,8 +1356,8 @@ void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
{
const QSvgFeColorMatrix *colorMatrix =
static_cast<const QSvgFeColorMatrix *>(filterPrimitive);
- info.filterType = FilterNodeInfo::Type::ColorMatrix;
- info.filterParameter = QVariant::fromValue(colorMatrix->matrix());
+ step.filterType = FilterNodeInfo::Type::ColorMatrix;
+ step.filterParameter = QVariant::fromValue(colorMatrix->matrix());
break;
}
@@ -1244,10 +1368,10 @@ void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
if (gaussianBlur->edgeMode() == QSvgFeGaussianBlur::EdgeMode::Wrap)
info.wrapMode = QSGTexture::Repeat;
- info.filterType = FilterNodeInfo::Type::GaussianBlur;
+ step.filterType = FilterNodeInfo::Type::GaussianBlur;
if (!qFuzzyCompare(gaussianBlur->stdDeviationX(), gaussianBlur->stdDeviationY()))
qCWarning(lcQuickVectorImage) << "Separate X and Y deviations not supported for gaussian blur";
- info.filterParameter = std::max(gaussianBlur->stdDeviationX(),
+ step.filterParameter = std::max(gaussianBlur->stdDeviationX(),
gaussianBlur->stdDeviationY());
break;
}
@@ -1257,18 +1381,17 @@ void QSvgVisitorImpl::visitFilterNodeEnd(const QSvgFilterContainer *node)
const QSvgFeFlood *flood =
static_cast<const QSvgFeFlood *>(filterPrimitive);
- info.filterType = FilterNodeInfo::Type::Flood;
- info.filterParameter = flood->color();
+ step.filterType = FilterNodeInfo::Type::Flood;
+ step.filterParameter = flood->color();
break;
}
default:
// Create a dummy filter node to make sure bindings still work for unsupported filters
- info.filterType = FilterNodeInfo::Type::None;
+ step.filterType = FilterNodeInfo::Type::None;
break;
}
- m_generator->generateFilterNode(info);
- m_filterPrimitives.clear();
+ info.steps.append(step);
}
bool QSvgVisitorImpl::visitFeFilterPrimitiveNodeStart(const QSvgFeFilterPrimitive *node)
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
index ff7b82d5e7..9e907ac04c 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
@@ -83,6 +83,9 @@ private:
void fillColorAnimationInfo(const QSvgNode *node, PathNodeInfo &info);
void fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info);
void fillMotionPathAnimationInfo(const QSvgNode *node, NodeInfo &info);
+ void fillFilterPrimitiveInfo(const QSvgFilterContainer *node,
+ const QSvgFeFilterPrimitive *filterPrimitive,
+ FilterNodeInfo &info);
void handleBaseNodeSetup(const QSvgNode *node);
void handleBaseNode(const QSvgNode *node);
void handleBaseNodeEnd(const QSvgNode *node);
diff --git a/src/quickvectorimage/helpers/shaders_ng/feblend.frag b/src/quickvectorimage/helpers/shaders_ng/feblend.frag
new file mode 100644
index 0000000000..8df2bbf8f2
--- /dev/null
+++ b/src/quickvectorimage/helpers/shaders_ng/feblend.frag
@@ -0,0 +1,58 @@
+// 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
+#version 440
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+layout(std140, binding = 0) uniform buf {
+ // qt_Matrix and qt_Opacity must always be both present
+ // if the built-in vertex shader is used.
+ mat4 qt_Matrix;
+ float qt_Opacity;
+};
+
+layout(binding = 1) uniform sampler2D source;
+layout(binding = 2) uniform sampler2D source2;
+
+// https://www.w3.org/TR/compositing-1/#blending
+vec3 B(vec3 Cb, vec3 Cs)
+{
+#if defined(BLEND_NORMAL)
+ return Cs;
+#elif defined(BLEND_MULTIPLY)
+ return Cb * Cs;
+#elif defined(BLEND_SCREEN)
+ return Cb + Cs - (Cb * Cs);
+#elif defined(BLEND_DARKEN)
+ return min(Cb, Cs);
+#elif defined(BLEND_LIGHTEN)
+ return max(Cb, Cs);
+#else
+ return vec3(0.0);
+#endif
+}
+
+void main()
+{
+ vec4 s1 = texture(source, qt_TexCoord0.st);
+ vec4 s2 = texture(source2, qt_TexCoord0.st);
+
+ float as = s1.a;
+ float ab = s2.a;
+
+ vec3 Cs = s1.rgb;
+ vec3 Cb = s2.rgb;
+
+ // "Un-premultiply" source
+ float epsilon = 0.0000001;
+ Cs /= max(epsilon, as);
+ Cb /= max(epsilon, ab);
+
+ vec3 Cr = (1.0 - ab) * Cs + ab * clamp(B(Cb, Cs), 0.0, 1.0);
+
+ // co is premultiplied
+ vec3 co = as * Cr + (1.0 - as) * ab * Cb;
+ float ao = as + (1.0 - as) * ab;
+
+ fragColor = vec4(co, ao);
+}
diff --git a/src/quickvectorimage/helpers/shaders_ng/fecomposite.frag b/src/quickvectorimage/helpers/shaders_ng/fecomposite.frag
new file mode 100644
index 0000000000..d246c544bc
--- /dev/null
+++ b/src/quickvectorimage/helpers/shaders_ng/fecomposite.frag
@@ -0,0 +1,63 @@
+// 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
+#version 440
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+layout(std140, binding = 0) uniform buf {
+ // qt_Matrix and qt_Opacity must always be both present
+ // if the built-in vertex shader is used.
+ mat4 qt_Matrix;
+ float qt_Opacity;
+
+ vec4 k;
+};
+
+layout(binding = 1) uniform sampler2D source;
+layout(binding = 2) uniform sampler2D source2;
+
+void main()
+{
+ vec4 s1 = texture(source, qt_TexCoord0.st);
+ vec4 s2 = texture(source2, qt_TexCoord0.st);
+
+#if defined(COMPOSITE_ARITHMETIC)
+ fragColor = k.x * s1 * s2 + k.y * s1 + k.z * s2 + k.w;
+#else
+ // General Porter-Duff
+ // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators
+
+ float as = s1.a;
+ float ab = s2.a;
+
+ vec3 Cs = s1.rgb;
+ vec3 Cb = s2.rgb;
+
+# if defined(COMPOSITE_OVER)
+ float Fa = 1.0;
+ float Fb = 1.0 - as;
+# elif defined(COMPOSITE_IN)
+ float Fa = ab;
+ float Fb = 0.0;
+# elif defined(COMPOSITE_OUT)
+ float Fa = 1.0 - ab;
+ float Fb = 0.0;
+# elif defined(COMPOSITE_ATOP)
+ float Fa = ab;
+ float Fb = 1.0 - as;
+# elif defined(COMPOSITE_XOR)
+ float Fa = 1.0 - ab;
+ float Fb = 1.0 - as;
+# else // COMPOSITE_LIGHTER
+ float Fa = 1.0;
+ float Fb = 1.0;
+# endif
+
+ vec3 co = as * Fa * Cs + ab * Fb * Cb;
+ float ao = as * Fa + ab * Fb;
+
+ fragColor = clamp(vec4(co, ao), 0.0, 1.0);
+
+#endif // COMPOSITE_ARITHMETIC
+
+}