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