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