diff options
| author | Eskil Abrahamsen Blomfeldt <[email protected]> | 2025-12-03 11:28:37 +0100 |
|---|---|---|
| committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2025-12-04 21:01:18 +0100 |
| commit | a6d9da57f18779ecbd67df8e3df015cccb94dcbe (patch) | |
| tree | 75b917116f4abe79b0139faabe2685aee8d232d2 /src | |
| parent | fdba000bcd0b34740b919d9d2d62eaf056aa434c (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.txt | 192 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquicknodeinfo_p.h | 40 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquickqmlgenerator.cpp | 201 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qquickqmlgenerator_p.h | 1 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qsvgvisitorimpl.cpp | 161 | ||||
| -rw-r--r-- | src/quickvectorimage/generator/qsvgvisitorimpl_p.h | 3 | ||||
| -rw-r--r-- | src/quickvectorimage/helpers/shaders_ng/feblend.frag | 58 | ||||
| -rw-r--r-- | src/quickvectorimage/helpers/shaders_ng/fecomposite.frag | 63 |
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 + +} |
