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