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
qquickvectorimage.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
4#include <QtCore/qurl.h>
5#include <QtCore/QScopeGuard>
11#include <QtQuickVectorImageGenerator/private/qquickitemgenerator_p.h>
12#include <QtQuickVectorImageGenerator/private/qquickvectorimageglobal_p.h>
13#include <QtQuickVectorImageGenerator/private/qquickanimationrootitem_p.h>
14#include <QtCore/private/qfactoryloader_p.h>
15#include <QtCore/qloggingcategory.h>
16
17#include <private/qquicktranslate_p.h>
18#include <private/qquickanimation_p.h>
19
20QT_BEGIN_NAMESPACE
21
22Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, itemGenPluginLoader,
24 QLatin1String("/vectorimageformats"), Qt::CaseInsensitive))
25
26static bool useQmlGenerator()
27{
28 static const bool val = !qEnvironmentVariableIsSet("QT_QUICKVECTORIMAGE_USE_ITEM_GENERATOR");
29 return val;
30}
31
32/*!
33 \qmlmodule QtQuick.VectorImage
34 \title Qt Quick Vector Image QML Types
35 \ingroup qmlmodules
36 \brief Provides QML types for displaying vector image files.
37 \since 6.8
38
39 To use the types in this module, import the module with the following line:
40
41 \qml
42 import QtQuick.VectorImage
43 \endqml
44
45 Qt Quick Vector Image provides support for displaying vector image files in a Qt Quick
46 scene.
47
48 It currently supports the \c SVG file format. In addition, Lottie support
49 can be enabled by setting the
50 \l{QtQuick.VectorImage::VectorImage::}{assumeTrustedSource} property to true
51 and including the plugin from the \l{Qt Lottie Animation} module.
52
53 Qt supports multiple options for displaying SVG files. For an overview and comparison of
54 the different ones, see the documentation of the \l{svgtoqml} tool.
55
56 \section1 QML Types
57*/
58
59void QQuickVectorImagePrivate::setSource(const QUrl &source)
60{
61 Q_Q(QQuickVectorImage);
62 if (imageSource.source() == source)
63 return;
64
65 imageSource.setSource(source);
66 loadFile();
67 emit q->sourceChanged();
68}
69
70void QQuickVectorImagePrivate::setSourceData(const QByteArray &data)
71{
72 imageSource.setData(data);
73 loadFile();
74}
75
76void QQuickVectorImagePrivate::loadFile()
77{
78 Q_Q(QQuickVectorImage);
79
80 if (!q->isComponentComplete())
81 return;
82
83 QQmlContext *ctx = qmlContext(q);
84 imageSource.resolveLocalFileName(ctx);
85
86 if (rootItem && (!retainWhileLoading || imageSource.isEmpty())) {
87 rootItem->deleteLater();
88 rootItem = nullptr;
89 emit q->generatedItemChanged();
90 if (incubator == nullptr)
91 emit q->statusChanged();
92 }
93
94 if (imageSource.isEmpty())
95 return;
96
97 if (incubator != nullptr) {
98 // If the incubator is still alive, it means it was interrupted before we could add the
99 // object to the parent item.
100 QObject *obj = incubator->object();
101 delete obj;
102
103 incubator->disconnect(q);
104 incubator->deleteLater();
105 }
106
107 if (pendingRootItem != nullptr) {
108 delete pendingRootItem;
109 pendingRootItem = nullptr;
110 }
111
112 QQuickVectorImageGenerator::GeneratorFlags flags;
113 if (preferredRendererType == QQuickVectorImage::CurveRenderer)
114 flags.setFlag(QQuickVectorImageGenerator::CurveRenderer);
115 if (assumeTrustedSource)
116 flags.setFlag(QQuickVectorImageGenerator::AssumeTrustedSource);
117 if (m_asyncShapes)
118 flags.setFlag(QQuickVectorImageGenerator::AsyncShapes);
119 if (asynchronous)
120 flags.setFlag(QQuickVectorImageGenerator::AsynchronousLoading);
121
122 if (useQmlGenerator()) {
123 QQmlIncubator::IncubationMode mode =
124 asynchronous ? QQmlIncubator::Asynchronous : QQmlIncubator::Synchronous;
125
126 if (!context || context->engine() != qmlContext(q)->engine())
127 context.reset(new QQmlContext(qmlContext(q)->engine()));
128
129 incubator = new QQuickVectorImageIncubator(mode, context.get(), q);
130 QObject::connect(incubator, &QQuickVectorImageIncubator::statusUpdated, q,
131 &QQuickVectorImage::updateItem);
132 incubator->start(imageSource, flags);
133 } else {
134 QQuickItemGenerator gen(imageSource, flags, qmlContext(q));
135
136 bool generatedWithPlugin = false;
137 if (flags.testFlag(QQuickVectorImageGenerator::AssumeTrustedSource)) {
138 QFactoryLoader *loader = itemGenPluginLoader();
139 const qsizetype count = loader->keyMap().size();
140 for (qsizetype i = 0; i < count && !generatedWithPlugin; ++i) {
141 QQuickVectorImagePlugin *plugin =
142 qobject_cast<QQuickVectorImagePlugin *>(loader->instance(i));
143 if (plugin != nullptr) {
144 std::unique_ptr<QQuickVectorImagePluginGenerator> pluginGen(
145 plugin->createGenerator(imageSource));
146 if (pluginGen != nullptr)
147 generatedWithPlugin = pluginGen->generate(&gen);
148 }
149 }
150 }
151
152 if (!generatedWithPlugin)
153 gen.generate();
154
155 if (gen.errorState() != QQuickVectorImageGenerator::NoError) {
156 qCWarning(lcQuickVectorImage)
157 << "QQuickItemGenerator: failed to generate" << imageSource
158 << "(errorState:" << gen.errorState() << ")";
159 } else {
160 pendingRootItem = gen.takeRootItem();
161 }
162 q->updateItem();
163 }
164}
165
166void QQuickVectorImage::updateItem()
167{
168 Q_D(QQuickVectorImage);
169
170 const QQuickItem *oldGenItem = generatedItem();
171 auto emitter = qScopeGuard([&] { // emit at any function exit
172 if (generatedItem() != oldGenItem)
173 emit generatedItemChanged();
174 emit statusChanged();
175 });
176
177 QQuickItem *item = nullptr;
178 if (d->incubator != nullptr) {
179 if (d->incubator->object() == nullptr || !d->incubator->isReady())
180 return;
181 item = qobject_cast<QQuickItem *>(d->incubator->object());
182 if (item == nullptr) {
183 qCWarning(lcQuickVectorImage)
184 << "QQuickVectorImage::updateItem: Root item not a QQuickItem:"
185 << d->incubator->errors();
186 return;
187 }
188 } else {
189 item = d->pendingRootItem;
190 d->pendingRootItem = nullptr;
191 if (item == nullptr)
192 return;
193 }
194
195 if (d->rootItem != nullptr)
196 d->rootItem->deleteLater();
197
198 d->rootItem = new QQuickItem(this);
199 d->rootItem->setParentItem(this);
200 d->rootItem->setImplicitWidth(item->width());
201 d->rootItem->setImplicitHeight(item->height());
202
203 item->setParent(d->rootItem);
204 item->setParentItem(d->rootItem);
205
206 setImplicitWidth(d->rootItem->width());
207 setImplicitHeight(d->rootItem->height());
208
209 updateAnimationProperties();
210 updateRootItemScale();
211 update();
212
213 static int freezeTime = qEnvironmentVariableIntValue("QT_QUICKVECTORIMAGE_FREEZE");
214 if (freezeTime != 0) {
215 if (freezeTime < 0)
216 freezeTime = 400; // TBD: calculate better default, e.g. midtime of total anim duration
217 animations()->setPaused(true);
218 const QList<QQuickAbstractAnimation *> anims = d->rootItem->findChildren<QQuickAbstractAnimation *>();
219 for (QQuickAbstractAnimation *anim : anims) {
220 if (anim->group() == nullptr)
221 anim->setCurrentTime(freezeTime);
222 }
223 }
224
225 if (d->incubator) {
226 QQuickVectorImageIncubatorPrivate *dd = QQuickVectorImageIncubatorPrivate::get(d->incubator);
227 auto componentGuard = dd->takeComponentGuard();
228 if (!componentGuard.isNull()) {
229 Q_ASSERT(componentGuard.component() == nullptr);
230 connect(d->rootItem, &QObject::destroyed, d->rootItem,
231 [componentGuard = std::move(componentGuard)]() {
232 // componentGuard cleans up when it goes out of scope
233 Q_UNUSED(componentGuard);
234 });
235 }
236
237 d->incubator->disconnect(this);
238 d->incubator->deleteLater();
239 d->incubator = nullptr;
240 }
241}
242
243/*!
244 \qmltype VectorImage
245 \inqmlmodule QtQuick.VectorImage
246 \inherits Item
247 \brief Loads a vector image file and displays it in a Qt Quick scene.
248 \since 6.8
249
250 The VectorImage can be used to load a vector image file and display this as an item in a Qt
251 Quick scene.
252
253 It currently supports the \c SVG file format. In addition, Lottie support can be enabled by
254 setting the \l{assumeTrustedSource} property to true and including the plugin from the
255 \l{Qt Lottie Animation} module.
256
257 \note This complements the approach of loading the vector image file through an \l Image
258 element: \l Image creates a raster version of the image at the requested size. VectorImage
259 builds a Qt Quick scene that represents the image. This means the resulting item can be scaled
260 and rotated without losing quality, and it will typically consume less memory than the
261 rasterized version.
262*/
263QQuickVectorImage::QQuickVectorImage(QQuickItem *parent)
264 : QQuickItem(*(new QQuickVectorImagePrivate), parent)
265{
266 setFlag(QQuickItem::ItemHasContents, true);
267
268 QObject::connect(this, &QQuickItem::widthChanged, this, &QQuickVectorImage::updateRootItemScale);
269 QObject::connect(this, &QQuickItem::heightChanged, this, &QQuickVectorImage::updateRootItemScale);
270 QObject::connect(this, &QQuickVectorImage::fillModeChanged, this, &QQuickVectorImage::updateRootItemScale);
271}
272
273QQuickVectorImage::~QQuickVectorImage()
274{
275 Q_D(QQuickVectorImage);
276 // This may have a running thread, so we need to delete it before we start deleting children
277 delete d->incubator;
278 d->incubator = nullptr;
279}
280
281/*!
282 \qmlproperty url QtQuick.VectorImage::VectorImage::source
283
284 This property holds the URL of the vector image file to load.
285
286 VectorImage currently supports the \c SVG file format. In addition, Lottie support can be
287 enabled by setting the \l{assumeTrustedSource} property to true and including the plugin from
288 the \l{Qt Lottie Animation} module.
289*/
290QUrl QQuickVectorImage::source() const
291{
292 Q_D(const QQuickVectorImage);
293 return d->imageSource.source();
294}
295
296void QQuickVectorImage::setSource(const QUrl &source)
297{
298 Q_D(QQuickVectorImage);
299 d->setSource(source);
300}
301
302void QQuickVectorImage::updateRootItemScale()
303{
304 Q_D(QQuickVectorImage);
305
306 if (d->rootItem == nullptr
307 || qFuzzyIsNull(d->rootItem->width())
308 || qFuzzyIsNull(d->rootItem->height())) {
309 return;
310 }
311
312 auto xformProp = d->rootItem->transform();
313 QQuickScale *scaleTransform = nullptr;
314 if (xformProp.count(&xformProp) == 0) {
315 scaleTransform = new QQuickScale;
316 scaleTransform->setParent(d->rootItem);
317 xformProp.append(&xformProp, scaleTransform);
318 } else {
319 scaleTransform = qobject_cast<QQuickScale *>(xformProp.at(&xformProp, 0));
320 }
321
322 if (scaleTransform != nullptr) {
323 qreal xScale = width() / d->rootItem->width();
324 qreal yScale = height() / d->rootItem->height();
325
326 switch (d->fillMode) {
327 case QQuickVectorImage::NoResize:
328 xScale = yScale = 1.0;
329 break;
330 case QQuickVectorImage::PreserveAspectFit:
331 xScale = yScale = qMin(xScale, yScale);
332 break;
333 case QQuickVectorImage::PreserveAspectCrop:
334 xScale = yScale = qMax(xScale, yScale);
335 break;
336 case QQuickVectorImage::Stretch:
337 // Already correct
338 break;
339 };
340
341 scaleTransform->setXScale(xScale);
342 scaleTransform->setYScale(yScale);
343 }
344}
345
346void QQuickVectorImage::updateAnimationProperties()
347{
348 Q_D(QQuickVectorImage);
349 if (Q_UNLIKELY(d->rootItem == nullptr || d->rootItem->childItems().isEmpty()))
350 return;
351
352 QQuickItem *childItem = d->rootItem->childItems().first();
353 if (Q_LIKELY(d->animations != nullptr)) {
354 if (auto *root = qobject_cast<QQuickAnimationRootItem *>(childItem)) {
355 root->setLoops(d->animations->loops());
356 root->setPaused(d->animations->paused());
357 } else {
358 childItem->setProperty("loops", d->animations->loops());
359 childItem->setProperty("paused", d->animations->paused());
360 }
361 }
362}
363
364QQuickVectorImageAnimations *QQuickVectorImage::animations()
365{
366 Q_D(QQuickVectorImage);
367 if (d->animations == nullptr) {
368 d->animations = new QQuickVectorImageAnimations;
369 QQml_setParent_noEvent(d->animations, this);
370 QObject::connect(d->animations, &QQuickVectorImageAnimations::loopsChanged, this, &QQuickVectorImage::updateAnimationProperties);
371 QObject::connect(d->animations, &QQuickVectorImageAnimations::pausedChanged, this, &QQuickVectorImage::updateAnimationProperties);
372 }
373
374 return d->animations;
375}
376
377/*!
378 \qmlproperty enumeration QtQuick.VectorImage::VectorImage::fillMode
379
380 This property defines what happens if the width and height of the VectorImage differs from
381 the implicit size of its contents.
382
383 \value VectorImage.NoResize The contents are still rendered at the size provided by
384 the input.
385 \value VectorImage.Stretch The contents are scaled to match the width and height of
386 the \c{VectorImage}. (This is the default.)
387 \value VectorImage.PreserveAspectFit The contents are scaled to fit inside the bounds of the
388 \c VectorImage, while preserving aspect ratio. The
389 actual bounding rect of the contents will sometimes be
390 smaller than the \c VectorImage item.
391 \value VectorImage.PreserveAspectCrop The contents are scaled to fill the \c VectorImage item,
392 while preserving the aspect ratio. The actual bounds of
393 the contents will sometimes be larger than the
394 \c VectorImage item.
395*/
396
397QQuickVectorImage::FillMode QQuickVectorImage::fillMode() const
398{
399 Q_D(const QQuickVectorImage);
400 return d->fillMode;
401}
402
403void QQuickVectorImage::setFillMode(FillMode newFillMode)
404{
405 Q_D(QQuickVectorImage);
406 if (d->fillMode == newFillMode)
407 return;
408 d->fillMode = newFillMode;
409 emit fillModeChanged();
410}
411
412/*!
413 \qmlproperty enumeration QtQuick.VectorImage::VectorImage::preferredRendererType
414
415 Requests a specific backend to use for rendering shapes in the \c VectorImage.
416
417 \value VectorImage.GeometryRenderer Equivalent to Shape.GeometryRenderer. This backend flattens
418 curves and triangulates the result. It will give aliased results unless multi-sampling is
419 enabled, and curve flattening may be visible when the item is scaled.
420 \value VectorImage.CurveRenderer Equivalent to Shape.CurveRenderer. With this backend, curves
421 are rendered on the GPU and anti-aliasing is built in. Will typically give better visual
422 results, but at some extra cost to performance.
423
424 The default is \c{VectorImage.GeometryRenderer}.
425*/
426
427QQuickVectorImage::RendererType QQuickVectorImage::preferredRendererType() const
428{
429 Q_D(const QQuickVectorImage);
430 return d->preferredRendererType;
431}
432
433void QQuickVectorImage::setPreferredRendererType(RendererType newPreferredRendererType)
434{
435 Q_D(QQuickVectorImage);
436 if (d->preferredRendererType == newPreferredRendererType)
437 return;
438 d->preferredRendererType = newPreferredRendererType;
439 d->loadFile();
440 emit preferredRendererTypeChanged();
441}
442
443/*!
444 \qmlproperty bool QtQuick.VectorImage::VectorImage::asynchronousShapes
445 \since 6.11
446
447 This property controls the {QtQuick.Shapes::Shape::asynchronous}{asynchronous} property of the
448 \l Shape items in the Quick scene that VectorImage builds to represent the image.
449
450 Setting this property to \c true will offload the CPU part of the rendering processing of the
451 shapes to separate worker threads. This can improve CPU utilization and user interface
452 responsiveness.
453
454 By default this property is \c false.
455
456 \sa asynchronous
457*/
458
459bool QQuickVectorImage::asynchronousShapes() const
460{
461 Q_D(const QQuickVectorImage);
462 return d->m_asyncShapes;
463}
464
465void QQuickVectorImage::setAsynchronousShapes(bool asynchronous)
466{
467 Q_D(QQuickVectorImage);
468 if (d->m_asyncShapes == asynchronous)
469 return;
470 d->m_asyncShapes = asynchronous;
471 emit asynchronousShapesChanged();
472}
473
474/*!
475 \qmlproperty bool QtQuick.VectorImage::VectorImage::asynchronous
476 \since 6.12
477
478 This property holds whether the image will be loaded asynchronously. When set to to \c true,
479 the UI will remain reactive while the image is loading. The \l status property can be used
480 to check the current progress.
481
482 By default this property is \c false.
483
484 \sa asynchronousShapes, status
485*/
486
487bool QQuickVectorImage::asynchronous() const
488{
489 Q_D(const QQuickVectorImage);
490 return d->asynchronous;
491}
492
493void QQuickVectorImage::setAsynchronous(bool asynchronous)
494{
495 Q_D(QQuickVectorImage);
496 if (d->asynchronous == asynchronous)
497 return;
498 d->asynchronous = asynchronous;
499 emit asynchronousChanged();
500}
501
502/*!
503 \qmlproperty bool QtQuick.VectorImage::VectorImage::retainWhileLoading
504 \since 6.12
505
506 This property defines the behavior when the \l source property is changed and loading happens
507 asynchronously. This is the case when the \l asynchronous property is set to \c true.
508
509 If \c retainWhileLoading is \c false (the default), the old image is discarded immediately, and
510 the component is cleared while the new image is being loaded. If set to \c true, the old image
511 is retained and remains visible until the new one is ready.
512
513 Enabling this property can avoid flickering in cases where loading the new image takes a long
514 time. It comes at the cost of some extra memory use while the new image is being loaded.
515
516 \sa asynchronous
517*/
518bool QQuickVectorImage::retainWhileLoading() const
519{
520 Q_D(const QQuickVectorImage);
521 return d->retainWhileLoading;
522}
523
524void QQuickVectorImage::setRetainWhileLoading(bool retainWhileLoading)
525{
526 Q_D(QQuickVectorImage);
527 if (d->retainWhileLoading == retainWhileLoading)
528 return;
529 d->retainWhileLoading = retainWhileLoading;
530 emit retainWhileLoadingChanged();
531}
532
533/*!
534 \qmlproperty enumeration QtQuick.VectorImage::VectorImage::status
535 \since 6.12
536
537 This property holds the status of vector image loading. It can be one of:
538
539 \value VectorImage.Null No vector image has been set
540 \value VectorImage.Ready The vector image has been loaded
541 \value VectorImage.Loading The vector image is currently being loaded
542 \value VectorImage.Error An error occurred while loading the vector image
543*/
544QQuickVectorImage::Status QQuickVectorImage::status() const
545{
546 Q_D(const QQuickVectorImage);
547 if (d->incubator == nullptr) {
548 if (d->rootItem != nullptr)
549 return Status::Ready;
550 else if (!isComponentComplete() || d->imageSource.isEmpty())
551 return Status::Null;
552 else
553 return Status::Error;
554 }
555
556 switch (d->incubator->status()) {
557 case QQmlIncubator::Null:
558 return Status::Null;
559 case QQmlIncubator::Loading:
560 return Status::Loading;
561 case QQmlIncubator::Error:
562 return Status::Error;
563 case QQmlIncubator::Ready:
564 return Status::Ready;
565 };
566
567 return Status::Error;
568}
569
570/*!
571 \qmlproperty bool QtQuick.VectorImage::VectorImage::assumeTrustedSource
572 \since 6.10
573
574 Setting this to true when loading trusted source files expands support for some features that
575 may be unsafe in an uncontrolled setting. For SVG in particular, this maps to the
576 \l{QtSvg::Option}{AssumeTrustedSource option}.
577
578 When this is set to true, VectorImage will also try to load the image using the Lottie format
579 plugin if this is available. See \l{Qt Lottie Animation} for additional information.
580
581 By default this property is \c false.
582
583 \sa svgtoqml, lottietoqml
584 */
585
586bool QQuickVectorImage::assumeTrustedSource() const
587{
588 Q_D(const QQuickVectorImage);
589 return d->assumeTrustedSource;
590}
591
592void QQuickVectorImage::setAssumeTrustedSource(bool assumeTrustedSource)
593{
594 Q_D(QQuickVectorImage);
595 if (d->assumeTrustedSource == assumeTrustedSource)
596 return;
597 d->assumeTrustedSource = assumeTrustedSource;
598 d->loadFile();
599 emit assumeTrustedSourceChanged();
600}
601
602/*!
603 \qmlproperty Item QtQuick.VectorImage::VectorImage::generatedItem
604 \since 6.12
605 \readonly
606
607 When a vector image file is loaded, this property holds the top level Item of the generated Qt
608 Quick scene. When no file is loaded, this property is \c null.
609*/
610
611QQuickItem *QQuickVectorImage::generatedItem() const
612{
613 Q_D(const QQuickVectorImage);
614 return d->rootItem ? d->rootItem->childItems().value(0) : nullptr;
615}
616
617void QQuickVectorImage::componentComplete()
618{
619 Q_D(QQuickVectorImage);
620 QQuickItem::componentComplete();
621
622 d->loadFile();
623}
624
625/*!
626 \qmlpropertygroup QtQuick.VectorImage::VectorImage::animations
627 \qmlproperty bool QtQuick.VectorImage::VectorImage::animations.paused
628 \qmlproperty int QtQuick.VectorImage::VectorImage::animations.loops
629 \since 6.10
630
631 These properties can be used to control animations in the image, if it contains any.
632
633 The \c paused property can be set to true to temporarily pause all animations. When the
634 property is reset to \c false, the animations will resume where they were. By default this
635 property is \c false.
636
637 The \c loops property defines the number of times the animations in the document will repeat.
638 By default this property is 1. Any animations that is set to loop indefinitely in the source
639 image will be unaffected by this property. To make all animations in the document repeat
640 indefinitely, the \c loops property can be set to \c{Animation.Infinite}.
641*/
642int QQuickVectorImageAnimations::loops() const
643{
644 return m_loops;
645}
646
647void QQuickVectorImageAnimations::setLoops(int loops)
648{
649 if (m_loops == loops)
650 return;
651 m_loops = loops;
652 emit loopsChanged();
653}
654
655bool QQuickVectorImageAnimations::paused() const
656{
657 return m_paused;
658}
659
660void QQuickVectorImageAnimations::setPaused(bool paused)
661{
662 if (m_paused == paused)
663 return;
664 m_paused = paused;
665 emit pausedChanged();
666}
667
668void QQuickVectorImageAnimations::restart()
669{
670 QQuickVectorImage *parentVectorImage = qobject_cast<QQuickVectorImage *>(parent());
671 if (Q_UNLIKELY(parentVectorImage == nullptr)) {
672 qCWarning(lcQuickVectorImage) << Q_FUNC_INFO << "Parent is not a VectorImage";
673 return;
674 }
675
676 QQuickVectorImagePrivate *d = QQuickVectorImagePrivate::get(parentVectorImage);
677
678 if (Q_UNLIKELY(d->rootItem == nullptr || d->rootItem->childItems().isEmpty()))
679 return;
680
681 QQuickItem *childItem = d->rootItem->childItems().first();
682 QMetaObject::invokeMethod(childItem, "restart");
683}
684
685QT_END_NAMESPACE
686
687#include <moc_qquickvectorimage_p.cpp>
#define QQuickVectorImageFormatsPluginFactory_iid