Flutter Engine
The Flutter Engine
Skottie.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
11#include "include/core/SkData.h"
14#include "include/core/SkRect.h"
28#include "modules/skottie/src/Transform.h" // IWYU pragma: keep
34#include "src/core/SkTHash.h"
36#include "src/utils/SkJSON.h"
37
38#include <algorithm>
39#include <chrono>
40#include <cmath>
41#include <cstdarg>
42#include <cstdio>
43#include <cstring>
44#include <functional>
45#include <memory>
46#include <ratio>
47#include <utility>
48
49#if !defined(SK_DISABLE_LEGACY_SHAPER_FACTORY)
51#endif
52
53namespace sksg {
54class Color;
55}
56
57namespace skottie {
58
59namespace internal {
60
62 fRoot = std::move(root);
63}
64
66 if (fRoot) {
67 fRoot->revalidate(nullptr, SkMatrix::I());
68 }
69}
70
72 const char fmt[], ...) const {
73 if (!fLogger) {
74 return;
75 }
76
77 char buff[1024];
78 va_list va;
79 va_start(va, fmt);
80 const auto len = vsnprintf(buff, sizeof(buff), fmt, va);
81 va_end(va);
82
83 if (len < 0) {
84 SkDebugf("!! Could not format log message !!\n");
85 return;
86 }
87
88 if (len >= SkToInt(sizeof(buff))) {
89 static constexpr char kEllipsesStr[] = "...";
90 strcpy(buff + sizeof(buff) - sizeof(kEllipsesStr), kEllipsesStr);
91 }
92
93 SkString jsonstr = json ? json->toString() : SkString();
94
95 fLogger->log(lvl, buff, jsonstr.c_str());
96}
97
98class OpacityAdapter final : public DiscardableAdapterBase<OpacityAdapter, sksg::OpacityEffect> {
99public:
102 const AnimationBuilder& abuilder)
103 : INHERITED(sksg::OpacityEffect::Make(std::move(child))) {
104 this->bind(abuilder, jobject["o"], fOpacity);
105 }
106
107private:
108 void onSync() override {
109 this->node()->setOpacity(fOpacity * 0.01f);
110 }
111
112 ScalarValue fOpacity = 100;
113
114 using INHERITED = DiscardableAdapterBase<OpacityAdapter, sksg::OpacityEffect>;
115};
116
117
119 sk_sp<sksg::RenderNode> child_node) const {
120 if (!child_node)
121 return nullptr;
122
123 auto adapter = OpacityAdapter::Make(jobject, child_node, *this);
124 if (adapter->isStatic()) {
125 adapter->seek(0);
126 }
127 auto dispatched = this->dispatchOpacityProperty(adapter->node());
128 if (adapter->isStatic()) {
129 if (!dispatched && adapter->node()->getOpacity() >= 1) {
130 // No obeservable effects - we can discard.
131 return child_node;
132 }
133 } else {
134 fCurrentAnimatorScope->push_back(adapter);
135 }
136
137 return adapter->node();
138}
139
143 sk_sp<ExpressionManager> expressionmgr,
144 sk_sp<SkShapers::Factory> shapingFactory,
146 const SkSize& comp_size, float duration, float framerate,
147 uint32_t flags)
148 : fResourceProvider(std::move(rp))
149 , fFontMgr(std::move(fontmgr))
150 , fPropertyObserver(std::move(pobserver))
151 , fLogger(std::move(logger))
152 , fMarkerObserver(std::move(mobserver))
153 , fPrecompInterceptor(std::move(pi))
154 , fExpressionManager(std::move(expressionmgr))
155 , fShapingFactory(std::move(shapingFactory))
156 , fRevalidator(sk_make_sp<SceneGraphRevalidator>())
157 , fSlotManager(sk_make_sp<SlotManager>(fRevalidator))
158 , fStats(stats)
159 , fCompSize(comp_size)
160 , fDuration(duration)
161 , fFrameRate(framerate)
162 , fFlags(flags)
163 , fHasNontrivialBlending(false) {}
164
166 this->dispatchMarkers(jroot["markers"]);
167
168 AutoScope ascope(this);
170
171 this->parseAssets(jroot["assets"]);
172 this->parseFonts(jroot["fonts"], jroot["chars"]);
173 fSlotsRoot = jroot["slots"];
174
175 auto root = CompositionBuilder(*this, fCompSize, jroot).build(*this);
176
177 auto animators = ascope.release();
178 fStats->fAnimatorCount = animators.size();
179
180 // Point the revalidator to our final root, and perform initial revalidation.
181 fRevalidator->setRoot(root);
182 fRevalidator->revalidate();
183
184 return { std::move(root), std::move(animators), std::move(fSlotManager)};
185}
186
187void AnimationBuilder::parseAssets(const skjson::ArrayValue* jassets) {
188 if (!jassets) {
189 return;
190 }
191
192 for (const skjson::ObjectValue* asset : *jassets) {
193 if (asset) {
194 fAssets.set(ParseDefault<SkString>((*asset)["id"], SkString()), { asset, false });
195 }
196 }
197}
198
199void AnimationBuilder::dispatchMarkers(const skjson::ArrayValue* jmarkers) const {
200 if (!fMarkerObserver || !jmarkers) {
201 return;
202 }
203
204 // For frame-number -> t conversions.
205 const auto frameRatio = 1 / (fFrameRate * fDuration);
206
207 for (const skjson::ObjectValue* m : *jmarkers) {
208 if (!m) continue;
209
210 const skjson::StringValue* name = (*m)["cm"];
211 const auto time = ParseDefault((*m)["tm"], -1.0f),
212 duration = ParseDefault((*m)["dr"], -1.0f);
213
214 if (name && time >= 0 && duration >= 0) {
215 fMarkerObserver->onMarker(
216 name->begin(),
217 // "tm" is in frames
218 time * frameRatio,
219 // ... as is "dr"
220 (time + duration) * frameRatio
221 );
222 } else {
223 this->log(Logger::Level::kWarning, m, "Ignoring unexpected marker.");
224 }
225 }
226}
227
229 bool dispatched = false;
230 if (fPropertyObserver) {
231 const char * node_name = fPropertyObserverContext;
232 fPropertyObserver->onColorProperty(node_name,
233 [&]() {
234 dispatched = true;
235 return std::make_unique<ColorPropertyHandle>(c, fRevalidator);
236 });
237 }
238
239 return dispatched;
240}
241
243 bool dispatched = false;
244
245 if (fPropertyObserver) {
246 fPropertyObserver->onOpacityProperty(fPropertyObserverContext,
247 [&]() {
248 dispatched = true;
249 return std::make_unique<OpacityPropertyHandle>(o, fRevalidator);
250 });
251 }
252
253 return dispatched;
254}
255
257 const skjson::ObjectValue* jtext) const {
258 bool dispatched = false;
259
260 if (jtext) {
261 if (const skjson::StringValue* slotID = (*jtext)["sid"]) {
262 fSlotManager->trackTextValue(SkString(slotID->begin()), t);
263 dispatched = true;
264 }
265 }
266
267 if (fPropertyObserver) {
268 fPropertyObserver->onTextProperty(fPropertyObserverContext,
269 [&]() {
270 dispatched = true;
271 return std::make_unique<TextPropertyHandle>(t, fRevalidator);
272 });
273 }
274
275 return dispatched;
276}
277
279 bool dispatched = false;
280
281 if (fPropertyObserver) {
282 fPropertyObserver->onTransformProperty(fPropertyObserverContext,
283 [&]() {
284 dispatched = true;
285 return std::make_unique<TransformPropertyHandle>(t, fRevalidator);
286 });
287 }
288
289 return dispatched;
290}
291
293 return fExpressionManager;
294}
295
296void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer,
297 const skjson::ObjectValue& obj) {
298 const skjson::StringValue* name = obj["nm"];
299 fBuilder->fPropertyObserverContext = name ? name->begin() : fPrevContext;
300}
301
302} // namespace internal
303
305Animation::Builder::Builder(const Builder&) = default;
308
310 fResourceProvider = std::move(rp);
311 return *this;
312}
313
315 fFontMgr = std::move(fmgr);
316 return *this;
317}
318
320 fPropertyObserver = std::move(pobserver);
321 return *this;
322}
323
325 fLogger = std::move(logger);
326 return *this;
327}
328
330 fMarkerObserver = std::move(mobserver);
331 return *this;
332}
333
335 fPrecompInterceptor = std::move(pi);
336 return *this;
337}
338
340 fExpressionManager = std::move(em);
341 return *this;
342}
343
345 fShapingFactory = std::move(factory);
346 return *this;
347}
348
350 if (!stream->hasLength()) {
351 // TODO: handle explicit buffering?
352 if (fLogger) {
353 fLogger->log(Logger::Level::kError, "Cannot parse streaming content.\n");
354 }
355 return nullptr;
356 }
357
358 auto data = SkData::MakeFromStream(stream, stream->getLength());
359 if (!data) {
360 if (fLogger) {
361 fLogger->log(Logger::Level::kError, "Failed to read the input stream.\n");
362 }
363 return nullptr;
364 }
365
366 return this->make(static_cast<const char*>(data->data()), data->size());
367}
368
369sk_sp<Animation> Animation::Builder::make(const char* data, size_t data_len) {
370 TRACE_EVENT0("skottie", TRACE_FUNC);
371
372 // Sanitize factory args.
373 class NullResourceProvider final : public ResourceProvider {
374 sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; }
375 };
376 auto resolvedProvider = fResourceProvider
377 ? fResourceProvider : sk_make_sp<NullResourceProvider>();
378
379 fStats = Stats{};
380
381 fStats.fJsonSize = data_len;
382 const auto t0 = std::chrono::steady_clock::now();
383
384 const skjson::DOM dom(data, data_len);
385 if (!dom.root().is<skjson::ObjectValue>()) {
386 // TODO: more error info.
387 if (fLogger) {
388 fLogger->log(Logger::Level::kError, "Failed to parse JSON input.\n");
389 }
390 return nullptr;
391 }
392 const auto& json = dom.root().as<skjson::ObjectValue>();
393
394 const auto t1 = std::chrono::steady_clock::now();
395 fStats.fJsonParseTimeMS = std::chrono::duration<float, std::milli>{t1-t0}.count();
396
397 const auto version = ParseDefault<SkString>(json["v"], SkString());
398 const auto size = SkSize::Make(ParseDefault<float>(json["w"], 0.0f),
399 ParseDefault<float>(json["h"], 0.0f));
400 const auto fps = ParseDefault<float>(json["fr"], -1.0f),
401 inPoint = ParseDefault<float>(json["ip"], 0.0f),
402 outPoint = std::max(ParseDefault<float>(json["op"], SK_ScalarMax), inPoint),
404
405 if (size.isEmpty() || version.isEmpty() || fps <= 0 ||
407 if (fLogger) {
408 const auto msg = SkStringPrintf(
409 "Invalid animation params (version: %s, size: [%f %f], frame rate: %f, "
410 "in-point: %f, out-point: %f)\n",
412 fLogger->log(Logger::Level::kError, msg.c_str());
413 }
414 return nullptr;
415 }
416
417#if defined(SK_DISABLE_LEGACY_SHAPER_FACTORY)
418 auto factory = fShapingFactory ? fShapingFactory : ::SkShapers::Primitive::Factory();
419#else
420 auto factory = fShapingFactory ? fShapingFactory : ::SkShapers::BestAvailable();
421#endif
422 SkASSERT(resolvedProvider);
423 internal::AnimationBuilder builder(std::move(resolvedProvider), fFontMgr,
424 std::move(fPropertyObserver),
425 std::move(fLogger),
426 std::move(fMarkerObserver),
427 std::move(fPrecompInterceptor),
428 std::move(fExpressionManager),
429 std::move(factory),
430 &fStats, size, duration, fps, fFlags);
431 auto ainfo = builder.parse(json);
432
433 fSlotManager = ainfo.fSlotManager;
434
435 const auto t2 = std::chrono::steady_clock::now();
436 fStats.fSceneParseTimeMS = std::chrono::duration<float, std::milli>{t2-t1}.count();
437 fStats.fTotalLoadTimeMS = std::chrono::duration<float, std::milli>{t2-t0}.count();
438
439 if (!ainfo.fSceneRoot && fLogger) {
440 fLogger->log(Logger::Level::kError, "Could not parse animation.\n");
441 }
442
443 uint32_t flags = 0;
444 if (builder.hasNontrivialBlending()) {
445 flags |= Animation::Flags::kRequiresTopLevelIsolation;
446 }
447
448 return sk_sp<Animation>(new Animation(std::move(ainfo.fSceneRoot),
449 std::move(ainfo.fAnimators),
450 std::move(version),
451 size,
452 inPoint,
453 outPoint,
454 duration,
455 fps,
456 flags));
457}
458
460 const auto data = SkData::MakeFromFileName(path);
461
462 return data ? this->make(static_cast<const char*>(data->data()), data->size())
463 : nullptr;
464}
465
466Animation::Animation(sk_sp<sksg::RenderNode> scene_root,
467 std::vector<sk_sp<internal::Animator>>&& animators,
468 SkString version, const SkSize& size,
469 double inPoint, double outPoint, double duration, double fps, uint32_t flags)
470 : fSceneRoot(std::move(scene_root))
471 , fAnimators(std::move(animators))
472 , fVersion(std::move(version))
473 , fSize(size)
474 , fInPoint(inPoint)
475 , fOutPoint(outPoint)
476 , fDuration(duration)
477 , fFPS(fps)
478 , fFlags(flags) {}
479
480Animation::~Animation() = default;
481
482void Animation::render(SkCanvas* canvas, const SkRect* dstR) const {
483 this->render(canvas, dstR, 0);
484}
485
486void Animation::render(SkCanvas* canvas, const SkRect* dstR, RenderFlags renderFlags) const {
487 TRACE_EVENT0("skottie", TRACE_FUNC);
488
489 if (!fSceneRoot)
490 return;
491
492 SkAutoCanvasRestore restore(canvas, true);
493
494 const SkRect srcR = SkRect::MakeSize(this->size());
495 if (dstR) {
497 }
498
499 if (!(renderFlags & RenderFlag::kDisableTopLevelClipping)) {
500 canvas->clipRect(srcR);
501 }
502
503 if ((fFlags & Flags::kRequiresTopLevelIsolation) &&
504 !(renderFlags & RenderFlag::kSkipTopLevelIsolation)) {
505 // The animation uses non-trivial blending, and needs
506 // to be rendered into a separate/transparent layer.
507 canvas->saveLayer(srcR, nullptr);
508 }
509
510 fSceneRoot->render(canvas);
511}
512
514 TRACE_EVENT0("skottie", TRACE_FUNC);
515
516 if (!fSceneRoot)
517 return;
518
519 // Per AE/Lottie semantics out_point is exclusive.
520 const auto kLastValidFrame = std::nextafterf(fOutPoint, fInPoint),
521 comp_time = SkTPin<float>(fInPoint + t, fInPoint, kLastValidFrame);
522
523 for (const auto& anim : fAnimators) {
524 anim->seek(comp_time);
525 }
526
527 fSceneRoot->revalidate(ic, SkMatrix::I());
528}
529
531 this->seekFrame(t * fFPS, ic);
532}
533
535 return Builder().make(data, length);
536}
537
539 return Builder().make(stream);
540}
541
543 return Builder().makeFromFile(path);
544}
545
546} // namespace skottie
uint16_t fFlags
Definition: ShapeLayer.cpp:106
#define SkASSERT(cond)
Definition: SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
static bool SkIsFinite(T x, Pack... values)
static constexpr float sk_ieee_float_divide(float numer, float denom)
sk_sp< T > sk_make_sp(Args &&... args)
Definition: SkRefCnt.h:371
#define SK_ScalarMax
Definition: SkScalar.h:24
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
constexpr int SkToInt(S x)
Definition: SkTo.h:29
#define TRACE_FUNC
Definition: SkTraceEvent.h:30
SI T load(const P *ptr)
Definition: Transform_inl.h:98
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition: SkCanvas.cpp:496
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
Definition: SkCanvas.cpp:1361
void concat(const SkMatrix &matrix)
Definition: SkCanvas.cpp:1318
static sk_sp< SkData > MakeFromStream(SkStream *, size_t size)
Definition: SkData.cpp:208
static sk_sp< SkData > MakeFromFileName(const char path[])
Definition: SkData.cpp:148
static SkMatrix RectToRect(const SkRect &src, const SkRect &dst, ScaleToFit mode=kFill_ScaleToFit)
Definition: SkMatrix.h:157
@ kCenter_ScaleToFit
scales and aligns to center
Definition: SkMatrix.h:139
static const SkMatrix & I()
Definition: SkMatrix.cpp:1544
bool isEmpty() const
Definition: SkString.h:130
const char * c_str() const
Definition: SkString.h:133
V * set(K key, V val)
Definition: SkTHash.h:487
SkString toString() const
Definition: SkJSON.cpp:938
Builder & setExpressionManager(sk_sp< ExpressionManager >)
Definition: Skottie.cpp:339
Builder & setPropertyObserver(sk_sp< PropertyObserver >)
Definition: Skottie.cpp:319
Builder(uint32_t flags=0)
Definition: Skottie.cpp:304
Builder & setResourceProvider(sk_sp< ResourceProvider >)
Definition: Skottie.cpp:309
Builder & setFontManager(sk_sp< SkFontMgr >)
Definition: Skottie.cpp:314
Builder & setPrecompInterceptor(sk_sp< PrecompInterceptor >)
Definition: Skottie.cpp:334
Builder & setMarkerObserver(sk_sp< MarkerObserver >)
Definition: Skottie.cpp:329
Builder & setLogger(sk_sp< Logger >)
Definition: Skottie.cpp:324
sk_sp< Animation > make(SkStream *)
Definition: Skottie.cpp:349
Builder & setTextShapingFactory(sk_sp< SkShapers::Factory >)
Definition: Skottie.cpp:344
sk_sp< Animation > makeFromFile(const char path[])
Definition: Skottie.cpp:459
static sk_sp< Animation > MakeFromFile(const char path[])
Definition: Skottie.cpp:542
void seekFrame(double t, sksg::InvalidationController *ic=nullptr)
Definition: Skottie.cpp:513
double inPoint() const
Definition: Skottie.h:278
const SkSize & size() const
Definition: Skottie.h:286
double fps() const
Definition: Skottie.h:273
void seekFrameTime(double t, sksg::InvalidationController *=nullptr)
Definition: Skottie.cpp:530
const SkString & version() const
Definition: Skottie.h:285
double outPoint() const
Definition: Skottie.h:283
double duration() const
Definition: Skottie.h:268
static sk_sp< Animation > Make(const char *data, size_t length)
Definition: Skottie.cpp:534
void render(SkCanvas *canvas, const SkRect *dst=nullptr) const
Definition: Skottie.cpp:482
uint32_t RenderFlags
Definition: Skottie.h:220
bool bind(const AnimationBuilder &, const skjson::ObjectValue *, T *)
bool dispatchOpacityProperty(const sk_sp< sksg::OpacityEffect > &) const
Definition: Skottie.cpp:242
sk_sp< sksg::RenderNode > attachOpacity(const skjson::ObjectValue &, sk_sp< sksg::RenderNode >) const
Definition: Skottie.cpp:118
void parseFonts(const skjson::ObjectValue *jfonts, const skjson::ArrayValue *jchars)
Definition: TextLayer.cpp:115
bool dispatchTransformProperty(const sk_sp< TransformAdapter2D > &) const
Definition: Skottie.cpp:278
bool dispatchColorProperty(const sk_sp< sksg::Color > &) const
Definition: Skottie.cpp:228
sk_sp< ExpressionManager > expression_manager() const
Definition: Skottie.cpp:292
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
Definition: Skottie.cpp:71
AnimationInfo parse(const skjson::ObjectValue &)
Definition: Skottie.cpp:165
bool dispatchTextProperty(const sk_sp< TextAdapter > &, const skjson::ObjectValue *jtext) const
Definition: Skottie.cpp:256
AnimationBuilder(sk_sp< ResourceProvider >, sk_sp< SkFontMgr >, sk_sp< PropertyObserver >, sk_sp< Logger >, sk_sp< MarkerObserver >, sk_sp< PrecompInterceptor >, sk_sp< ExpressionManager >, sk_sp< SkShapers::Factory >, Animation::Builder::Stats *, const SkSize &comp_size, float duration, float framerate, uint32_t flags)
Definition: Skottie.cpp:140
OpacityAdapter(const skjson::ObjectValue &jobject, sk_sp< sksg::RenderNode > child, const AnimationBuilder &abuilder)
Definition: Skottie.cpp:100
void setRoot(sk_sp< sksg::RenderNode >)
Definition: Skottie.cpp:61
const SkRect & revalidate(InvalidationController *, const SkMatrix &)
Definition: SkSGNode.cpp:134
void render(SkCanvas *, const RenderContext *=nullptr) const
double duration
Definition: examples.cpp:30
FlutterSemanticsFlag flags
static float max(float r, float g, float b)
Definition: hsl.cpp:49
size_t length
SK_API sk_sp< SkShader > Color(SkColor)
SKSHAPER_API sk_sp< Factory > Factory()
sk_sp< Factory > BestAvailable()
va_start(args, format)
va_end(args)
Definition: dom.py:1
DlVertices::Builder Builder
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
dictionary stats
Definition: malisc.py:20
string root
Definition: scale_cpu.py:20
static void make(SkBitmap *bitmap, SkColorType colorType, SkAlphaType alphaType, sk_sp< SkColorSpace > colorSpace)
Definition: encode_srgb.cpp:35
SkScalar ScalarValue
Definition: SkottieValue.h:22
T ParseDefault(const skjson::Value &v, const T &defaultValue)
Definition: SkottieJson.h:23
Definition: Skottie.h:32
Definition: ref_ptr.h:256
static double time(int loops, Benchmark *bench, Target *target)
Definition: nanobench.cpp:394
static SkString fmt(SkColor4f c)
Definition: p3.cpp:43
static constexpr SkRect MakeSize(const SkSize &size)
Definition: SkRect.h:633
Definition: SkSize.h:52
static constexpr SkSize Make(SkScalar w, SkScalar h)
Definition: SkSize.h:56
bool isEmpty() const
Definition: SkSize.h:71
SkScalar width() const
Definition: SkSize.h:76
SkScalar height() const
Definition: SkSize.h:77
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131