/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "Skottie.h" #include "SkCanvas.h" #include "SkData.h" #include "SkFontMgr.h" #include "SkImage.h" #include "SkMakeUnique.h" #include "SkPaint.h" #include "SkPoint.h" #include "SkSGColor.h" #include "SkSGInvalidationController.h" #include "SkSGOpacityEffect.h" #include "SkSGPath.h" #include "SkSGScene.h" #include "SkSGTransform.h" #include "SkStream.h" #include "SkTArray.h" #include "SkTime.h" #include "SkTo.h" #include "SkottieAdapter.h" #include "SkottieJson.h" #include "SkottiePriv.h" #include "SkottieProperty.h" #include "SkottieValue.h" #include "SkTraceEvent.h" #include #include "stdlib.h" namespace skottie { namespace internal { void AnimationBuilder::log(Logger::Level lvl, const skjson::Value* json, const char fmt[], ...) const { if (!fLogger) { return; } char buff[1024]; va_list va; va_start(va, fmt); const auto len = vsprintf(buff, fmt, va); va_end(va); if (len < 0 || len >= SkToInt(sizeof(buff))) { SkDebugf("!! Could not format log message !!\n"); return; } SkString jsonstr = json ? json->toString() : SkString(); fLogger->log(lvl, buff, jsonstr.c_str()); } sk_sp AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& t, AnimatorScope* ascope, sk_sp parent) const { static const VectorValue g_default_vec_0 = { 0, 0}, g_default_vec_100 = {100, 100}; auto matrix = sksg::Matrix::Make(SkMatrix::I()); auto adapter = sk_make_sp(matrix); auto bound = this->bindProperty(t["a"], ascope, [adapter](const VectorValue& a) { adapter->setAnchorPoint(ValueTraits::As(a)); }, g_default_vec_0); bound |= this->bindProperty(t["p"], ascope, [adapter](const VectorValue& p) { adapter->setPosition(ValueTraits::As(p)); }, g_default_vec_0); bound |= this->bindProperty(t["s"], ascope, [adapter](const VectorValue& s) { adapter->setScale(ValueTraits::As(s)); }, g_default_vec_100); const auto* jrotation = &t["r"]; if (jrotation->is()) { // 3d rotations have separate rx,ry,rz components. While we don't fully support them, // we can still make use of rz. jrotation = &t["rz"]; } bound |= this->bindProperty(*jrotation, ascope, [adapter](const ScalarValue& r) { adapter->setRotation(r); }, 0.0f); bound |= this->bindProperty(t["sk"], ascope, [adapter](const ScalarValue& sk) { adapter->setSkew(sk); }, 0.0f); bound |= this->bindProperty(t["sa"], ascope, [adapter](const ScalarValue& sa) { adapter->setSkewAxis(sa); }, 0.0f); const auto dispatched = this->dispatchTransformProperty(adapter); return (bound || dispatched) ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix)) : parent; } sk_sp AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t, AnimatorScope* ascope, sk_sp parent) const { static const VectorValue g_default_vec_0 = { 0, 0, 0}, g_default_vec_100 = {100, 100, 100}; auto matrix = sksg::Matrix::Make(SkMatrix::I()); auto adapter = sk_make_sp(matrix); auto bound = this->bindProperty(t["a"], ascope, [adapter](const VectorValue& a) { adapter->setAnchorPoint(TransformAdapter3D::Vec3(a)); }, g_default_vec_0); bound |= this->bindProperty(t["p"], ascope, [adapter](const VectorValue& p) { adapter->setPosition(TransformAdapter3D::Vec3(p)); }, g_default_vec_0); bound |= this->bindProperty(t["s"], ascope, [adapter](const VectorValue& s) { adapter->setScale(TransformAdapter3D::Vec3(s)); }, g_default_vec_100); // Orientation and rx/ry/rz are mapped to the same rotation property -- the difference is // in how they get interpolated (vector vs. scalar/decomposed interpolation). bound |= this->bindProperty(t["or"], ascope, [adapter](const VectorValue& o) { adapter->setRotation(TransformAdapter3D::Vec3(o)); }, g_default_vec_0); bound |= this->bindProperty(t["rx"], ascope, [adapter](const ScalarValue& rx) { const auto& r = adapter->getRotation(); adapter->setRotation(TransformAdapter3D::Vec3({rx, r.fY, r.fZ})); }, 0.0f); bound |= this->bindProperty(t["ry"], ascope, [adapter](const ScalarValue& ry) { const auto& r = adapter->getRotation(); adapter->setRotation(TransformAdapter3D::Vec3({r.fX, ry, r.fZ})); }, 0.0f); bound |= this->bindProperty(t["rz"], ascope, [adapter](const ScalarValue& rz) { const auto& r = adapter->getRotation(); adapter->setRotation(TransformAdapter3D::Vec3({r.fX, r.fY, rz})); }, 0.0f); // TODO: dispatch 3D transform properties return (bound) ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix)) : parent; } sk_sp AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform, AnimatorScope* ascope, sk_sp childNode) const { if (!childNode) return nullptr; auto opacityNode = sksg::OpacityEffect::Make(childNode); const auto bound = this->bindProperty(jtransform["o"], ascope, [opacityNode](const ScalarValue& o) { // BM opacity is [0..100] opacityNode->setOpacity(o * 0.01f); }, 100.0f); const auto dispatched = this->dispatchOpacityProperty(opacityNode); // We can ignore constant full opacity. return (bound || dispatched) ? std::move(opacityNode) : childNode; } sk_sp AnimationBuilder::attachPath(const skjson::Value& jpath, AnimatorScope* ascope) const { auto path_node = sksg::Path::Make(); return this->bindProperty(jpath, ascope, [path_node](const ShapeValue& p) { // FillType is tracked in the SG node, not in keyframes -- make sure we preserve it. auto path = ValueTraits::As(p); path.setFillType(path_node->getFillType()); path_node->setPath(path); }) ? path_node : nullptr; } sk_sp AnimationBuilder::attachColor(const skjson::ObjectValue& jcolor, AnimatorScope* ascope, const char prop_name[]) const { auto color_node = sksg::Color::Make(SK_ColorBLACK); this->bindProperty(jcolor[prop_name], ascope, [color_node](const VectorValue& c) { color_node->setColor(ValueTraits::As(c)); }); this->dispatchColorProperty(color_node); return color_node; } AnimationBuilder::AnimationBuilder(sk_sp rp, sk_sp fontmgr, sk_sp pobserver, sk_sp logger, sk_sp mobserver, Animation::Builder::Stats* stats, float duration, float framerate) : fResourceProvider(std::move(rp)) , fLazyFontMgr(std::move(fontmgr)) , fPropertyObserver(std::move(pobserver)) , fLogger(std::move(logger)) , fMarkerObserver(std::move(mobserver)) , fStats(stats) , fDuration(duration) , fFrameRate(framerate) {} std::unique_ptr AnimationBuilder::parse(const skjson::ObjectValue& jroot) { this->dispatchMarkers(jroot["markers"]); this->parseAssets(jroot["assets"]); this->parseFonts(jroot["fonts"], jroot["chars"]); AnimatorScope animators; auto root = this->attachComposition(jroot, &animators); fStats->fAnimatorCount = animators.size(); return sksg::Scene::Make(std::move(root), std::move(animators)); } void AnimationBuilder::parseAssets(const skjson::ArrayValue* jassets) { if (!jassets) { return; } for (const skjson::ObjectValue* asset : *jassets) { if (asset) { fAssets.set(ParseDefault((*asset)["id"], SkString()), { asset, false }); } } } void AnimationBuilder::dispatchMarkers(const skjson::ArrayValue* jmarkers) const { if (!fMarkerObserver || !jmarkers) { return; } // For frame-number -> t conversions. const auto frameRatio = 1 / (fFrameRate * fDuration); for (const skjson::ObjectValue* m : *jmarkers) { if (!m) continue; const skjson::StringValue* name = (*m)["cm"]; const auto time = ParseDefault((*m)["tm"], -1.0f), duration = ParseDefault((*m)["dr"], -1.0f); if (name && time >= 0 && duration >= 0) { fMarkerObserver->onMarker( name->begin(), // "tm" is in frames time * frameRatio, // ... as is "dr" (time + duration) * frameRatio ); } else { this->log(Logger::Level::kWarning, m, "Ignoring unexpected marker."); } } } bool AnimationBuilder::dispatchColorProperty(const sk_sp& c) const { bool dispatched = false; if (fPropertyObserver) { fPropertyObserver->onColorProperty(fPropertyObserverContext, [&]() { dispatched = true; return std::unique_ptr(new ColorPropertyHandle(c)); }); } return dispatched; } bool AnimationBuilder::dispatchOpacityProperty(const sk_sp& o) const { bool dispatched = false; if (fPropertyObserver) { fPropertyObserver->onOpacityProperty(fPropertyObserverContext, [&]() { dispatched = true; return std::unique_ptr(new OpacityPropertyHandle(o)); }); } return dispatched; } bool AnimationBuilder::dispatchTransformProperty(const sk_sp& t) const { bool dispatched = false; if (fPropertyObserver) { fPropertyObserver->onTransformProperty(fPropertyObserverContext, [&]() { dispatched = true; return std::unique_ptr(new TransformPropertyHandle(t)); }); } return dispatched; } void AnimationBuilder::AutoPropertyTracker::updateContext(PropertyObserver* observer, const skjson::ObjectValue& obj) { const skjson::StringValue* name = obj["nm"]; fBuilder->fPropertyObserverContext = name ? name->begin() : nullptr; } } // namespace internal sk_sp ResourceProvider::load(const char[], const char[]) const { return nullptr; } sk_sp ResourceProvider::loadImageAsset(const char path[], const char name[]) const { return nullptr; } sk_sp ResourceProvider::loadFont(const char[], const char[]) const { return nullptr; } void Logger::log(Level, const char[], const char*) {} Animation::Builder::Builder() = default; Animation::Builder::~Builder() = default; Animation::Builder& Animation::Builder::setResourceProvider(sk_sp rp) { fResourceProvider = std::move(rp); return *this; } Animation::Builder& Animation::Builder::setFontManager(sk_sp fmgr) { fFontMgr = std::move(fmgr); return *this; } Animation::Builder& Animation::Builder::setPropertyObserver(sk_sp pobserver) { fPropertyObserver = std::move(pobserver); return *this; } Animation::Builder& Animation::Builder::setLogger(sk_sp logger) { fLogger = std::move(logger); return *this; } Animation::Builder& Animation::Builder::setMarkerObserver(sk_sp mobserver) { fMarkerObserver = std::move(mobserver); return *this; } sk_sp Animation::Builder::make(SkStream* stream) { if (!stream->hasLength()) { // TODO: handle explicit buffering? if (fLogger) { fLogger->log(Logger::Level::kError, "Cannot parse streaming content.\n"); } return nullptr; } auto data = SkData::MakeFromStream(stream, stream->getLength()); if (!data) { if (fLogger) { fLogger->log(Logger::Level::kError, "Failed to read the input stream.\n"); } return nullptr; } return this->make(static_cast(data->data()), data->size()); } sk_sp Animation::Builder::make(const char* data, size_t data_len) { TRACE_EVENT0("skottie", TRACE_FUNC); // Sanitize factory args. class NullResourceProvider final : public ResourceProvider { sk_sp load(const char[], const char[]) const override { return nullptr; } }; auto resolvedProvider = fResourceProvider ? fResourceProvider : sk_make_sp(); memset(&fStats, 0, sizeof(struct Stats)); fStats.fJsonSize = data_len; const auto t0 = SkTime::GetMSecs(); const skjson::DOM dom(data, data_len); if (!dom.root().is()) { // TODO: more error info. if (fLogger) { fLogger->log(Logger::Level::kError, "Failed to parse JSON input.\n"); } return nullptr; } const auto& json = dom.root().as(); const auto t1 = SkTime::GetMSecs(); fStats.fJsonParseTimeMS = t1 - t0; const auto version = ParseDefault(json["v"], SkString()); const auto size = SkSize::Make(ParseDefault(json["w"], 0.0f), ParseDefault(json["h"], 0.0f)); const auto fps = ParseDefault(json["fr"], -1.0f), inPoint = ParseDefault(json["ip"], 0.0f), outPoint = SkTMax(ParseDefault(json["op"], SK_ScalarMax), inPoint), duration = sk_ieee_float_divide(outPoint - inPoint, fps); if (size.isEmpty() || version.isEmpty() || fps <= 0 || !SkScalarIsFinite(inPoint) || !SkScalarIsFinite(outPoint) || !SkScalarIsFinite(duration)) { if (fLogger) { const auto msg = SkStringPrintf( "Invalid animation params (version: %s, size: [%f %f], frame rate: %f, " "in-point: %f, out-point: %f)\n", version.c_str(), size.width(), size.height(), fps, inPoint, outPoint); fLogger->log(Logger::Level::kError, msg.c_str()); } return nullptr; } SkASSERT(resolvedProvider); internal::AnimationBuilder builder(std::move(resolvedProvider), fFontMgr, std::move(fPropertyObserver), std::move(fLogger), std::move(fMarkerObserver), &fStats, duration, fps); auto scene = builder.parse(json); const auto t2 = SkTime::GetMSecs(); fStats.fSceneParseTimeMS = t2 - t1; fStats.fTotalLoadTimeMS = t2 - t0; if (!scene && fLogger) { fLogger->log(Logger::Level::kError, "Could not parse animation.\n"); } return sk_sp( new Animation(std::move(scene), std::move(version), size, inPoint, outPoint, duration)); } sk_sp Animation::Builder::makeFromFile(const char path[]) { const auto data = SkData::MakeFromFileName(path); return data ? this->make(static_cast(data->data()), data->size()) : nullptr; } Animation::Animation(std::unique_ptr scene, SkString version, const SkSize& size, SkScalar inPoint, SkScalar outPoint, SkScalar duration) : fScene(std::move(scene)) , fVersion(std::move(version)) , fSize(size) , fInPoint(inPoint) , fOutPoint(outPoint) , fDuration(duration) { // In case the client calls render before the first tick. this->seek(0); } Animation::~Animation() = default; void Animation::setShowInval(bool show) { if (fScene) { fScene->setShowInval(show); } } void Animation::render(SkCanvas* canvas, const SkRect* dstR) const { TRACE_EVENT0("skottie", TRACE_FUNC); if (!fScene) return; SkAutoCanvasRestore restore(canvas, true); const SkRect srcR = SkRect::MakeSize(this->size()); if (dstR) { canvas->concat(SkMatrix::MakeRectToRect(srcR, *dstR, SkMatrix::kCenter_ScaleToFit)); } canvas->clipRect(srcR); fScene->render(canvas); } void Animation::seek(SkScalar t) { TRACE_EVENT0("skottie", TRACE_FUNC); if (!fScene) return; fScene->animate(fInPoint + SkTPin(t, 0.0f, 1.0f) * (fOutPoint - fInPoint)); } sk_sp Animation::Make(const char* data, size_t length) { return Builder().make(data, length); } sk_sp Animation::Make(SkStream* stream) { return Builder().make(stream); } sk_sp Animation::MakeFromFile(const char path[]) { return Builder().makeFromFile(path); } } // namespace skottie