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