1 | // Copyright (C) 2018 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "lottieanimation.h" |
5 | |
6 | #include <QQuickPaintedItem> |
7 | #include <QJsonDocument> |
8 | #include <QJsonObject> |
9 | #include <QJsonArray> |
10 | #include <QJsonValue> |
11 | #include <QFile> |
12 | #include <QPointF> |
13 | #include <QPainter> |
14 | #include <QImage> |
15 | #include <QTimer> |
16 | #include <QMetaObject> |
17 | #include <QLoggingCategory> |
18 | #include <QThread> |
19 | #include <QQmlContext> |
20 | #include <QQmlFile> |
21 | #include <math.h> |
22 | |
23 | #include <QtBodymovin/private/bmbase_p.h> |
24 | #include <QtBodymovin/private/bmlayer_p.h> |
25 | |
26 | #include "rasterrenderer/batchrenderer.h" |
27 | #include "rasterrenderer/lottierasterrenderer.h" |
28 | |
29 | QT_BEGIN_NAMESPACE |
30 | |
31 | Q_LOGGING_CATEGORY(lcLottieQtBodymovinRender, "qt.lottieqt.bodymovin.render" ); |
32 | Q_LOGGING_CATEGORY(lcLottieQtBodymovinParser, "qt.lottieqt.bodymovin.parser" ); |
33 | |
34 | /*! |
35 | \qmltype LottieAnimation |
36 | \inqmlmodule Qt.labs.lottieqt |
37 | \since 5.13 |
38 | \inherits Item |
39 | \brief A Bodymovin player for Qt. |
40 | |
41 | The LottieAnimation type shows Bodymovin format files. |
42 | |
43 | LottieAnimation is used to load and render Bodymovin files exported |
44 | from Adobe After Effects. Currently, only subset of the full Bodymovin |
45 | specification is supported. Most notable deviations are: |
46 | |
47 | \list |
48 | \li Only Shape layer supported |
49 | \li Only integer frame-mode of a timeline supported |
50 | (real frame numbers and time are rounded to the nearest integer) |
51 | \li Expressions are not supported |
52 | \endlist |
53 | |
54 | For the full list of devations, please see see the \l{Limitations} |
55 | section. |
56 | |
57 | \section1 Example Usage |
58 | |
59 | The following example shows a simple usage of the LottieAnimation type |
60 | |
61 | \qml |
62 | LottieAnimation { |
63 | loops: 2 |
64 | quality: LottieAnimation.MediumQuality |
65 | source: "animation.json" |
66 | autoPlay: false |
67 | onStatusChanged: { |
68 | if (status === LottieAnimation.Ready) { |
69 | // any acvities needed before |
70 | // playing starts go here |
71 | gotoAndPlay(startFrame); |
72 | } |
73 | } |
74 | onFinished: { |
75 | console.log("Finished playing") |
76 | } |
77 | } |
78 | \endqml |
79 | |
80 | \note Changing width or height of the element does not change the size |
81 | of the animation within. Also, it is not possible to align the the content |
82 | inside of a \c LottieAnimation element. To achieve this, position the |
83 | animation inside e.g. an \c Item. |
84 | |
85 | \section1 Rendering Performance |
86 | |
87 | Internally, the rendered frame data is cached to improve performance. You |
88 | can control the memory usage by setting the QLOTTIE_RENDER_CACHE_SIZE |
89 | environment variable (default value is 2). |
90 | |
91 | You can monitor the rendering performance by turning on two logging categories: |
92 | |
93 | \list |
94 | \li \c qt.lottieqt.bodymovin.render - Provides information how the animation |
95 | is rendered |
96 | \li \c qt.lottieqt.bodymovin.render.thread - Provides information how the |
97 | rendering process proceeds. |
98 | \endlist |
99 | |
100 | Specifically, you can monitor does the frame cache gets constantly full, or |
101 | does the rendering process have to wait for frames to become ready. The |
102 | first case implies that the animation is too complex, and the rendering |
103 | cannot keep up the pace. Try making the animation simpler, or optimize |
104 | the QML scene. |
105 | */ |
106 | |
107 | /*! |
108 | \qmlproperty bool LottieAnimation::autoPlay |
109 | |
110 | Defines whether the player will start playing animation automatically after |
111 | the animation file has been loaded. |
112 | |
113 | The default value is \c true. |
114 | */ |
115 | |
116 | /*! |
117 | \qmlproperty int LottieAnimation::loops |
118 | |
119 | This property holds the number of loops the player will repeat. |
120 | The value \c LottieAnimation.Infinite means that the the player repeats |
121 | the animation continuously. |
122 | |
123 | The default value is \c 1. |
124 | */ |
125 | |
126 | /*! |
127 | \qmlsignal LottieAnimation::finished() |
128 | |
129 | This signal is emitted when the player has finished playing. In case of |
130 | looping, the signal is emitted when the last loop has been finished. |
131 | */ |
132 | |
133 | LottieAnimation::LottieAnimation(QQuickItem *parent) |
134 | : QQuickPaintedItem(parent) |
135 | { |
136 | m_frameAdvance = new QTimer(this); |
137 | m_frameAdvance->setInterval(1000 / m_frameRate); |
138 | m_frameAdvance->setSingleShot(false); |
139 | connect (sender: m_frameAdvance, signal: &QTimer::timeout, context: this, slot: &LottieAnimation::renderNextFrame); |
140 | |
141 | m_frameRenderThread = BatchRenderer::instance(); |
142 | |
143 | qRegisterMetaType<LottieAnimation*>(); |
144 | |
145 | setAntialiasing(m_quality == HighQuality); |
146 | } |
147 | |
148 | LottieAnimation::~LottieAnimation() |
149 | { |
150 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "deregisterAnimator" , Q_ARG(LottieAnimation*, this)); |
151 | } |
152 | |
153 | void LottieAnimation::componentComplete() |
154 | { |
155 | QQuickPaintedItem::componentComplete(); |
156 | |
157 | if (m_source.isValid()) |
158 | load(); |
159 | } |
160 | |
161 | void LottieAnimation::paint(QPainter *painter) |
162 | { |
163 | BMBase* bmTree = m_frameRenderThread->getFrame(animator: this, frameNumber: m_currentFrame); |
164 | |
165 | if (!bmTree) { |
166 | qCDebug(lcLottieQtBodymovinRender) << "LottieAnimation::paint: Got empty element tree." |
167 | "Cannot draw (Animator:" << static_cast<void*>(this) << ")" ; |
168 | return; |
169 | } |
170 | |
171 | LottieRasterRenderer renderer(painter); |
172 | |
173 | qCDebug(lcLottieQtBodymovinRender) << static_cast<void*>(this) << "Start to paint frame" << m_currentFrame; |
174 | |
175 | for (BMBase *elem : bmTree->children()) { |
176 | if (elem->active(frame: m_currentFrame)) |
177 | elem->render(renderer); |
178 | else |
179 | qCDebug(lcLottieQtBodymovinRender) << "Element '" << elem->name() << "' inactive. No need to paint" ; |
180 | } |
181 | |
182 | m_frameRenderThread->frameRendered(animator: this, frameNumber: m_currentFrame); |
183 | |
184 | m_currentFrame += m_direction; |
185 | |
186 | if (m_currentFrame < m_startFrame || m_currentFrame > m_endFrame) { |
187 | m_currentLoop += (m_loops > 0 ? 1 : 0); |
188 | } |
189 | |
190 | if ((m_loops - m_currentLoop) != 0) { |
191 | m_currentFrame = m_currentFrame < m_startFrame ? m_endFrame : |
192 | m_currentFrame > m_endFrame ? m_startFrame : m_currentFrame; |
193 | } |
194 | } |
195 | |
196 | /*! |
197 | \qmlproperty enumeration LottieAnimation::status |
198 | |
199 | This property holds the current status of the LottieAnimation element. |
200 | |
201 | \value LottieAnimation.Null |
202 | An initial value that is used when the source is not defined |
203 | (Default) |
204 | |
205 | \value LottieAnimation.Loading |
206 | The player is loading a Bodymovin file |
207 | |
208 | \value LottieAnimation.Ready |
209 | Loading has finished successfully and the player is ready to play |
210 | the animation |
211 | |
212 | \value LottieAnimation.Error |
213 | An error occurred while loading the animation |
214 | |
215 | For example, you could implement \c onStatusChanged signal |
216 | handler to monitor progress of loading an animation as follows: |
217 | |
218 | \qml |
219 | LottieAnimation { |
220 | source: "animation.json" |
221 | autoPlay: false |
222 | onStatusChanged: { |
223 | if (status === LottieAnimation.Ready) |
224 | start(); |
225 | } |
226 | \endqml |
227 | */ |
228 | LottieAnimation::Status LottieAnimation::status() const |
229 | { |
230 | return m_status; |
231 | } |
232 | |
233 | void LottieAnimation::setStatus(LottieAnimation::Status status) |
234 | { |
235 | if (Q_UNLIKELY(m_status == status)) |
236 | return; |
237 | |
238 | m_status = status; |
239 | emit statusChanged(); |
240 | } |
241 | |
242 | /*! |
243 | \qmlproperty url LottieAnimation::source |
244 | |
245 | The source of the Bodymovin asset that LottieAnimation plays. |
246 | |
247 | LottieAnimation can handle any URL scheme supported by Qt. |
248 | The URL may be absolute, or relative to the URL of the component. |
249 | |
250 | Setting the source property starts loading the animation asynchronously. |
251 | To monitor progress of loading, connect to the \l status change signal. |
252 | */ |
253 | QUrl LottieAnimation::source() const |
254 | { |
255 | return m_source; |
256 | } |
257 | |
258 | void LottieAnimation::setSource(const QUrl &source) |
259 | { |
260 | if (m_source != source) { |
261 | m_source = source; |
262 | emit sourceChanged(); |
263 | |
264 | if (isComponentComplete()) |
265 | load(); |
266 | } |
267 | } |
268 | |
269 | /*! |
270 | \qmlproperty int LottieAnimation::startFrame |
271 | \readonly |
272 | |
273 | Frame number of the start of the animation. The value |
274 | is available after the animation has been loaded and |
275 | ready to play. |
276 | */ |
277 | int LottieAnimation::startFrame() const |
278 | { |
279 | return m_startFrame; |
280 | } |
281 | |
282 | void LottieAnimation::setStartFrame(int startFrame) |
283 | { |
284 | if (Q_UNLIKELY(m_startFrame == startFrame)) |
285 | return; |
286 | |
287 | m_startFrame = startFrame; |
288 | emit startFrameChanged(); |
289 | } |
290 | |
291 | /*! |
292 | \qmlproperty int LottieAnimation::endFrame |
293 | \readonly |
294 | |
295 | Frame number of the end of the animation. The value |
296 | is available after the animation has been loaded and |
297 | ready to play. |
298 | */ |
299 | int LottieAnimation::endFrame() const |
300 | { |
301 | return m_endFrame; |
302 | } |
303 | |
304 | void LottieAnimation::setEndFrame(int endFrame) |
305 | { |
306 | if (Q_UNLIKELY(m_endFrame == endFrame)) |
307 | return; |
308 | |
309 | m_endFrame = endFrame; |
310 | emit endFrameChanged(); |
311 | } |
312 | |
313 | int LottieAnimation::currentFrame() const |
314 | { |
315 | return m_currentFrame; |
316 | } |
317 | |
318 | QVersionNumber LottieAnimation::version() const |
319 | { |
320 | return m_version; |
321 | } |
322 | |
323 | /*! |
324 | \qmlproperty int LottieAnimation::frameRate |
325 | |
326 | This property holds the frame rate value of the Bodymovin animation. |
327 | |
328 | \c frameRate changes after the asset has been loaded. Changing the |
329 | frame rate does not have effect before that, as the value defined in the |
330 | asset overrides the value. To change the frame rate, you can write: |
331 | |
332 | \qml |
333 | LottieAnimation { |
334 | source: "animation.json" |
335 | onStatusChanged: { |
336 | if (status === LottieAnimation.Ready) |
337 | frameRate = 60; |
338 | } |
339 | \endqml |
340 | */ |
341 | int LottieAnimation::frameRate() const |
342 | { |
343 | return m_frameRate; |
344 | } |
345 | |
346 | void LottieAnimation::setFrameRate(int frameRate) |
347 | { |
348 | if (Q_UNLIKELY(m_frameRate == frameRate || frameRate <= 0)) |
349 | return; |
350 | |
351 | m_frameRate = frameRate; |
352 | emit frameRateChanged(); |
353 | |
354 | m_frameAdvance->setInterval(1000 / m_frameRate); |
355 | } |
356 | |
357 | void LottieAnimation::resetFrameRate() |
358 | { |
359 | setFrameRate(m_animFrameRate); |
360 | } |
361 | |
362 | /*! |
363 | \qmlproperty enumeration LottieAnimation::quality |
364 | |
365 | Speficies the rendering quality of the bodymovin player. |
366 | If \c LowQuality is selected the rendering will happen into a frame |
367 | buffer object, whereas with other options, the rendering will be done |
368 | onto \c QImage (which in turn will be rendered on the screen). |
369 | |
370 | \value LottieAnimation.LowQuality |
371 | Antialiasing or a smooth pixmap transformation algorithm are not |
372 | used |
373 | |
374 | \value LottieAnimation.MediumQuality |
375 | Smooth pixmap transformation algorithm is used but no antialiasing |
376 | (Default) |
377 | |
378 | \value LottieAnimation.HighQuality |
379 | Antialiasing and a smooth pixmap tranformation algorithm are both |
380 | used |
381 | */ |
382 | LottieAnimation::Quality LottieAnimation::quality() const |
383 | { |
384 | return m_quality; |
385 | } |
386 | |
387 | void LottieAnimation::setQuality(LottieAnimation::Quality quality) |
388 | { |
389 | if (m_quality != quality) { |
390 | m_quality = quality; |
391 | if (quality == LowQuality) |
392 | setRenderTarget(QQuickPaintedItem::FramebufferObject); |
393 | else |
394 | setRenderTarget(QQuickPaintedItem::Image); |
395 | setSmooth(quality != LowQuality); |
396 | setAntialiasing(quality == HighQuality); |
397 | emit qualityChanged(); |
398 | } |
399 | } |
400 | |
401 | void LottieAnimation::reset() |
402 | { |
403 | m_currentFrame = m_direction > 0 ? m_startFrame : m_endFrame; |
404 | m_currentLoop = 0; |
405 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "gotoFrame" , |
406 | Q_ARG(LottieAnimation*, this), |
407 | Q_ARG(int, m_currentFrame)); |
408 | } |
409 | |
410 | /*! |
411 | \qmlmethod void LottieAnimation::start() |
412 | |
413 | Starts playing the animation from the beginning. |
414 | */ |
415 | void LottieAnimation::start() |
416 | { |
417 | reset(); |
418 | m_frameAdvance->start(); |
419 | } |
420 | |
421 | /*! |
422 | \qmlmethod void LottieAnimation::play() |
423 | |
424 | Starts or continues playing from the current position. |
425 | */ |
426 | void LottieAnimation::play() |
427 | { |
428 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "gotoFrame" , |
429 | Q_ARG(LottieAnimation*, this), |
430 | Q_ARG(int, m_currentFrame)); |
431 | m_frameAdvance->start(); |
432 | } |
433 | |
434 | /*! |
435 | \qmlmethod void LottieAnimation::pause() |
436 | |
437 | Pauses the playback. |
438 | */ |
439 | void LottieAnimation::pause() |
440 | { |
441 | m_frameAdvance->stop(); |
442 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "gotoFrame" , |
443 | Q_ARG(LottieAnimation*, this), |
444 | Q_ARG(int, m_currentFrame)); |
445 | } |
446 | |
447 | /*! |
448 | \qmlmethod void LottieAnimation::togglePause() |
449 | |
450 | Toggles the status of player between playing and paused states. |
451 | */ |
452 | void LottieAnimation::togglePause() |
453 | { |
454 | if (m_frameAdvance->isActive()) { |
455 | pause(); |
456 | } else { |
457 | play(); |
458 | } |
459 | } |
460 | |
461 | /*! |
462 | \qmlmethod void LottieAnimation::stop() |
463 | |
464 | Stops the playback and returns to startFrame. |
465 | */ |
466 | void LottieAnimation::stop() |
467 | { |
468 | m_frameAdvance->stop(); |
469 | reset(); |
470 | renderNextFrame(); |
471 | } |
472 | |
473 | /*! |
474 | \qmlmethod void LottieAnimation::gotoAndPlay(int frame) |
475 | |
476 | Plays the asset from the given \a frame. |
477 | */ |
478 | void LottieAnimation::gotoAndPlay(int frame) |
479 | { |
480 | gotoFrame(frame); |
481 | m_currentLoop = 0; |
482 | m_frameAdvance->start(); |
483 | } |
484 | |
485 | /*! |
486 | \qmlmethod bool LottieAnimation::gotoAndPlay(string frameMarker) |
487 | |
488 | Plays the asset from the frame that has a marker with the given \a frameMarker. |
489 | Returns \c true if the frameMarker was found, \c false otherwise. |
490 | */ |
491 | bool LottieAnimation::gotoAndPlay(const QString &frameMarker) |
492 | { |
493 | if (m_markers.contains(key: frameMarker)) { |
494 | gotoAndPlay(frame: m_markers.value(key: frameMarker)); |
495 | return true; |
496 | } else |
497 | return false; |
498 | } |
499 | |
500 | /*! |
501 | \qmlmethod void LottieAnimation::gotoAndStop(int frame) |
502 | |
503 | Moves the playhead to the given \a frame and stops. |
504 | */ |
505 | void LottieAnimation::gotoAndStop(int frame) |
506 | { |
507 | gotoFrame(frame); |
508 | m_frameAdvance->stop(); |
509 | renderNextFrame(); |
510 | } |
511 | |
512 | /*! |
513 | \qmlmethod bool LottieAnimation::gotoAndStop(string frameMarker) |
514 | |
515 | Moves the playhead to the given marker and stops. |
516 | Returns \c true if \a frameMarker was found, \c false otherwise. |
517 | */ |
518 | bool LottieAnimation::gotoAndStop(const QString &frameMarker) |
519 | { |
520 | if (m_markers.contains(key: frameMarker)) { |
521 | gotoAndStop(frame: m_markers.value(key: frameMarker)); |
522 | return true; |
523 | } else |
524 | return false; |
525 | } |
526 | |
527 | void LottieAnimation::gotoFrame(int frame) |
528 | { |
529 | m_currentFrame = qMax(a: m_startFrame, b: qMin(a: frame, b: m_endFrame)); |
530 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "gotoFrame" , |
531 | Q_ARG(LottieAnimation*, this), |
532 | Q_ARG(int, m_currentFrame)); |
533 | } |
534 | |
535 | /*! |
536 | \qmlmethod double LottieAnimation::getDuration(bool inFrames) |
537 | |
538 | Returns the duration of the currently playing asset. |
539 | |
540 | If a given \a inFrames is \c true, the return value is the duration in |
541 | number of frames. Otherwise, returns the duration in seconds. |
542 | */ |
543 | double LottieAnimation::getDuration(bool inFrames) |
544 | { |
545 | return (m_endFrame - m_startFrame) / |
546 | static_cast<double>(inFrames ? 1 : m_frameRate); |
547 | } |
548 | |
549 | /*! |
550 | \qmlproperty enumeration LottieAnimation::direction |
551 | |
552 | This property holds the direction of rendering. |
553 | |
554 | \value LottieAnimation.Forward |
555 | Forward direction (Default) |
556 | |
557 | \value LottieAnimation.Reverse |
558 | Reverse direction |
559 | */ |
560 | LottieAnimation::Direction LottieAnimation::direction() const |
561 | { |
562 | return static_cast<Direction>(m_direction); |
563 | } |
564 | |
565 | void LottieAnimation::setDirection(LottieAnimation::Direction direction) |
566 | { |
567 | if (Q_UNLIKELY(static_cast<Direction>(m_direction) == direction)) |
568 | return; |
569 | |
570 | m_direction = direction; |
571 | m_currentLoop = 0; |
572 | emit directionChanged(); |
573 | |
574 | m_frameRenderThread->gotoFrame(animator: this, frame: m_currentFrame); |
575 | } |
576 | |
577 | void LottieAnimation::load() |
578 | { |
579 | setStatus(Loading); |
580 | |
581 | const QQmlContext *context = qmlContext(this); |
582 | const QUrl loadUrl = context ? context->resolvedUrl(m_source) : m_source; |
583 | m_file.reset(other: new QQmlFile(qmlEngine(this), loadUrl)); |
584 | if (m_file->isLoading()) |
585 | m_file->connectFinished(this, SLOT(loadFinished())); |
586 | else |
587 | loadFinished(); |
588 | } |
589 | |
590 | void LottieAnimation::loadFinished() |
591 | { |
592 | if (Q_UNLIKELY(m_file->isError())) { |
593 | m_file.reset(); |
594 | setStatus(Error); |
595 | return; |
596 | } |
597 | |
598 | Q_ASSERT(m_file->isReady()); |
599 | const QByteArray json = m_file->dataByteArray(); |
600 | m_file.reset(); |
601 | |
602 | if (Q_UNLIKELY(parse(json) == -1)) { |
603 | setStatus(Error); |
604 | return; |
605 | } |
606 | |
607 | QMetaObject::invokeMethod(obj: m_frameRenderThread, member: "registerAnimator" , Q_ARG(LottieAnimation*, this)); |
608 | |
609 | if (m_autoPlay) |
610 | start(); |
611 | |
612 | m_frameRenderThread->start(); |
613 | |
614 | setStatus(Ready); |
615 | } |
616 | |
617 | QByteArray LottieAnimation::jsonSource() const |
618 | { |
619 | return m_jsonSource; |
620 | } |
621 | |
622 | void LottieAnimation::renderNextFrame() |
623 | { |
624 | if (m_currentFrame >= m_startFrame && m_currentFrame <= m_endFrame) { |
625 | if (m_frameRenderThread->getFrame(animator: this, frameNumber: m_currentFrame)) { |
626 | update(); |
627 | } else if (!m_waitForFrameConn) { |
628 | qCDebug(lcLottieQtBodymovinRender) << static_cast<void*>(this) |
629 | << "Frame cache was empty for frame" << m_currentFrame; |
630 | m_waitForFrameConn = connect(sender: m_frameRenderThread, signal: &BatchRenderer::frameReady, |
631 | context: this, slot: [this](LottieAnimation *target, int ) { |
632 | if (target != this) |
633 | return; |
634 | qCDebug(lcLottieQtBodymovinRender) << static_cast<void*>(this) |
635 | << "Frame ready" << frameNumber; |
636 | disconnect(m_waitForFrameConn); |
637 | update(); |
638 | }); |
639 | } |
640 | } else if (m_loops == m_currentLoop) { |
641 | if ( m_loops != Infinite) |
642 | m_frameAdvance->stop(); |
643 | emit finished(); |
644 | } |
645 | } |
646 | |
647 | int LottieAnimation::parse(QByteArray jsonSource) |
648 | { |
649 | m_jsonSource = jsonSource; |
650 | |
651 | QJsonParseError error; |
652 | QJsonDocument doc = QJsonDocument::fromJson(json: m_jsonSource, error: &error); |
653 | if (Q_UNLIKELY(error.error != QJsonParseError::NoError)) { |
654 | qCWarning(lcLottieQtBodymovinParser) |
655 | << "JSON parse error:" << error.errorString(); |
656 | return -1; |
657 | } |
658 | |
659 | QJsonObject rootObj = doc.object(); |
660 | if (Q_UNLIKELY(rootObj.empty())) |
661 | return -1; |
662 | |
663 | QStringList versionString = rootObj.value(key: QLatin1String("v" )).toString().split(sep: u'.'); |
664 | QList<int> version; |
665 | foreach (QString v, versionString) |
666 | version.append(t: v.toInt()); |
667 | m_version = QVersionNumber(version); |
668 | |
669 | int startFrame = rootObj.value(key: QLatin1String("ip" )).toVariant().toInt(); |
670 | int endFrame = rootObj.value(key: QLatin1String("op" )).toVariant().toInt(); |
671 | m_animFrameRate = rootObj.value(key: QLatin1String("fr" )).toVariant().toInt(); |
672 | m_animWidth = rootObj.value(key: QLatin1String("w" )).toVariant().toReal(); |
673 | m_animHeight = rootObj.value(key: QLatin1String("h" )).toVariant().toReal(); |
674 | |
675 | QJsonArray markerArr = rootObj.value(key: QLatin1String("markers" )).toArray(); |
676 | QJsonArray::const_iterator markerIt = markerArr.constBegin(); |
677 | while (markerIt != markerArr.constEnd()) { |
678 | QString marker = (*markerIt).toObject().value(key: QLatin1String("cm" )).toString(); |
679 | int frame = (*markerIt).toObject().value(key: QLatin1String("tm" )).toInt(); |
680 | m_markers.insert(key: marker, value: frame); |
681 | |
682 | if ((*markerIt).toObject().value(key: QLatin1String("dr" )).toInt()) |
683 | qCWarning(lcLottieQtBodymovinParser) |
684 | << "property 'dr' not support in a marker" ; |
685 | ++markerIt; |
686 | } |
687 | |
688 | if (rootObj.value(key: QLatin1String("chars" )).toArray().count()) |
689 | qCWarning(lcLottieQtBodymovinParser) << "chars not supported" ; |
690 | |
691 | setWidth(m_animWidth); |
692 | setHeight(m_animHeight); |
693 | setStartFrame(startFrame); |
694 | setEndFrame(endFrame); |
695 | setFrameRate(m_animFrameRate); |
696 | |
697 | return 0; |
698 | } |
699 | |
700 | QT_END_NAMESPACE |
701 | |