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