8#include <private/qsgcurveprocessor_p.h>
9#include <private/qquickshape_p.h>
10#include <private/qquadpath_p.h>
11#include <private/qquickitem_p.h>
12#include <private/qquickimagebase_p_p.h>
13#include <private/qquicktext_p.h>
14#include <private/qquicktranslate_p.h>
15#include <private/qquickimage_p.h>
17#include <QtCore/qloggingcategory.h>
18#include <QtCore/qdir.h>
19#include <QtCore/qstandardpaths.h>
23using namespace Qt::StringLiterals;
29 s.replace(QLatin1Char(
'\\'), QLatin1String(
"\\\\"));
30 s.replace(QLatin1Char(
'"'), QLatin1String(
"\\\""));
35QQuickAnimatedProperty::PropertyAnimation QQuickAnimatedProperty::PropertyAnimation::simplified()
const
37 QQuickAnimatedProperty::PropertyAnimation res = *
this;
38 int consecutiveEquals = 0;
39 int prevTimePoint = -1;
41 for (
const auto &[timePoint, value] : frames.asKeyValueRange()) {
42 if (value != prevValue) {
43 consecutiveEquals = 1;
45 }
else if (consecutiveEquals < 2) {
49 res.frames.remove(prevTimePoint);
50 res.easingPerFrame.remove(prevTimePoint);
52 prevTimePoint = timePoint;
58QQuickQmlGenerator::QQuickQmlGenerator(
const QQuickVectorImageSource &source,
59 QQuickVectorImageGenerator::GeneratorFlags flags,
60 const QString &outFileName)
61 : QQuickGenerator(source, flags)
62 , outputFileName(outFileName)
64 m_result.open(QIODevice::ReadWrite);
65 m_oldIndentLevels.push(0);
67 if (outFileName.isEmpty()) {
68 setRetainFilePaths(
true);
69 setAssetFileDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
70 setAssetFilePrefix(QStringLiteral(
"_qt_vectorimage_"));
71 setUrlPrefix(QStringLiteral(
"file:"));
75QQuickQmlGenerator::~QQuickQmlGenerator()
79bool QQuickQmlGenerator::save()
81 if (Q_UNLIKELY(errorState()))
85 if (!outputFileName.isEmpty()) {
86 QFileInfo fileInfo(outputFileName);
87 QDir dir(fileInfo.absolutePath());
88 if (!dir.exists() && !dir.mkpath(QStringLiteral(
"."))) {
89 qCWarning(lcQuickVectorImage) <<
"Failed to create path" << dir.absolutePath();
92 QFile outFile(outputFileName);
93 if (outFile.open(QIODevice::WriteOnly)) {
94 outFile.write(m_result.data());
97 qCWarning(lcQuickVectorImage) <<
"Failed to write to file" << outFile.fileName();
103 if (lcQuickVectorImage().isDebugEnabled())
104 qCDebug(lcQuickVectorImage).noquote() << m_result.data().left(300);
109void QQuickQmlGenerator::setShapeTypeName(
const QString &name)
111 m_shapeTypeName = name.toLatin1();
114QString QQuickQmlGenerator::shapeTypeName()
const
116 return QString::fromLatin1(m_shapeTypeName);
119void QQuickQmlGenerator::setCommentString(
const QString commentString)
121 m_commentString = commentString;
124QString QQuickQmlGenerator::commentString()
const
126 return m_commentString;
129QString QQuickQmlGenerator::generateNodeBase(
const NodeInfo &info,
const QString &idSuffix)
131 static qint64 maxNodes = qEnvironmentVariableIntegerValue(
"QT_QUICKVECTORIMAGE_MAX_NODES").value_or(10000);
132 if (Q_UNLIKELY(!checkSanityLimit(++m_nodeCounter, maxNodes,
"nodes"_L1)))
135 if (!info.nodeId.isEmpty())
136 stream() <<
"objectName: \"" << info.nodeId <<
"\"";
138 if (!info.id.isEmpty())
139 stream() <<
"id: " << info.id << idSuffix;
141 if (!info.bounds.isNull() || !info.boundsReferenceId.isEmpty()) {
142 stream() <<
"property var originalBounds: ";
143 if (!info.bounds.isNull()) {
144 stream(SameLine) <<
"Qt.rect(" << info.bounds.x() <<
", " << info.bounds.y() <<
", "
145 << info.bounds.width() <<
", " << info.bounds.height() <<
")";
147 stream(SameLine) << info.boundsReferenceId <<
".originalBounds";
149 stream() <<
"width: originalBounds.width";
150 stream() <<
"height: originalBounds.height";
153 stream() <<
"transformOrigin: Item.TopLeft";
155 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
156 if (!info.isDefaultOpacity)
157 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
158 generateItemAnimations(info.id, info);
164void QQuickQmlGenerator::generateNodeEnd(
const NodeInfo &info)
166 if (Q_UNLIKELY(errorState()))
170 generateShaderUse(info);
173void QQuickQmlGenerator::generateItemAnimations(
const QString &idString,
const NodeInfo &info)
175 const bool hasTransform = info.transform.isAnimated()
176 || !info.maskId.isEmpty()
177 || !info.filterId.isEmpty()
178 || !info.isDefaultTransform
179 || !info.transformReferenceId.isEmpty()
180 || info.motionPath.isAnimated();
183 stream() <<
"transform: TransformGroup {";
186 bool hasNonConstantTransform =
false;
187 int earliestOverrideGroup = -1;
189 if (!idString.isEmpty()) {
190 stream() <<
"id: " << idString <<
"_transform_base_group";
192 if (!info.maskId.isEmpty() || !info.filterId.isEmpty())
193 stream() <<
"Translate { x: " << idString <<
".sourceX; y: " << idString <<
".sourceY }";
195 if (info.transform.isAnimated()) {
196 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
197 stream() <<
"TransformGroup {";
200 if (!idString.isEmpty())
201 stream() <<
"id: " << idString <<
"_transform_group_" << groupIndex;
203 int animationStart = info.transform.animationGroup(groupIndex);
204 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
205 ? info.transform.animationGroup(groupIndex + 1)
206 : info.transform.animationCount();
208 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
209 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
210 if (replace && earliestOverrideGroup < 0)
211 earliestOverrideGroup = groupIndex;
213 for (
int i = nextAnimationStart - 1; i >= animationStart; --i) {
214 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
215 if (animation.frames.isEmpty())
218 const QVariantList ¶meters = animation.frames.first().value<QVariantList>();
219 switch (animation.subtype) {
220 case QTransform::TxTranslate:
221 if (animation.isConstant()) {
222 const QPointF translation = parameters.value(0).value<QPointF>();
223 if (!translation.isNull())
224 stream() <<
"Translate { x: " << translation.x() <<
"; y: " << translation.y() <<
" }";
226 hasNonConstantTransform =
true;
227 stream() <<
"Translate { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
230 case QTransform::TxScale:
231 if (animation.isConstant()) {
232 const QPointF scale = parameters.value(0).value<QPointF>();
233 if (scale != QPointF(1, 1))
234 stream() <<
"Scale { xScale: " << scale.x() <<
"; yScale: " << scale.y() <<
" }";
236 hasNonConstantTransform =
true;
237 stream() <<
"Scale { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
"}";
240 case QTransform::TxRotate:
241 if (animation.isConstant()) {
242 const QPointF center = parameters.value(0).value<QPointF>();
243 const qreal angle = parameters.value(1).toReal();
244 if (!qFuzzyIsNull(angle))
245 stream() <<
"Rotation { angle: " << angle <<
"; origin.x: " << center.x() <<
"; origin.y: " << center.y() <<
" }";
247 hasNonConstantTransform =
true;
248 stream() <<
"Rotation { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
251 case QTransform::TxShear:
252 if (animation.isConstant()) {
253 const QPointF skew = parameters.value(0).value<QPointF>();
255 stream() <<
"Shear { xAngle: " << skew.x() <<
"; yAngle: " << skew.y() <<
" }";
257 hasNonConstantTransform =
true;
258 stream() <<
"Shear { id: " << idString <<
"_transform_" << groupIndex <<
"_" << i <<
" }";
271 if (info.motionPath.isAnimated()) {
272 QVariantList defaultProps = info.motionPath.defaultValue().value<QVariantList>();
273 const bool adaptAngle = defaultProps.value(1).toBool();
274 const qreal baseRotation = defaultProps.value(2).toReal();
275 QString interpolatorId = idString + QStringLiteral(
"_motion_interpolator");
276 if (adaptAngle || !qFuzzyIsNull(baseRotation)) {
277 stream() <<
"Rotation {";
281 stream() <<
"angle: " << interpolatorId <<
".angle";
282 if (!qFuzzyIsNull(baseRotation))
283 stream(SameLine) <<
" + " << baseRotation;
285 stream() <<
"angle: " << baseRotation;
292 stream() <<
"Translate {";
295 stream() <<
"x: " << interpolatorId <<
".x";
296 stream() <<
"y: " << interpolatorId <<
".y";
303 if (!info.isDefaultTransform) {
304 QTransform xf = info.transform.defaultValue().value<QTransform>();
305 if (xf.type() <= QTransform::TxTranslate) {
306 stream() <<
"Translate { x: " << xf.dx() <<
"; y: " << xf.dy() <<
"}";
308 stream() <<
"Matrix4x4 { matrix: ";
309 generateTransform(xf);
310 stream(SameLine) <<
"}";
314 if (!info.transformReferenceId.isEmpty())
315 stream() <<
"Matrix4x4 { matrix: " << info.transformReferenceId <<
".transformMatrix }";
320 if (hasNonConstantTransform) {
321 generateAnimateTransform(idString, info);
322 }
else if (info.transform.isAnimated() && earliestOverrideGroup >= 0) {
325 stream() <<
"Component.onCompleted: {";
328 stream() << idString <<
"_transform_base_group.activateOverride("
329 << idString <<
"_transform_group_" << earliestOverrideGroup <<
")";
336 generateAnimateMotionPath(idString, info.motionPath);
338 generatePropertyAnimation(info.opacity, idString, QStringLiteral(
"opacity"));
341void QQuickQmlGenerator::generateShaderUse(
const NodeInfo &info)
343 const bool hasMask = !info.maskId.isEmpty();
344 const bool hasFilters = !info.filterId.isEmpty();
345 if (!hasMask && !hasFilters)
348 const QString effectId = hasFilters
349 ? info.filterId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_effect")
352 QString animatedItemId;
354 stream() <<
"ShaderEffectSource {";
357 const QString seId = info.id + QStringLiteral(
"_se");
358 stream() <<
"id: " << seId;
360 stream() <<
"ItemSpy {";
362 stream() <<
"id: " << info.id <<
"_itemspy";
363 stream() <<
"anchors.fill: parent";
367 stream() <<
"hideSource: true";
368 stream() <<
"wrapMode: " << info.filterId <<
"_filterParameters.wrapMode";
369 stream() <<
"sourceItem: " << info.id;
370 stream() <<
"sourceRect: " << info.filterId
371 <<
"_filterParameters.adaptToFilterRect("
372 << info.id <<
".originalBounds.x, "
373 << info.id <<
".originalBounds.y, "
374 << info.id <<
".originalBounds.width, "
375 << info.id <<
".originalBounds.height)";
376 stream() <<
"textureSize: " << info.id <<
"_itemspy.requiredTextureSize";
377 stream() <<
"width: sourceRect.width";
378 stream() <<
"height: sourceRect.height";
379 stream() <<
"visible: false";
384 stream() <<
"Loader {";
387 animatedItemId = effectId;
388 stream() <<
"id: " << effectId;
390 stream() <<
"property var filterSourceItem: " << seId;
391 stream() <<
"sourceComponent: " << info.filterId <<
"_container";
392 stream() <<
"property real sourceX: " << info.id <<
".originalBounds.x";
393 stream() <<
"property real sourceY: " << info.id <<
".originalBounds.y";
394 stream() <<
"width: " << info.id <<
".originalBounds.width";
395 stream() <<
"height: " << info.id <<
".originalBounds.height";
405 stream() <<
"ShaderEffectSource {";
408 const QString maskId = info.maskId + QStringLiteral(
"_") + info.id + QStringLiteral(
"_mask");
409 stream() <<
"id: " << maskId;
410 stream() <<
"sourceItem: " << info.maskId;
411 stream() <<
"visible: false";
412 stream() <<
"hideSource: true";
414 if (m_contentRelativeMasks.contains(info.maskId)) {
415 stream() <<
"Binding { target: " << info.maskId
416 <<
"; property: \"contentX\"; value: " << info.id <<
".originalBounds.x }";
417 stream() <<
"Binding { target: " << info.maskId
418 <<
"; property: \"contentY\"; value: " << info.id <<
".originalBounds.y }";
419 stream() <<
"Binding { target: " << info.maskId
420 <<
"; property: \"contentWidth\"; value: " << info.id
421 <<
".originalBounds.width }";
422 stream() <<
"Binding { target: " << info.maskId
423 <<
"; property: \"contentHeight\"; value: " << info.id
424 <<
".originalBounds.height }";
427 stream() <<
"ItemSpy {";
429 stream() <<
"id: " << maskId <<
"_itemspy";
430 stream() <<
"anchors.fill: parent";
433 stream() <<
"textureSize: " << maskId <<
"_itemspy.requiredTextureSize";
435 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
436 << info.id <<
".originalBounds.x,"
437 << info.id <<
".originalBounds.y,"
438 << info.id <<
".originalBounds.width,"
439 << info.id <<
".originalBounds.height)";
441 stream() <<
"width: sourceRect.width";
442 stream() <<
"height: sourceRect.height";
448 stream() <<
"ShaderEffectSource {";
451 const QString seId = info.id + QStringLiteral(
"_masked_se");
452 stream() <<
"id: " << seId;
454 stream() <<
"ItemSpy {";
456 stream() <<
"id: " << info.id <<
"_masked_se_itemspy";
457 stream() <<
"anchors.fill: parent";
461 stream() <<
"hideSource: true";
463 stream() <<
"sourceItem: " << effectId;
465 stream() <<
"sourceItem: " << info.id;
466 stream() <<
"textureSize: " << info.id <<
"_masked_se_itemspy.requiredTextureSize";
468 stream() <<
"sourceRect: " << info.maskId <<
".maskRect("
469 << info.id <<
".originalBounds.x,"
470 << info.id <<
".originalBounds.y,"
471 << info.id <<
".originalBounds.width,"
472 << info.id <<
".originalBounds.height)";
474 stream() <<
"sourceRect: " << info.maskId <<
".maskRect(0, 0,"
475 << info.id <<
".originalBounds.width,"
476 << info.id <<
".originalBounds.height)";
478 stream() <<
"width: sourceRect.width";
479 stream() <<
"height: sourceRect.height";
480 stream() <<
"smooth: false";
481 stream() <<
"visible: false";
486 stream() <<
"ShaderEffect {";
489 const QString maskShaderId = maskId + QStringLiteral(
"_se");
490 animatedItemId = maskShaderId;
492 stream() <<
"id:" << maskShaderId;
494 stream() <<
"property real sourceX: " << maskId <<
".sourceRect.x";
495 stream() <<
"property real sourceY: " << maskId <<
".sourceRect.y";
496 stream() <<
"width: " << maskId <<
".sourceRect.width";
497 stream() <<
"height: " << maskId <<
".sourceRect.height";
499 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb\"";
500 stream() <<
"property var source: " << seId;
501 stream() <<
"property var maskSource: " << maskId;
502 stream() <<
"property bool isAlpha: " << (info.isMaskAlpha ?
"true" :
"false");
503 stream() <<
"property bool isInverted: " << (info.isMaskInverted ?
"true" :
"false");
506 if (!info.isDefaultOpacity)
507 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
509 generateItemAnimations(animatedItemId, info);
515bool QQuickQmlGenerator::generateDefsNode(
const StructureNodeInfo &info)
517 if (Q_UNLIKELY(errorState()))
520 if (info.stage == StructureNodeStage::Start) {
521 m_oldIndentLevels.push(m_indentLevel);
523 stream() <<
"Component {";
526 stream() <<
"id: " << info.id <<
"_container";
528 stream() <<
"Item {";
531 generateTimelineFields(info);
532 if (!info.transformReferenceChildId.isEmpty()) {
533 stream() <<
"property alias transformMatrix: "
534 << info.transformReferenceChildId <<
".transformMatrix";
537 generateNodeBase(info, QStringLiteral(
"_defs"));
539 generateNodeEnd(info);
544 stream() << m_defsSuffix;
545 m_defsSuffix.clear();
547 m_indentLevel = m_oldIndentLevels.pop();
553void QQuickQmlGenerator::generateImageNode(
const ImageNodeInfo &info)
555 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
558 const QFileInfo outputFileInfo(outputFileName);
559 const QDir outputDir(outputFileInfo.absolutePath());
563 if (!m_retainFilePaths || info.externalFileReference.isEmpty()) {
564 filePath = m_assetFileDirectory;
565 if (filePath.isEmpty())
566 filePath = outputDir.absolutePath();
568 if (!filePath.isEmpty() && !filePath.endsWith(u'/'))
571 QDir fileDir(filePath);
572 if (!fileDir.exists()) {
573 if (!fileDir.mkpath(QStringLiteral(
".")))
574 qCWarning(lcQuickVectorImage) <<
"Failed to create image resource directory:" << filePath;
577 filePath += QStringLiteral(
"%1%2.png").arg(m_assetFilePrefix.isEmpty()
578 ? QStringLiteral(
"svg_asset_")
580 .arg(info.image.cacheKey());
582 if (!info.image.save(filePath))
583 qCWarning(lcQuickVectorImage) <<
"Unabled to save image resource" << filePath;
584 qCDebug(lcQuickVectorImage) <<
"Saving copy of IMAGE" << filePath;
586 filePath = info.externalFileReference;
589 const QFileInfo assetFileInfo(filePath);
591 registerGraphicItem();
592 stream() <<
"Image {";
595 generateNodeBase(info);
596 stream() <<
"x: " << info.rect.x();
597 stream() <<
"y: " << info.rect.y();
598 stream() <<
"width: " << info.rect.width();
599 stream() <<
"height: " << info.rect.height();
600 stream() <<
"source: \"" << m_urlPrefix << outputDir.relativeFilePath(assetFileInfo.absoluteFilePath()) <<
"\"";
601 generateNodeEnd(info);
604void QQuickQmlGenerator::generateMarkers(
const PathNodeInfo &info)
606 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
607 for (
int i = 0; i < path.elementCount(); ++i) {
608 const QPainterPath::Element element = path.elementAt(i);
613 auto getMeanAngle = [](QPointF p0, QPointF p1, QPointF p2) -> qreal {
614 QPointF t1 = p1 - p0;
615 QPointF t2 = p2 - p1;
616 qreal hyp1 = hypot(t1.x(), t1.y());
621 qreal hyp2 = hypot(t2.x(), t2.y());
626 QPointF tangent = t1 + t2;
627 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.;
631 markerId = info.markerStartId;
632 angle = path.angleAtPercent(0.0);
633 }
else if (i == path.elementCount() - 1) {
634 markerId = info.markerEndId;
635 angle = path.angleAtPercent(1.0);
636 }
else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
637 markerId = info.markerMidId;
639 const QPainterPath::Element prevElement = path.elementAt(i - 1);
640 const QPainterPath::Element nextElement = path.elementAt(i + 1);
642 QPointF p1(prevElement.x, prevElement.y);
643 QPointF p2(element.x, element.y);
644 QPointF p3(nextElement.x, nextElement.y);
646 angle = getMeanAngle(p1, p2, p3);
649 if (!markerId.isEmpty()) {
650 stream() <<
"Loader {";
654 stream() <<
"sourceComponent: " << markerId <<
"_container";
655 stream() <<
"property real strokeWidth: " << info.strokeStyle.width.defaultValue().toReal();
656 stream() <<
"transform: [";
659 stream() <<
"Scale { "
660 <<
"xScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1; "
661 <<
"yScale: " << markerId <<
"_markerParameters.startReversed ? -1 : 1 },";
663 stream() <<
"Rotation { angle: " << markerId <<
"_markerParameters.autoAngle(" << -angle <<
") },";
664 stream() <<
"Translate { x: " << element.x <<
"; y: " << element.y <<
"}";
675void QQuickQmlGenerator::generatePath(
const PathNodeInfo &info,
const QRectF &overrideBoundingRect)
677 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
680 if (m_inShapeItemLevel > 0) {
681 if (!info.isDefaultTransform)
682 qWarning() <<
"Skipped transform for node" << info.nodeId <<
"type" << info.typeName <<
"(this is not supposed to happen)";
683 optimizePaths(info, overrideBoundingRect);
685 m_inShapeItemLevel++;
686 stream() << shapeName() <<
" {";
689 generateNodeBase(info);
691 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
692 stream() <<
"preferredRendererType: Shape.CurveRenderer";
693 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
694 stream() <<
"asynchronous: true";
695 optimizePaths(info, overrideBoundingRect);
698 if (!info.markerStartId.isEmpty()
699 || !info.markerMidId.isEmpty()
700 || !info.markerEndId.isEmpty()) {
701 generateMarkers(info);
704 generateNodeEnd(info);
705 m_inShapeItemLevel--;
709void QQuickQmlGenerator::generateGradient(
const QGradient *grad,
710 const QString &propertyName,
711 const QRectF &coordinateConversion)
713 const QSizeF &scale = coordinateConversion.size();
714 const QPointF &translation = coordinateConversion.topLeft();
716 if (grad->type() == QGradient::LinearGradient) {
717 auto *linGrad =
static_cast<
const QLinearGradient *>(grad);
718 stream() << propertyName <<
": LinearGradient {";
721 QRectF gradRect(linGrad->start(), linGrad->finalStop());
723 stream() <<
"x1: " << (gradRect.left() * scale.width()) + translation.x();
724 stream() <<
"y1: " << (gradRect.top() * scale.height()) + translation.y();
725 stream() <<
"x2: " << (gradRect.right() * scale.width()) + translation.x();
726 stream() <<
"y2: " << (gradRect.bottom() * scale.height()) + translation.y();
727 for (
auto &stop : linGrad->stops())
728 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
729 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
730 }
else if (grad->type() == QGradient::RadialGradient) {
731 auto *radGrad =
static_cast<
const QRadialGradient*>(grad);
732 stream() << propertyName <<
": RadialGradient {";
735 stream() <<
"centerX: " << (radGrad->center().x() * scale.width()) + translation.x();
736 stream() <<
"centerY: " << (radGrad->center().y() * scale.height()) + translation.y();
737 stream() <<
"centerRadius: " << (radGrad->radius() * scale.width());
738 stream() <<
"focalX:" << (radGrad->focalPoint().x() * scale.width()) + translation.x();
739 stream() <<
"focalY:" << (radGrad->focalPoint().y() * scale.height()) + translation.y();
740 for (
auto &stop : radGrad->stops())
741 stream() <<
"GradientStop { position: " << QString::number(stop.first,
'g', 7)
742 <<
"; color: \"" << stop.second.name(QColor::HexArgb) <<
"\" }";
745 stream() <<
"spread: ShapeGradient.";
746 switch (grad->spread()) {
747 case QGradient::PadSpread:
748 stream(SameLine) <<
"PadSpread";
750 case QGradient::ReflectSpread:
751 stream(SameLine) <<
"ReflectSpread";
753 case QGradient::RepeatSpread:
754 stream(SameLine) <<
"RepeatSpread";
762void QQuickQmlGenerator::generateAnimationBindings()
765 if (Q_UNLIKELY(!isRuntimeGenerator()))
766 prefix = QStringLiteral(
".animations");
768 stream() <<
"loops: " << m_topLevelIdString << prefix <<
".loops";
769 stream() <<
"paused: " << m_topLevelIdString << prefix <<
".paused";
770 stream() <<
"running: true";
773 stream() <<
"onLoopsChanged: { if (running) { restart() } }";
776void QQuickQmlGenerator::generateEasing(
const QQuickAnimatedProperty::PropertyAnimation &animation,
777 int time,
int streamFlags)
779 if (animation.easingPerFrame.contains(time)) {
780 QBezier bezier = animation.easingPerFrame.value(time);
781 QPointF c1 = bezier.pt2();
782 QPointF c2 = bezier.pt3();
784 bool isLinear = (c1 == c1.transposed() && c2 == c2.transposed());
786 int nextIdx = m_easings.size();
787 QString &id = m_easings[{c1.x(), c1.y(), c2.x(), c2.y()}];
789 id = QString(QLatin1String(
"easing_%1")).arg(nextIdx, 2, 10, QLatin1Char(
'0'));
790 if (streamFlags & SameLine)
791 stream(streamFlags) <<
"; ";
792 stream(streamFlags) <<
"easing: " << m_topLevelIdString <<
"." << id;
797void QQuickQmlGenerator::generatePropertyAnimation(
const QQuickAnimatedProperty &property,
798 const QString &targetName,
799 const QString &propertyName,
800 AnimationType animationType)
802 if (!property.isAnimated())
805 if (usingTimelineAnimation())
806 return generatePropertyTimeline(property, targetName, propertyName, animationType);
808 QString mainAnimationId = targetName
809 + QStringLiteral(
"_")
811 + QStringLiteral(
"_animation");
812 mainAnimationId.replace(QLatin1Char(
'.'), QLatin1Char(
'_'));
815 if (Q_UNLIKELY(!isRuntimeGenerator()))
816 prefix = QStringLiteral(
".animations");
818 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
820 stream() <<
"ParallelAnimation {";
823 stream() <<
"id: " << mainAnimationId;
825 generateAnimationBindings();
827 for (
int i = 0; i < property.animationCount(); ++i) {
828 const QQuickAnimatedProperty::PropertyAnimation &animation = property.animation(i);
830 stream() <<
"SequentialAnimation {";
833 const int startOffset = processAnimationTime(animation.startOffset);
835 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
837 stream() <<
"SequentialAnimation {";
840 const int repeatCount = animation.repeatCount;
842 stream() <<
"loops: Animation.Infinite";
844 stream() <<
"loops: " << repeatCount;
846 int previousTime = 0;
847 QVariant previousValue;
848 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
849 const int time = it.key();
850 const int frameTime = processAnimationTime(time - previousTime);
851 const QVariant &value = it.value();
853 if (previousValue.isValid() && previousValue == value) {
855 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
856 }
else if (animationType == AnimationType::Auto && value.typeId() == QMetaType::Bool) {
859 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
860 stream() <<
"ScriptAction {";
863 stream() <<
"script:" << targetName <<
"." << propertyName <<
" = " << value.toString();
868 generateAnimatedPropertySetter(targetName,
878 previousValue = value;
881 if (!(animation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd)) {
882 stream() <<
"ScriptAction {";
884 stream() <<
"script: ";
886 switch (animationType) {
887 case AnimationType::Auto:
888 stream(SameLine) << targetName <<
"." << propertyName <<
" = ";
890 case AnimationType::ColorOpacity:
891 stream(SameLine) << targetName <<
"." << propertyName <<
".a = ";
895 QVariant value = property.defaultValue();
896 if (value.typeId() == QMetaType::QColor)
897 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
899 stream(SameLine) << value.toReal();
916void QQuickQmlGenerator::generateTimelinePropertySetter(
917 const QString &targetName,
918 const QString &propertyName,
919 const QQuickAnimatedProperty::PropertyAnimation &animation,
920 std::function<QVariant(
const QVariant &)>
const& extractValue,
923 if (animation.repeatCount != 1 || animation.startOffset
924 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
925 qCWarning(lcQuickVectorImage) <<
"Animation feature not implemented in timeline mode, for"
926 << targetName << propertyName;
929 stream() <<
"KeyframeGroup {";
931 stream() <<
"target: " << targetName;
932 stream() <<
"property: \"" << propertyName <<
"\"";
934 for (
const auto &[frame, rawValue] : animation.frames.asKeyValueRange()) {
936 if (rawValue.typeId() == QMetaType::QVariantList)
937 value = extractValue(rawValue.toList().value(valueIndex));
939 value = extractValue(rawValue);
941 stream() <<
"Keyframe { frame: " << frame <<
"; value: ";
942 if (value.typeId() == QMetaType::QVector3D) {
943 const QVector3D &v = value.value<QVector3D>();
944 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
945 }
else if (value.typeId() == QMetaType::QColor) {
946 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
948 stream(SameLine) << value.toReal();
950 generateEasing(animation, frame, SameLine);
951 stream(SameLine) <<
" }";
958void QQuickQmlGenerator::generatePropertyTimeline(
const QQuickAnimatedProperty &property,
959 const QString &targetName,
960 const QString &propertyName,
961 AnimationType animationType)
963 if (animationType == QQuickQmlGenerator::AnimationType::ColorOpacity) {
964 qCWarning(lcQuickVectorImage) <<
"ColorOpacity animation not available in timeline mode";
968 if (property.animationGroupCount() > 1 || property.animationCount() > 1) {
969 qCWarning(lcQuickVectorImage) <<
"Property feature not implemented in timeline mode, for"
970 << targetName << propertyName;
973 stream() <<
"Timeline {";
975 stream() <<
"currentFrame: " << property.timelineReferenceId() <<
".frameCounter";
976 stream() <<
"enabled: true";
978 auto extractor = [](
const QVariant &value) {
return value; };
979 generateTimelinePropertySetter(targetName, propertyName, property.animation(0), extractor);
985void QQuickQmlGenerator::generateTransform(
const QTransform &xf)
988 stream(SameLine) <<
"PlanarTransform.fromAffineMatrix("
989 << xf.m11() <<
", " << xf.m12() <<
", "
990 << xf.m21() <<
", " << xf.m22() <<
", "
991 << xf.dx() <<
", " << xf.dy() <<
")";
994 stream(SameLine) <<
"Qt.matrix4x4(";
996 const auto *data = m.data();
997 for (
int i = 0; i < 4; i++) {
998 stream() << data[i] <<
", " << data[i+4] <<
", " << data[i+8] <<
", " << data[i+12];
1000 stream(SameLine) <<
", ";
1002 stream(SameLine) <<
")";
1007void QQuickQmlGenerator::outputShapePath(
const PathNodeInfo &info,
const QPainterPath *painterPath,
const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector,
const QRectF &boundingRect)
1009 Q_UNUSED(pathSelector)
1010 Q_ASSERT(painterPath || quadPath);
1012 if (Q_UNLIKELY(errorState()))
1015 const bool invalidGradientBounds = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
1016 && (qFuzzyIsNull(boundingRect.width()) ||
1017 qFuzzyIsNull(boundingRect.height()));
1019 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
1020 const bool noPen = (strokeColor == QColorConstants::Transparent || !strokeColor.isValid())
1021 && !info.strokeStyle.color.isAnimated()
1022 && !info.strokeStyle.opacity.isAnimated()
1023 && (info.strokeGrad.type() == QGradient::NoGradient
1024 || invalidGradientBounds);
1025 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
1028 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
1029 const bool noFill = info.grad.type() == QGradient::NoGradient
1030 && fillColor == QColorConstants::Transparent
1031 && !info.fillColor.isAnimated()
1032 && !info.fillOpacity.isAnimated();
1033 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
1036 if (noPen && noFill)
1038 auto fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
1039 registerGraphicItem();
1040 stream() <<
"ShapePath {";
1043 QString shapePathId = info.id;
1044 if (pathSelector & QQuickVectorImageGenerator::FillPath)
1045 shapePathId += QStringLiteral(
"_fill");
1046 if (pathSelector & QQuickVectorImageGenerator::StrokePath)
1047 shapePathId += QStringLiteral(
"_stroke");
1049 stream() <<
"id: " << shapePathId;
1051 if (!info.nodeId.isEmpty()) {
1052 switch (pathSelector) {
1053 case QQuickVectorImageGenerator::FillPath:
1054 stream() <<
"objectName: \"svg_fill_path:" << info.nodeId <<
"\"";
1056 case QQuickVectorImageGenerator::StrokePath:
1057 stream() <<
"objectName: \"svg_stroke_path:" << info.nodeId <<
"\"";
1059 case QQuickVectorImageGenerator::FillAndStroke:
1060 stream() <<
"objectName: \"svg_path:" << info.nodeId <<
"\"";
1065 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
1066 stream() <<
"strokeColor: \"transparent\"";
1068 if (info.strokeGrad.type() != QGradient::NoGradient && !invalidGradientBounds) {
1069 QRectF coordinateSys = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
1071 : QRectF(0.0, 0.0, 1.0, 1.0);
1072 generateGradient(&info.strokeGrad, QStringLiteral(
"strokeGradient"), coordinateSys);
1073 }
else if (info.strokeStyle.opacity.isAnimated()) {
1074 stream() <<
"property color strokeBase: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1075 stream() <<
"property real strokeOpacity: " << info.strokeStyle.opacity.defaultValue().toReal();
1076 stream() <<
"strokeColor: Qt.rgba(strokeBase.r, strokeBase.g, strokeBase.b, strokeOpacity)";
1078 stream() <<
"strokeColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1080 stream() <<
"strokeWidth: " << info.strokeStyle.width.defaultValue().toReal();
1081 stream() <<
"capStyle: " << QQuickVectorImageGenerator::Utils::strokeCapStyleString(info.strokeStyle.lineCapStyle);
1082 stream() <<
"joinStyle: " << QQuickVectorImageGenerator::Utils::strokeJoinStyleString(info.strokeStyle.lineJoinStyle);
1083 stream() <<
"miterLimit: " << info.strokeStyle.miterLimit;
1084 if (info.strokeStyle.cosmetic)
1085 stream() <<
"cosmeticStroke: true";
1086 if (info.strokeStyle.dashArray.length() != 0) {
1087 stream() <<
"strokeStyle: " <<
"ShapePath.DashLine";
1088 stream() <<
"dashPattern: " << QQuickVectorImageGenerator::Utils::listString(info.strokeStyle.dashArray);
1089 stream() <<
"dashOffset: " << info.strokeStyle.dashOffset.defaultValue().toReal();
1093 QTransform fillTransform = info.fillTransform;
1094 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
1095 stream() <<
"fillColor: \"transparent\"";
1096 }
else if (info.grad.type() != QGradient::NoGradient) {
1097 generateGradient(&info.grad, QStringLiteral(
"fillGradient"));
1100 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
1101 QTransform objectToUserSpace;
1102 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
1103 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
1104 fillTransform *= objectToUserSpace;
1107 if (info.fillOpacity.isAnimated()) {
1108 stream() <<
"property color fillBase: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
1109 stream() <<
"property real fillOpacity:" << info.fillOpacity.defaultValue().toReal();
1110 stream() <<
"fillColor: Qt.rgba(fillBase.r, fillBase.g, fillBase.b, fillOpacity)";
1112 stream() <<
"fillColor: \"" << fillColor.name(QColor::HexArgb) <<
"\"";
1116 if (!info.patternId.isEmpty()) {
1117 stream() <<
"fillItem: ShaderEffectSource {";
1120 stream() <<
"parent: " << info.id;
1121 stream() <<
"sourceItem: " << info.patternId;
1122 stream() <<
"hideSource: true";
1123 stream() <<
"visible: false";
1124 stream() <<
"width: " << info.patternId <<
".width";
1125 stream() <<
"height: " << info.patternId <<
".height";
1126 stream() <<
"wrapMode: ShaderEffectSource.Repeat";
1127 stream() <<
"textureSize: Qt.size(width * __qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1128 <<
"height * __qt_toplevel_scale_itemspy.requiredTextureSize.height)";;
1129 stream() <<
"sourceRect: " << info.patternId <<
".sourceRect("
1130 << info.id <<
".width, "
1131 << info.id <<
".height)";
1138 stream() <<
"function calculateFillTransform(xScale, yScale) {";
1141 stream() <<
"var m = ";
1142 generateTransform(fillTransform);
1144 stream() <<
"m.translate(" << info.patternId <<
".sourceOffset("
1145 << info.id <<
".width, "
1146 << info.id <<
".height))";
1148 stream() <<
"m.scale(1.0 / xScale, 1.0 / yScale, 1.0)";
1149 stream() <<
"return m";
1154 stream() <<
"fillTransform: calculateFillTransform(__qt_toplevel_scale_itemspy.requiredTextureSize.width, "
1155 <<
"__qt_toplevel_scale_itemspy.requiredTextureSize.height)";
1157 }
else if (!fillTransform.isIdentity()) {
1158 const QTransform &xf = fillTransform;
1159 stream() <<
"fillTransform: ";
1160 if (info.fillTransform.type() == QTransform::TxTranslate)
1161 stream(SameLine) <<
"PlanarTransform.fromTranslate(" << xf.dx() <<
", " << xf.dy() <<
")";
1162 else if (info.fillTransform.type() == QTransform::TxScale && !xf.dx() && !xf.dy())
1163 stream(SameLine) <<
"PlanarTransform.fromScale(" << xf.m11() <<
", " << xf.m22() <<
")";
1165 generateTransform(xf);
1168 if (info.trim.enabled) {
1169 stream() <<
"trim.start: " << info.trim.start.defaultValue().toReal();
1170 stream() <<
"trim.end: " << info.trim.end.defaultValue().toReal();
1171 stream() <<
"trim.offset: " << info.trim.offset.defaultValue().toReal();
1175 if (fillRule == QQuickShapePath::WindingFill)
1176 stream() <<
"fillRule: ShapePath.WindingFill";
1178 stream() <<
"fillRule: ShapePath.OddEvenFill";
1182 hintStr = QQuickVectorImageGenerator::Utils::pathHintString(*quadPath);
1183 if (!hintStr.isEmpty())
1184 stream() << hintStr;
1186 QQuickAnimatedProperty pathFactor(QVariant::fromValue(0));
1187 pathFactor.setTimelineReferenceId(info.path.timelineReferenceId());
1188 QString pathId = shapePathId +
"_ip"_L1;
1189 if (!info.path.isAnimated() || (info.path.animation(0).startOffset == 0 && info.path.animation(0).isConstant())) {
1190 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(*painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
1191 stream() <<
"PathSvg { path: \"" << svgPathString <<
"\" }";
1193 stream() <<
"PathInterpolated {";
1195 stream() <<
"id: " << pathId;
1196 stream() <<
"svgPaths: [";
1198 QQuickAnimatedProperty::PropertyAnimation pathFactorAnim = info.path.animation(0);
1199 auto &frames = pathFactorAnim.frames;
1202 for (
auto it = frames.begin(); it != frames.end(); ++it) {
1203 QString svg = QQuickVectorImageGenerator::Utils::toSvgString(it->value<QPainterPath>());
1204 if (svg != lastSvg) {
1206 stream(SameLine) <<
",";
1207 stream() <<
"\"" << svg <<
"\"";
1211 *it = QVariant::fromValue(pathIdx);
1213 pathFactor.addAnimation(pathFactorAnim);
1223 if (pathFactor.isAnimated())
1224 generatePropertyAnimation(pathFactor, pathId,
"factor"_L1);
1226 if (info.trim.enabled) {
1227 generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"start"));
1228 generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"end"));
1229 generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(
".trim"), QStringLiteral(
"offset"));
1232 if (info.strokeStyle.opacity.isAnimated()) {
1233 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeBase"));
1234 generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral(
"strokeOpacity"));
1236 generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral(
"strokeColor"));
1238 if (info.strokeStyle.width.isAnimated())
1239 generatePropertyAnimation(info.strokeStyle.width, shapePathId, QStringLiteral(
"strokeWidth"));
1240 if (info.strokeStyle.dashOffset.isAnimated())
1241 generatePropertyAnimation(info.strokeStyle.dashOffset, shapePathId, QStringLiteral(
"dashOffset"));
1243 if (info.fillOpacity.isAnimated()) {
1244 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillBase"));
1245 generatePropertyAnimation(info.fillOpacity, shapePathId, QStringLiteral(
"fillOpacity"));
1247 generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral(
"fillColor"));
1251void QQuickQmlGenerator::generateNode(
const NodeInfo &info)
1253 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1256 stream() <<
"// Missing Implementation for SVG Node: " << info.typeName;
1257 stream() <<
"// Adding an empty Item and skipping";
1258 stream() <<
"Item {";
1260 generateNodeBase(info);
1261 generateNodeEnd(info);
1264void QQuickQmlGenerator::generateTextNode(
const TextNodeInfo &info)
1266 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1269 stream() <<
"Item {";
1271 generateNodeBase(info);
1272 registerGraphicItem();
1274 if (!info.isTextArea)
1275 stream() <<
"Item { id: textAlignItem_" << m_textNodeCounter <<
"; x: " << info.position.x() <<
"; y: " << info.position.y() <<
"}";
1277 stream() <<
"Text {";
1281 const QString textItemId = QStringLiteral(
"_qt_textItem_%1").arg(m_textNodeCounter);
1282 stream() <<
"id: " << textItemId;
1284 generatePropertyAnimation(info.fillColor, textItemId, QStringLiteral(
"color"));
1285 generatePropertyAnimation(info.fillOpacity, textItemId, QStringLiteral(
"color"), AnimationType::ColorOpacity);
1286 generatePropertyAnimation(info.strokeColor, textItemId, QStringLiteral(
"styleColor"));
1287 generatePropertyAnimation(info.strokeOpacity, textItemId, QStringLiteral(
"styleColor"), AnimationType::ColorOpacity);
1289 if (info.isTextArea) {
1290 stream() <<
"x: " << info.position.x();
1291 stream() <<
"y: " << info.position.y();
1292 if (info.size.width() > 0) {
1293 stream() <<
"width: " << info.size.width();
1294 stream() <<
"wrapMode: Text.Wrap";
1296 if (info.size.height() > 0)
1297 stream() <<
"height: " << info.size.height();
1298 stream() <<
"clip: true";
1300 QString hAlign = QStringLiteral(
"left");
1301 stream() <<
"anchors.baseline: textAlignItem_" << m_textNodeCounter <<
".top";
1302 switch (info.alignment) {
1303 case Qt::AlignHCenter:
1304 hAlign = QStringLiteral(
"horizontalCenter");
1306 case Qt::AlignRight:
1307 hAlign = QStringLiteral(
"right");
1310 qCDebug(lcQuickVectorImage) <<
"Unexpected text alignment" << info.alignment;
1315 stream() <<
"anchors." << hAlign <<
": textAlignItem_" << m_textNodeCounter <<
".left";
1317 m_textNodeCounter++;
1319 stream() <<
"color: \"" << info.fillColor.defaultValue().value<QColor>().name(QColor::HexArgb) <<
"\"";
1320 stream() <<
"textFormat:" << (info.needsRichText ?
"Text.RichText" :
"Text.StyledText");
1322 stream() <<
"text: \"" << sanitizeString(info.text) <<
"\"";
1323 stream() <<
"font.family: \"" << sanitizeString(info.font.family()) <<
"\"";
1324 if (info.font.pixelSize() > 0)
1325 stream() <<
"font.pixelSize:" << info.font.pixelSize();
1326 else if (info.font.pointSize() > 0)
1327 stream() <<
"font.pixelSize:" << info.font.pointSizeF();
1328 if (info.font.underline())
1329 stream() <<
"font.underline: true";
1330 if (info.font.weight() != QFont::Normal)
1331 stream() <<
"font.weight: " <<
int(info.font.weight());
1332 if (info.font.italic())
1333 stream() <<
"font.italic: true";
1334 switch (info.font.hintingPreference()) {
1335 case QFont::PreferFullHinting:
1336 stream() <<
"font.hintingPreference: Font.PreferFullHinting";
1338 case QFont::PreferVerticalHinting:
1339 stream() <<
"font.hintingPreference: Font.PreferVerticalHinting";
1341 case QFont::PreferNoHinting:
1342 stream() <<
"font.hintingPreference: Font.PreferNoHinting";
1344 case QFont::PreferDefaultHinting:
1345 stream() <<
"font.hintingPreference: Font.PreferDefaultHinting";
1349 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
1350 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
1351 stream() <<
"styleColor: \"" << strokeColor.name(QColor::HexArgb) <<
"\"";
1352 stream() <<
"style: Text.Outline";
1358 generateNodeEnd(info);
1361void QQuickQmlGenerator::generateUseNode(
const UseNodeInfo &info)
1363 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1366 if (info.stage == StructureNodeStage::Start) {
1367 stream() <<
"Item {";
1369 generateNodeBase(info);
1372 if (!info.startPos.isNull()) {
1373 stream() <<
"Item {";
1375 stream() <<
"transform: Translate { x: " << info.startPos.x()
1376 <<
"; y: " << info.startPos.y() <<
" }";
1379 if (!info.startPos.isNull()) {
1383 generateNodeEnd(info);
1387void QQuickQmlGenerator::generatePathContainer(
const StructureNodeInfo &info)
1390 stream() << shapeName() <<
" {";
1392 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
1393 stream() <<
"preferredRendererType: Shape.CurveRenderer";
1394 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
1395 stream() <<
"asynchronous: true";
1398 m_inShapeItemLevel++;
1401void QQuickQmlGenerator::generateAnimateMotionPath(
const QString &targetName,
1402 const QQuickAnimatedProperty &property)
1404 if (!property.isAnimated())
1407 QPainterPath path = property.defaultValue().value<QVariantList>().value(0).value<QPainterPath>();
1408 const QString mainAnimationId = targetName + QStringLiteral(
"_motion_interpolator");
1409 stream() <<
"PathInterpolator {";
1411 stream() <<
"id: " << mainAnimationId;
1412 const QString svgPathString = QQuickVectorImageGenerator::Utils::toSvgString(path);
1413 stream() <<
"path: Path { PathSvg { path: \"" << svgPathString <<
"\" } }";
1417 generatePropertyAnimation(property, mainAnimationId, QStringLiteral(
"progress"));
1420void QQuickQmlGenerator::generateAnimatedPropertySetter(
const QString &targetName,
1421 const QString &propertyName,
1422 const QVariant &value,
1423 const QQuickAnimatedProperty::PropertyAnimation &animation,
1426 AnimationType animationType)
1428 if (frameTime > 0) {
1429 switch (animationType) {
1430 case AnimationType::Auto:
1431 if (value.typeId() == QMetaType::QColor)
1432 stream() <<
"ColorAnimation {";
1434 stream() <<
"PropertyAnimation {";
1436 case AnimationType::ColorOpacity:
1437 stream() <<
"ColorOpacityAnimation {";
1442 stream() <<
"duration: " << frameTime;
1443 stream() <<
"target: " << targetName;
1444 stream() <<
"property: \"" << propertyName <<
"\"";
1446 if (value.typeId() == QMetaType::QVector3D) {
1447 const QVector3D &v = value.value<QVector3D>();
1448 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1449 }
else if (value.typeId() == QMetaType::QColor) {
1450 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1452 stream(SameLine) << value.toReal();
1454 generateEasing(animation, time);
1458 stream() <<
"ScriptAction {";
1460 stream() <<
"script:" << targetName <<
"." << propertyName;
1461 if (animationType == AnimationType::ColorOpacity)
1462 stream(SameLine) <<
".a";
1464 stream(SameLine) <<
" = ";
1465 if (value.typeId() == QMetaType::QVector3D) {
1466 const QVector3D &v = value.value<QVector3D>();
1467 stream(SameLine) <<
"Qt.vector3d(" << v.x() <<
", " << v.y() <<
", " << v.z() <<
")";
1468 }
else if (value.typeId() == QMetaType::QColor) {
1469 stream(SameLine) <<
"\"" << value.toString() <<
"\"";
1471 stream(SameLine) << value.toReal();
1478void QQuickQmlGenerator::generateAnimateTransform(
const QString &targetName,
const NodeInfo &info)
1480 if (!info.transform.isAnimated())
1483 if (usingTimelineAnimation())
1484 return generateTransformTimeline(targetName, info);
1486 const QString mainAnimationId = targetName
1487 + QStringLiteral(
"_transform_animation");
1490 if (Q_UNLIKELY(!isRuntimeGenerator()))
1491 prefix = QStringLiteral(
".animations");
1492 stream() <<
"Connections { target: " << m_topLevelIdString << prefix <<
"; function onRestart() {" << mainAnimationId <<
".restart() } }";
1494 stream() <<
"ParallelAnimation {";
1497 stream() <<
"id:" << mainAnimationId;
1499 generateAnimationBindings();
1500 for (
int groupIndex = 0; groupIndex < info.transform.animationGroupCount(); ++groupIndex) {
1501 int animationStart = info.transform.animationGroup(groupIndex);
1502 int nextAnimationStart = groupIndex + 1 < info.transform.animationGroupCount()
1503 ? info.transform.animationGroup(groupIndex + 1)
1504 : info.transform.animationCount();
1507 const QQuickAnimatedProperty::PropertyAnimation &firstAnimation = info.transform.animation(animationStart);
1508 const bool freeze = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd;
1509 const bool replace = firstAnimation.flags & QQuickAnimatedProperty::PropertyAnimation::ReplacePreviousAnimations;
1511 stream() <<
"SequentialAnimation {";
1514 const int startOffset = processAnimationTime(firstAnimation.startOffset);
1515 if (startOffset > 0)
1516 stream() <<
"PauseAnimation { duration: " << startOffset <<
" }";
1518 const int repeatCount = firstAnimation.repeatCount;
1519 if (repeatCount < 0)
1520 stream() <<
"loops: Animation.Infinite";
1522 stream() <<
"loops: " << repeatCount;
1525 stream() <<
"ScriptAction {";
1528 stream() <<
"script: " << targetName <<
"_transform_base_group"
1529 <<
".activateOverride(" << targetName <<
"_transform_group_" << groupIndex <<
")";
1535 stream() <<
"ParallelAnimation {";
1538 for (
int i = animationStart; i < nextAnimationStart; ++i) {
1539 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1540 if (animation.isConstant())
1542 bool hasRotationCenter =
false;
1543 if (animation.subtype == QTransform::TxRotate) {
1544 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1545 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1546 if (!center.isNull()) {
1547 hasRotationCenter =
true;
1553 stream() <<
"SequentialAnimation {";
1556 int previousTime = 0;
1557 QVariantList previousParameters;
1558 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1559 const int time = it.key();
1560 const int frameTime = processAnimationTime(time - previousTime);
1561 const QVariantList ¶meters = it.value().value<QVariantList>();
1562 if (parameters.isEmpty())
1565 if (parameters == previousParameters) {
1567 stream() <<
"PauseAnimation { duration: " << frameTime <<
" }";
1569 stream() <<
"ParallelAnimation {";
1572 const QString propertyTargetName = targetName
1573 + QStringLiteral(
"_transform_")
1574 + QString::number(groupIndex)
1575 + QStringLiteral(
"_")
1576 + QString::number(i);
1578 switch (animation.subtype) {
1579 case QTransform::TxTranslate:
1581 const QPointF translation = parameters.first().value<QPointF>();
1583 generateAnimatedPropertySetter(propertyTargetName,
1584 QStringLiteral(
"x"),
1589 generateAnimatedPropertySetter(propertyTargetName,
1590 QStringLiteral(
"y"),
1597 case QTransform::TxScale:
1599 const QPointF scale = parameters.first().value<QPointF>();
1600 generateAnimatedPropertySetter(propertyTargetName,
1601 QStringLiteral(
"xScale"),
1606 generateAnimatedPropertySetter(propertyTargetName,
1607 QStringLiteral(
"yScale"),
1614 case QTransform::TxRotate:
1616 Q_ASSERT(parameters.size() == 2);
1617 const qreal angle = parameters.value(1).toReal();
1618 if (hasRotationCenter) {
1619 const QPointF center = parameters.value(0).value<QPointF>();
1620 generateAnimatedPropertySetter(propertyTargetName,
1621 QStringLiteral(
"origin"),
1622 QVector3D(center.x(), center.y(), 0.0),
1627 generateAnimatedPropertySetter(propertyTargetName,
1628 QStringLiteral(
"angle"),
1635 case QTransform::TxShear:
1637 const QPointF skew = parameters.first().value<QPointF>();
1639 generateAnimatedPropertySetter(propertyTargetName,
1640 QStringLiteral(
"xAngle"),
1646 generateAnimatedPropertySetter(propertyTargetName,
1647 QStringLiteral(
"yAngle"),
1662 previousTime = time;
1663 previousParameters = parameters;
1675 if (firstAnimation.repeatCount >= 0) {
1676 stream() <<
"ScriptAction {";
1679 stream() <<
"script: {";
1683 stream() << targetName <<
"_transform_base_group.deactivate("
1684 << targetName <<
"_transform_group_" << groupIndex <<
")";
1685 }
else if (!replace) {
1686 stream() << targetName <<
"_transform_base_group.deactivateOverride("
1687 << targetName <<
"_transform_group_" << groupIndex <<
")";
1705void QQuickQmlGenerator::generateTransformTimeline(
const QString &targetName,
const NodeInfo &info)
1707 stream() <<
"Timeline {";
1709 stream() <<
"currentFrame: " << info.transform.timelineReferenceId() <<
".frameCounter";
1710 stream() <<
"enabled: true";
1712 const int groupIndex = 0;
1713 for (
int i = 0; i < info.transform.animationCount(); ++i) {
1714 const QQuickAnimatedProperty::PropertyAnimation &animation = info.transform.animation(i);
1715 if (animation.isConstant())
1717 if (info.transform.animationGroupCount() > 1
1718 || animation.repeatCount != 1 || animation.startOffset
1719 || animation.flags != QQuickAnimatedProperty::PropertyAnimation::FreezeAtEnd) {
1720 qCWarning(lcQuickVectorImage) <<
"Feature not implemented in timeline xf animation mode, for"
1721 << targetName <<
"subtype" << animation.subtype;
1724 bool hasRotationCenter =
false;
1725 if (animation.subtype == QTransform::TxRotate) {
1726 for (
auto it = animation.frames.constBegin(); it != animation.frames.constEnd(); ++it) {
1727 const QPointF center = it->value<QVariantList>().value(0).value<QPointF>();
1728 if (!center.isNull()) {
1729 hasRotationCenter =
true;
1735 auto pointFxExtractor = [](
const QVariant &value) {
return value.toPointF().x(); };
1736 auto pointFyExtractor = [](
const QVariant &value) {
return value.toPointF().y(); };
1737 auto realExtractor = [](
const QVariant &value) {
return value.toReal(); };
1738 auto pointFtoVector3dExtractor = [](
const QVariant &v) {
return QVector3D(v.toPointF()); };
1740 const QString propertyTargetName = targetName
1741 + QStringLiteral(
"_transform_")
1742 + QString::number(groupIndex)
1743 + QStringLiteral(
"_")
1744 + QString::number(i);
1746 switch (animation.subtype) {
1747 case QTransform::TxTranslate:
1748 generateTimelinePropertySetter(propertyTargetName,
"x"_L1, animation, pointFxExtractor);
1749 generateTimelinePropertySetter(propertyTargetName,
"y"_L1, animation, pointFyExtractor);
1751 case QTransform::TxScale:
1752 generateTimelinePropertySetter(propertyTargetName,
"xScale"_L1, animation, pointFxExtractor);
1753 generateTimelinePropertySetter(propertyTargetName,
"yScale"_L1, animation, pointFyExtractor);
1755 case QTransform::TxRotate:
1756 if (hasRotationCenter)
1757 generateTimelinePropertySetter(propertyTargetName,
"origin"_L1, animation, pointFtoVector3dExtractor);
1758 generateTimelinePropertySetter(propertyTargetName,
"angle"_L1, animation, realExtractor, 1);
1760 case QTransform::TxShear:
1761 generateTimelinePropertySetter(propertyTargetName,
"xAngle"_L1, animation, pointFxExtractor);
1762 generateTimelinePropertySetter(propertyTargetName,
"yAngle"_L1, animation, pointFyExtractor);
1771bool QQuickQmlGenerator::generateStructureNode(
const StructureNodeInfo &info)
1773 const bool isStartStage = (info.stage == StructureNodeStage::Start);
1774 bool doWrap =
false;
1776 doWrap = !info.opacityGroupBounds.isNull() || !info.opacityGroupBoundsReferenceId.isEmpty();
1778 doWrap = (!m_opacityWrapperIds.isEmpty() && m_opacityWrapperIds.top() == info.id);
1781 res = generateStructureNode_helper(info);
1783 StructureNodeInfo wrappedInfo = info;
1784 wrappedInfo.isDefaultOpacity =
true;
1785 wrappedInfo.opacity = QQuickAnimatedProperty(QVariant::fromValue(1.0));
1786 const QString wrapperId = info.id +
"_opacityWrapper"_L1;
1788 stream() <<
"Item { // Opacity group wrapper";
1790 stream() <<
"id: " << wrapperId;
1791 stream() <<
"layer.sourceRect: ";
1792 if (!info.opacityGroupBoundsReferenceId.isEmpty()) {
1793 stream(SameLine) << info.opacityGroupBoundsReferenceId <<
".originalBounds";
1795 stream(SameLine) <<
"Qt.rect("
1796 << info.opacityGroupBounds.x() <<
", "
1797 << info.opacityGroupBounds.y() <<
", "
1798 << info.opacityGroupBounds.width() <<
", "
1799 << info.opacityGroupBounds.height() <<
")";
1801 stream() <<
"opacity: " << info.opacity.defaultValue().toReal();
1802 generatePropertyAnimation(info.opacity, wrapperId,
"opacity"_L1);
1803 m_opacityWrapperIds.push(info.id);
1804 m_opacityWrapperShapeCounts.push(0);
1807 res = generateStructureNode_helper(wrappedInfo);
1809 if (!isStartStage) {
1810 m_opacityWrapperIds.pop();
1811 if (m_opacityWrapperShapeCounts.pop() > 1) {
1812 const QString itemSpyId = wrapperId +
"_itemspy"_L1;
1813 stream() <<
"layer.enabled: true";
1814 stream() <<
"x: layer.sourceRect.x";
1815 stream() <<
"y: layer.sourceRect.y";
1816 stream() <<
"width: layer.sourceRect.width";
1817 stream() <<
"height: layer.sourceRect.height";
1818 stream() <<
"layer.textureSize: " << itemSpyId <<
".requiredTextureSize";
1819 stream() <<
"ItemSpy {";
1821 stream() <<
"id: " << itemSpyId;
1822 stream() <<
"anchors.fill: parent";
1833bool QQuickQmlGenerator::generateStructureNode_helper(
const StructureNodeInfo &info)
1835 if (Q_UNLIKELY(errorState() || !isNodeVisible(info)))
1838 const bool isPathContainer = !info.forceSeparatePaths && info.isPathContainer;
1839 if (info.stage == StructureNodeStage::Start) {
1840 if (!info.clipBox.isEmpty()) {
1841 stream() <<
"Item { // Clip";
1844 stream() <<
"width: " << info.clipBox.width();
1845 stream() <<
"height: " << info.clipBox.height();
1846 stream() <<
"clip: true";
1849 if (isPathContainer) {
1850 generatePathContainer(info);
1851 }
else if (!info.customItemType.isEmpty()) {
1852 stream() << info.customItemType <<
" {";
1854 stream() <<
"Item { // Structure node";
1858 generateTimelineFields(info);
1860 if (!info.viewBox.isEmpty()) {
1861 stream() <<
"transform: [";
1863 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
1865 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
1866 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
1871 generateNodeBase(info);
1873 generateNodeEnd(info);
1874 if (isPathContainer)
1875 m_inShapeItemLevel--;
1877 if (!info.clipBox.isEmpty()) {
1886bool QQuickQmlGenerator::generateMaskNode(
const MaskNodeInfo &info)
1888 if (Q_UNLIKELY(errorState()))
1892 if (info.stage == StructureNodeStage::Start) {
1893 if (info.isMaskContentRelativeCoordinates) {
1894 m_contentRelativeMasks.insert(info.id);
1895 stream() <<
"Item {";
1897 stream() <<
"transform: Matrix4x4 {";
1899 stream() <<
"matrix: Qt.matrix4x4(";
1901 stream() << info.id <<
".contentWidth, 0, 0, " << info.id <<
".contentX,";
1902 stream() <<
"0, " << info.id <<
".contentHeight, 0, " << info.id <<
".contentY,";
1903 stream() <<
"0, 0, 1, 0,";
1904 stream() <<
"0, 0, 0, 1)";
1912 if (info.stage == StructureNodeStage::End) {
1913 if (info.isMaskContentRelativeCoordinates) {
1919 startDefsSuffixBlock();
1920 stream() <<
"Loader {";
1923 stream() <<
"id: " << info.id;
1924 if (!info.keepMaskVisible)
1925 stream() <<
"visible: false";
1926 stream() <<
"sourceComponent: " << info.id <<
"_container";
1927 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
1928 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
1930 if (info.boundsReferenceId.isEmpty()) {
1931 stream() <<
"property real maskX: " << info.maskRect.left();
1932 stream() <<
"property real maskY: " << info.maskRect.top();
1933 stream() <<
"property real maskWidth: " << info.maskRect.width();
1934 stream() <<
"property real maskHeight: " << info.maskRect.height();
1937 if (info.isMaskContentRelativeCoordinates) {
1938 stream() <<
"property real contentX: 0";
1939 stream() <<
"property real contentY: 0";
1940 stream() <<
"property real contentWidth: 1";
1941 stream() <<
"property real contentHeight: 1";
1944 stream() <<
"function maskRect(otherX, otherY, otherWidth, otherHeight) {";
1947 stream() <<
"return ";
1948 if (!info.boundsReferenceId.isEmpty()) {
1949 stream(SameLine) << info.boundsReferenceId <<
".originalBounds";
1950 }
else if (info.isMaskRectRelativeCoordinates) {
1953 << info.id <<
".maskX * otherWidth + otherX,"
1954 << info.id <<
".maskY * otherHeight + otherY,"
1955 << info.id <<
".maskWidth * otherWidth,"
1956 << info.id <<
".maskHeight * otherHeight)";
1960 << info.id <<
".maskX, "
1961 << info.id <<
".maskY, "
1962 << info.id <<
".maskWidth, "
1963 << info.id <<
".maskHeight)";
1972 endDefsSuffixBlock();
1978void QQuickQmlGenerator::generateFilterNode(
const FilterNodeInfo &info)
1980 if (Q_UNLIKELY(errorState()))
1983 stream() <<
"Item {";
1986 generateNodeBase(info);
1988 stream() <<
"property real originalWidth: filterSourceItem.sourceItem.originalBounds.width";
1989 stream() <<
"property real originalHeight: filterSourceItem.sourceItem.originalBounds.height";
1990 stream() <<
"property rect filterRect: " << info.id <<
"_filterParameters"
1991 <<
".adaptToFilterRect(0, 0, originalWidth, originalHeight)";
1993 for (qsizetype i = 0; i < info.steps.size();)
1994 i = generateFilterStep(info, i);
1997 startDefsSuffixBlock();
1998 stream() <<
"QtObject {";
2001 stream() <<
"id: " << info.id <<
"_filterParameters";
2002 stream() <<
"property int wrapMode: ";
2003 if (info.wrapMode == QSGTexture::Repeat)
2004 stream(SameLine) <<
"ShaderEffectSource.Repeat";
2006 stream(SameLine) <<
"ShaderEffectSource.ClampToEdge";
2008 stream() <<
"property rect filterRect: Qt.rect("
2009 << info.filterRect.x() <<
", "
2010 << info.filterRect.y() <<
", "
2011 << info.filterRect.width() <<
", "
2012 << info.filterRect.height() <<
")";
2014 stream() <<
"function adaptToFilterRect(sx, sy, sw, sh) {";
2017 if (info.csFilterRect == FilterNodeInfo::CoordinateSystem::Absolute) {
2018 stream() <<
"return Qt.rect(filterRect.x, filterRect.y, filterRect.width, filterRect.height)";
2020 stream() <<
"return Qt.rect(sx + sw * filterRect.x, sy + sh * filterRect.y, sw * filterRect.width, sh * filterRect.height)";
2028 endDefsSuffixBlock();
2030 generateNodeEnd(info);
2033qsizetype QQuickQmlGenerator::generateFilterStep(
const FilterNodeInfo &info,
2034 qsizetype stepIndex)
2036 const FilterNodeInfo::FilterStep &step = info.steps.at(stepIndex);
2037 const QString primitiveId = info.id + QStringLiteral(
"_primitive") + QString::number(stepIndex);
2041 QString inputId = step.input1 != FilterNodeInfo::FilterInput::SourceColor
2043 : QStringLiteral(
"filterSourceItem");
2045 bool isComposite =
false;
2046 switch (step.filterType) {
2047 case FilterNodeInfo::Type::Merge:
2049 const int maxNodeCount = 8;
2052 QList<std::pair<FilterNodeInfo::FilterInput, QString> > inputs;
2053 for (; stepIndex < info.steps.size(); ++stepIndex) {
2054 const FilterNodeInfo::FilterStep &nodeStep = info.steps.at(stepIndex);
2055 if (nodeStep.filterType != FilterNodeInfo::Type::MergeNode)
2058 inputs.emplace_back(nodeStep.input1, nodeStep.namedInput1);
2061 if (inputs.size() > maxNodeCount) {
2062 qCWarning(lcQuickVectorImage) <<
"Maximum of" << maxNodeCount
2063 <<
"nodes exceeded in merge effect.";
2066 if (inputs.isEmpty()) {
2067 qCWarning(lcQuickVectorImage) <<
"Merge effect requires at least one node.";
2071 stream() <<
"ShaderEffect {";
2074 stream() <<
"id: " << primitiveId;
2075 stream() <<
"visible: false";
2077 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb\"";
2078 stream() <<
"width: source1.width";
2079 stream() <<
"height: source1.height";
2080 stream() <<
"property int sourceCount: " << std::min(qsizetype(8), inputs.size());
2082 for (
int i = 0; i < maxNodeCount; ++i) {
2083 auto input = i < inputs.size()
2085 : std::pair(FilterNodeInfo::FilterInput::None, u"null"_s);
2087 QString inputId = input.first != FilterNodeInfo::FilterInput::SourceColor
2089 : QStringLiteral(
"filterSourceItem");
2091 stream() <<
"property var source" << (i + 1) <<
": " << inputId;
2099 case FilterNodeInfo::Type::CompositeOver:
2100 case FilterNodeInfo::Type::CompositeOut:
2101 case FilterNodeInfo::Type::CompositeIn:
2102 case FilterNodeInfo::Type::CompositeXor:
2103 case FilterNodeInfo::Type::CompositeAtop:
2104 case FilterNodeInfo::Type::CompositeArithmetic:
2105 case FilterNodeInfo::Type::CompositeLighter:
2109 case FilterNodeInfo::Type::BlendNormal:
2110 case FilterNodeInfo::Type::BlendMultiply:
2111 case FilterNodeInfo::Type::BlendScreen:
2112 case FilterNodeInfo::Type::BlendDarken:
2113 case FilterNodeInfo::Type::BlendLighten:
2115 stream() <<
"ShaderEffect {";
2118 QString input2Id = step.input2 != FilterNodeInfo::FilterInput::SourceColor
2120 : QStringLiteral(
"filterSourceItem");
2122 stream() <<
"id: " << primitiveId;
2123 stream() <<
"visible: false";
2126 switch (step.filterType) {
2127 case FilterNodeInfo::Type::CompositeOver:
2128 shader = QStringLiteral(
"fecompositeover");
2130 case FilterNodeInfo::Type::CompositeOut:
2131 shader = QStringLiteral(
"fecompositeout");
2133 case FilterNodeInfo::Type::CompositeIn:
2134 shader = QStringLiteral(
"fecompositein");
2136 case FilterNodeInfo::Type::CompositeXor:
2137 shader = QStringLiteral(
"fecompositexor");
2139 case FilterNodeInfo::Type::CompositeAtop:
2140 shader = QStringLiteral(
"fecompositeatop");
2142 case FilterNodeInfo::Type::CompositeArithmetic:
2143 shader = QStringLiteral(
"fecompositearithmetic");
2145 case FilterNodeInfo::Type::CompositeLighter:
2146 shader = QStringLiteral(
"fecompositelighter");
2148 case FilterNodeInfo::Type::BlendNormal:
2149 shader = QStringLiteral(
"feblendnormal");
2151 case FilterNodeInfo::Type::BlendMultiply:
2152 shader = QStringLiteral(
"feblendmultiply");
2154 case FilterNodeInfo::Type::BlendScreen:
2155 shader = QStringLiteral(
"feblendscreen");
2157 case FilterNodeInfo::Type::BlendDarken:
2158 shader = QStringLiteral(
"feblenddarken");
2160 case FilterNodeInfo::Type::BlendLighten:
2161 shader = QStringLiteral(
"feblendlighten");
2167 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"
2168 << shader <<
".frag.qsb\"";
2169 stream() <<
"property var source: " << inputId;
2170 stream() <<
"property var source2: " << input2Id;
2171 stream() <<
"width: source.width";
2172 stream() <<
"height: source.height";
2175 QVector4D k = step.filterParameter.value<QVector4D>();
2176 stream() <<
"property var k: Qt.vector4d("
2189 case FilterNodeInfo::Type::Flood:
2191 stream() <<
"Rectangle {";
2194 stream() <<
"id: " << primitiveId;
2195 stream() <<
"visible: false";
2197 stream() <<
"width: " << inputId <<
".width";
2198 stream() <<
"height: " << inputId <<
".height";
2200 QColor floodColor = step.filterParameter.value<QColor>();
2201 stream() <<
"color: \"" << floodColor.name(QColor::HexArgb) <<
"\"";
2208 case FilterNodeInfo::Type::ColorMatrix:
2210 stream() <<
"ShaderEffect {";
2213 stream() <<
"id: " << primitiveId;
2214 stream() <<
"visible: false";
2216 stream() <<
"fragmentShader: \"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb\"";
2217 stream() <<
"property var source: " << inputId;
2218 stream() <<
"width: source.width";
2219 stream() <<
"height: source.height";
2221 QGenericMatrix<5, 5, qreal> matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal> >();
2222 for (
int row = 0; row < 4; ++row) {
2225 for (
int col = 0; col < 5; ++col)
2226 stream() <<
"property real m_" << row <<
"_" << col <<
": " << matrix(col, row);
2235 case FilterNodeInfo::Type::Offset:
2237 stream() <<
"ShaderEffectSource {";
2240 stream() <<
"id: " << primitiveId;
2241 stream() <<
"visible: false";
2242 stream() <<
"sourceItem: " << inputId;
2243 stream() <<
"width: sourceItem.width + offset.x";
2244 stream() <<
"height: sourceItem.height + offset.y";
2246 QVector2D offset = step.filterParameter.value<QVector2D>();
2247 stream() <<
"property vector2d offset: Qt.vector2d(";
2248 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute)
2249 stream(SameLine) << offset.x() <<
" / width, " << offset.y() <<
" / height)";
2251 stream(SameLine) << offset.x() <<
", " << offset.y() <<
")";
2253 stream() <<
"sourceRect: Qt.rect(-offset.x, -offset.y, width, height)";
2255 stream() <<
"ItemSpy {";
2257 stream() <<
"id: " << primitiveId <<
"_offset_itemspy";
2258 stream() <<
"anchors.fill: parent";
2262 stream() <<
"textureSize: " << primitiveId <<
"_offset_itemspy.requiredTextureSize";
2271 case FilterNodeInfo::Type::GaussianBlur:
2274 stream() <<
"MultiEffect {";
2277 stream() <<
"id: " << primitiveId;
2278 stream() <<
"visible: false";
2280 stream() <<
"source: " << inputId;
2281 stream() <<
"blurEnabled: true";
2282 stream() <<
"width: source.width";
2283 stream() <<
"height: source.height";
2285 const qreal maxDeviation(12.0);
2286 const qreal deviation = step.filterParameter.toReal();
2287 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative)
2288 stream() <<
"blur: Math.min(1.0, " << deviation <<
" * filterSourceItem.width / " << maxDeviation <<
")";
2290 stream() <<
"blur: " << std::min(qreal(1.0), deviation / maxDeviation);
2291 stream() <<
"blurMax: 64";
2299 qCWarning(lcQuickVectorImage) <<
"Unhandled filter type: " <<
int(step.filterType);
2301 stream() <<
"Item { id: " << primitiveId <<
" }";
2306 stream() <<
"ShaderEffectSource {";
2309 stream() <<
"id: " << step.outputName;
2310 if (stepIndex < info.steps.size())
2311 stream() <<
"visible: false";
2313 qreal x1, x2, y1, y2;
2314 step.filterPrimitiveRect.getCoords(&x1, &y1, &x2, &y2);
2315 if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Absolute) {
2316 stream() <<
"property real fpx1: " << x1;
2317 stream() <<
"property real fpy1: " << y1;
2318 stream() <<
"property real fpx2: " << x2;
2319 stream() <<
"property real fpy2: " << y2;
2320 }
else if (step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative) {
2324 stream() <<
"property real fpx1: " << x1 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2325 stream() <<
"property real fpy1: " << y1 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2326 stream() <<
"property real fpx2: " << x2 <<
" * filterSourceItem.sourceItem.originalBounds.width";
2327 stream() <<
"property real fpy2: " << y2 <<
" * filterSourceItem.sourceItem.originalBounds.height";
2329 stream() <<
"property real fpx1: parent.filterRect.x";
2330 stream() <<
"property real fpy1: parent.filterRect.y";
2331 stream() <<
"property real fpx2: parent.filterRect.x + parent.filterRect.width";
2332 stream() <<
"property real fpy2: parent.filterRect.y + parent.filterRect.height";
2335 stream() <<
"sourceItem: " << primitiveId;
2336 stream() <<
"sourceRect: Qt.rect(fpx1 - parent.filterRect.x, fpy1 - parent.filterRect.y, width, height)";
2338 stream() <<
"x: fpx1";
2339 stream() <<
"y: fpy1";
2340 stream() <<
"width: " <<
"fpx2 - fpx1";
2341 stream() <<
"height: " <<
"fpy2 - fpy1";
2343 stream() <<
"ItemSpy {";
2345 stream() <<
"id: " << primitiveId <<
"_itemspy";
2346 stream() <<
"anchors.fill: parent";
2350 stream() <<
"textureSize: " << primitiveId <<
"_itemspy.requiredTextureSize";
2358void QQuickQmlGenerator::generateTimelineFields(
const StructureNodeInfo &info)
2360 if (usingTimelineAnimation() && info.timelineInfo) {
2361 QString frameCounterRef = info.timelineInfo->frameCounterReference
2362 + QStringLiteral(
".frameCounter");
2364 if (info.timelineInfo->generateVisibility) {
2365 stream() <<
"visible: " << frameCounterRef <<
" >= " << info.timelineInfo->startFrame
2366 <<
" && " << frameCounterRef <<
" < " << info.timelineInfo->endFrame;
2369 if (info.timelineInfo->generateFrameCounter) {
2370 stream() <<
"property real frameCounter: ";
2371 if (info.timelineInfo->frameCounterMapper.isAnimated()) {
2373 stream(SameLine) <<
"0";
2374 generatePropertyTimeline(info.timelineInfo->frameCounterMapper, info.id,
"frameCounter"_L1);
2376 const auto offset = info.timelineInfo->frameCounterOffset;
2377 const auto multiplier = info.timelineInfo->frameCounterMultiplier;
2378 const bool needsParens = (offset && multiplier);
2380 stream(SameLine) <<
"(";
2381 stream(SameLine) << frameCounterRef;
2383 stream(SameLine) << (offset > 0 ?
" + " :
" - ") << qAbs(offset);
2385 stream(SameLine) <<
")";
2387 stream(SameLine) <<
" * " << multiplier;
2393bool QQuickQmlGenerator::generatePatternNode(
const PatternNodeInfo &info)
2395 if (info.stage == StructureNodeStage::Start) {
2398 startDefsSuffixBlock();
2399 stream() <<
"Loader {";
2402 stream() <<
"id: " << info.id;
2403 stream() <<
"sourceComponent: " << info.id <<
"_container";
2404 stream() <<
"width: item !== null ? item.originalBounds.width : 0";
2405 stream() <<
"height: item !== null ? item.originalBounds.height : 0";
2406 stream() <<
"visible: false";
2407 stream() <<
"function sourceRect(targetWidth, targetHeight) {";
2410 stream() <<
"return Qt.rect(0, 0, ";
2411 if (!info.isPatternRectRelativeCoordinates) {
2412 stream(SameLine) << info.patternRect.width() <<
", "
2413 << info.patternRect.height();
2415 stream(SameLine) << info.patternRect.width() <<
" * targetWidth, "
2416 << info.patternRect.height() <<
" * targetHeight";
2418 stream(SameLine) <<
")";
2422 stream() <<
"function sourceOffset(targetWidth, targetHeight) {";
2425 stream() <<
"return Qt.vector3d(";
2426 if (!info.isPatternRectRelativeCoordinates) {
2427 stream(SameLine) << info.patternRect.x() <<
", "
2428 << info.patternRect.y() <<
", ";
2430 stream(SameLine) << info.patternRect.x() <<
" * targetWidth, "
2431 << info.patternRect.y() <<
" * targetHeight, ";
2433 stream(SameLine) <<
"0.0)";
2441 endDefsSuffixBlock();
2447void QQuickQmlGenerator::generateDefsInstantiationNode(
const StructureNodeInfo &info)
2449 if (Q_UNLIKELY(errorState()))
2452 if (info.stage == StructureNodeStage::Start) {
2453 stream() <<
"Loader {";
2456 stream() <<
"sourceComponent: " << info.defsId <<
"_container";
2457 generateNodeBase(info);
2458 generateTimelineFields(info);
2465bool QQuickQmlGenerator::generateMarkerNode(
const MarkerNodeInfo &info)
2467 if (info.stage == StructureNodeStage::Start) {
2468 startDefsSuffixBlock();
2469 stream() <<
"QtObject {";
2472 stream() <<
"id: " << info.id <<
"_markerParameters";
2474 stream() <<
"property bool startReversed: ";
2475 if (info.orientation == MarkerNodeInfo::Orientation::AutoStartReverse)
2476 stream(SameLine) <<
"true";
2478 stream(SameLine) <<
"false";
2480 stream() <<
"function autoAngle(adaptedAngle) {";
2482 if (info.orientation == MarkerNodeInfo::Orientation::Value)
2483 stream() <<
"return " << info.angle;
2485 stream() <<
"return adaptedAngle";
2491 endDefsSuffixBlock();
2493 if (!info.clipBox.isEmpty()) {
2494 stream() <<
"Item {";
2497 stream() <<
"x: " << info.clipBox.x();
2498 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2499 stream(SameLine) <<
" * strokeWidth";
2500 stream() <<
"y: " << info.clipBox.y();
2501 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2502 stream(SameLine) <<
" * strokeWidth";
2503 stream() <<
"width: " << info.clipBox.width();
2504 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2505 stream(SameLine) <<
" * strokeWidth";
2506 stream() <<
"height: " << info.clipBox.height();
2507 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2508 stream(SameLine) <<
" * strokeWidth";
2509 stream() <<
"clip: true";
2512 stream() <<
"Item {";
2515 if (!info.clipBox.isEmpty()) {
2516 stream() <<
"x: " << -info.clipBox.x();
2517 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2518 stream(SameLine) <<
" * strokeWidth";
2519 stream() <<
"y: " << -info.clipBox.y();
2520 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2521 stream(SameLine) <<
" * strokeWidth";
2524 stream() <<
"id: " << info.id;
2526 stream() <<
"property real markerWidth: " << info.markerSize.width();
2527 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2528 stream(SameLine) <<
" * strokeWidth";
2530 stream() <<
"property real markerHeight: " << info.markerSize.height();
2531 if (info.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth)
2532 stream(SameLine) <<
" * strokeWidth";
2534 stream() <<
"function calculateMarkerScale(w, h) {";
2537 stream() <<
"var scaleX = 1.0";
2538 stream() <<
"var scaleY = 1.0";
2539 stream() <<
"var offsetX = 0.0";
2540 stream() <<
"var offsetY = 0.0";
2541 if (info.viewBox.width() > 0)
2542 stream() <<
"if (w > 0) scaleX = w / " << info.viewBox.width();
2543 if (info.viewBox.height() > 0)
2544 stream() <<
"if (h > 0) scaleY = h / " << info.viewBox.height();
2546 if (info.preserveAspectRatio & MarkerNodeInfo::xyMask) {
2547 stream() <<
"if (scaleX != scaleY) {";
2550 if (info.preserveAspectRatio & MarkerNodeInfo::meet)
2551 stream() <<
"scaleX = scaleY = Math.min(scaleX, scaleY)";
2553 stream() <<
"scaleX = scaleY = Math.max(scaleX, scaleY)";
2555 QString overflowX = QStringLiteral(
"scaleX * %1 - w").arg(info.viewBox.width());
2556 QString overflowY = QStringLiteral(
"scaleY * %1 - h").arg(info.viewBox.height());
2558 const quint8 xRatio = info.preserveAspectRatio & MarkerNodeInfo::xMask;
2559 if (xRatio == MarkerNodeInfo::xMid)
2560 stream() <<
"offsetX -= " << overflowX <<
" / 2";
2561 else if (xRatio == MarkerNodeInfo::xMax)
2562 stream() <<
"offsetX -= " << overflowX;
2564 const quint8 yRatio = info.preserveAspectRatio & MarkerNodeInfo::yMask;
2565 if (yRatio == MarkerNodeInfo::yMid)
2566 stream() <<
"offsetY -= " << overflowY <<
" / 2";
2567 else if (yRatio == MarkerNodeInfo::yMax)
2568 stream() <<
"offsetY -= " << overflowY;
2574 stream() <<
"return Qt.vector4d("
2575 <<
"offsetX - " << info.anchorPoint.x() <<
" * scaleX, "
2576 <<
"offsetY - " << info.anchorPoint.y() <<
" * scaleY, "
2583 stream() <<
"property vector4d markerScale: calculateMarkerScale(markerWidth, markerHeight)";
2585 stream() <<
"transform: [";
2588 stream() <<
"Scale { xScale: " << info.id <<
".markerScale.z; yScale: " << info.id <<
".markerScale.w },";
2589 stream() <<
"Translate { x: " << info.id <<
".markerScale.x; y: " << info.id <<
".markerScale.y }";
2595 generateNodeEnd(info);
2597 if (!info.clipBox.isEmpty()) {
2606bool QQuickQmlGenerator::generateRootNode(
const StructureNodeInfo &info)
2608 if (Q_UNLIKELY(errorState()))
2611 const QStringList comments = m_commentString.split(u'\n');
2613 if (!isNodeVisible(info)) {
2616 if (comments.isEmpty()) {
2617 stream() <<
"// Generated from SVG";
2619 for (
const auto &comment : comments)
2620 stream() <<
"// " << comment;
2623 stream() <<
"import QtQuick";
2624 stream() <<
"import QtQuick.Shapes" << Qt::endl;
2625 stream() <<
"Item {";
2628 double w = info.size.width();
2629 double h = info.size.height();
2631 stream() <<
"implicitWidth: " << w;
2633 stream() <<
"implicitHeight: " << h;
2641 if (info.stage == StructureNodeStage::Start) {
2644 if (comments.isEmpty())
2645 stream() <<
"// Generated from SVG";
2647 for (
const auto &comment : comments)
2648 stream() <<
"// " << comment;
2650 stream() <<
"import QtQuick";
2651 stream() <<
"import QtQuick.VectorImage";
2652 stream() <<
"import QtQuick.VectorImage.Helpers";
2653 stream() <<
"import QtQuick.Shapes";
2654 stream() <<
"import QtQuick.Effects";
2655 if (usingTimelineAnimation())
2656 stream() <<
"import QtQuick.Timeline";
2658 for (
const auto &import : std::as_const(m_extraImports))
2659 stream() <<
"import " << import;
2661 stream() << Qt::endl <<
"Item {";
2664 double w = info.size.width();
2665 double h = info.size.height();
2667 stream() <<
"implicitWidth: " << w;
2669 stream() <<
"implicitHeight: " << h;
2671 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2672 stream() <<
"component AnimationsInfo : QtObject";
2677 stream() <<
"property bool paused: false";
2678 stream() <<
"property int loops: 1";
2679 stream() <<
"signal restart()";
2681 if (Q_UNLIKELY(!isRuntimeGenerator())) {
2684 stream() <<
"property AnimationsInfo animations : AnimationsInfo {}";
2687 stream() <<
"Item {";
2689 stream() <<
"width: 1";
2690 stream() <<
"height: 1";
2692 stream() <<
"ItemSpy { id: __qt_toplevel_scale_itemspy; anchors.fill: parent }";
2697 if (!info.viewBox.isEmpty()) {
2698 stream() <<
"transform: [";
2700 bool translate = !qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y());
2702 stream() <<
"Translate { x: " << -info.viewBox.x() <<
"; y: " << -info.viewBox.y() <<
" },";
2703 stream() <<
"Scale { xScale: width / " << info.viewBox.width() <<
"; yScale: height / " << info.viewBox.height() <<
" }";
2708 if (!info.forceSeparatePaths && info.isPathContainer) {
2709 m_topLevelIdString = QStringLiteral(
"__qt_toplevel");
2710 stream() <<
"id: " << m_topLevelIdString;
2712 generatePathContainer(info);
2715 generateNodeBase(info);
2717 m_topLevelIdString = generateNodeBase(info);
2718 if (m_topLevelIdString.isEmpty())
2719 qCWarning(lcQuickVectorImage) <<
"No ID specified for top level item";
2722 if (usingTimelineAnimation() && info.timelineInfo) {
2723 stream() <<
"property real startFrame: " << info.timelineInfo->startFrame;
2724 stream() <<
"property real endFrame: " << info.timelineInfo->endFrame;
2725 stream() <<
"property real frameRate: " << info.timelineInfo->frameRate;
2726 stream() <<
"property real frameCounter: " << info.timelineInfo->startFrame;
2727 if (!info.timelineInfo->markers.isEmpty()) {
2728 QStringList markerEntries;
2729 markerEntries.reserve(info.timelineInfo->markers.size());
2730 for (
const LottieMarkerInfo &marker : info.timelineInfo->markers) {
2731 markerEntries << u'"' + sanitizeString(marker.name) + u"\": ["
2732 + QString::number(marker.frame) + u", "
2733 + QString::number(marker.duration) + u']';
2735 stream() <<
"property var markers: ({" << markerEntries.join(
", "_L1) <<
"})";
2737 stream() <<
"NumberAnimation on frameCounter {";
2739 stream() <<
"objectName: \"_qt_frameCounterAnimation\"";
2740 stream() <<
"from: " << m_topLevelIdString <<
".startFrame";
2741 stream() <<
"to: " << m_topLevelIdString <<
".endFrame";
2742 stream() <<
"duration: " << processAnimationTime(1000) <<
" * Math.abs(to - from) / "
2743 <<
"Math.max(" << m_topLevelIdString <<
".frameRate, 1)";
2744 generateAnimationBindings();
2747 stream() <<
"visible: frameCounter >= " << info.timelineInfo->startFrame
2748 <<
" && frameCounter < " << info.timelineInfo->endFrame;
2751 if (m_inShapeItemLevel > 0) {
2752 m_inShapeItemLevel--;
2757 for (
const auto [coords, id] : m_easings.asKeyValueRange()) {
2758 stream() <<
"readonly property easingCurve " << id <<
": ({ type: Easing.BezierSpline, bezierCurve: [ ";
2759 for (
auto coord : coords)
2760 stream(SameLine) << coord <<
", ";
2761 stream(SameLine) <<
"1, 1 ] })";
2764 generateNodeEnd(info);
2771void QQuickQmlGenerator::startDefsSuffixBlock()
2773 int tmp = m_oldIndentLevels.top();
2774 m_oldIndentLevels.push(m_indentLevel);
2775 m_indentLevel = tmp;
2776 m_stream.setString(&m_defsSuffix);
2779void QQuickQmlGenerator::endDefsSuffixBlock()
2781 m_indentLevel = m_oldIndentLevels.pop();
2782 m_stream.setDevice(&m_result);
2785QStringView QQuickQmlGenerator::indent()
2787 int indentWidth = m_indentLevel * 4;
2788 if (indentWidth > m_indentString.size())
2789 m_indentString.fill(QLatin1Char(
' '), indentWidth * 2);
2790 return QStringView(m_indentString).first(indentWidth);
2793QTextStream &QQuickQmlGenerator::stream(
int flags)
2795 if (m_stream.device() ==
nullptr && m_stream.string() ==
nullptr)
2796 m_stream.setDevice(&m_result);
2797 else if (!(flags & StreamFlags::SameLine))
2798 m_stream << Qt::endl << indent();
2800 static qint64 maxBufferSize = qEnvironmentVariableIntegerValue(
"QT_QUICKVECTORIMAGE_MAX_BUFFER").value_or(64 << 20);
2801 if (m_stream.device()) {
2802 if (Q_UNLIKELY(!checkSanityLimit(m_stream.device()->size(), maxBufferSize,
"buffer size"_L1)))
2803 m_stream.device()->reset();
2805 if (Q_UNLIKELY(!checkSanityLimit(m_stream.string()->size(), maxBufferSize,
"buffer string size"_L1)))
2806 m_stream.string()->clear();
2812const char *QQuickQmlGenerator::shapeName()
const
2814 return m_shapeTypeName.isEmpty() ?
"Shape" : m_shapeTypeName.constData();
Combined button and popup list for selecting options.
static QString sanitizeString(const QString &input)