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 8 #include "Skottie.h" 9 10 #include "SkCanvas.h" 11 #include "SkData.h" 12 #include "SkFontMgr.h" 13 #include "SkImage.h" 14 #include "SkMakeUnique.h" 15 #include "SkPaint.h" 16 #include "SkPoint.h" 17 #include "SkSGColor.h" 18 #include "SkSGInvalidationController.h" 19 #include "SkSGOpacityEffect.h" 20 #include "SkSGPath.h" 21 #include "SkSGRenderEffect.h" 22 #include "SkSGScene.h" 23 #include "SkSGTransform.h" 24 #include "SkStream.h" 25 #include "SkTArray.h" 26 #include "SkTo.h" 27 #include "SkottieAdapter.h" 28 #include "SkottieJson.h" 29 #include "SkottiePriv.h" 30 #include "SkottieProperty.h" 31 #include "SkottieValue.h" 32 #include "SkTraceEvent.h" 33 34 #include <chrono> 35 #include <cmath> 36 37 #include "stdlib.h" 38 39 namespace skottie { 40 41 namespace internal { 42 43 void AnimationBuilder::log(Logger::Level lvl, const skjson::Value* json, 44 const char fmt[], ...) const { 45 if (!fLogger) { 46 return; 47 } 48 49 char buff[1024]; 50 va_list va; 51 va_start(va, fmt); 52 const auto len = vsnprintf(buff, sizeof(buff), fmt, va); 53 va_end(va); 54 55 if (len < 0) { 56 SkDebugf("!! Could not format log message !!\n"); 57 return; 58 } 59 60 if (len >= SkToInt(sizeof(buff))) { 61 static constexpr char kEllipsesStr[] = "..."; 62 strcpy(buff + sizeof(buff) - sizeof(kEllipsesStr), kEllipsesStr); 63 } 64 65 SkString jsonstr = json ? json->toString() : SkString(); 66 67 fLogger->log(lvl, buff, jsonstr.c_str()); 68 } 69 70 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& t, 71 AnimatorScope* ascope, 72 sk_sp<sksg::Transform> parent) const { 73 static const VectorValue g_default_vec_0 = { 0, 0}, 74 g_default_vec_100 = {100, 100}; 75 76 auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I()); 77 auto adapter = sk_make_sp<TransformAdapter2D>(matrix); 78 79 auto bound = this->bindProperty<VectorValue>(t["a"], ascope, 80 [adapter](const VectorValue& a) { 81 adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a)); 82 }, g_default_vec_0); 83 bound |= this->bindProperty<VectorValue>(t["p"], ascope, 84 [adapter](const VectorValue& p) { 85 adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p)); 86 }, g_default_vec_0); 87 bound |= this->bindProperty<VectorValue>(t["s"], ascope, 88 [adapter](const VectorValue& s) { 89 adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s)); 90 }, g_default_vec_100); 91 92 const auto* jrotation = &t["r"]; 93 if (jrotation->is<skjson::NullValue>()) { 94 // 3d rotations have separate rx,ry,rz components. While we don't fully support them, 95 // we can still make use of rz. 96 jrotation = &t["rz"]; 97 } 98 bound |= this->bindProperty<ScalarValue>(*jrotation, ascope, 99 [adapter](const ScalarValue& r) { 100 adapter->setRotation(r); 101 }, 0.0f); 102 bound |= this->bindProperty<ScalarValue>(t["sk"], ascope, 103 [adapter](const ScalarValue& sk) { 104 adapter->setSkew(sk); 105 }, 0.0f); 106 bound |= this->bindProperty<ScalarValue>(t["sa"], ascope, 107 [adapter](const ScalarValue& sa) { 108 adapter->setSkewAxis(sa); 109 }, 0.0f); 110 111 const auto dispatched = this->dispatchTransformProperty(adapter); 112 113 return (bound || dispatched) 114 ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix)) 115 : parent; 116 } 117 118 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t, 119 AnimatorScope* ascope, 120 sk_sp<sksg::Transform> parent) const { 121 static const VectorValue g_default_vec_0 = { 0, 0, 0}, 122 g_default_vec_100 = {100, 100, 100}; 123 124 auto matrix = sksg::Matrix<SkMatrix44>::Make(SkMatrix::I()); 125 auto adapter = sk_make_sp<TransformAdapter3D>(matrix); 126 127 auto bound = this->bindProperty<VectorValue>(t["a"], ascope, 128 [adapter](const VectorValue& a) { 129 adapter->setAnchorPoint(TransformAdapter3D::Vec3(a)); 130 }, g_default_vec_0); 131 bound |= this->bindProperty<VectorValue>(t["p"], ascope, 132 [adapter](const VectorValue& p) { 133 adapter->setPosition(TransformAdapter3D::Vec3(p)); 134 }, g_default_vec_0); 135 bound |= this->bindProperty<VectorValue>(t["s"], ascope, 136 [adapter](const VectorValue& s) { 137 adapter->setScale(TransformAdapter3D::Vec3(s)); 138 }, g_default_vec_100); 139 140 // Orientation and rx/ry/rz are mapped to the same rotation property -- the difference is 141 // in how they get interpolated (vector vs. scalar/decomposed interpolation). 142 bound |= this->bindProperty<VectorValue>(t["or"], ascope, 143 [adapter](const VectorValue& o) { 144 adapter->setRotation(TransformAdapter3D::Vec3(o)); 145 }, g_default_vec_0); 146 147 bound |= this->bindProperty<ScalarValue>(t["rx"], ascope, 148 [adapter](const ScalarValue& rx) { 149 const auto& r = adapter->getRotation(); 150 adapter->setRotation(TransformAdapter3D::Vec3({rx, r.fY, r.fZ})); 151 }, 0.0f); 152 153 bound |= this->bindProperty<ScalarValue>(t["ry"], ascope, 154 [adapter](const ScalarValue& ry) { 155 const auto& r = adapter->getRotation(); 156 adapter->setRotation(TransformAdapter3D::Vec3({r.fX, ry, r.fZ})); 157 }, 0.0f); 158 159 bound |= this->bindProperty<ScalarValue>(t["rz"], ascope, 160 [adapter](const ScalarValue& rz) { 161 const auto& r = adapter->getRotation(); 162 adapter->setRotation(TransformAdapter3D::Vec3({r.fX, r.fY, rz})); 163 }, 0.0f); 164 165 // TODO: dispatch 3D transform properties 166 167 return (bound) 168 ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix)) 169 : parent; 170 } 171 172 sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform, 173 AnimatorScope* ascope, 174 sk_sp<sksg::RenderNode> childNode) const { 175 if (!childNode) 176 return nullptr; 177 178 auto opacityNode = sksg::OpacityEffect::Make(childNode); 179 180 const auto bound = this->bindProperty<ScalarValue>(jtransform["o"], ascope, 181 [opacityNode](const ScalarValue& o) { 182 // BM opacity is [0..100] 183 opacityNode->setOpacity(o * 0.01f); 184 }, 100.0f); 185 const auto dispatched = this->dispatchOpacityProperty(opacityNode); 186 187 // We can ignore constant full opacity. 188 return (bound || dispatched) ? std::move(opacityNode) : childNode; 189 } 190 191 namespace { 192 193 static SkBlendMode GetBlendMode(const skjson::ObjectValue& jobject, 194 const AnimationBuilder* abuilder) { 195 static constexpr SkBlendMode kBlendModeMap[] = { 196 SkBlendMode::kSrcOver, // 0:'normal' 197 SkBlendMode::kMultiply, // 1:'multiply' 198 SkBlendMode::kScreen, // 2:'screen' 199 SkBlendMode::kOverlay, // 3:'overlay 200 SkBlendMode::kDarken, // 4:'darken' 201 SkBlendMode::kLighten, // 5:'lighten' 202 SkBlendMode::kColorDodge, // 6:'color-dodge' 203 SkBlendMode::kColorBurn, // 7:'color-burn' 204 SkBlendMode::kHardLight, // 8:'hard-light' 205 SkBlendMode::kSoftLight, // 9:'soft-light' 206 SkBlendMode::kDifference, // 10:'difference' 207 SkBlendMode::kExclusion, // 11:'exclusion' 208 SkBlendMode::kHue, // 12:'hue' 209 SkBlendMode::kSaturation, // 13:'saturation' 210 SkBlendMode::kColor, // 14:'color' 211 SkBlendMode::kLuminosity, // 15:'luminosity' 212 }; 213 214 const auto bm_index = ParseDefault<size_t>(jobject["bm"], 0); 215 if (bm_index >= SK_ARRAY_COUNT(kBlendModeMap)) { 216 abuilder->log(Logger::Level::kWarning, &jobject, 217 "Unsupported blend mode %lu\n", bm_index); 218 return SkBlendMode::kSrcOver; 219 } 220 221 return kBlendModeMap[bm_index]; 222 } 223 224 } // namespace 225 226 sk_sp<sksg::RenderNode> AnimationBuilder::attachBlendMode(const skjson::ObjectValue& jobject, 227 sk_sp<sksg::RenderNode> child) const { 228 const auto bm = GetBlendMode(jobject, this); 229 if (bm != SkBlendMode::kSrcOver) { 230 fHasNontrivialBlending = true; 231 child = sksg::BlendModeEffect::Make(std::move(child), bm); 232 } 233 234 return child; 235 } 236 237 sk_sp<sksg::Path> AnimationBuilder::attachPath(const skjson::Value& jpath, 238 AnimatorScope* ascope) const { 239 auto path_node = sksg::Path::Make(); 240 return this->bindProperty<ShapeValue>(jpath, ascope, 241 [path_node](const ShapeValue& p) { 242 // FillType is tracked in the SG node, not in keyframes -- make sure we preserve it. 243 auto path = ValueTraits<ShapeValue>::As<SkPath>(p); 244 path.setFillType(path_node->getFillType()); 245 path_node->setPath(path); 246 }) 247 ? path_node 248 : nullptr; 249 } 250 251 sk_sp<sksg::Color> AnimationBuilder::attachColor(const skjson::ObjectValue& jcolor, 252 AnimatorScope* ascope, 253 const char prop_name[]) const { 254 auto color_node = sksg::Color::Make(SK_ColorBLACK); 255 256 this->bindProperty<VectorValue>(jcolor[prop_name], ascope, 257 [color_node](const VectorValue& c) { 258 color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c)); 259 }); 260 this->dispatchColorProperty(color_node); 261 262 return color_node; 263 } 264 265 AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr> fontmgr, 266 sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger, 267 sk_sp<MarkerObserver> mobserver, 268 Animation::Builder::Stats* stats, 269 float duration, float framerate) 270 : fResourceProvider(std::move(rp)) 271 , fLazyFontMgr(std::move(fontmgr)) 272 , fPropertyObserver(std::move(pobserver)) 273 , fLogger(std::move(logger)) 274 , fMarkerObserver(std::move(mobserver)) 275 , fStats(stats) 276 , fDuration(duration) 277 , fFrameRate(framerate) 278 , fHasNontrivialBlending(false) {} 279 280 std::unique_ptr<sksg::Scene> AnimationBuilder::parse(const skjson::ObjectValue& jroot) { 281 this->dispatchMarkers(jroot["markers"]); 282 283 this->parseAssets(jroot["assets"]); 284 this->parseFonts(jroot["fonts"], jroot["chars"]); 285 286 AnimatorScope animators; 287 auto root = this->attachComposition(jroot, &animators); 288 289 fStats->fAnimatorCount = animators.size(); 290 291 return sksg::Scene::Make(std::move(root), std::move(animators)); 292 } 293 294 void AnimationBuilder::parseAssets(const skjson::ArrayValue* jassets) { 295 if (!jassets) { 296 return; 297 } 298 299 for (const skjson::ObjectValue* asset : *jassets) { 300 if (asset) { 301 fAssets.set(ParseDefault<SkString>((*asset)["id"], SkString()), { asset, false }); 302 } 303 } 304 } 305 306 void AnimationBuilder::dispatchMarkers(const skjson::ArrayValue* jmarkers) const { 307 if (!fMarkerObserver || !jmarkers) { 308 return; 309 } 310 311 // For frame-number -> t conversions. 312 const auto frameRatio = 1 / (fFrameRate * fDuration); 313 314 for (const skjson::ObjectValue* m : *jmarkers) { 315 if (!m) continue; 316 317 const skjson::StringValue* name = (*m)["cm"]; 318 const auto time = ParseDefault((*m)["tm"], -1.0f), 319 duration = ParseDefault((*m)["dr"], -1.0f); 320 321 if (name && time >= 0 && duration >= 0) { 322 fMarkerObserver->onMarker( 323 name->begin(), 324 // "tm" is in frames 325 time * frameRatio, 326 // ... as is "dr" 327 (time + duration) * frameRatio 328 ); 329 } else { 330 this->log(Logger::Level::kWarning, m, "Ignoring unexpected marker."); 331 } 332 } 333 } 334 335 bool AnimationBuilder::dispatchColorProperty(const sk_sp<sksg::Color>& c) const { 336 bool dispatched = false; 337 338 if (fPropertyObserver) { 339 fPropertyObserver->onColorProperty(fPropertyObserverContext, 340 [&]() { 341 dispatched = true; 342 return std::unique_ptr<ColorPropertyHandle>(new ColorPropertyHandle(c)); 343 }); 344 } 345 346 return dispatched; 347 } 348 349 bool AnimationBuilder::dispatchOpacityProperty(const sk_sp<sksg::OpacityEffect>& o) const { 350 bool dispatched = false; 351 352 if (fPropertyObserver) { 353 fPropertyObserver->onOpacityProperty(fPropertyObserverContext, 354 [&]() { 355 dispatched = true; 356 return std::unique_ptr<OpacityPropertyHandle>(new OpacityPropertyHandle(o)); 357 }); 358 } 359 360 return dispatched; 361 } 362 363 bool AnimationBuilder::dispatchTransformProperty(const sk_sp<TransformAdapter2D>& t) const { 364 bool dispatched = false; 365 366 if (fPropertyObserver) { 367 fPropertyObserver->onTransformProperty(fPropertyObserverContext, 368 [&]() { 369 dispatched = true; 370 return std::unique_ptr<TransformPropertyHandle>(new TransformPropertyHandle(t)); 371 }); 372 } 373 374 return dispatched; 375 } 376 377 void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer, 378 const skjson::ObjectValue& obj) { 379 380 const skjson::StringValue* name = obj["nm"]; 381 382 fBuilder->fPropertyObserverContext = name ? name->begin() : nullptr; 383 } 384 385 } // namespace internal 386 387 sk_sp<SkData> ResourceProvider::load(const char[], const char[]) const { 388 return nullptr; 389 } 390 391 sk_sp<ImageAsset> ResourceProvider::loadImageAsset(const char path[], const char name[]) const { 392 return nullptr; 393 } 394 395 sk_sp<SkData> ResourceProvider::loadFont(const char[], const char[]) const { 396 return nullptr; 397 } 398 399 void Logger::log(Level, const char[], const char*) {} 400 401 Animation::Builder::Builder() = default; 402 Animation::Builder::~Builder() = default; 403 404 Animation::Builder& Animation::Builder::setResourceProvider(sk_sp<ResourceProvider> rp) { 405 fResourceProvider = std::move(rp); 406 return *this; 407 } 408 409 Animation::Builder& Animation::Builder::setFontManager(sk_sp<SkFontMgr> fmgr) { 410 fFontMgr = std::move(fmgr); 411 return *this; 412 } 413 414 Animation::Builder& Animation::Builder::setPropertyObserver(sk_sp<PropertyObserver> pobserver) { 415 fPropertyObserver = std::move(pobserver); 416 return *this; 417 } 418 419 Animation::Builder& Animation::Builder::setLogger(sk_sp<Logger> logger) { 420 fLogger = std::move(logger); 421 return *this; 422 } 423 424 Animation::Builder& Animation::Builder::setMarkerObserver(sk_sp<MarkerObserver> mobserver) { 425 fMarkerObserver = std::move(mobserver); 426 return *this; 427 } 428 429 sk_sp<Animation> Animation::Builder::make(SkStream* stream) { 430 if (!stream->hasLength()) { 431 // TODO: handle explicit buffering? 432 if (fLogger) { 433 fLogger->log(Logger::Level::kError, "Cannot parse streaming content.\n"); 434 } 435 return nullptr; 436 } 437 438 auto data = SkData::MakeFromStream(stream, stream->getLength()); 439 if (!data) { 440 if (fLogger) { 441 fLogger->log(Logger::Level::kError, "Failed to read the input stream.\n"); 442 } 443 return nullptr; 444 } 445 446 return this->make(static_cast<const char*>(data->data()), data->size()); 447 } 448 449 sk_sp<Animation> Animation::Builder::make(const char* data, size_t data_len) { 450 TRACE_EVENT0("skottie", TRACE_FUNC); 451 452 // Sanitize factory args. 453 class NullResourceProvider final : public ResourceProvider { 454 sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; } 455 }; 456 auto resolvedProvider = fResourceProvider 457 ? fResourceProvider : sk_make_sp<NullResourceProvider>(); 458 459 memset(&fStats, 0, sizeof(struct Stats)); 460 461 fStats.fJsonSize = data_len; 462 const auto t0 = std::chrono::steady_clock::now(); 463 464 const skjson::DOM dom(data, data_len); 465 if (!dom.root().is<skjson::ObjectValue>()) { 466 // TODO: more error info. 467 if (fLogger) { 468 fLogger->log(Logger::Level::kError, "Failed to parse JSON input.\n"); 469 } 470 return nullptr; 471 } 472 const auto& json = dom.root().as<skjson::ObjectValue>(); 473 474 const auto t1 = std::chrono::steady_clock::now(); 475 fStats.fJsonParseTimeMS = std::chrono::duration<float, std::milli>{t1-t0}.count(); 476 477 const auto version = ParseDefault<SkString>(json["v"], SkString()); 478 const auto size = SkSize::Make(ParseDefault<float>(json["w"], 0.0f), 479 ParseDefault<float>(json["h"], 0.0f)); 480 const auto fps = ParseDefault<float>(json["fr"], -1.0f), 481 inPoint = ParseDefault<float>(json["ip"], 0.0f), 482 outPoint = SkTMax(ParseDefault<float>(json["op"], SK_ScalarMax), inPoint), 483 duration = sk_ieee_float_divide(outPoint - inPoint, fps); 484 485 if (size.isEmpty() || version.isEmpty() || fps <= 0 || 486 !SkScalarIsFinite(inPoint) || !SkScalarIsFinite(outPoint) || !SkScalarIsFinite(duration)) { 487 if (fLogger) { 488 const auto msg = SkStringPrintf( 489 "Invalid animation params (version: %s, size: [%f %f], frame rate: %f, " 490 "in-point: %f, out-point: %f)\n", 491 version.c_str(), size.width(), size.height(), fps, inPoint, outPoint); 492 fLogger->log(Logger::Level::kError, msg.c_str()); 493 } 494 return nullptr; 495 } 496 497 SkASSERT(resolvedProvider); 498 internal::AnimationBuilder builder(std::move(resolvedProvider), fFontMgr, 499 std::move(fPropertyObserver), 500 std::move(fLogger), 501 std::move(fMarkerObserver), 502 &fStats, duration, fps); 503 auto scene = builder.parse(json); 504 505 const auto t2 = std::chrono::steady_clock::now(); 506 fStats.fSceneParseTimeMS = std::chrono::duration<float, std::milli>{t2-t1}.count(); 507 fStats.fTotalLoadTimeMS = std::chrono::duration<float, std::milli>{t2-t0}.count(); 508 509 if (!scene && fLogger) { 510 fLogger->log(Logger::Level::kError, "Could not parse animation.\n"); 511 } 512 513 uint32_t flags = 0; 514 if (builder.hasNontrivialBlending()) { 515 flags |= Flags::kRequiresTopLevelIsolation; 516 } 517 518 return sk_sp<Animation>(new Animation(std::move(scene), 519 std::move(version), 520 size, 521 inPoint, 522 outPoint, 523 duration, 524 flags)); 525 } 526 527 sk_sp<Animation> Animation::Builder::makeFromFile(const char path[]) { 528 const auto data = SkData::MakeFromFileName(path); 529 530 return data ? this->make(static_cast<const char*>(data->data()), data->size()) 531 : nullptr; 532 } 533 534 Animation::Animation(std::unique_ptr<sksg::Scene> scene, SkString version, const SkSize& size, 535 SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags) 536 : fScene(std::move(scene)) 537 , fVersion(std::move(version)) 538 , fSize(size) 539 , fInPoint(inPoint) 540 , fOutPoint(outPoint) 541 , fDuration(duration) 542 , fFlags(flags) { 543 544 // In case the client calls render before the first tick. 545 this->seek(0); 546 } 547 548 Animation::~Animation() = default; 549 550 void Animation::setShowInval(bool show) { 551 if (fScene) { 552 fScene->setShowInval(show); 553 } 554 } 555 556 void Animation::render(SkCanvas* canvas, const SkRect* dstR) const { 557 this->render(canvas, dstR, 0); 558 } 559 560 void Animation::render(SkCanvas* canvas, const SkRect* dstR, RenderFlags renderFlags) const { 561 TRACE_EVENT0("skottie", TRACE_FUNC); 562 563 if (!fScene) 564 return; 565 566 SkAutoCanvasRestore restore(canvas, true); 567 568 const SkRect srcR = SkRect::MakeSize(this->size()); 569 if (dstR) { 570 canvas->concat(SkMatrix::MakeRectToRect(srcR, *dstR, SkMatrix::kCenter_ScaleToFit)); 571 } 572 573 if ((fFlags & Flags::kRequiresTopLevelIsolation) && 574 !(renderFlags & RenderFlag::kSkipTopLevelIsolation)) { 575 // The animation uses non-trivial blending, and needs 576 // to be rendered into a separate/transparent layer. 577 canvas->saveLayer(srcR, nullptr); 578 } 579 580 canvas->clipRect(srcR); 581 582 fScene->render(canvas); 583 } 584 585 void Animation::seek(SkScalar t) { 586 TRACE_EVENT0("skottie", TRACE_FUNC); 587 588 if (!fScene) 589 return; 590 591 fScene->animate(fInPoint + SkTPin(t, 0.0f, 1.0f) * (fOutPoint - fInPoint)); 592 } 593 594 sk_sp<Animation> Animation::Make(const char* data, size_t length) { 595 return Builder().make(data, length); 596 } 597 598 sk_sp<Animation> Animation::Make(SkStream* stream) { 599 return Builder().make(stream); 600 } 601 602 sk_sp<Animation> Animation::MakeFromFile(const char path[]) { 603 return Builder().makeFromFile(path); 604 } 605 606 } // namespace skottie 607