Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickitemgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include <QtQuickVectorImageHelpers/private/qquickitemspy_p.h>
7#include <QtQuickVectorImageHelpers/private/qquicktransformgroup_p.h>
8#include <QtQuickVectorImageHelpers/private/qquickpathinterpolated_p.h>
11
12#include <private/qquickitem_p.h>
13#include <private/qquicktranslate_p.h>
14#include <private/qquickshape_p.h>
15#include <private/qquickpath_p.h>
16#include <private/qquickimage_p.h>
17#include <private/qquicktext_p.h>
18#include <private/qquickrectangle_p.h>
19#include <private/qquickshadereffect_p.h>
20#include <private/qquickshadereffectsource_p.h>
21#include <private/qquickmultieffect_p.h>
22#include <private/qquickanimation_p.h>
23#include <private/qquickanimation_p_p.h>
24#include <private/qquickpathinterpolator_p.h>
25#include <private/qquickimage_p_p.h>
26
27#include "utils_p.h"
28
29#include <QtCore/qdir.h>
30#include <QtCore/qfileinfo.h>
31#include <QtCore/qloggingcategory.h>
32#include <QtGui/qfontmetrics.h>
33#include <QtGui/private/qbezier_p.h>
34#include <QtQml/qqmlcontext.h>
35#include <QtQml/qqmlcomponent.h>
36#include <QtQml/qqmlengine.h>
37#include <QtQml/qqmllist.h>
38#include <QtQml/qqmlparserstatus.h>
39#include <QtCore/qpointer.h>
40
42
43using namespace Qt::StringLiterals;
44
46{
47 Q_OBJECT
48 Q_PROPERTY(QColor baseColor READ baseColor WRITE setBaseColor)
50public:
57
58 QColor baseColor() const { return m_baseColor; }
60 {
62 return;
64 apply();
65 }
66
67 qreal opacity() const { return m_opacity; }
69 {
70 if (m_opacity == opacity)
71 return;
73 apply();
74 }
75
76private:
77 void apply()
78 {
82 }
83
84 std::function<void(const QColor &)> m_setter;
85 QColor m_baseColor;
86 qreal m_opacity;
87};
88
108
110{
111public:
112 explicit QQuickCallbackAnimationJob(std::function<void()> function)
114 {
115 }
116
117protected:
118 void updateState(State newState, State oldState) override
119 {
120 Q_UNUSED(oldState);
121 if (newState == Running && m_function)
122 m_function();
123 }
124
125private:
126 std::function<void()> m_function;
127};
128
130{
131public:
132 explicit QQuickFunctionAction(std::function<void()> function, QObject *parent = nullptr)
134 {
135 }
136
137protected:
138 QAbstractAnimationJob *transition(QQuickStateActions &, QQmlProperties &, TransitionDirection,
139 QObject * = nullptr) override
140 {
141 return initInstance(new QQuickCallbackAnimationJob(m_function));
142 }
143
144private:
145 std::function<void()> m_function;
146};
147
148QQuickItemGenerator::QQuickItemGenerator(const QQuickVectorImageSource &source,
149 QQuickVectorImageGenerator::GeneratorFlags flags,
150 QQmlContext *context)
151 : QQuickGenerator(source, flags), m_context(context)
152{
153}
154
155QQuickItemGenerator::~QQuickItemGenerator()
156{
157 delete m_rootItem;
158}
159
160void QQuickItemGenerator::setAnimationProvider(std::unique_ptr<QQuickGeneratorAnimationProvider> provider)
161{
162 m_animationProvider = std::move(provider);
163 if (m_animationProvider) {
164 m_animationProvider->setDeferFunction([this](std::function<void()> call) {
165 deferToGuiThread(std::move(call));
166 });
167 }
168}
169
170QQuickItem *QQuickItemGenerator::takeRootItem()
171{
172 QQuickItem *item = m_rootItem;
173 m_rootItem = nullptr;
174 return item;
175}
176
177void QQuickItemGenerator::setDeferGuiThreadCalls(bool enabled)
178{
179 m_deferGuiThreadCalls = enabled;
180}
181
182QList<std::function<void()>> QQuickItemGenerator::takeDeferredCalls()
183{
184 return std::exchange(m_deferredCalls, {});
185}
186
187void QQuickItemGenerator::setCancellationFlag(const std::atomic<bool> *cancelled)
188{
189 m_cancelled = cancelled;
190}
191
192bool QQuickItemGenerator::isStopped()
193{
194 if (Q_UNLIKELY(m_cancelled != nullptr && !errorState()
195 && m_cancelled->load(std::memory_order_relaxed))) {
196 m_errorState = QQuickVectorImageGenerator::Cancelled;
197 }
198 return errorState() != QQuickVectorImageGenerator::NoError;
199}
200
201// Calls passed here must only capture objects in the generated item tree (and the QML context),
202// never the generator itself, since they may be executed after the generator has been destroyed.
203void QQuickItemGenerator::deferToGuiThread(std::function<void()> call)
204{
205 if (m_deferGuiThreadCalls)
206 m_deferredCalls.append(std::move(call));
207 else
208 call();
209}
210
211// Sets the QML context of the object and runs setup between classBegin() and componentComplete().
212// Everything which depends on the QML engine (such as loading images or shaders) must be done
213// in setup.
214void QQuickItemGenerator::initializeQmlObject(QObject *object, std::function<void()> setup)
215{
216 deferToGuiThread([object, context = m_context, setup = std::move(setup)]() {
217 if (context)
218 QQmlEngine::setContextForObject(object, context);
219 auto *parserStatus = qobject_cast<QQmlParserStatus *>(object);
220 if (parserStatus)
221 parserStatus->classBegin();
222 if (setup)
223 setup();
224 if (parserStatus)
225 parserStatus->componentComplete();
226 });
227}
228
229void QQuickItemGenerator::startMasterAnimation(QQuickAbstractAnimation *animation)
230{
231 auto *root = qobject_cast<QQuickAnimationRootItem *>(m_rootItem);
232 deferToGuiThread([animation, root]() {
233 if (root)
234 root->addMasterAnimation(animation);
235 animation->setRunning(true);
236 });
237}
238
239QList<std::function<void()>> *QQuickItemGenerator::activeRecord() const
240{
241 if (m_currentDefsRecord)
242 return m_currentDefsRecord;
243 return m_currentMarkerRecord;
244}
245
246QQuickShape *QQuickItemGenerator::createShapeContainer()
247{
248 auto *shape = new QQuickShape;
249 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
250 shape->setPreferredRendererType(QQuickShape::CurveRenderer);
251 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
252 shape->setAsynchronous(true);
253 return shape;
254}
255
256void QQuickItemGenerator::pushItem(QQuickItem *item)
257{
258 if (!m_itemStack.isEmpty()) {
259 item->setParent(m_itemStack.top());
260 item->setParentItem(m_itemStack.top());
261 }
262 m_itemStack.push(item);
263}
264
265QQuickItem *QQuickItemGenerator::popItem()
266{
267 return m_itemStack.isEmpty() ? nullptr : m_itemStack.pop();
268}
269
270QQuickItem *QQuickItemGenerator::currentItem() const
271{
272 return m_itemStack.isEmpty() ? nullptr : m_itemStack.top();
273}
274
275QRectF QQuickItemGenerator::resolveBounds(const NodeInfo &info) const
276{
277 if (!info.bounds.isNull())
278 return info.bounds;
279
280 if (info.boundsReferenceId.isEmpty())
281 return {};
282
283 return m_bounds.value(info.boundsReferenceId);
284}
285
286void QQuickItemGenerator::registerBounds(const QString &id, const QRectF &bounds)
287{
288 if (id.isEmpty() || bounds.isNull())
289 return;
290
291 m_bounds.insert(id, bounds);
292}
293
294QString QQuickItemGenerator::generateNodeBase(const NodeInfo &info, const QString &idSuffix)
295{
296 static qint64 maxNodes =
297 qEnvironmentVariableIntegerValue("QT_QUICKVECTORIMAGE_MAX_NODES").value_or(10000);
298 if (Q_UNLIKELY(isStopped()))
299 return {};
300 if (Q_UNLIKELY(!checkSanityLimit(++m_nodeCounter, maxNodes, "nodes"_L1)))
301 return {};
302
303 QQuickItem *item = currentItem();
304 if (!item)
305 return info.id;
306
307 if (!info.nodeId.isEmpty())
308 item->setObjectName(info.nodeId);
309
310 item->setTransformOrigin(QQuickItem::TopLeft);
311
312 const QRectF bounds = resolveBounds(info);
313 if (!bounds.isNull()) {
314 registerBounds(info.id + idSuffix, bounds);
315 item->setWidth(bounds.width());
316 item->setHeight(bounds.height());
317 }
318
319 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
320 if (!info.isDefaultOpacity && !info.opacity.isAnimated())
321 item->setOpacity(info.opacity.defaultValue().toReal());
322 }
323
324 if (!info.isDefaultTransform && !info.transform.isAnimated()) {
325 QTransform transform = info.transform.defaultValue().value<QTransform>();
326 auto *matrix = new QQuickMatrix4x4(item);
327 QMatrix4x4 m(transform);
328 m.optimize();
329 matrix->setMatrix(m);
330 auto transformProp = item->transform();
331 transformProp.append(&transformProp, matrix);
332 }
333
334 if (info.maskId.isEmpty())
335 generateItemAnimations(item, info);
336
337 return info.id;
338}
339
340bool QQuickItemGenerator::generateRootNode(const StructureNodeInfo &info)
341{
342 if (Q_UNLIKELY(isStopped()))
343 return false;
344
345 if (info.stage == StructureNodeStage::Start) {
346 auto *root = new QQuickAnimationRootItem;
347 if (info.size.width() > 0)
348 root->setImplicitWidth(info.size.width());
349 if (info.size.height() > 0)
350 root->setImplicitHeight(info.size.height());
351 m_rootItem = root;
352 m_containerSize = info.viewBox.isEmpty() ? QSizeF(info.size) : info.viewBox.size();
353
354 if (!isNodeVisible(info))
355 return false;
356
357 if (!info.viewBox.isEmpty() && info.size.width() > 0 && info.size.height() > 0) {
358 auto transformProp = root->transform();
359 if (!qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y())) {
360 auto *translate = new QQuickTranslate(root);
361 translate->setX(-info.viewBox.x());
362 translate->setY(-info.viewBox.y());
363 transformProp.append(&transformProp, translate);
364 }
365 auto *scale = new QQuickScale(root);
366 scale->setXScale(info.size.width() / info.viewBox.width());
367 scale->setYScale(info.size.height() / info.viewBox.height());
368 transformProp.append(&transformProp, scale);
369 }
370
371 m_topLevelScaleSpy = new QQuickItemSpy(root);
372 m_topLevelScaleSpy->setWidth(1);
373 m_topLevelScaleSpy->setHeight(1);
374 m_topLevelScaleSpy->setVisible(false);
375
376 pushItem(root);
377
378 if (m_flags.testFlag(QQuickVectorImageGenerator::TimelineAnimation) && info.timelineInfo) {
379 root->setStartFrame(info.timelineInfo->startFrame);
380 root->setEndFrame(info.timelineInfo->endFrame);
381 root->setFrameRate(info.timelineInfo->frameRate);
382
383 QVariantMap markerMap;
384 for (const LottieMarkerInfo &marker : info.timelineInfo->markers)
385 markerMap.insert(marker.name, QVariantList{ marker.frame, marker.duration });
386 if (!markerMap.isEmpty())
387 root->setMarkersData(markerMap);
388 }
389
390 bool scopePushed = false;
391 if (m_animationProvider && info.timelineInfo) {
392 if (auto *master = m_animationProvider->enterTimelineScope(root, info))
393 root->addMasterAnimation(master);
394 scopePushed = true;
395 }
396 m_scopePushed.push(scopePushed);
397
398 generateNodeBase(info);
399 } else {
400 for (const PendingLinkedTransform &pending : m_pendingLinkedTransforms) {
401 Q_ASSERT(pending.item);
402 QQuickTransformSource *source = resolveTransformReference(pending.transformReferenceId);
403 if (!source) {
404 qCWarning(lcQuickVectorImage)
405 << "generateRootNode: transformReferenceId does not refer to a "
406 "transform source item:"
407 << pending.transformReferenceId;
408 continue;
409 }
410 new TransformLinker(source, pending.linkedMatrix, pending.item);
411 }
412 m_pendingLinkedTransforms.clear();
413
414 if (m_scopePushed.pop() && m_animationProvider)
415 m_animationProvider->exitTimelineScope();
416 popItem();
417 }
418
419 return true;
420}
421
422bool QQuickItemGenerator::generateStructureNode(const StructureNodeInfo &info)
423{
424 if (Q_UNLIKELY(isStopped()))
425 return false;
426
427 if (auto *rec = activeRecord()) {
428 rec->append([this, info]() { generateStructureNode(info); });
429 return true;
430 }
431
432 if (!isNodeVisible(info))
433 return false;
434
435 const bool isStart = (info.stage == StructureNodeStage::Start);
436 if (isStart)
437 m_opacityGroupPushed.push(!info.opacityGroupBounds.isNull());
438
439 const bool inOpacityGroup = !m_opacityGroupPushed.isEmpty() && m_opacityGroupPushed.top();
440 std::optional<StructureNodeInfo> groupedInfo;
441 if (inOpacityGroup) {
442 groupedInfo = info;
443 groupedInfo->isDefaultOpacity = true;
444 groupedInfo->opacity = QQuickAnimatedProperty(QVariant::fromValue(1.0));
445 if (isStart)
446 beginOpacityGroup(info);
447 }
448
449 const bool res = generateStructureNodeItem(groupedInfo ? *groupedInfo : info);
450
451 if (!isStart && !m_opacityGroupPushed.isEmpty()) {
452 m_opacityGroupPushed.pop();
453 if (inOpacityGroup)
454 endOpacityGroup();
455 }
456 return res;
457}
458
459void QQuickItemGenerator::beginOpacityGroup(const StructureNodeInfo &info)
460{
461 auto *wrapper = new QQuickItem;
462 pushItem(wrapper);
463 wrapper->setOpacity(info.opacity.defaultValue().toReal());
464 bindAnimatedProperty(wrapper, QStringLiteral("opacity"), info.opacity);
465 m_opacityGroups.push({ wrapper, info.opacityGroupBounds, 0 });
466}
467
468void QQuickItemGenerator::endOpacityGroup()
469{
470 const OpacityGroup group = m_opacityGroups.pop();
471 popItem();
472
473 if (group.graphicItemCount <= 1)
474 return;
475
476 deferToGuiThread([wrapper = group.wrapper, bounds = group.bounds]() {
477 wrapper->setPosition(bounds.topLeft());
478 wrapper->setSize(bounds.size());
479
480 auto *spy = new QQuickItemSpy(wrapper);
481 spy->setSize(bounds.size());
482
483 QQuickItemLayer *layer = QQuickItemPrivate::get(wrapper)->layer();
484 layer->setSourceRect(bounds);
485 auto updateTextureSize = [layer, spy]() {
486 const QSizeF size = spy->requiredTextureSize();
487 layer->setSize(QSize(qRound(size.width()), qRound(size.height())));
488 };
489 QObject::connect(spy, &QQuickItemSpy::requiredTextureSizeChanged, wrapper,
490 updateTextureSize);
491 updateTextureSize();
492 layer->setEnabled(true);
493 });
494}
495
496bool QQuickItemGenerator::generateStructureNodeItem(const StructureNodeInfo &info)
497{
498 if (info.stage == StructureNodeStage::Start) {
499 if (!info.clipBox.isEmpty()) {
500 auto *clipItem = new QQuickItem;
501 clipItem->setWidth(info.clipBox.width());
502 clipItem->setHeight(info.clipBox.height());
503 clipItem->setClip(true);
504 pushItem(clipItem);
505 }
506
507 QQuickItem *item = nullptr;
508 if (!info.forceSeparatePaths && info.isPathContainer) {
509 item = createShapeContainer();
510 } else {
511 if (m_animationProvider && !info.customItemType.isEmpty())
512 item = m_animationProvider->createCustomItem(info.customItemType);
513 if (!item)
514 item = new QQuickItem;
515
516 if (!info.viewBox.isEmpty()) {
517 auto transformProp = item->transform();
518 if (!qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y())) {
519 auto *translate = new QQuickTranslate(item);
520 translate->setX(-info.viewBox.x());
521 translate->setY(-info.viewBox.y());
522 transformProp.append(&transformProp, translate);
523 }
524 auto *scale = new QQuickScale(item);
525 scale->setXScale(info.size.width() / info.viewBox.width());
526 scale->setYScale(info.size.height() / info.viewBox.height());
527 transformProp.append(&transformProp, scale);
528 }
529 }
530
531 bool scopePushed = false;
532 if (m_animationProvider && info.timelineInfo) {
533 if (auto *master = m_animationProvider->enterTimelineScope(item, info)) {
534 if (auto *root = qobject_cast<QQuickAnimationRootItem *>(m_rootItem))
535 root->addMasterAnimation(master);
536 }
537 scopePushed = true;
538 }
539 m_scopePushed.push(scopePushed);
540
541 if (!info.id.isEmpty()) {
542 if (auto *source = qobject_cast<QQuickTransformSource *>(item))
543 m_transformSourceItems.insert(info.id, source);
544 }
545
546 pushItem(item);
547 generateNodeBase(info);
548 } else {
549 if (m_scopePushed.pop() && m_animationProvider)
550 m_animationProvider->exitTimelineScope();
551
552 QQuickItem *item = popItem();
553 QQuickItem *effectItem = item;
554 QPointF sourceOrigin;
555 if (!info.filterId.isEmpty())
556 effectItem = generateFilter(item, info, &sourceOrigin);
557 if (!info.maskId.isEmpty())
558 generateMask(effectItem ? effectItem : item, info, sourceOrigin);
559
560 if (!info.clipBox.isEmpty())
561 popItem();
562 }
563
564 return true;
565}
566
567void QQuickItemGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
568{
569 if (Q_UNLIKELY(isStopped()))
570 return;
571
572 if (auto *rec = activeRecord()) {
573 rec->append(
574 [this, info, overrideBoundingRect]() { generatePath(info, overrideBoundingRect); });
575 return;
576 }
577
578 if (!isNodeVisible(info))
579 return;
580
581 if (qobject_cast<QQuickShape *>(currentItem()) && info.markerStartId.isEmpty()
582 && info.markerMidId.isEmpty() && info.markerEndId.isEmpty() && info.filterId.isEmpty()) {
583 optimizePaths(info, overrideBoundingRect);
584 } else {
585 auto *shape = createShapeContainer();
586 pushItem(shape);
587 generateNodeBase(info);
588 optimizePaths(info, overrideBoundingRect);
589 QQuickItem *item = popItem();
590 if (!info.markerStartId.isEmpty() || !info.markerMidId.isEmpty()
591 || !info.markerEndId.isEmpty()) {
592 generateMarkers(info);
593 }
594 QQuickItem *effectItem = item;
595 QPointF sourceOrigin;
596 if (!info.filterId.isEmpty())
597 effectItem = generateFilter(item, info, &sourceOrigin);
598 if (!info.maskId.isEmpty())
599 generateMask(effectItem ? effectItem : item, info, sourceOrigin);
600 }
601}
602
603static QVariant extractPointX(const QVariant &value)
604{
605 return QVariant(value.toList().value(0).toPointF().x());
606}
607
608static QVariant extractPointY(const QVariant &value)
609{
610 return QVariant(value.toList().value(0).toPointF().y());
611}
612
614extractAnimation(const QQuickAnimatedProperty::PropertyAnimation &anim,
615 const std::function<QVariant(const QVariant &)> &extractor)
616{
617 QQuickAnimatedProperty::PropertyAnimation extracted = anim;
618 for (auto it = extracted.frames.begin(); it != extracted.frames.end(); ++it)
619 *it = extractor(*it);
620 return extracted;
621}
622
624makeAnimatedProperty(const QQuickAnimatedProperty &owner,
625 const QQuickAnimatedProperty::PropertyAnimation &anim,
626 const QVariant &defaultValue = QVariant{})
627{
628 QQuickAnimatedProperty property(defaultValue);
629 property.setTimelineReferenceId(owner.timelineReferenceId());
630 if (!anim.frames.isEmpty())
631 property.addAnimation(anim);
632 return property;
633}
634
635static QQuickShapeGradient *createShapeGradient(const QGradient &grad, const QRectF &coordSys,
636 QObject *parent)
637{
638 const qreal sx = coordSys.width();
639 const qreal sy = coordSys.height();
640 const qreal tx = coordSys.x();
641 const qreal ty = coordSys.y();
642
643 QQuickShapeGradient *result = nullptr;
644
645 if (grad.type() == QGradient::LinearGradient) {
646 const auto *linGrad = static_cast<const QLinearGradient *>(&grad);
647 auto *g = new QQuickShapeLinearGradient(parent);
648 g->setX1(linGrad->start().x() * sx + tx);
649 g->setY1(linGrad->start().y() * sy + ty);
650 g->setX2(linGrad->finalStop().x() * sx + tx);
651 g->setY2(linGrad->finalStop().y() * sy + ty);
652 result = g;
653 } else if (grad.type() == QGradient::RadialGradient) {
654 const auto *radGrad = static_cast<const QRadialGradient *>(&grad);
655 auto *g = new QQuickShapeRadialGradient(parent);
656 g->setCenterX(radGrad->center().x() * sx + tx);
657 g->setCenterY(radGrad->center().y() * sy + ty);
658 g->setCenterRadius(radGrad->radius() * sx);
659 g->setFocalX(radGrad->focalPoint().x() * sx + tx);
660 g->setFocalY(radGrad->focalPoint().y() * sy + ty);
661 result = g;
662 } else {
663 return nullptr;
664 }
665
666 for (const auto &stop : grad.stops()) {
667 auto *s = new QQuickGradientStop(result);
668 s->setPosition(stop.first);
669 s->setColor(stop.second);
670 auto stopsProp = result->stops();
671 stopsProp.append(&stopsProp, s);
672 }
673 result->setSpread(QQuickShapeGradient::SpreadMode(grad.spread()));
674 return result;
675}
676
677void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *path,
678 const QQuadPath *quadPath,
679 QQuickVectorImageGenerator::PathSelector pathSelector,
680 const QRectF &boundingRect)
681{
682 Q_ASSERT(path || quadPath);
683
684 if (Q_UNLIKELY(isStopped()))
685 return;
686
687 auto *shape = qobject_cast<QQuickShape *>(currentItem());
688 if (!shape)
689 return;
690
691 const bool invalidGradientBounds = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
692 && (qFuzzyIsNull(boundingRect.width()) || qFuzzyIsNull(boundingRect.height()));
693 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
694 const bool noPen = (strokeColor == QColorConstants::Transparent || !strokeColor.isValid())
695 && !info.strokeStyle.color.isAnimated() && !info.strokeStyle.opacity.isAnimated()
696 && (info.strokeGrad.type() == QGradient::NoGradient || invalidGradientBounds);
697 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
698 return;
699
700 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
701 const bool noFill = info.grad.type() == QGradient::NoGradient && info.patternId.isEmpty()
702 && fillColor == QColorConstants::Transparent && !info.fillColor.isAnimated()
703 && !info.fillOpacity.isAnimated();
704 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
705 return;
706
707 if (noPen && noFill)
708 return;
709
710 if (!m_opacityGroups.isEmpty())
711 m_opacityGroups.top().graphicItemCount++;
712 auto *shapePath = new QQuickShapePath;
713 shapePath->setParent(shape);
714
715 if (!info.nodeId.isEmpty()) {
716 switch (pathSelector) {
717 case QQuickVectorImageGenerator::FillPath:
718 shapePath->setObjectName(u"svg_fill_path:"_s + info.nodeId);
719 break;
720 case QQuickVectorImageGenerator::StrokePath:
721 shapePath->setObjectName(u"svg_stroke_path:"_s + info.nodeId);
722 break;
723 case QQuickVectorImageGenerator::FillAndStroke:
724 shapePath->setObjectName(u"svg_path:"_s + info.nodeId);
725 break;
726 }
727 }
728
729 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
730 shapePath->setStrokeColor(QColorConstants::Transparent);
731 } else {
732 if (info.strokeGrad.type() != QGradient::NoGradient && !invalidGradientBounds) {
733 QRectF coordinateSys = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
734 ? boundingRect
735 : QRectF(0.0, 0.0, 1.0, 1.0);
736 shapePath->setStrokeGradient(
737 createShapeGradient(info.strokeGrad, coordinateSys, shapePath));
738 } else {
739 shapePath->setStrokeColor(strokeColor);
740 }
741 shapePath->setStrokeWidth(info.strokeStyle.width.defaultValue().toReal());
742 shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
743 shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
744 shapePath->setMiterLimit(info.strokeStyle.miterLimit);
745 if (info.strokeStyle.cosmetic)
746 shapePath->setCosmeticStroke(true);
747 if (!info.strokeStyle.dashArray.isEmpty()) {
748 shapePath->setStrokeStyle(QQuickShapePath::DashLine);
749 shapePath->setDashPattern(info.strokeStyle.dashArray);
750 shapePath->setDashOffset(info.strokeStyle.dashOffset.defaultValue().toReal());
751 }
752 }
753
754 QTransform fillTransform = info.fillTransform;
755 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
756 shapePath->setFillColor(QColorConstants::Transparent);
757 } else if (!info.patternId.isEmpty()) {
758 generatePattern(shapePath, info, boundingRect, fillTransform);
759 } else if (info.grad.type() != QGradient::NoGradient) {
760 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
761 QTransform objectToUserSpace;
762 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
763 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
764 fillTransform *= objectToUserSpace;
765 }
766 shapePath->setFillGradient(
767 createShapeGradient(info.grad, QRectF(0.0, 0.0, 1.0, 1.0), shapePath));
768 } else {
769 shapePath->setFillColor(fillColor);
770 }
771 if (!fillTransform.isIdentity())
772 shapePath->setFillTransform(QMatrix4x4(fillTransform));
773
774 shapePath->setFillRule(
775 QQuickShapePath::FillRule(path ? path->fillRule() : quadPath->fillRule()));
776
777 if (quadPath)
778 shapePath->setPathHints(QQuickShapePath::PathHints(int(quadPath->pathHints())));
779
780 if (info.trim.enabled) {
781 shapePath->trim()->setStart(info.trim.start.defaultValue().toReal());
782 shapePath->trim()->setEnd(info.trim.end.defaultValue().toReal());
783 shapePath->trim()->setOffset(info.trim.offset.defaultValue().toReal());
784 }
785
786 QQuickPathInterpolated *pathInterpolatedObj = nullptr;
787 QQuickAnimatedProperty::PropertyAnimation pathIndexAnim;
788
789 const bool pathAnimated = info.path.isAnimated() && info.path.animationCount() > 0
790 && !(info.path.animation(0).startOffset == 0 && info.path.animation(0).isConstant());
791
792 if (pathAnimated) {
793 const auto &pathAnim = info.path.animation(0);
794 QStringList svgPaths;
795 QMap<int, QVariant> indexFrames;
796 QString lastSvg;
797 int pathIdx = -1;
798 for (auto it = pathAnim.frames.constBegin(); it != pathAnim.frames.constEnd(); ++it) {
799 const QString svg =
800 QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
801 if (svg != lastSvg) {
802 svgPaths.append(svg);
803 ++pathIdx;
804 lastSvg = svg;
805 }
806 indexFrames.insert(it.key(), QVariant::fromValue(qreal(pathIdx)));
807 }
808
809 if (svgPaths.size() > 1) {
810 auto *interpolated = new QQuickPathInterpolated(shapePath);
811 interpolated->setSvgPaths(svgPaths);
812 auto pathElems = shapePath->pathElements();
813 pathElems.append(&pathElems, interpolated);
814 pathIndexAnim = pathAnim;
815 pathIndexAnim.frames = indexFrames;
816 pathInterpolatedObj = interpolated;
817 }
818 }
819
820 if (!pathInterpolatedObj) {
821 const QString svgString = path ? QQuickVectorImageGenerator::Utils::toSvgString(*path)
822 : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
823 auto *pathSvg = new QQuickPathSvg(shapePath);
824 pathSvg->setPath(svgString);
825 auto pathElems = shapePath->pathElements();
826 pathElems.append(&pathElems, pathSvg);
827 }
828
829 auto shapeData = shape->data();
830 shapeData.append(&shapeData, shapePath);
831
832 const bool hasPathAnim = pathInterpolatedObj != nullptr;
833 const bool hasFillColorAnim = info.fillColor.isAnimated();
834 const bool hasFillOpacityAnim = info.fillOpacity.isAnimated();
835 const bool hasStrokeColorAnim = info.strokeStyle.color.isAnimated();
836 const bool hasStrokeOpacityAnim = info.strokeStyle.opacity.isAnimated();
837 const bool hasStrokeWidthAnim = info.strokeStyle.width.isAnimated();
838 const bool hasDashOffsetAnim = info.strokeStyle.dashOffset.isAnimated();
839 const bool hasTrimStartAnim = info.trim.start.isAnimated();
840 const bool hasTrimEndAnim = info.trim.end.isAnimated();
841 const bool hasTrimOffsetAnim = info.trim.offset.isAnimated();
842
843 if (!hasPathAnim && !hasFillColorAnim && !hasFillOpacityAnim && !hasStrokeColorAnim
844 && !hasStrokeOpacityAnim && !hasStrokeWidthAnim && !hasDashOffsetAnim && !hasTrimStartAnim
845 && !hasTrimEndAnim && !hasTrimOffsetAnim) {
846 return;
847 }
848
849 if (hasPathAnim) {
850 bindAnimatedProperty(pathInterpolatedObj, QStringLiteral("factor"),
851 makeAnimatedProperty(info.path, pathIndexAnim));
852 }
853
854 if (hasTrimStartAnim || hasTrimEndAnim || hasTrimOffsetAnim) {
855 if (hasTrimStartAnim) {
856 bindAnimatedProperty(shapePath->trim(), QStringLiteral("start"), info.trim.start);
857 }
858 if (hasTrimEndAnim)
859 bindAnimatedProperty(shapePath->trim(), QStringLiteral("end"), info.trim.end);
860 if (hasTrimOffsetAnim) {
861 bindAnimatedProperty(shapePath->trim(), QStringLiteral("offset"), info.trim.offset);
862 }
863 }
864
865 if (hasFillColorAnim || hasFillOpacityAnim) {
866 bindColorWithOpacity(shapePath, QStringLiteral("fillColor"), info.fillColor,
867 info.fillOpacity,
868 [shapePath](const QColor &color) { shapePath->setFillColor(color); });
869 }
870
871 if (hasStrokeColorAnim || hasStrokeOpacityAnim) {
872 bindColorWithOpacity(shapePath, QStringLiteral("strokeColor"), info.strokeStyle.color,
873 info.strokeStyle.opacity, [shapePath](const QColor &color) {
874 shapePath->setStrokeColor(color);
875 });
876 }
877
878 if (hasStrokeWidthAnim) {
879 bindAnimatedProperty(shapePath, QStringLiteral("strokeWidth"), info.strokeStyle.width);
880 }
881
882 if (hasDashOffsetAnim) {
883 bindAnimatedProperty(shapePath, QStringLiteral("dashOffset"),
884 info.strokeStyle.dashOffset);
885 }
886}
887
888void QQuickItemGenerator::generateImageNode(const ImageNodeInfo &info)
889{
890 if (Q_UNLIKELY(isStopped()))
891 return;
892
893 if (auto *rec = activeRecord()) {
894 rec->append([this, info]() { generateImageNode(info); });
895 return;
896 }
897
898 if (!isNodeVisible(info))
899 return;
900
901 if (!m_opacityGroups.isEmpty())
902 m_opacityGroups.top().graphicItemCount++;
903 auto *image = new QQuickImage;
904 pushItem(image);
905 generateNodeBase(info);
906 image->setX(info.rect.x());
907 image->setY(info.rect.y());
908 image->setWidth(info.rect.width());
909 image->setHeight(info.rect.height());
910
911 {
912 auto *imagePriv = static_cast<QQuickImageBasePrivate *>(QQuickItemPrivate::get(image));
913 imagePriv->currentPix->setImage(info.image);
914 }
915
916 QQuickItem *item = popItem();
917 {
918 QQuickItem *effectItem = item;
919 QPointF sourceOrigin;
920 if (!info.filterId.isEmpty())
921 effectItem = generateFilter(item, info, &sourceOrigin);
922 if (!info.maskId.isEmpty())
923 generateMask(effectItem ? effectItem : item, info, sourceOrigin);
924 }
925}
926
927void QQuickItemGenerator::generateTextNode(const TextNodeInfo &info)
928{
929 if (Q_UNLIKELY(isStopped()))
930 return;
931
932 if (auto *rec = activeRecord()) {
933 rec->append([this, info]() { generateTextNode(info); });
934 return;
935 }
936
937 if (!isNodeVisible(info))
938 return;
939
940 if (!m_opacityGroups.isEmpty())
941 m_opacityGroups.top().graphicItemCount++;
942 auto *item = new QQuickItem;
943 pushItem(item);
944 generateNodeBase(info);
945
946 auto *text = new QQuickText;
947 text->setParent(item);
948 text->setParentItem(item);
949
950 text->setColor(info.fillColor.defaultValue().value<QColor>());
951 text->setFont(info.font);
952 text->setText(info.text);
953 text->setTextFormat(info.needsRichText ? QQuickText::RichText : QQuickText::StyledText);
954
955 if (info.isTextArea) {
956 text->setX(info.position.x());
957 text->setY(info.position.y());
958 if (info.size.width() > 0) {
959 text->setWidth(info.size.width());
960 text->setWrapMode(QQuickText::Wrap);
961 }
962 if (info.size.height() > 0)
963 text->setHeight(info.size.height());
964 text->setClip(true);
965 } else {
966 const qreal anchorX = info.position.x();
967 const qreal anchorY = info.position.y();
968 text->setX(anchorX);
969 auto updateY = [text, anchorY]() { text->setY(anchorY - text->baselineOffset()); };
970 QObject::connect(text, &QQuickItem::baselineOffsetChanged, text, updateY);
971 updateY();
972 if (info.alignment == Qt::AlignHCenter || info.alignment == Qt::AlignRight) {
973 const Qt::Alignment alignment = info.alignment;
974 auto updateX = [text, anchorX, alignment](qreal contentWidth) {
975 text->setX(alignment == Qt::AlignHCenter ? anchorX - qRound(contentWidth / 2)
976 : anchorX - contentWidth);
977 };
978 QObject::connect(text, &QQuickText::contentWidthChanged, text, updateX);
979 updateX(text->contentWidth());
980 }
981 }
982
983 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
984 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
985 text->setStyleColor(strokeColor);
986 text->setStyle(QQuickText::Outline);
987 }
988
989 const bool hasFillColorAnim = info.fillColor.isAnimated();
990 const bool hasFillOpacityAnim = info.fillOpacity.isAnimated();
991 const bool hasStrokeColorAnim = info.strokeColor.isAnimated();
992 const bool hasStrokeOpacityAnim = info.strokeOpacity.isAnimated();
993
994 if (hasFillColorAnim || hasFillOpacityAnim) {
995 bindColorWithOpacity(text, QStringLiteral("color"), info.fillColor, info.fillOpacity,
996 [text](const QColor &color) { text->setColor(color); });
997 }
998
999 if (hasStrokeColorAnim || hasStrokeOpacityAnim) {
1000 bindColorWithOpacity(text, QStringLiteral("styleColor"), info.strokeColor,
1001 info.strokeOpacity,
1002 [text](const QColor &color) { text->setStyleColor(color); });
1003 }
1004
1005 QQuickItem *textItem = popItem();
1006 {
1007 QQuickItem *effectItem = textItem;
1008 QPointF sourceOrigin;
1009 if (!info.filterId.isEmpty())
1010 effectItem = generateFilter(textItem, info, &sourceOrigin);
1011 if (!info.maskId.isEmpty())
1012 generateMask(effectItem ? effectItem : textItem, info, sourceOrigin);
1013 }
1014}
1015
1016void QQuickItemGenerator::generateNode(const NodeInfo &info)
1017{
1018 if (auto *rec = activeRecord()) {
1019 rec->append([this, info]() { generateNode(info); });
1020 return;
1021 }
1022 qCDebug(lcQuickVectorImage) << "generateNode: not yet implemented";
1023 Q_UNUSED(info)
1024}
1025
1026void QQuickItemGenerator::generateUseNode(const UseNodeInfo &info)
1027{
1028 if (Q_UNLIKELY(isStopped()))
1029 return;
1030
1031 if (auto *rec = activeRecord()) {
1032 rec->append([this, info]() { generateUseNode(info); });
1033 return;
1034 }
1035
1036 if (!isNodeVisible(info))
1037 return;
1038
1039 if (info.stage == StructureNodeStage::Start) {
1040 auto *item = new QQuickItem;
1041 pushItem(item);
1042 generateNodeBase(info);
1043
1044 // The offset is part of the content, so it is not replaced by transform animations
1045 if (!info.startPos.isNull()) {
1046 auto *contentItem = new QQuickItem;
1047 auto *translate = new QQuickTranslate(contentItem);
1048 translate->setX(info.startPos.x());
1049 translate->setY(info.startPos.y());
1050 auto transformProp = contentItem->transform();
1051 transformProp.append(&transformProp, translate);
1052 pushItem(contentItem);
1053 }
1054 } else {
1055 if (!info.startPos.isNull())
1056 popItem();
1057 QQuickItem *item = popItem();
1058 QQuickItem *effectItem = item;
1059 QPointF sourceOrigin;
1060 if (!info.filterId.isEmpty())
1061 effectItem = generateFilter(item, info, &sourceOrigin);
1062 if (!info.maskId.isEmpty())
1063 generateMask(effectItem ? effectItem : item, info, sourceOrigin);
1064 }
1065}
1066
1067bool QQuickItemGenerator::generateDefsNode(const StructureNodeInfo &info)
1068{
1069 if (Q_UNLIKELY(isStopped()))
1070 return false;
1071
1072 if (info.stage == StructureNodeStage::Start) {
1073 if (!info.transformReferenceChildId.isEmpty())
1074 m_transformReferenceChildIds.insert(info.id + ".item"_L1,
1075 info.transformReferenceChildId);
1076 beginDefsRecord(info.id);
1077 return true;
1078 }
1079
1080 endDefsRecord();
1081
1082 return true;
1083}
1084
1085QQuickTransformSource *
1086QQuickItemGenerator::resolveTransformReference(const QString &referenceId) const
1087{
1088 auto source = m_transformSourceItems.constFind(referenceId);
1089 if (source != m_transformSourceItems.cend())
1090 return source.value();
1091
1092 auto childId = m_transformReferenceChildIds.constFind(referenceId);
1093 if (childId == m_transformReferenceChildIds.cend())
1094 return nullptr;
1095
1096 source = m_transformSourceItems.constFind(childId.value());
1097 if (source == m_transformSourceItems.cend())
1098 return nullptr;
1099
1100 return source.value();
1101}
1102
1103void QQuickItemGenerator::beginDefsRecord(const QString &id)
1104{
1105 m_defsIdStack.push(m_currentDefsId);
1106 m_currentDefsId = id;
1107
1108 QList<std::function<void()>> &record = m_defs[id];
1109 record.clear();
1110 m_currentDefsRecord = &record;
1111}
1112
1113void QQuickItemGenerator::endDefsRecord()
1114{
1115 m_currentDefsId.clear();
1116 m_currentDefsRecord = nullptr;
1117
1118 if (m_defsIdStack.isEmpty())
1119 return;
1120
1121 m_currentDefsId = m_defsIdStack.pop();
1122 if (m_currentDefsId.isEmpty())
1123 return;
1124
1125 auto outer = m_defs.find(m_currentDefsId);
1126 if (outer != m_defs.end())
1127 m_currentDefsRecord = &outer.value();
1128}
1129
1130void QQuickItemGenerator::replayDefsRecord(const QString &id)
1131{
1132 auto it = m_defs.find(id);
1133 if (it == m_defs.end())
1134 return;
1135
1136 auto *container = new QQuickItem;
1137 container->setParent(m_rootItem);
1138 m_itemStack.push(container);
1139 // Replayed def content is not drawn inside the enclosing opacity group
1140 QStack<OpacityGroup> outerOpacityGroups;
1141 outerOpacityGroups.swap(m_opacityGroups);
1142 for (const auto &step : *it)
1143 step();
1144 m_opacityGroups.swap(outerOpacityGroups);
1145 m_itemStack.pop();
1146}
1147
1148// Replaying the record runs the generate calls that register the def.
1149template <typename T>
1150typename QHash<QString, T>::iterator
1151QQuickItemGenerator::resolveDef(QHash<QString, T> &registeredDefs, const QString &id)
1152{
1153 auto it = registeredDefs.find(id);
1154 if (it != registeredDefs.end())
1155 return it;
1156
1157 replayDefsRecord(id);
1158 return registeredDefs.find(id);
1159}
1160
1161void QQuickItemGenerator::generateDefsInstantiationNode(const StructureNodeInfo &info)
1162{
1163 if (Q_UNLIKELY(isStopped()))
1164 return;
1165
1166 if (auto *rec = activeRecord()) {
1167 rec->append([this, info]() { generateDefsInstantiationNode(info); });
1168 return;
1169 }
1170
1171 if (info.stage != StructureNodeStage::Start)
1172 return;
1173
1174 auto it = m_defs.find(info.defsId);
1175 if (it == m_defs.end()) {
1176 qCWarning(lcQuickVectorImage)
1177 << "generateDefsInstantiationNode: unknown defs id:" << info.defsId;
1178 return;
1179 }
1180
1181 const QRectF bounds = resolveBounds(info);
1182 registerBounds(info.id, bounds);
1183 registerBounds(info.defsId + "_defs"_L1, bounds);
1184
1185 const qsizetype pendingStart = m_pendingLinkedTransforms.size();
1186 const QHash<QString, QQuickTransformSource *> outerSources = m_transformSourceItems;
1187
1188 if (m_animationProvider) {
1189 m_animationProvider->enterTimelineScope(
1190 info.defsId + "_defs"_L1,
1191 info.timelineInfo ? info.timelineInfo->frameCounterReference : QString{});
1192 }
1193
1194 for (const auto &step : *it)
1195 step();
1196
1197 if (m_animationProvider)
1198 m_animationProvider->exitTimelineScope();
1199
1200 QList<PendingLinkedTransform> unresolved;
1201 for (qsizetype i = pendingStart; i < m_pendingLinkedTransforms.size(); ++i) {
1202 const PendingLinkedTransform &pending = m_pendingLinkedTransforms.at(i);
1203 QQuickTransformSource *source = resolveTransformReference(pending.transformReferenceId);
1204 if (!source)
1205 unresolved.append(pending);
1206 else
1207 new TransformLinker(source, pending.linkedMatrix, pending.item);
1208 }
1209 m_pendingLinkedTransforms.resize(pendingStart);
1210 m_pendingLinkedTransforms.append(unresolved);
1211 m_transformSourceItems = outerSources;
1212}
1213
1214bool QQuickItemGenerator::generateMaskNode(const MaskNodeInfo &info)
1215{
1216 if (Q_UNLIKELY(isStopped()))
1217 return false;
1218
1219 if (auto *rec = activeRecord()) {
1220 rec->append([this, info]() { generateMaskNode(info); });
1221 return true;
1222 }
1223
1224 if (info.stage == StructureNodeStage::Start) {
1225 if (!info.isDefaultTransform) {
1226 auto *xfItem = new QQuickItem;
1227 xfItem->setTransformOrigin(QQuickItem::TopLeft);
1228 auto *matrix = new QQuickMatrix4x4(xfItem);
1229 matrix->setMatrix(QMatrix4x4(info.transform.defaultValue().value<QTransform>()));
1230 auto transformProp = xfItem->transform();
1231 transformProp.append(&transformProp, matrix);
1232 pushItem(xfItem);
1233 }
1234 if (info.isMaskContentRelativeCoordinates) {
1235 auto *transformerItem = new QQuickItem;
1236 pushItem(transformerItem);
1237 }
1238 return true;
1239 }
1240
1241 generateMaskContainer(info);
1242 return true;
1243}
1244
1245static QQuickShaderEffectSource *makeSES(QQuickItem *item, const QRectF &rect, QQuickItem *parent)
1246{
1247 auto *ses = new QQuickShaderEffectSource;
1248 ses->setSourceItem(item);
1249 ses->setWidth(rect.width());
1250 ses->setHeight(rect.height());
1251 ses->setVisible(false);
1252 ses->setParent(parent);
1253 ses->setParentItem(parent);
1254 return ses;
1255}
1256
1257// The shader and its properties must be set up with initializeQmlObject()
1258static QQuickShaderEffect *makeFilterEffect(QQuickItem *inputItem, QQuickItem *parent)
1259{
1260 auto *effect = new QQuickShaderEffect;
1261 effect->bindableWidth().setBinding([inputItem] { return inputItem->width(); });
1262 effect->bindableHeight().setBinding([inputItem] { return inputItem->height(); });
1263 effect->setVisible(false);
1264 effect->setParent(parent);
1265 effect->setParentItem(parent);
1266 return effect;
1267}
1268
1269static QQuickShaderEffectSource *makeEffectSES(QQuickShaderEffect *effect, const QRectF &stepRect,
1270 const QRectF &filterRect, QQuickItem *parent)
1271{
1272 auto *wrapper = new QQuickItem;
1273 wrapper->setWidth(stepRect.width());
1274 wrapper->setHeight(stepRect.height());
1275 wrapper->setClip(true);
1276 wrapper->setParent(parent);
1277 wrapper->setParentItem(parent);
1278 effect->setVisible(true);
1279 effect->setParentItem(wrapper);
1280 effect->setX(filterRect.x() - stepRect.x());
1281 effect->setY(filterRect.y() - stepRect.y());
1282 auto *ses = makeSES(wrapper, stepRect, parent);
1283 ses->setHideSource(true);
1284 return ses;
1285}
1286
1287void QQuickItemGenerator::bindTextureSize(QQuickShaderEffectSource *ses)
1288{
1289 auto *spy = new QQuickItemSpy(ses);
1290 spy->setVisible(false);
1291
1292 auto updateTextureSize = [ses, spy]() {
1293 spy->setWidth(ses->width());
1294 spy->setHeight(ses->height());
1295 const QSizeF textureSize = spy->requiredTextureSize();
1296 ses->setTextureSize(QSize(qRound(textureSize.width()), qRound(textureSize.height())));
1297 };
1298 QObject::connect(spy, &QQuickItemSpy::requiredTextureSizeChanged, ses, updateTextureSize);
1299 QObject::connect(ses, &QQuickItem::widthChanged, ses, updateTextureSize);
1300 QObject::connect(ses, &QQuickItem::heightChanged, ses, updateTextureSize);
1301 updateTextureSize();
1302}
1303
1304void QQuickItemGenerator::bindPatternTextureSize(QQuickShaderEffectSource *ses)
1305{
1306 if (!m_topLevelScaleSpy)
1307 return;
1308
1309 QQuickItemSpy *scaleSpy = m_topLevelScaleSpy;
1310 auto updateTextureSize = [ses, scaleSpy]() {
1311 const QSizeF unitScale = scaleSpy->requiredTextureSize();
1312 const qreal width = ses->width() * unitScale.width();
1313 const qreal height = ses->height() * unitScale.height();
1314 ses->setTextureSize(QSize(qRound(width), qRound(height)));
1315 };
1316 QObject::connect(scaleSpy, &QQuickItemSpy::requiredTextureSizeChanged, ses, updateTextureSize);
1317 QObject::connect(ses, &QQuickItem::widthChanged, ses, updateTextureSize);
1318 QObject::connect(ses, &QQuickItem::heightChanged, ses, updateTextureSize);
1319 updateTextureSize();
1320}
1321
1322void QQuickItemGenerator::generateMaskContainer(const MaskNodeInfo &info)
1323{
1324 QQuickItem *transformer = nullptr;
1325 QQuickMatrix4x4 *transformerMatrix = nullptr;
1326 if (info.isMaskContentRelativeCoordinates) {
1327 transformer = popItem();
1328 transformerMatrix = new QQuickMatrix4x4(transformer);
1329 auto transformProp = transformer->transform();
1330 transformProp.append(&transformProp, transformerMatrix);
1331 }
1332
1333 if (!info.isDefaultTransform)
1334 popItem();
1335
1336 auto *container = currentItem();
1337 const bool hasBoundsReference = !info.boundsReferenceId.isEmpty();
1338 const QRectF maskRect = hasBoundsReference ? resolveBounds(info) : info.maskRect;
1339 m_maskDefs[info.id] = { container,
1340 maskRect,
1341 hasBoundsReference ? false : info.isMaskRectRelativeCoordinates,
1342 info.isMaskContentRelativeCoordinates,
1343 transformer,
1344 transformerMatrix };
1345}
1346
1347void QQuickItemGenerator::generateMask(QQuickItem *item, const NodeInfo &info,
1348 const QPointF &sourceOrigin)
1349{
1350 replayDefsRecord(info.maskId);
1351 auto it = m_maskDefs.find(info.maskId);
1352 if (it == m_maskDefs.end()) {
1353 qCWarning(lcQuickVectorImage) << "generateMask: unknown mask id:" << info.maskId;
1354 return;
1355 }
1356 MaskDef &maskDef = *it;
1357 QQuickItem *parentItem = item->parentItem();
1358
1359 const qreal w = item->width();
1360 const qreal h = item->height();
1361
1362 const QRectF resolvedBounds = resolveBounds(info);
1363 const QRectF svgBounds =
1364 resolvedBounds.isNull() ? QRectF(item->x(), item->y(), w, h) : resolvedBounds;
1365 QRectF svgMaskRect;
1366 if (maskDef.isMaskRectRelativeCoordinates) {
1367 svgMaskRect = QRectF(maskDef.maskRect.x() * svgBounds.width() + svgBounds.x(),
1368 maskDef.maskRect.y() * svgBounds.height() + svgBounds.y(),
1369 maskDef.maskRect.width() * svgBounds.width(),
1370 maskDef.maskRect.height() * svgBounds.height());
1371 } else {
1372 svgMaskRect = maskDef.maskRect;
1373 }
1374
1375 if (maskDef.isMaskContentRelativeCoordinates && maskDef.transformerMatrix) {
1376 QMatrix4x4 mat;
1377 mat.translate(svgBounds.x(), svgBounds.y());
1378 mat.scale(svgBounds.width(), svgBounds.height(), 1.0f);
1379 maskDef.transformerMatrix->setMatrix(mat);
1380 }
1381
1382 maskDef.container->setParent(m_rootItem);
1383 maskDef.container->setParentItem(m_rootItem);
1384 const qreal containerW = m_containerSize.width() > 0 ? m_containerSize.width() : w;
1385 const qreal containerH = m_containerSize.height() > 0 ? m_containerSize.height() : h;
1386 maskDef.container->setWidth(containerW);
1387 maskDef.container->setHeight(containerH);
1388
1389 static const QUrl maskShaderUrl(
1390 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb"_s);
1391
1392 auto *maskSES = makeSES(maskDef.container, svgMaskRect, parentItem);
1393 maskSES->setHideSource(true);
1394 maskSES->setSourceRect(svgMaskRect);
1395 bindTextureSize(maskSES);
1396
1397 auto *itemSES = makeSES(item, svgMaskRect, parentItem);
1398 itemSES->setHideSource(true);
1399 itemSES->setSmooth(false);
1400 itemSES->setSourceRect(svgMaskRect.translated(-sourceOrigin));
1401 bindTextureSize(itemSES);
1402
1403 auto *shaderEffect = new QQuickShaderEffect;
1404 initializeQmlObject(shaderEffect, [shaderEffect, itemSES, maskSES,
1405 isAlpha = info.isMaskAlpha,
1406 isInverted = info.isMaskInverted]() {
1407 shaderEffect->setFragmentShader(maskShaderUrl);
1408 shaderEffect->setProperty("source", QVariant::fromValue<QQuickItem *>(itemSES));
1409 shaderEffect->setProperty("maskSource", QVariant::fromValue<QQuickItem *>(maskSES));
1410 shaderEffect->setProperty("isAlpha", isAlpha);
1411 shaderEffect->setProperty("isInverted", isInverted);
1412 });
1413
1414 if (!info.isDefaultOpacity)
1415 shaderEffect->setOpacity(info.opacity.defaultValue().toReal());
1416
1417 shaderEffect->setTransformOrigin(QQuickItem::TopLeft);
1418 shaderEffect->setParent(parentItem);
1419 shaderEffect->setParentItem(parentItem);
1420 shaderEffect->setWidth(svgMaskRect.width());
1421 shaderEffect->setHeight(svgMaskRect.height());
1422
1423 if (!info.isDefaultTransform) {
1424 const QTransform elementXf = info.transform.defaultValue().value<QTransform>();
1425 QMatrix4x4 mat(elementXf);
1426 mat.translate(svgMaskRect.x(), svgMaskRect.y());
1427 mat.optimize();
1428 auto *matrix = new QQuickMatrix4x4(shaderEffect);
1429 matrix->setMatrix(mat);
1430 auto transformProp = shaderEffect->transform();
1431 transformProp.append(&transformProp, matrix);
1432 shaderEffect->setX(0);
1433 shaderEffect->setY(0);
1434 } else if (info.transform.isAnimated()) {
1435 auto *translate = new QQuickTranslate(shaderEffect);
1436 translate->setX(svgMaskRect.x());
1437 translate->setY(svgMaskRect.y());
1438 auto transformProp = shaderEffect->transform();
1439 transformProp.append(&transformProp, translate);
1440 shaderEffect->setX(0);
1441 shaderEffect->setY(0);
1442 } else {
1443 shaderEffect->setX(svgMaskRect.x());
1444 shaderEffect->setY(svgMaskRect.y());
1445 }
1446
1447 generateItemAnimations(shaderEffect, info);
1448}
1449
1450void QQuickItemGenerator::generateFilterNode(const FilterNodeInfo &info)
1451{
1452 if (auto *rec = activeRecord()) {
1453 rec->append([this, info]() { generateFilterNode(info); });
1454 return;
1455 }
1456 m_filterDefs[info.id] = info;
1457}
1458
1459static QRectF resolveRect(const QRectF &rect, FilterNodeInfo::CoordinateSystem cs,
1460 const QRectF &itemBounds)
1461{
1463 return QRectF(itemBounds.x() + rect.x() * itemBounds.width(),
1464 itemBounds.y() + rect.y() * itemBounds.height(),
1465 rect.width() * itemBounds.width(), rect.height() * itemBounds.height());
1466 return rect;
1467}
1468
1469QQuickItem *QQuickItemGenerator::generateFilter(QQuickItem *item, const NodeInfo &info,
1470 QPointF *outputOrigin)
1471{
1472 auto it = resolveDef(m_filterDefs, info.filterId);
1473 if (it == m_filterDefs.end()) {
1474 qCWarning(lcQuickVectorImage) << "applyFilter: unknown filter id:" << info.filterId;
1475 return nullptr;
1476 }
1477 const FilterNodeInfo &filterInfo = *it;
1478
1479 if (filterInfo.steps.isEmpty())
1480 return nullptr;
1481
1482 QQuickItem *parentItem = item->parentItem();
1483 const QRectF resolvedBounds = resolveBounds(info);
1484 const QRectF itemBounds = resolvedBounds.isNull()
1485 ? QRectF(item->x(), item->y(), item->width(), item->height())
1486 : resolvedBounds;
1487
1488 QRectF filterRect = resolveRect(filterInfo.filterRect, filterInfo.csFilterRect, itemBounds);
1489 if (filterRect.isEmpty())
1490 filterRect = itemBounds;
1491
1492 auto *sourceGraphic = makeSES(item, filterRect, m_rootItem);
1493 sourceGraphic->setHideSource(true);
1494 sourceGraphic->setSourceRect(filterRect);
1495 if (filterInfo.wrapMode == QSGTexture::Repeat)
1496 sourceGraphic->setWrapMode(QQuickShaderEffectSource::Repeat);
1497
1498 if (filterInfo.csFilterRect == FilterNodeInfo::CoordinateSystem::Relative) {
1499 const qreal wFactor = filterInfo.filterRect.width();
1500 const qreal hFactor = filterInfo.filterRect.height();
1501 sourceGraphic->bindableWidth().setBinding(
1502 [item, wFactor] { return item->width() * wFactor; });
1503 sourceGraphic->bindableHeight().setBinding(
1504 [item, hFactor] { return item->height() * hFactor; });
1505
1506 const QRectF fractions = filterInfo.filterRect;
1507 auto updateSourceRect = [sourceGraphic, item, fractions]() {
1508 sourceGraphic->setSourceRect(QRectF(item->x() + fractions.x() * item->width(),
1509 item->y() + fractions.y() * item->height(),
1510 fractions.width() * item->width(),
1511 fractions.height() * item->height()));
1512 };
1513 QObject::connect(item, &QQuickItem::widthChanged, sourceGraphic, updateSourceRect);
1514 QObject::connect(item, &QQuickItem::heightChanged, sourceGraphic, updateSourceRect);
1515 }
1516
1517 bindTextureSize(sourceGraphic);
1518
1519 QHash<QString, QQuickShaderEffectSource *> namedOutputs;
1520 const QString sourceAlphaName = filterInfo.id + u"_source_alpha"_s;
1521 QQuickShaderEffectSource *sourceAlpha = nullptr;
1522 QQuickShaderEffectSource *lastOutput = sourceGraphic;
1523
1524 const auto resolveInput = [&](FilterNodeInfo::FilterInput inputType,
1525 const QString &name) -> QQuickShaderEffectSource * {
1526 switch (inputType) {
1527 case FilterNodeInfo::FilterInput::SourceColor:
1528 return sourceGraphic;
1529 case FilterNodeInfo::FilterInput::SourceAlpha:
1530 return sourceAlpha;
1531 case FilterNodeInfo::FilterInput::Name:
1532 return namedOutputs.value(name, sourceGraphic);
1533 default:
1534 return lastOutput;
1535 }
1536 };
1537
1538 QRectF lastStepRect = filterRect;
1539
1540 for (int i = 0; i < filterInfo.steps.size(); ++i) {
1541 const FilterNodeInfo::FilterStep &step = filterInfo.steps.at(i);
1542 const QRectF stepRect =
1543 (step.filterPrimitiveRect.isNull()
1544 || step.csFilterParameter == FilterNodeInfo::CoordinateSystem::MatchFilterRect)
1545 ? filterRect
1546 : resolveRect(step.filterPrimitiveRect, step.csFilterParameter, itemBounds);
1547 QQuickShaderEffectSource *output = nullptr;
1548 if (step.filterType == FilterNodeInfo::Type::Merge) {
1549 QList<QQuickShaderEffectSource *> mergeInputs;
1550 while (i + 1 < filterInfo.steps.size()
1551 && filterInfo.steps.at(i + 1).filterType == FilterNodeInfo::Type::MergeNode) {
1552 ++i;
1553 const FilterNodeInfo::FilterStep &mergeNode = filterInfo.steps.at(i);
1554 mergeInputs.append(resolveInput(mergeNode.input1, mergeNode.namedInput1));
1555 }
1556 output = generateFilterMerge(mergeInputs, stepRect, filterRect);
1557 } else {
1558 auto *input1 = resolveInput(step.input1, step.namedInput1);
1559 auto *input2 = resolveInput(step.input2, step.namedInput2);
1560 output = generateFilterStep(step, input1, input2, stepRect, filterRect);
1561 }
1562
1563 if (output) {
1564 bindTextureSize(output);
1565 lastOutput = output;
1566 lastStepRect = stepRect;
1567 if (!step.outputName.isEmpty()) {
1568 namedOutputs[step.outputName] = output;
1569 if (step.outputName == sourceAlphaName)
1570 sourceAlpha = output;
1571 }
1572 }
1573 }
1574
1575 if (lastOutput == sourceGraphic)
1576 return nullptr;
1577
1578 lastOutput->setParent(parentItem);
1579 lastOutput->setParentItem(parentItem);
1580 lastOutput->setVisible(true);
1581
1582 if (!info.isDefaultOpacity)
1583 lastOutput->setOpacity(info.opacity.defaultValue().toReal());
1584
1585 if (!info.isDefaultTransform) {
1586 const QTransform elementXf = info.transform.defaultValue().value<QTransform>();
1587 QMatrix4x4 mat(elementXf);
1588 mat.translate(lastStepRect.x(), lastStepRect.y());
1589 mat.optimize();
1590 auto *matrix = new QQuickMatrix4x4(lastOutput);
1591 matrix->setMatrix(mat);
1592 auto transformProp = lastOutput->transform();
1593 transformProp.append(&transformProp, matrix);
1594 } else {
1595 lastOutput->setX(lastStepRect.x());
1596 lastOutput->setY(lastStepRect.y());
1597 }
1598
1599 if (outputOrigin)
1600 *outputOrigin = lastStepRect.topLeft();
1601
1602 return lastOutput;
1603}
1604
1605QQuickShaderEffectSource *QQuickItemGenerator::generateFilterStep(
1606 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
1607 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
1608{
1609 switch (step.filterType) {
1610 case FilterNodeInfo::Type::Flood:
1611 return generateFilterFlood(step, input1, stepRect, filterRect);
1612 case FilterNodeInfo::Type::Offset:
1613 return generateFilterOffset(step, input1, stepRect);
1614 case FilterNodeInfo::Type::ColorMatrix:
1615 return generateFilterColorMatrix(step, input1, stepRect, filterRect);
1616 case FilterNodeInfo::Type::BlendNormal:
1617 case FilterNodeInfo::Type::BlendMultiply:
1618 case FilterNodeInfo::Type::BlendScreen:
1619 case FilterNodeInfo::Type::BlendDarken:
1620 case FilterNodeInfo::Type::BlendLighten:
1621 return generateFilterBlend(step, input1, input2, stepRect, filterRect);
1622 case FilterNodeInfo::Type::CompositeOver:
1623 case FilterNodeInfo::Type::CompositeIn:
1624 case FilterNodeInfo::Type::CompositeOut:
1625 case FilterNodeInfo::Type::CompositeAtop:
1626 case FilterNodeInfo::Type::CompositeXor:
1627 case FilterNodeInfo::Type::CompositeLighter:
1628 case FilterNodeInfo::Type::CompositeArithmetic:
1629 return generateFilterComposite(step, input1, input2, stepRect, filterRect);
1630 case FilterNodeInfo::Type::GaussianBlur:
1631 return generateFilterGaussianBlur(step, input1, stepRect, filterRect);
1632 default:
1633 qCDebug(lcQuickVectorImage) << "generateFilterStep: filter type not yet implemented";
1634 return nullptr;
1635 }
1636}
1637
1638QQuickShaderEffectSource *
1639QQuickItemGenerator::generateFilterMerge(const QList<QQuickShaderEffectSource *> &inputs,
1640 const QRectF &stepRect, const QRectF &filterRect)
1641{
1642 const int maxNodeCount = 8;
1643 if (inputs.isEmpty()) {
1644 qCWarning(lcQuickVectorImage) << "generateFilterMerge: requires at least one input";
1645 return nullptr;
1646 }
1647 if (inputs.size() > maxNodeCount)
1648 qCWarning(lcQuickVectorImage)
1649 << "generateFilterMerge: maximum of" << maxNodeCount << "nodes exceeded";
1650
1651 static const QUrl shader(
1652 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb"_s);
1653 auto *effect = makeFilterEffect(inputs.first(), m_rootItem);
1654
1655 const int count = qMin(maxNodeCount, inputs.size());
1656 initializeQmlObject(effect, [effect, inputs, count]() {
1657 effect->setFragmentShader(shader);
1658 effect->setProperty("sourceCount", count);
1659 for (int i = 0; i < maxNodeCount; ++i) {
1660 QQuickItem *src = i < inputs.size() ? inputs.at(i) : nullptr;
1661 effect->setProperty(QStringLiteral("source%1").arg(i + 1).toLatin1(),
1662 QVariant::fromValue(src));
1663 }
1664 });
1665
1666 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1667}
1668
1669QQuickShaderEffectSource *
1670QQuickItemGenerator::generateFilterFlood(const FilterNodeInfo::FilterStep &step,
1671 QQuickShaderEffectSource *input, const QRectF &stepRect,
1672 const QRectF &filterRect)
1673{
1674 auto *rect = new QQuickRectangle;
1675 rect->setColor(step.filterParameter.value<QColor>());
1676 rect->setVisible(false);
1677 rect->setParent(m_rootItem);
1678 rect->setParentItem(m_rootItem);
1679 if (input) {
1680 rect->bindableWidth().setBinding([input] { return input->width(); });
1681 rect->bindableHeight().setBinding([input] { return input->height(); });
1682 } else {
1683 rect->setWidth(filterRect.width());
1684 rect->setHeight(filterRect.height());
1685 }
1686 auto *ses = makeSES(rect, stepRect, m_rootItem);
1687 ses->setSourceRect(QRectF(stepRect.x() - filterRect.x(), stepRect.y() - filterRect.y(),
1688 stepRect.width(), stepRect.height()));
1689 return ses;
1690}
1691
1692QQuickShaderEffectSource *
1693QQuickItemGenerator::generateFilterOffset(const FilterNodeInfo::FilterStep &step,
1694 QQuickShaderEffectSource *input, const QRectF &stepRect)
1695{
1696 const QVector2D offset = step.filterParameter.value<QVector2D>();
1697 const qreal offsetX = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1698 ? offset.x() * stepRect.width()
1699 : offset.x();
1700 const qreal offsetY = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1701 ? offset.y() * stepRect.height()
1702 : offset.y();
1703
1704 const QRectF offsetRect(0, 0, input->width() + offsetX, input->height() + offsetY);
1705 auto *offsetSES = makeSES(input, offsetRect, m_rootItem);
1706 offsetSES->setSourceRect(QRectF(-offsetX, -offsetY, offsetRect.width(), offsetRect.height()));
1707 bindTextureSize(offsetSES);
1708
1709 auto *ses = makeSES(offsetSES, stepRect, m_rootItem);
1710 ses->setSourceRect(QRectF(0, 0, stepRect.width(), stepRect.height()));
1711 return ses;
1712}
1713
1714QQuickShaderEffectSource *
1715QQuickItemGenerator::generateFilterColorMatrix(const FilterNodeInfo::FilterStep &step,
1716 QQuickShaderEffectSource *input,
1717 const QRectF &stepRect, const QRectF &filterRect)
1718{
1719 static const QUrl shader(
1720 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb"_s);
1721 auto *effect = makeFilterEffect(input, m_rootItem);
1722
1723 const auto matrix = std::make_shared<QGenericMatrix<5, 5, qreal>>(
1724 step.filterParameter.value<QGenericMatrix<5, 5, qreal>>());
1725 initializeQmlObject(effect, [effect, input, matrix]() {
1726 effect->setFragmentShader(shader);
1727 effect->setProperty("source", QVariant::fromValue(input));
1728 for (int row = 0; row < 4; ++row) {
1729 for (int col = 0; col < 5; ++col) {
1730 effect->setProperty(QStringLiteral("m_%1_%2").arg(row).arg(col).toLatin1(),
1731 (*matrix)(col, row));
1732 }
1733 }
1734 });
1735
1736 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1737}
1738
1739QQuickShaderEffectSource *QQuickItemGenerator::generateFilterBlend(
1740 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
1741 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
1742{
1743 QString shaderName;
1744 switch (step.filterType) {
1745 case FilterNodeInfo::Type::BlendNormal:
1746 shaderName = u"feblendnormal"_s;
1747 break;
1748 case FilterNodeInfo::Type::BlendMultiply:
1749 shaderName = u"feblendmultiply"_s;
1750 break;
1751 case FilterNodeInfo::Type::BlendScreen:
1752 shaderName = u"feblendscreen"_s;
1753 break;
1754 case FilterNodeInfo::Type::BlendDarken:
1755 shaderName = u"feblenddarken"_s;
1756 break;
1757 case FilterNodeInfo::Type::BlendLighten:
1758 shaderName = u"feblendlighten"_s;
1759 break;
1760 default:
1761 Q_UNREACHABLE();
1762 }
1763
1764 const QUrl shaderUrl(u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"_s + shaderName
1765 + u".frag.qsb"_s);
1766 auto *effect = makeFilterEffect(input1, m_rootItem);
1767 initializeQmlObject(effect, [effect, shaderUrl, input1, input2]() {
1768 effect->setFragmentShader(shaderUrl);
1769 effect->setProperty("source", QVariant::fromValue(input1));
1770 effect->setProperty("source2", QVariant::fromValue(input2));
1771 });
1772 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1773}
1774
1775QQuickShaderEffectSource *QQuickItemGenerator::generateFilterComposite(
1776 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
1777 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
1778{
1779 QString shaderName;
1780 switch (step.filterType) {
1781 case FilterNodeInfo::Type::CompositeOver:
1782 shaderName = u"fecompositeover"_s;
1783 break;
1784 case FilterNodeInfo::Type::CompositeIn:
1785 shaderName = u"fecompositein"_s;
1786 break;
1787 case FilterNodeInfo::Type::CompositeOut:
1788 shaderName = u"fecompositeout"_s;
1789 break;
1790 case FilterNodeInfo::Type::CompositeAtop:
1791 shaderName = u"fecompositeatop"_s;
1792 break;
1793 case FilterNodeInfo::Type::CompositeXor:
1794 shaderName = u"fecompositexor"_s;
1795 break;
1796 case FilterNodeInfo::Type::CompositeLighter:
1797 shaderName = u"fecompositelighter"_s;
1798 break;
1799 case FilterNodeInfo::Type::CompositeArithmetic:
1800 shaderName = u"fecompositearithmetic"_s;
1801 break;
1802 default:
1803 Q_UNREACHABLE();
1804 }
1805
1806 const QUrl shaderUrl(u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"_s + shaderName
1807 + u".frag.qsb"_s);
1808 auto *effect = makeFilterEffect(input1, m_rootItem);
1809 const bool isArithmetic = step.filterType == FilterNodeInfo::Type::CompositeArithmetic;
1810 const QVector4D k = isArithmetic ? step.filterParameter.value<QVector4D>() : QVector4D{};
1811 initializeQmlObject(effect, [effect, shaderUrl, input1, input2, isArithmetic, k]() {
1812 effect->setFragmentShader(shaderUrl);
1813 effect->setProperty("source", QVariant::fromValue(input1));
1814 effect->setProperty("source2", QVariant::fromValue(input2));
1815 if (isArithmetic)
1816 effect->setProperty("k", QVariant::fromValue(k));
1817 });
1818 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1819}
1820
1821QQuickShaderEffectSource *
1822QQuickItemGenerator::generateFilterGaussianBlur(const FilterNodeInfo::FilterStep &step,
1823 QQuickShaderEffectSource *input,
1824 const QRectF &stepRect, const QRectF &filterRect)
1825{
1826 constexpr qreal maxDeviation = 12.0;
1827 const qreal deviation = step.filterParameter.toReal();
1828 const qreal blurValue = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1829 ? std::min(1.0, deviation * filterRect.width() / maxDeviation)
1830 : std::min(1.0, deviation / maxDeviation);
1831
1832 auto *effect = new QQuickMultiEffect;
1833 effect->bindableWidth().setBinding([input] { return input->width(); });
1834 effect->bindableHeight().setBinding([input] { return input->height(); });
1835 effect->setVisible(false);
1836 effect->setParent(m_rootItem);
1837 effect->setParentItem(m_rootItem);
1838 initializeQmlObject(effect, [effect, input, blurValue]() {
1839 effect->setSource(input);
1840 effect->setBlurEnabled(true);
1841 effect->setBlur(blurValue);
1842 effect->setBlurMax(64);
1843 });
1844
1845 auto *wrapper = new QQuickItem;
1846 wrapper->setWidth(stepRect.width());
1847 wrapper->setHeight(stepRect.height());
1848 wrapper->setClip(true);
1849 wrapper->setParent(m_rootItem);
1850 wrapper->setParentItem(m_rootItem);
1851 effect->setVisible(true);
1852 effect->setParentItem(wrapper);
1853 effect->setX(filterRect.x() - stepRect.x());
1854 effect->setY(filterRect.y() - stepRect.y());
1855 auto *ses = makeSES(wrapper, stepRect, m_rootItem);
1856 ses->setHideSource(true);
1857 return ses;
1858}
1859
1860bool QQuickItemGenerator::generateMarkerNode(const MarkerNodeInfo &info)
1861{
1862 if (Q_UNLIKELY(isStopped()))
1863 return false;
1864
1865 if (m_currentDefsRecord) {
1866 m_currentDefsRecord->append([this, info]() { generateMarkerNode(info); });
1867 return true;
1868 }
1869
1870 if (info.stage == StructureNodeStage::Start) {
1871 m_markerDefs[info.id].info = info;
1872 m_currentMarkerRecord = &m_markerDefs[info.id].recording;
1873 return true;
1874 }
1875
1876 m_markerDefs[info.id].info = info;
1877 m_currentMarkerRecord = nullptr;
1878 return true;
1879}
1880
1881bool QQuickItemGenerator::generatePatternNode(const PatternNodeInfo &info)
1882{
1883 if (Q_UNLIKELY(isStopped()))
1884 return false;
1885
1886 if (auto *rec = activeRecord()) {
1887 rec->append([this, info]() { generatePatternNode(info); });
1888 return true;
1889 }
1890
1891 if (info.stage == StructureNodeStage::Start) {
1892 auto *containerItem = new QQuickItem;
1893 pushItem(containerItem);
1894 return true;
1895 }
1896
1897 generatePatternContainer(info);
1898 return true;
1899}
1900
1901void QQuickItemGenerator::generatePatternContainer(const PatternNodeInfo &info)
1902{
1903 auto *container = popItem();
1904 container->setParent(m_rootItem);
1905 container->setParentItem(m_rootItem);
1906 container->setVisible(false);
1907 if (!info.isPatternRectRelativeCoordinates) {
1908 container->setWidth(info.patternRect.width());
1909 container->setHeight(info.patternRect.height());
1910 }
1911 m_patternDefs[info.id] = { container, info.patternRect, info.isPatternRectRelativeCoordinates };
1912}
1913
1914void QQuickItemGenerator::generatePattern(QQuickShapePath *shapePath, const PathNodeInfo &info,
1915 const QRectF &boundingRect, QTransform &fillTransform)
1916{
1917 auto it = resolveDef(m_patternDefs, info.patternId);
1918 if (it == m_patternDefs.end()) {
1919 qCWarning(lcQuickVectorImage) << "generatePattern: unknown pattern id:" << info.patternId;
1920 return;
1921 }
1922 PatternDef &patternDef = *it;
1923
1924 qreal tileW, tileH, offsetX, offsetY;
1925 if (patternDef.isPatternRectRelativeCoordinates) {
1926 tileW = patternDef.patternRect.width() * boundingRect.width();
1927 tileH = patternDef.patternRect.height() * boundingRect.height();
1928 offsetX = patternDef.patternRect.x() * boundingRect.width();
1929 offsetY = patternDef.patternRect.y() * boundingRect.height();
1930 patternDef.container->setWidth(tileW);
1931 patternDef.container->setHeight(tileH);
1932 } else {
1933 tileW = patternDef.patternRect.width();
1934 tileH = patternDef.patternRect.height();
1935 offsetX = patternDef.patternRect.x();
1936 offsetY = patternDef.patternRect.y();
1937 }
1938
1939 auto *ses = makeSES(patternDef.container, QRectF(0, 0, tileW, tileH), m_rootItem);
1940 ses->setHideSource(true);
1941 ses->setWrapMode(QQuickShaderEffectSource::Repeat);
1942 ses->setSourceRect(QRectF(0, 0, tileW, tileH));
1943 shapePath->setFillItem(ses);
1944 bindPatternTextureSize(ses);
1945
1946 // Fill transform has to include the inverse of the scene scale, since the texture size
1947 // is scaled by this amount
1948 if (m_topLevelScaleSpy) {
1949 const QTransform baseTransform = fillTransform;
1950 auto *scaleSpy = m_topLevelScaleSpy;
1951 auto updateFillTransform = [shapePath, offsetX, offsetY, baseTransform, scaleSpy]() {
1952 const QSizeF unitScale = scaleSpy->requiredTextureSize();
1953 QTransform xf = baseTransform;
1954 xf.translate(offsetX, offsetY);
1955 xf.scale(1.0 / unitScale.width(), 1.0 / unitScale.height());
1956 shapePath->setFillTransform(QMatrix4x4(xf));
1957 };
1958 QObject::connect(scaleSpy, &QQuickItemSpy::requiredTextureSizeChanged, shapePath,
1959 updateFillTransform);
1960 updateFillTransform();
1961 }
1962
1963 fillTransform.translate(offsetX, offsetY);
1964}
1965
1966static qreal meanAngle(QPointF p0, QPointF p1, QPointF p2)
1967{
1968 QPointF t1 = p1 - p0;
1969 QPointF t2 = p2 - p1;
1970 qreal hyp1 = hypot(t1.x(), t1.y());
1971 if (hyp1 > 0)
1972 t1 /= hyp1;
1973 else
1974 return 0.0;
1975 qreal hyp2 = hypot(t2.x(), t2.y());
1976 if (hyp2 > 0)
1977 t2 /= hyp2;
1978 else
1979 return 0.0;
1980 QPointF tangent = t1 + t2;
1981 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.0;
1982}
1983
1984void QQuickItemGenerator::generateMarkers(const PathNodeInfo &info)
1985{
1986 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
1987
1988 for (int i = 0; i < path.elementCount(); ++i) {
1989 const QPainterPath::Element element = path.elementAt(i);
1990 QString markerId;
1991 qreal angle = 0;
1992
1993 if (i == 0) {
1994 markerId = info.markerStartId;
1995 angle = path.angleAtPercent(0.0);
1996 } else if (i == path.elementCount() - 1) {
1997 markerId = info.markerEndId;
1998 angle = path.angleAtPercent(1.0);
1999 } else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
2000 markerId = info.markerMidId;
2001 QPointF p1(path.elementAt(i - 1).x, path.elementAt(i - 1).y);
2002 QPointF p2(element.x, element.y);
2003 QPointF p3(path.elementAt(i + 1).x, path.elementAt(i + 1).y);
2004 angle = meanAngle(p1, p2, p3);
2005 }
2006
2007 if (markerId.isEmpty())
2008 continue;
2009
2010 auto it = resolveDef(m_markerDefs, markerId);
2011 if (it == m_markerDefs.end()) {
2012 qCWarning(lcQuickVectorImage) << "generateMarkers: unknown marker id:" << markerId;
2013 continue;
2014 }
2015 const MarkerDef &markerDef = *it;
2016 const MarkerNodeInfo &minfo = markerDef.info;
2017
2018 const qreal sw = info.strokeStyle.width.defaultValue().toReal();
2019 const qreal markerW = minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth
2020 ? minfo.markerSize.width() * sw
2021 : minfo.markerSize.width();
2022 const qreal markerH = minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth
2023 ? minfo.markerSize.height() * sw
2024 : minfo.markerSize.height();
2025
2026 qreal scaleX = 1.0, scaleY = 1.0, offsetX = 0.0, offsetY = 0.0;
2027 if (minfo.viewBox.width() > 0)
2028 scaleX = markerW / minfo.viewBox.width();
2029 if (minfo.viewBox.height() > 0)
2030 scaleY = markerH / minfo.viewBox.height();
2031
2032 if (minfo.preserveAspectRatio & MarkerNodeInfo::xyMask) {
2033 if (!qFuzzyCompare(scaleX, scaleY)) {
2034 if (minfo.preserveAspectRatio & MarkerNodeInfo::meet)
2035 scaleX = scaleY = qMin(scaleX, scaleY);
2036 else
2037 scaleX = scaleY = qMax(scaleX, scaleY);
2038
2039 const qreal overflowX = scaleX * minfo.viewBox.width() - markerW;
2040 const qreal overflowY = scaleY * minfo.viewBox.height() - markerH;
2041
2042 const quint8 xRatio = minfo.preserveAspectRatio & MarkerNodeInfo::xMask;
2043 if (xRatio == MarkerNodeInfo::xMid)
2044 offsetX -= overflowX / 2;
2045 else if (xRatio == MarkerNodeInfo::xMax)
2046 offsetX -= overflowX;
2047
2048 const quint8 yRatio = minfo.preserveAspectRatio & MarkerNodeInfo::yMask;
2049 if (yRatio == MarkerNodeInfo::yMid)
2050 offsetY -= overflowY / 2;
2051 else if (yRatio == MarkerNodeInfo::yMax)
2052 offsetY -= overflowY;
2053 }
2054 }
2055
2056 const qreal anchorOffsetX = offsetX - minfo.anchorPoint.x() * scaleX;
2057 const qreal anchorOffsetY = offsetY - minfo.anchorPoint.y() * scaleY;
2058
2059 const qreal instanceAngle =
2060 minfo.orientation == MarkerNodeInfo::Orientation::Value ? minfo.angle : -angle;
2061
2062 auto *outerItem = new QQuickItem;
2063 auto outerXform = outerItem->transform();
2064 if (i == 0 && minfo.orientation == MarkerNodeInfo::Orientation::AutoStartReverse) {
2065 auto *flip = new QQuickScale(outerItem);
2066 flip->setXScale(-1);
2067 flip->setYScale(-1);
2068 outerXform.append(&outerXform, flip);
2069 }
2070 auto *rot = new QQuickRotation(outerItem);
2071 rot->setAngle(instanceAngle);
2072 outerXform.append(&outerXform, rot);
2073 auto *outerTr = new QQuickTranslate(outerItem);
2074 outerTr->setX(element.x);
2075 outerTr->setY(element.y);
2076 outerXform.append(&outerXform, outerTr);
2077 pushItem(outerItem);
2078
2079 QQuickItem *clipItem = nullptr;
2080 if (!minfo.clipBox.isEmpty()) {
2081 clipItem = new QQuickItem;
2082 const qreal unitScale =
2083 minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth ? sw : 1.0;
2084 clipItem->setX(minfo.clipBox.x() * unitScale);
2085 clipItem->setY(minfo.clipBox.y() * unitScale);
2086 clipItem->setWidth(minfo.clipBox.width() * unitScale);
2087 clipItem->setHeight(minfo.clipBox.height() * unitScale);
2088 clipItem->setClip(true);
2089 pushItem(clipItem);
2090 }
2091
2092 auto *innerItem = new QQuickItem;
2093 auto innerXform = innerItem->transform();
2094 auto *innerScale = new QQuickScale(innerItem);
2095 innerScale->setXScale(scaleX);
2096 innerScale->setYScale(scaleY);
2097 innerXform.append(&innerXform, innerScale);
2098 auto *innerTr = new QQuickTranslate(innerItem);
2099 innerTr->setX(anchorOffsetX);
2100 innerTr->setY(anchorOffsetY);
2101 innerXform.append(&innerXform, innerTr);
2102 if (clipItem) {
2103 auto *offsetItem = new QQuickItem;
2104 const qreal unitScale =
2105 minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth ? sw : 1.0;
2106 offsetItem->setX(-minfo.clipBox.x() * unitScale);
2107 offsetItem->setY(-minfo.clipBox.y() * unitScale);
2108 pushItem(offsetItem);
2109 }
2110 pushItem(innerItem);
2111
2112 for (const auto &step : markerDef.recording)
2113 step();
2114
2115 popItem();
2116 if (clipItem) {
2117 popItem();
2118 popItem();
2119 }
2120 popItem();
2121 }
2122}
2123
2124static QEasingCurve easingForAnimationFrame(const QQuickAnimatedProperty::PropertyAnimation &anim,
2125 int time,
2126 QMap<std::array<qreal, 4>, QEasingCurve> &cache)
2127{
2128 QEasingCurve easing;
2129 auto it = anim.easingPerFrame.constFind(time);
2130 if (it == anim.easingPerFrame.constEnd())
2131 return easing;
2132
2133 const QBezier &bezier = it.value();
2134 const QPointF c1 = bezier.pt2();
2135 const QPointF c2 = bezier.pt3();
2136 if (c1 == c1.transposed() && c2 == c2.transposed())
2137 return easing; // linear
2138
2139 const std::array<qreal, 4> key{ c1.x(), c1.y(), c2.x(), c2.y() };
2140 auto cacheIt = cache.constFind(key);
2141 if (cacheIt != cache.constEnd())
2142 return cacheIt.value();
2143
2144 easing.setType(QEasingCurve::BezierSpline);
2145 easing.addCubicBezierSegment(c1, c2, QPointF(1, 1));
2146 cache.insert(key, easing);
2147 return easing;
2148}
2149
2150static void completeParserStatus(QQuickAbstractAnimation *anim)
2151{
2152 if (auto *ps = qobject_cast<QQmlParserStatus *>(anim))
2153 ps->componentComplete();
2154}
2155
2157createSegmentAnimation(QObject *target, const QString &property, const QVariant &value,
2158 const QQuickAnimatedProperty::PropertyAnimation &anim, int frameTime,
2159 int time, QObject *parent,
2160 QMap<std::array<qreal, 4>, QEasingCurve> &easingCache)
2161{
2162 QQuickPropertyAnimation *segment = value.typeId() == QMetaType::QColor
2163 ? static_cast<QQuickPropertyAnimation *>(new QQuickColorAnimation(parent))
2164 : new QQuickPropertyAnimation(parent);
2165 segment->setTargetObject(target);
2166 segment->setProperty(property);
2167 segment->setDuration(frameTime);
2168 segment->setTo(value);
2169 segment->setEasing(easingForAnimationFrame(anim, time, easingCache));
2170 completeParserStatus(segment);
2171 return segment;
2172}
2173
2174static QQuickPropertyAction *createImmediateSetter(QObject *target, const QString &property,
2175 const QVariant &value, QObject *parent)
2176{
2177 auto *action = new QQuickPropertyAction(parent);
2178 action->setTargetObject(target);
2179 action->setProperty(property);
2180 action->setValue(value);
2181 completeParserStatus(action);
2182 return action;
2183}
2184
2185static QQuickAbstractAnimation *
2186createAnimationForOneEntry(QObject *target, const QString &property,
2187 const QQuickAnimatedProperty::PropertyAnimation &anim,
2188 const QVariant &defaultValue, QObject *parent,
2189 QMap<std::array<qreal, 4>, QEasingCurve> &easingCache)
2190{
2191 auto *outer = new QQuickSequentialAnimation(parent);
2192 auto outerAnims = outer->animations();
2193
2194 const int startOffset =
2196 if (startOffset > 0) {
2197 auto *pause = new QQuickPauseAnimation(outer);
2198 pause->setDuration(startOffset);
2199 completeParserStatus(pause);
2200 outerAnims.append(&outerAnims, pause);
2201 }
2202
2203 auto *inner = new QQuickSequentialAnimation(outer);
2204 inner->setLoops(anim.repeatCount < 0 ? QQuickAbstractAnimation::Infinite : anim.repeatCount);
2205 auto innerAnims = inner->animations();
2206
2207 int previousTime = 0;
2208 QVariant previousValue;
2209 bool havePreviousValue = false;
2210 for (auto it = anim.frames.constBegin(); it != anim.frames.constEnd(); ++it) {
2211 const int time = it.key();
2212 const int frameTime =
2214 const QVariant &value = it.value();
2215
2216 if (havePreviousValue && previousValue == value) {
2217 if (frameTime > 0) {
2218 auto *pause = new QQuickPauseAnimation(inner);
2219 pause->setDuration(frameTime);
2220 completeParserStatus(pause);
2221 innerAnims.append(&innerAnims, pause);
2222 }
2223 } else if (value.typeId() == QMetaType::Bool) {
2224 if (frameTime > 0) {
2225 auto *pause = new QQuickPauseAnimation(inner);
2226 pause->setDuration(frameTime);
2227 completeParserStatus(pause);
2228 innerAnims.append(&innerAnims, pause);
2229 }
2230 innerAnims.append(&innerAnims, createImmediateSetter(target, property, value, inner));
2231 } else if (frameTime > 0) {
2232 innerAnims.append(&innerAnims,
2233 createSegmentAnimation(target, property, value, anim, frameTime, time,
2234 inner, easingCache));
2235 } else {
2236 innerAnims.append(&innerAnims, createImmediateSetter(target, property, value, inner));
2237 }
2238
2239 previousTime = time;
2240 previousValue = value;
2241 havePreviousValue = true;
2242 }
2243
2244 if (!(anim.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
2245 innerAnims.append(&innerAnims,
2246 createImmediateSetter(target, property, defaultValue, inner));
2247 }
2248
2249 completeParserStatus(inner);
2250 outerAnims.append(&outerAnims, inner);
2251 completeParserStatus(outer);
2252 return outer;
2253}
2254
2255void QQuickItemGenerator::bindAnimatedProperty(QObject *target, const QString &propertyName,
2256 const QQuickAnimatedProperty &property)
2257{
2258 if (!target || !property.isAnimated())
2259 return;
2260
2261 if (m_animationProvider) {
2262 if (property.animationGroupCount() > 1 || property.animationCount() > 1) {
2263 qCWarning(lcQuickVectorImage)
2264 << "Property feature not implemented in timeline mode, for" << target
2265 << propertyName;
2266 }
2267 m_animationProvider->bindProperty(target, propertyName.toUtf8(), property);
2268 return;
2269 }
2270
2271 const QVariant fallback = property.defaultValue().isValid()
2272 ? property.defaultValue()
2273 : target->property(propertyName.toUtf8().constData());
2274
2275 if (property.animationCount() == 1) {
2276 const QQuickAnimatedProperty::PropertyAnimation &anim = property.animation(0);
2277 if (anim.frames.isEmpty())
2278 return;
2279
2280 auto *entry = createAnimationForOneEntry(target, propertyName, anim, fallback, target,
2281 m_easingCache);
2282 startMasterAnimation(entry);
2283 return;
2284 }
2285
2286 auto *master = new QQuickParallelAnimation(target);
2287 auto masterAnims = master->animations();
2288 for (int i = 0; i < property.animationCount(); ++i) {
2289 const QQuickAnimatedProperty::PropertyAnimation &anim = property.animation(i);
2290 if (anim.frames.isEmpty())
2291 continue;
2292
2293 masterAnims.append(&masterAnims,
2294 createAnimationForOneEntry(target, propertyName, anim, fallback, master,
2295 m_easingCache));
2296 }
2297 completeParserStatus(master);
2298 startMasterAnimation(master);
2299}
2300
2301void QQuickItemGenerator::bindColorWithOpacity(QObject *target, const QString &colorPropertyName,
2302 const QQuickAnimatedProperty &color,
2303 const QQuickAnimatedProperty &opacity,
2304 std::function<void(const QColor &)> setter)
2305{
2306 if (!opacity.isAnimated()) {
2307 QQuickAnimatedProperty colorOnTarget = color;
2308 colorOnTarget.setDefaultValue(QVariant{});
2309 bindAnimatedProperty(target, colorPropertyName, colorOnTarget);
2310 return;
2311 }
2312
2313 auto *appliedColor =
2314 new OpacityAppliedColor(std::move(setter), color.defaultValue().value<QColor>(),
2315 opacity.defaultValue().toReal(), target);
2316 bindAnimatedProperty(appliedColor, QStringLiteral("baseColor"), color);
2317 bindAnimatedProperty(appliedColor, QStringLiteral("opacity"), opacity);
2318}
2319
2320QQuickTransform *QQuickItemGenerator::createAnimatedTransformGroup(QQuickItem *item,
2321 const NodeInfo &info)
2322{
2323 if (!info.transform.isAnimated())
2324 return nullptr;
2325
2326 auto *baseGroup = new QQuickTransformGroup(item);
2327 QQuickTransformGroup *earliestOverrideGroup = nullptr;
2328 bool anyNonConstant = false;
2329
2330 for (int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
2331 int animStart = info.transform.animationGroup(groupIndex);
2332 int nextAnimStart = (groupIndex + 1 < info.transform.animationGroupCount())
2333 ? info.transform.animationGroup(groupIndex + 1)
2334 : info.transform.animationCount();
2335
2336 auto *subGroup = new QQuickTransformGroup(baseGroup);
2337 auto baseSeq = baseGroup->transformSequence();
2338 baseSeq.append(&baseSeq, subGroup);
2339
2340 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation =
2341 info.transform.animation(animStart);
2342 const bool replace = firstAnimation.flags
2343 & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
2344 const bool freeze =
2345 firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
2346 bool hasNonConstant = false;
2347
2348 if (replace && !earliestOverrideGroup)
2349 earliestOverrideGroup = subGroup;
2350
2351 for (int i = nextAnimStart - 1; i >= animStart; --i) {
2352 const QQuickAnimatedProperty::PropertyAnimation &anim = info.transform.animation(i);
2353 if (anim.frames.isEmpty())
2354 continue;
2355
2356 const QVariantList &firstParams = anim.frames.first().value<QVariantList>();
2357 auto subSeq = subGroup->transformSequence();
2358 if (!anim.isConstant())
2359 hasNonConstant = true;
2360
2361 switch (anim.subtype) {
2362 case QTransform::TxTranslate: {
2363 auto *translate = new QQuickTranslate(subGroup);
2364 if (anim.isConstant()) {
2365 const QPointF point = firstParams.value(0).value<QPointF>();
2366 translate->setX(point.x());
2367 translate->setY(point.y());
2368 } else {
2369 const QPointF defaultPoint = firstParams.value(0).value<QPointF>();
2370 translate->setX(defaultPoint.x());
2371 translate->setY(defaultPoint.y());
2372 bindAnimatedProperty(translate, QStringLiteral("x"),
2373 makeAnimatedProperty(info.transform,
2374 extractAnimation(anim, extractPointX),
2375 0.0));
2376 bindAnimatedProperty(translate, QStringLiteral("y"),
2377 makeAnimatedProperty(info.transform,
2378 extractAnimation(anim, extractPointY),
2379 0.0));
2380 }
2381 subSeq.append(&subSeq, translate);
2382 break;
2383 }
2384 case QTransform::TxScale: {
2385 auto *scale = new QQuickScale(subGroup);
2386 if (anim.isConstant()) {
2387 const QPointF point = firstParams.value(0).value<QPointF>();
2388 scale->setXScale(point.x());
2389 scale->setYScale(point.y());
2390 } else {
2391 const QPointF defaultPoint = firstParams.value(0).value<QPointF>();
2392 scale->setXScale(defaultPoint.x());
2393 scale->setYScale(defaultPoint.y());
2394 bindAnimatedProperty(scale, QStringLiteral("xScale"),
2395 makeAnimatedProperty(info.transform,
2396 extractAnimation(anim, extractPointX),
2397 1.0));
2398 bindAnimatedProperty(scale, QStringLiteral("yScale"),
2399 makeAnimatedProperty(info.transform,
2400 extractAnimation(anim, extractPointY),
2401 1.0));
2402 }
2403 subSeq.append(&subSeq, scale);
2404 break;
2405 }
2406 case QTransform::TxRotate: {
2407 auto *rotation = new QQuickRotation(subGroup);
2408 bool hasCenter = false;
2409 for (auto it = anim.frames.constBegin(); it != anim.frames.constEnd(); ++it) {
2410 if (!it->value<QVariantList>().value(0).value<QPointF>().isNull()) {
2411 hasCenter = true;
2412 break;
2413 }
2414 }
2415 if (anim.isConstant()) {
2416 const QPointF center = firstParams.value(0).value<QPointF>();
2417 const qreal angle = firstParams.value(1).toReal();
2418 rotation->setAngle(angle);
2419 rotation->setOrigin(QVector3D(center));
2420 } else {
2421 const QPointF defaultCenter = firstParams.value(0).value<QPointF>();
2422 const qreal defaultAngle = firstParams.value(1).toReal();
2423 rotation->setAngle(defaultAngle);
2424 rotation->setOrigin(QVector3D(defaultCenter));
2425 if (hasCenter) {
2426 auto extractOrigin = [](const QVariant &v) {
2427 return QVariant::fromValue(QVector3D(v.toList().value(0).toPointF()));
2428 };
2429 bindAnimatedProperty(
2430 rotation, QStringLiteral("origin"),
2431 makeAnimatedProperty(info.transform,
2432 extractAnimation(anim, extractOrigin),
2433 QVariant::fromValue(QVector3D(0, 0, 0))));
2434 }
2435 auto extractAngle = [](const QVariant &v) {
2436 return QVariant(v.toList().value(1).toReal());
2437 };
2438 bindAnimatedProperty(rotation, QStringLiteral("angle"),
2439 makeAnimatedProperty(info.transform,
2440 extractAnimation(anim, extractAngle),
2441 0.0));
2442 }
2443 subSeq.append(&subSeq, rotation);
2444 break;
2445 }
2446 case QTransform::TxShear: {
2447 auto *shear = new QQuickShear(subGroup);
2448 if (anim.isConstant()) {
2449 const QPointF point = firstParams.value(0).value<QPointF>();
2450 shear->setXAngle(point.x());
2451 shear->setYAngle(point.y());
2452 } else {
2453 const QPointF defaultPoint = firstParams.value(0).value<QPointF>();
2454 shear->setXAngle(defaultPoint.x());
2455 shear->setYAngle(defaultPoint.y());
2456 bindAnimatedProperty(shear, QStringLiteral("xAngle"),
2457 makeAnimatedProperty(info.transform,
2458 extractAnimation(anim, extractPointX),
2459 0.0));
2460 bindAnimatedProperty(shear, QStringLiteral("yAngle"),
2461 makeAnimatedProperty(info.transform,
2462 extractAnimation(anim, extractPointY),
2463 0.0));
2464 }
2465 subSeq.append(&subSeq, shear);
2466 break;
2467 }
2468 default:
2469 break;
2470 }
2471 }
2472
2473 anyNonConstant = anyNonConstant || hasNonConstant;
2474
2475 if (replace && hasNonConstant) {
2476 const int startOffsetMs = QQuickVectorImageGenerator::Utils::processAnimationTime(
2477 firstAnimation.startOffset);
2478
2479 auto *activateSeq = new QQuickSequentialAnimation(item);
2480 auto activateAnims = activateSeq->animations();
2481 if (startOffsetMs > 0) {
2482 auto *pause = new QQuickPauseAnimation(activateSeq);
2483 pause->setDuration(startOffsetMs);
2484 completeParserStatus(pause);
2485 activateAnims.append(&activateAnims, pause);
2486 }
2487 auto *activateAction = new QQuickFunctionAction(
2488 [baseGroup, subGroup]() { baseGroup->activateOverride(subGroup); },
2489 activateSeq);
2490 completeParserStatus(activateAction);
2491 activateAnims.append(&activateAnims, activateAction);
2492 completeParserStatus(activateSeq);
2493 startMasterAnimation(activateSeq);
2494
2495 if (firstAnimation.repeatCount >= 0) {
2496 const int loopDurationMs = firstAnimation.frames.isEmpty()
2497 ? 0
2498 : QQuickVectorImageGenerator::Utils::processAnimationTime(
2499 firstAnimation.frames.lastKey());
2500 const int totalDurationMs =
2501 startOffsetMs + loopDurationMs * qMax(firstAnimation.repeatCount, 1);
2502
2503 auto *endSeq = new QQuickSequentialAnimation(item);
2504 auto endAnims = endSeq->animations();
2505 if (totalDurationMs > 0) {
2506 auto *pause = new QQuickPauseAnimation(endSeq);
2507 pause->setDuration(totalDurationMs);
2508 completeParserStatus(pause);
2509 endAnims.append(&endAnims, pause);
2510 }
2511 auto *endAction = new QQuickFunctionAction(
2512 [baseGroup, subGroup, freeze]() {
2513 if (!freeze)
2514 baseGroup->deactivate(subGroup);
2515 },
2516 endSeq);
2517 completeParserStatus(endAction);
2518 endAnims.append(&endAnims, endAction);
2519 completeParserStatus(endSeq);
2520 startMasterAnimation(endSeq);
2521 }
2522 }
2523 }
2524
2525 if (!info.isDefaultTransform) {
2526 const QTransform transform = info.transform.defaultValue().value<QTransform>();
2527 auto *staticMatrix = new QQuickMatrix4x4(baseGroup);
2528 QMatrix4x4 m(transform);
2529 m.optimize();
2530 staticMatrix->setMatrix(m);
2531 auto baseSeq = baseGroup->transformSequence();
2532 baseSeq.append(&baseSeq, staticMatrix);
2533 }
2534
2535 if (!anyNonConstant && earliestOverrideGroup)
2536 baseGroup->activateOverride(earliestOverrideGroup);
2537
2538 return baseGroup;
2539}
2540
2541void QQuickItemGenerator::bindMotionPath(QQuickItem *item, const QQuickAnimatedProperty &motionPath)
2542{
2543 if (!motionPath.isAnimated() || motionPath.animationCount() == 0)
2544 return;
2545
2546 const QVariantList defaultProps = motionPath.defaultValue().value<QVariantList>();
2547 const QPainterPath path = defaultProps.value(0).value<QPainterPath>();
2548 const bool adaptAngle = defaultProps.value(1).toBool();
2549 const qreal baseRotation = defaultProps.value(2).toReal();
2550
2551 const QQuickAnimatedProperty::PropertyAnimation &pathAnim = motionPath.animation(0);
2552
2553 auto *pathObj = new QQuickPath(item);
2554 auto *pathStatus = qobject_cast<QQmlParserStatus *>(pathObj);
2555 pathStatus->classBegin();
2556 auto *pathSvg = new QQuickPathSvg(pathObj);
2557 auto pathElements = pathObj->pathElements();
2558 pathElements.append(&pathElements, pathSvg);
2559 pathSvg->setPath(QQuickVectorImageGenerator::Utils::toSvgString(path));
2560
2561 auto *interpolator = new QQuickPathInterpolator(item);
2562 interpolator->setPath(pathObj);
2563 pathStatus->componentComplete();
2564
2565 QQuickAnimatedProperty::PropertyAnimation progressAnim;
2566 progressAnim.frames = pathAnim.frames;
2567 progressAnim.easingPerFrame = pathAnim.easingPerFrame;
2568 progressAnim.flags = QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
2569
2570 bindAnimatedProperty(interpolator, QStringLiteral("progress"),
2571 makeAnimatedProperty(motionPath, progressAnim));
2572
2573 auto *translate = new QQuickTranslate(item);
2574 QObject::connect(interpolator, &QQuickPathInterpolator::xChanged, translate,
2575 [translate, interpolator]() { translate->setX(interpolator->x()); });
2576 QObject::connect(interpolator, &QQuickPathInterpolator::yChanged, translate,
2577 [translate, interpolator]() { translate->setY(interpolator->y()); });
2578 const qreal initialProgress = qBound(0.0, interpolator->property("progress").toDouble(), 1.0);
2579 const QPointF initialPoint = path.pointAtPercent(initialProgress);
2580 translate->setX(initialPoint.x());
2581 translate->setY(initialPoint.y());
2582
2583 if (adaptAngle || !qFuzzyIsNull(baseRotation)) {
2584 auto *rotation = new QQuickRotation(item);
2585 if (adaptAngle) {
2586 QObject::connect(interpolator, &QQuickPathInterpolator::angleChanged, rotation,
2587 [rotation, interpolator, baseRotation]() {
2588 rotation->setAngle(interpolator->angle() + baseRotation);
2589 });
2590 rotation->setAngle(interpolator->angle() + baseRotation);
2591 } else {
2592 rotation->setAngle(baseRotation);
2593 }
2594 auto transformProp = item->transform();
2595 transformProp.append(&transformProp, rotation);
2596 }
2597 auto transformProp = item->transform();
2598 transformProp.append(&transformProp, translate);
2599}
2600
2601void QQuickItemGenerator::generateItemAnimations(QQuickItem *item, const NodeInfo &info)
2602{
2603 const bool hasTransformAnim = info.transform.isAnimated();
2604 const bool hasOpacityAnim = info.opacity.isAnimated();
2605 const bool hasMotionPathAnim = info.motionPath.isAnimated();
2606 const bool hasLinkedTransform = !info.transformReferenceId.isEmpty();
2607
2608 if (hasOpacityAnim)
2609 bindAnimatedProperty(item, QStringLiteral("opacity"), info.opacity);
2610
2611 if (hasTransformAnim) {
2612 auto *baseGroup = createAnimatedTransformGroup(item, info);
2613 if (baseGroup) {
2614 auto transformProp = item->transform();
2615 transformProp.append(&transformProp, baseGroup);
2616 }
2617 }
2618
2619 if (hasMotionPathAnim)
2620 bindMotionPath(item, info.motionPath);
2621
2622 if (hasLinkedTransform) {
2623 auto *linkedMatrix = new QQuickMatrix4x4(item);
2624 auto transformProp = item->transform();
2625 transformProp.append(&transformProp, linkedMatrix);
2626 m_pendingLinkedTransforms.append({ item, info.transformReferenceId, linkedMatrix });
2627 }
2628}
2629
2630QT_END_NAMESPACE
2631
2632#include "qquickitemgenerator.moc"
void updateState(State newState, State oldState) override
QQuickCallbackAnimationJob(std::function< void()> function)
QAbstractAnimationJob * transition(QQuickStateActions &, QQmlProperties &, TransitionDirection, QObject *=nullptr) override
QQuickFunctionAction(std::function< void()> function, QObject *parent=nullptr)
int processAnimationTime(int timeMs)
Definition utils_p.h:239
Combined button and popup list for selecting options.
static qreal meanAngle(QPointF p0, QPointF p1, QPointF p2)
static void completeParserStatus(QQuickAbstractAnimation *anim)
static QQuickAnimatedProperty makeAnimatedProperty(const QQuickAnimatedProperty &owner, const QQuickAnimatedProperty::PropertyAnimation &anim, const QVariant &defaultValue=QVariant{})
static QRectF resolveRect(const QRectF &rect, FilterNodeInfo::CoordinateSystem cs, const QRectF &itemBounds)
static QQuickShaderEffect * makeFilterEffect(QQuickItem *inputItem, QQuickItem *parent)
static QQuickPropertyAction * createImmediateSetter(QObject *target, const QString &property, const QVariant &value, QObject *parent)
static QQuickAnimatedProperty::PropertyAnimation extractAnimation(const QQuickAnimatedProperty::PropertyAnimation &anim, const std::function< QVariant(const QVariant &)> &extractor)
static QQuickShaderEffectSource * makeSES(QQuickItem *item, const QRectF &rect, QQuickItem *parent)
static QVariant extractPointY(const QVariant &value)
static QVariant extractPointX(const QVariant &value)
static QQuickPropertyAnimation * createSegmentAnimation(QObject *target, const QString &property, const QVariant &value, const QQuickAnimatedProperty::PropertyAnimation &anim, int frameTime, int time, QObject *parent, QMap< std::array< qreal, 4 >, QEasingCurve > &easingCache)
static QEasingCurve easingForAnimationFrame(const QQuickAnimatedProperty::PropertyAnimation &anim, int time, QMap< std::array< qreal, 4 >, QEasingCurve > &cache)
static QQuickAbstractAnimation * createAnimationForOneEntry(QObject *target, const QString &property, const QQuickAnimatedProperty::PropertyAnimation &anim, const QVariant &defaultValue, QObject *parent, QMap< std::array< qreal, 4 >, QEasingCurve > &easingCache)
static QQuickShapeGradient * createShapeGradient(const QGradient &grad, const QRectF &coordSys, QObject *parent)
static QQuickShaderEffectSource * makeEffectSES(QQuickShaderEffect *effect, const QRectF &stepRect, const QRectF &filterRect, QQuickItem *parent)