aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental/skottie/Skottie.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'experimental/skottie/Skottie.cpp')
-rw-r--r--experimental/skottie/Skottie.cpp1327
1 files changed, 0 insertions, 1327 deletions
diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp
deleted file mode 100644
index 8396b5ba90..0000000000
--- a/experimental/skottie/Skottie.cpp
+++ /dev/null
@@ -1,1327 +0,0 @@
-/*
- * 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 "SkottieAdapter.h"
-#include "SkottieAnimator.h"
-#include "SkottieJson.h"
-#include "SkottieValue.h"
-#include "SkData.h"
-#include "SkImage.h"
-#include "SkMakeUnique.h"
-#include "SkOSPath.h"
-#include "SkPaint.h"
-#include "SkParse.h"
-#include "SkPoint.h"
-#include "SkSGClipEffect.h"
-#include "SkSGColor.h"
-#include "SkSGDraw.h"
-#include "SkSGGeometryTransform.h"
-#include "SkSGGradient.h"
-#include "SkSGGroup.h"
-#include "SkSGImage.h"
-#include "SkSGInvalidationController.h"
-#include "SkSGMaskEffect.h"
-#include "SkSGMerge.h"
-#include "SkSGOpacityEffect.h"
-#include "SkSGPath.h"
-#include "SkSGRect.h"
-#include "SkSGRoundEffect.h"
-#include "SkSGScene.h"
-#include "SkSGTransform.h"
-#include "SkSGTrimEffect.h"
-#include "SkStream.h"
-#include "SkTArray.h"
-#include "SkTime.h"
-#include "SkTHash.h"
-
-#include <cmath>
-#include <vector>
-
-#include "stdlib.h"
-
-namespace skottie {
-
-#define LOG SkDebugf
-
-namespace {
-
-struct AssetInfo {
- json::ValueRef fAsset;
- mutable bool fIsAttaching; // Used for cycle detection
-};
-
-using AssetMap = SkTHashMap<SkString, AssetInfo>;
-
-struct AttachContext {
- const ResourceProvider& fResources;
- const AssetMap& fAssets;
- const float fFrameRate;
- sksg::AnimatorList& fAnimators;
-};
-
-bool LogFail(const json::ValueRef& json, const char* msg) {
- const auto dump = json.toString();
- LOG("!! %s: %s\n", msg, dump.c_str());
- return false;
-}
-
-sk_sp<sksg::Matrix> AttachMatrix(const json::ValueRef& t, AttachContext* ctx,
- sk_sp<sksg::Matrix> parentMatrix) {
- if (!t.isObject())
- return nullptr;
-
- auto matrix = sksg::Matrix::Make(SkMatrix::I(), std::move(parentMatrix));
- auto adapter = sk_make_sp<TransformAdapter>(matrix);
- auto anchor_attached = BindProperty<VectorValue>(t["a"], &ctx->fAnimators,
- [adapter](const VectorValue& a) {
- adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
- });
- auto position_attached = BindProperty<VectorValue>(t["p"], &ctx->fAnimators,
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- auto scale_attached = BindProperty<VectorValue>(t["s"], &ctx->fAnimators,
- [adapter](const VectorValue& s) {
- adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
- });
-
- auto jrotation = t["r"];
- if (jrotation.isNull()) {
- // 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"];
- }
- auto rotation_attached = BindProperty<ScalarValue>(jrotation, &ctx->fAnimators,
- [adapter](const ScalarValue& r) {
- adapter->setRotation(r);
- });
- auto skew_attached = BindProperty<ScalarValue>(t["sk"], &ctx->fAnimators,
- [adapter](const ScalarValue& sk) {
- adapter->setSkew(sk);
- });
- auto skewaxis_attached = BindProperty<ScalarValue>(t["sa"], &ctx->fAnimators,
- [adapter](const ScalarValue& sa) {
- adapter->setSkewAxis(sa);
- });
-
- if (!anchor_attached &&
- !position_attached &&
- !scale_attached &&
- !rotation_attached &&
- !skew_attached &&
- !skewaxis_attached) {
- LogFail(t, "Could not parse transform");
- return nullptr;
- }
-
- return matrix;
-}
-
-sk_sp<sksg::RenderNode> AttachOpacity(const json::ValueRef& jtransform, AttachContext* ctx,
- sk_sp<sksg::RenderNode> childNode) {
- if (!jtransform.isObject() || !childNode)
- return childNode;
-
- static constexpr ScalarValue kNoopOpacity = 100;
- auto opacityNode = sksg::OpacityEffect::Make(childNode);
-
- if (!BindProperty<ScalarValue>(jtransform["o"], &ctx->fAnimators,
- [opacityNode](const ScalarValue& o) {
- // BM opacity is [0..100]
- opacityNode->setOpacity(o * 0.01f);
- }, &kNoopOpacity)) {
- // We can ignore static full opacity.
- return childNode;
- }
-
- return std::move(opacityNode);
-}
-
-sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef&, AttachContext* ctx);
-
-sk_sp<sksg::Path> AttachPath(const json::ValueRef& jpath, AttachContext* ctx) {
- auto path_node = sksg::Path::Make();
- return BindProperty<ShapeValue>(jpath, &ctx->fAnimators,
- [path_node](const ShapeValue& p) {
- path_node->setPath(ValueTraits<ShapeValue>::As<SkPath>(p));
- })
- ? path_node
- : nullptr;
-}
-
-sk_sp<sksg::GeometryNode> AttachPathGeometry(const json::ValueRef& jpath, AttachContext* ctx) {
- SkASSERT(jpath.isObject());
-
- return AttachPath(jpath["ks"], ctx);
-}
-
-sk_sp<sksg::GeometryNode> AttachRRectGeometry(const json::ValueRef& jrect, AttachContext* ctx) {
- SkASSERT(jrect.isObject());
-
- auto rect_node = sksg::RRect::Make();
- auto adapter = sk_make_sp<RRectAdapter>(rect_node);
-
- auto p_attached = BindProperty<VectorValue>(jrect["p"], &ctx->fAnimators,
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- auto s_attached = BindProperty<VectorValue>(jrect["s"], &ctx->fAnimators,
- [adapter](const VectorValue& s) {
- adapter->setSize(ValueTraits<VectorValue>::As<SkSize>(s));
- });
- auto r_attached = BindProperty<ScalarValue>(jrect["r"], &ctx->fAnimators,
- [adapter](const ScalarValue& r) {
- adapter->setRadius(SkSize::Make(r, r));
- });
-
- if (!p_attached && !s_attached && !r_attached) {
- return nullptr;
- }
-
- return std::move(rect_node);
-}
-
-sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const json::ValueRef& jellipse, AttachContext* ctx) {
- SkASSERT(jellipse.isObject());
-
- auto rect_node = sksg::RRect::Make();
- auto adapter = sk_make_sp<RRectAdapter>(rect_node);
-
- auto p_attached = BindProperty<VectorValue>(jellipse["p"], &ctx->fAnimators,
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- auto s_attached = BindProperty<VectorValue>(jellipse["s"], &ctx->fAnimators,
- [adapter](const VectorValue& s) {
- const auto sz = ValueTraits<VectorValue>::As<SkSize>(s);
- adapter->setSize(sz);
- adapter->setRadius(SkSize::Make(sz.width() / 2, sz.height() / 2));
- });
-
- if (!p_attached && !s_attached) {
- return nullptr;
- }
-
- return std::move(rect_node);
-}
-
-sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const json::ValueRef& jstar, AttachContext* ctx) {
- SkASSERT(jstar.isObject());
-
- static constexpr PolyStarAdapter::Type gTypes[] = {
- PolyStarAdapter::Type::kStar, // "sy": 1
- PolyStarAdapter::Type::kPoly, // "sy": 2
- };
-
- const auto type = jstar["sy"].toDefault<int>(0) - 1;
- if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gTypes))) {
- LogFail(jstar, "Unknown polystar type");
- return nullptr;
- }
-
- auto path_node = sksg::Path::Make();
- auto adapter = sk_make_sp<PolyStarAdapter>(path_node, gTypes[type]);
-
- BindProperty<VectorValue>(jstar["p"], &ctx->fAnimators,
- [adapter](const VectorValue& p) {
- adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
- });
- BindProperty<ScalarValue>(jstar["pt"], &ctx->fAnimators,
- [adapter](const ScalarValue& pt) {
- adapter->setPointCount(pt);
- });
- BindProperty<ScalarValue>(jstar["ir"], &ctx->fAnimators,
- [adapter](const ScalarValue& ir) {
- adapter->setInnerRadius(ir);
- });
- BindProperty<ScalarValue>(jstar["or"], &ctx->fAnimators,
- [adapter](const ScalarValue& otr) {
- adapter->setOuterRadius(otr);
- });
- BindProperty<ScalarValue>(jstar["is"], &ctx->fAnimators,
- [adapter](const ScalarValue& is) {
- adapter->setInnerRoundness(is);
- });
- BindProperty<ScalarValue>(jstar["os"], &ctx->fAnimators,
- [adapter](const ScalarValue& os) {
- adapter->setOuterRoundness(os);
- });
- BindProperty<ScalarValue>(jstar["r"], &ctx->fAnimators,
- [adapter](const ScalarValue& r) {
- adapter->setRotation(r);
- });
-
- return std::move(path_node);
-}
-
-sk_sp<sksg::Color> AttachColor(const json::ValueRef& obj, AttachContext* ctx) {
- SkASSERT(obj.isObject());
-
- auto color_node = sksg::Color::Make(SK_ColorBLACK);
- auto color_attached = BindProperty<VectorValue>(obj["c"], &ctx->fAnimators,
- [color_node](const VectorValue& c) {
- color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
- });
-
- return color_attached ? color_node : nullptr;
-}
-
-sk_sp<sksg::Gradient> AttachGradient(const json::ValueRef& obj, AttachContext* ctx) {
- SkASSERT(obj.isObject());
-
- const auto stops = obj["g"];
- if (!stops.isObject())
- return nullptr;
-
- const auto stopCount = stops["p"].toDefault<int>(-1);
- if (stopCount < 0)
- return nullptr;
-
- sk_sp<sksg::Gradient> gradient_node;
- sk_sp<GradientAdapter> adapter;
-
- if (obj["t"].toDefault<int>(1) == 1) {
- auto linear_node = sksg::LinearGradient::Make();
- adapter = sk_make_sp<LinearGradientAdapter>(linear_node, stopCount);
- gradient_node = std::move(linear_node);
- } else {
- auto radial_node = sksg::RadialGradient::Make();
- adapter = sk_make_sp<RadialGradientAdapter>(radial_node, stopCount);
-
- // TODO: highlight, angle
- gradient_node = std::move(radial_node);
- }
-
- BindProperty<VectorValue>(stops["k"], &ctx->fAnimators,
- [adapter](const VectorValue& stops) {
- adapter->setColorStops(stops);
- });
- BindProperty<VectorValue>(obj["s"], &ctx->fAnimators,
- [adapter](const VectorValue& s) {
- adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
- });
- BindProperty<VectorValue>(obj["e"], &ctx->fAnimators,
- [adapter](const VectorValue& e) {
- adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
- });
-
- return gradient_node;
-}
-
-sk_sp<sksg::PaintNode> AttachPaint(const json::ValueRef& jpaint, AttachContext* ctx,
- sk_sp<sksg::PaintNode> paint_node) {
- if (paint_node) {
- paint_node->setAntiAlias(true);
-
- BindProperty<ScalarValue>(jpaint["o"], &ctx->fAnimators,
- [paint_node](const ScalarValue& o) {
- // BM opacity is [0..100]
- paint_node->setOpacity(o * 0.01f);
- });
- }
-
- return paint_node;
-}
-
-sk_sp<sksg::PaintNode> AttachStroke(const json::ValueRef& jstroke, AttachContext* ctx,
- sk_sp<sksg::PaintNode> stroke_node) {
- SkASSERT(jstroke.isObject());
-
- if (!stroke_node)
- return nullptr;
-
- stroke_node->setStyle(SkPaint::kStroke_Style);
-
- auto width_attached = BindProperty<ScalarValue>(jstroke["w"], &ctx->fAnimators,
- [stroke_node](const ScalarValue& w) {
- stroke_node->setStrokeWidth(w);
- });
- if (!width_attached)
- return nullptr;
-
- stroke_node->setStrokeMiter(jstroke["ml"].toDefault(4.0f));
-
- static constexpr SkPaint::Join gJoins[] = {
- SkPaint::kMiter_Join,
- SkPaint::kRound_Join,
- SkPaint::kBevel_Join,
- };
- stroke_node->setStrokeJoin(gJoins[SkTPin<int>(jstroke["lj"].toDefault<int>(1) - 1,
- 0, SK_ARRAY_COUNT(gJoins) - 1)]);
-
- static constexpr SkPaint::Cap gCaps[] = {
- SkPaint::kButt_Cap,
- SkPaint::kRound_Cap,
- SkPaint::kSquare_Cap,
- };
- stroke_node->setStrokeCap(gCaps[SkTPin<int>(jstroke["lc"].toDefault<int>(1) - 1,
- 0, SK_ARRAY_COUNT(gCaps) - 1)]);
-
- return stroke_node;
-}
-
-sk_sp<sksg::PaintNode> AttachColorFill(const json::ValueRef& jfill, AttachContext* ctx) {
- SkASSERT(jfill.isObject());
-
- return AttachPaint(jfill, ctx, AttachColor(jfill, ctx));
-}
-
-sk_sp<sksg::PaintNode> AttachGradientFill(const json::ValueRef& jfill, AttachContext* ctx) {
- SkASSERT(jfill.isObject());
-
- return AttachPaint(jfill, ctx, AttachGradient(jfill, ctx));
-}
-
-sk_sp<sksg::PaintNode> AttachColorStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
- SkASSERT(jstroke.isObject());
-
- return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachColor(jstroke, ctx)));
-}
-
-sk_sp<sksg::PaintNode> AttachGradientStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
- SkASSERT(jstroke.isObject());
-
- return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachGradient(jstroke, ctx)));
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
- const json::ValueRef& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
- std::vector<sk_sp<sksg::GeometryNode>> merged;
-
- static constexpr sksg::Merge::Mode gModes[] = {
- sksg::Merge::Mode::kMerge, // "mm": 1
- sksg::Merge::Mode::kUnion, // "mm": 2
- sksg::Merge::Mode::kDifference, // "mm": 3
- sksg::Merge::Mode::kIntersect, // "mm": 4
- sksg::Merge::Mode::kXOR , // "mm": 5
- };
-
- const auto mode = gModes[SkTPin<int>(jmerge["mm"].toDefault(1) - 1,
- 0, SK_ARRAY_COUNT(gModes) - 1)];
- merged.push_back(sksg::Merge::Make(std::move(geos), mode));
-
- return merged;
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
- const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
-
- enum class Mode {
- kMerged, // "m": 1
- kSeparate, // "m": 2
- } gModes[] = { Mode::kMerged, Mode::kSeparate };
-
- const auto mode = gModes[SkTPin<int>(jtrim["m"].toDefault(1) - 1,
- 0, SK_ARRAY_COUNT(gModes) - 1)];
-
- std::vector<sk_sp<sksg::GeometryNode>> inputs;
- if (mode == Mode::kMerged) {
- inputs.push_back(sksg::Merge::Make(std::move(geos), sksg::Merge::Mode::kMerge));
- } else {
- inputs = std::move(geos);
- }
-
- std::vector<sk_sp<sksg::GeometryNode>> trimmed;
- trimmed.reserve(inputs.size());
- for (const auto& i : inputs) {
- const auto trimEffect = sksg::TrimEffect::Make(i);
- trimmed.push_back(trimEffect);
-
- const auto adapter = sk_make_sp<TrimEffectAdapter>(std::move(trimEffect));
- BindProperty<ScalarValue>(jtrim["s"], &ctx->fAnimators,
- [adapter](const ScalarValue& s) {
- adapter->setStart(s);
- });
- BindProperty<ScalarValue>(jtrim["e"], &ctx->fAnimators,
- [adapter](const ScalarValue& e) {
- adapter->setEnd(e);
- });
- BindProperty<ScalarValue>(jtrim["o"], &ctx->fAnimators,
- [adapter](const ScalarValue& o) {
- adapter->setOffset(o);
- });
- }
-
- return trimmed;
-}
-
-std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
- const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
-
- std::vector<sk_sp<sksg::GeometryNode>> rounded;
- rounded.reserve(geos.size());
-
- for (const auto& g : geos) {
- const auto roundEffect = sksg::RoundEffect::Make(std::move(g));
- rounded.push_back(roundEffect);
-
- BindProperty<ScalarValue>(jtrim["r"], &ctx->fAnimators,
- [roundEffect](const ScalarValue& r) {
- roundEffect->setRadius(r);
- });
- }
-
- return rounded;
-}
-
-using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const json::ValueRef&, AttachContext*);
-static constexpr GeometryAttacherT gGeometryAttachers[] = {
- AttachPathGeometry,
- AttachRRectGeometry,
- AttachEllipseGeometry,
- AttachPolystarGeometry,
-};
-
-using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const json::ValueRef&, AttachContext*);
-static constexpr PaintAttacherT gPaintAttachers[] = {
- AttachColorFill,
- AttachColorStroke,
- AttachGradientFill,
- AttachGradientStroke,
-};
-
-using GeometryEffectAttacherT =
- std::vector<sk_sp<sksg::GeometryNode>> (*)(const json::ValueRef&,
- AttachContext*,
- std::vector<sk_sp<sksg::GeometryNode>>&&);
-static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
- AttachMergeGeometryEffect,
- AttachTrimGeometryEffect,
- AttachRoundGeometryEffect,
-};
-
-enum class ShapeType {
- kGeometry,
- kGeometryEffect,
- kPaint,
- kGroup,
- kTransform,
-};
-
-struct ShapeInfo {
- const char* fTypeString;
- ShapeType fShapeType;
- uint32_t fAttacherIndex; // index into respective attacher tables
-};
-
-const ShapeInfo* FindShapeInfo(const json::ValueRef& shape) {
- static constexpr ShapeInfo gShapeInfo[] = {
- { "el", ShapeType::kGeometry , 2 }, // ellipse -> AttachEllipseGeometry
- { "fl", ShapeType::kPaint , 0 }, // fill -> AttachColorFill
- { "gf", ShapeType::kPaint , 2 }, // gfill -> AttachGradientFill
- { "gr", ShapeType::kGroup , 0 }, // group -> Inline handler
- { "gs", ShapeType::kPaint , 3 }, // gstroke -> AttachGradientStroke
- { "mm", ShapeType::kGeometryEffect, 0 }, // merge -> AttachMergeGeometryEffect
- { "rc", ShapeType::kGeometry , 1 }, // rrect -> AttachRRectGeometry
- { "rd", ShapeType::kGeometryEffect, 2 }, // round -> AttachRoundGeometryEffect
- { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry
- { "sr", ShapeType::kGeometry , 3 }, // polystar -> AttachPolyStarGeometry
- { "st", ShapeType::kPaint , 1 }, // stroke -> AttachColorStroke
- { "tm", ShapeType::kGeometryEffect, 1 }, // trim -> AttachTrimGeometryEffect
- { "tr", ShapeType::kTransform , 0 }, // transform -> Inline handler
- };
-
- SkString type;
- if (!shape["ty"].to(&type) || type.isEmpty())
- return nullptr;
-
- const auto* info = bsearch(type.c_str(),
- gShapeInfo,
- SK_ARRAY_COUNT(gShapeInfo),
- sizeof(ShapeInfo),
- [](const void* key, const void* info) {
- return strcmp(static_cast<const char*>(key),
- static_cast<const ShapeInfo*>(info)->fTypeString);
- });
-
- return static_cast<const ShapeInfo*>(info);
-}
-
-struct GeometryEffectRec {
- const json::ValueRef fJson;
- GeometryEffectAttacherT fAttach;
-};
-
-struct AttachShapeContext {
- AttachShapeContext(AttachContext* ctx,
- std::vector<sk_sp<sksg::GeometryNode>>* geos,
- std::vector<GeometryEffectRec>* effects,
- size_t committedAnimators)
- : fCtx(ctx)
- , fGeometryStack(geos)
- , fGeometryEffectStack(effects)
- , fCommittedAnimators(committedAnimators) {}
-
- AttachContext* fCtx;
- std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
- std::vector<GeometryEffectRec>* fGeometryEffectStack;
- size_t fCommittedAnimators;
-};
-
-sk_sp<sksg::RenderNode> AttachShape(const json::ValueRef& jshape, AttachShapeContext* shapeCtx) {
- if (!jshape.isArray())
- return nullptr;
-
- SkDEBUGCODE(const auto initialGeometryEffects = shapeCtx->fGeometryEffectStack->size();)
-
- sk_sp<sksg::Group> shape_group = sksg::Group::Make();
- sk_sp<sksg::RenderNode> shape_wrapper = shape_group;
- sk_sp<sksg::Matrix> shape_matrix;
-
- struct ShapeRec {
- const json::ValueRef fJson;
- const ShapeInfo& fInfo;
- };
-
- // First pass (bottom->top):
- //
- // * pick up the group transform and opacity
- // * push local geometry effects onto the stack
- // * store recs for next pass
- //
- std::vector<ShapeRec> recs;
- for (size_t i = 0; i < jshape.size(); ++i) {
- const auto s = jshape[jshape.size() - 1 - i];
- const auto* info = FindShapeInfo(s);
- if (!info) {
- LogFail(s["ty"], "Unknown shape");
- continue;
- }
-
- recs.push_back({ s, *info });
-
- switch (info->fShapeType) {
- case ShapeType::kTransform:
- if ((shape_matrix = AttachMatrix(s, shapeCtx->fCtx, nullptr))) {
- shape_wrapper = sksg::Transform::Make(std::move(shape_wrapper), shape_matrix);
- }
- shape_wrapper = AttachOpacity(s, shapeCtx->fCtx, std::move(shape_wrapper));
- break;
- case ShapeType::kGeometryEffect:
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
- shapeCtx->fGeometryEffectStack->push_back(
- { s, gGeometryEffectAttachers[info->fAttacherIndex] });
- break;
- default:
- break;
- }
- }
-
- // Second pass (top -> bottom, after 2x reverse):
- //
- // * track local geometry
- // * emit local paints
- //
- std::vector<sk_sp<sksg::GeometryNode>> geos;
- std::vector<sk_sp<sksg::RenderNode >> draws;
- for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
- switch (rec->fInfo.fShapeType) {
- case ShapeType::kGeometry: {
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
- if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- shapeCtx->fCtx)) {
- geos.push_back(std::move(geo));
- }
- } break;
- case ShapeType::kGeometryEffect: {
- // Apply the current effect and pop from the stack.
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
- if (!geos.empty()) {
- geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
- shapeCtx->fCtx,
- std::move(geos));
- }
-
- SkASSERT(shapeCtx->fGeometryEffectStack->back().fJson == rec->fJson);
- SkASSERT(shapeCtx->fGeometryEffectStack->back().fAttach ==
- gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
- shapeCtx->fGeometryEffectStack->pop_back();
- } break;
- case ShapeType::kGroup: {
- AttachShapeContext groupShapeCtx(shapeCtx->fCtx,
- &geos,
- shapeCtx->fGeometryEffectStack,
- shapeCtx->fCommittedAnimators);
- if (auto subgroup = AttachShape(rec->fJson["it"], &groupShapeCtx)) {
- draws.push_back(std::move(subgroup));
- SkASSERT(groupShapeCtx.fCommittedAnimators >= shapeCtx->fCommittedAnimators);
- shapeCtx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
- }
- } break;
- case ShapeType::kPaint: {
- SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
- auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, shapeCtx->fCtx);
- if (!paint || geos.empty())
- break;
-
- auto drawGeos = geos;
-
- // Apply all pending effects from the stack.
- for (auto it = shapeCtx->fGeometryEffectStack->rbegin();
- it != shapeCtx->fGeometryEffectStack->rend(); ++it) {
- drawGeos = it->fAttach(it->fJson, shapeCtx->fCtx, std::move(drawGeos));
- }
-
- // If we still have multiple geos, reduce using 'merge'.
- auto geo = drawGeos.size() > 1
- ? sksg::Merge::Make(std::move(drawGeos), sksg::Merge::Mode::kMerge)
- : drawGeos[0];
-
- SkASSERT(geo);
- draws.push_back(sksg::Draw::Make(std::move(geo), std::move(paint)));
- shapeCtx->fCommittedAnimators = shapeCtx->fCtx->fAnimators.size();
- } break;
- default:
- break;
- }
- }
-
- // By now we should have popped all local geometry effects.
- SkASSERT(shapeCtx->fGeometryEffectStack->size() == initialGeometryEffects);
-
- // Push transformed local geometries to parent list, for subsequent paints.
- for (const auto& geo : geos) {
- shapeCtx->fGeometryStack->push_back(shape_matrix
- ? sksg::GeometryTransform::Make(std::move(geo), shape_matrix)
- : std::move(geo));
- }
-
- // Emit local draws reversed (bottom->top, per spec).
- for (auto it = draws.rbegin(); it != draws.rend(); ++it) {
- shape_group->addChild(std::move(*it));
- }
-
- return draws.empty() ? nullptr : shape_wrapper;
-}
-
-sk_sp<sksg::RenderNode> AttachNestedAnimation(const char* path, AttachContext* ctx) {
- class SkottieSGAdapter final : public sksg::RenderNode {
- public:
- explicit SkottieSGAdapter(sk_sp<Animation> animation)
- : fAnimation(std::move(animation)) {
- SkASSERT(fAnimation);
- }
-
- protected:
- SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override {
- return SkRect::MakeSize(fAnimation->size());
- }
-
- void onRender(SkCanvas* canvas) const override {
- fAnimation->render(canvas);
- }
-
- private:
- const sk_sp<Animation> fAnimation;
- };
-
- class SkottieAnimatorAdapter final : public sksg::Animator {
- public:
- SkottieAnimatorAdapter(sk_sp<Animation> animation, float frameRate)
- : fAnimation(std::move(animation))
- , fFrameRate(frameRate) {
- SkASSERT(fAnimation);
- SkASSERT(fFrameRate > 0);
- }
-
- protected:
- void onTick(float t) {
- // map back from frame # to ms.
- const auto t_ms = t * 1000 / fFrameRate;
- fAnimation->animationTick(t_ms);
- }
-
- private:
- const sk_sp<Animation> fAnimation;
- const float fFrameRate;
- };
-
- const auto resStream = ctx->fResources.openStream(path);
- if (!resStream || !resStream->hasLength()) {
- LOG("!! Could not open: %s\n", path);
- return nullptr;
- }
-
- auto animation = Animation::Make(resStream.get(), ctx->fResources);
- if (!animation) {
- LOG("!! Could not load nested animation: %s\n", path);
- return nullptr;
- }
-
- ctx->fAnimators.push_back(skstd::make_unique<SkottieAnimatorAdapter>(animation,
- ctx->fFrameRate));
-
- return sk_make_sp<SkottieSGAdapter>(std::move(animation));
-}
-
-sk_sp<sksg::RenderNode> AttachAssetRef(const json::ValueRef& jlayer, AttachContext* ctx,
- sk_sp<sksg::RenderNode>(*attach_proc)(const json::ValueRef& comp, AttachContext* ctx)) {
-
- const auto refId = jlayer["refId"].toDefault(SkString());
- if (refId.isEmpty()) {
- LOG("!! Layer missing refId\n");
- return nullptr;
- }
-
- if (refId.startsWith("$")) {
- return AttachNestedAnimation(refId.c_str() + 1, ctx);
- }
-
- const auto* asset_info = ctx->fAssets.find(refId);
- if (!asset_info) {
- LOG("!! Asset not found: '%s'\n", refId.c_str());
- return nullptr;
- }
-
- if (asset_info->fIsAttaching) {
- LOG("!! Asset cycle detected for: '%s'\n", refId.c_str());
- return nullptr;
- }
-
- asset_info->fIsAttaching = true;
- auto asset = attach_proc(asset_info->fAsset, ctx);
- asset_info->fIsAttaching = false;
-
- return asset;
-}
-
-sk_sp<sksg::RenderNode> AttachCompLayer(const json::ValueRef& jlayer, AttachContext* ctx,
- float* time_bias, float* time_scale) {
- SkASSERT(jlayer.isObject());
-
- const auto start_time = jlayer["st"].toDefault(0.0f),
- stretch_time = jlayer["sr"].toDefault(1.0f);
-
- *time_bias = -start_time;
- *time_scale = sk_ieee_float_divide(1, stretch_time);
- if (SkScalarIsNaN(*time_scale)) {
- *time_scale = 1;
- }
-
- return AttachAssetRef(jlayer, ctx, AttachComposition);
-}
-
-sk_sp<sksg::RenderNode> AttachSolidLayer(const json::ValueRef& jlayer, AttachContext*,
- float*, float*) {
- SkASSERT(jlayer.isObject());
-
- const auto size = SkSize::Make(jlayer["sw"].toDefault(0.0f),
- jlayer["sh"].toDefault(0.0f));
- const auto hex = jlayer["sc"].toDefault(SkString());
- uint32_t c;
- if (size.isEmpty() ||
- !hex.startsWith("#") ||
- !SkParse::FindHex(hex.c_str() + 1, &c)) {
- LogFail(jlayer, "Could not parse solid layer");
- return nullptr;
- }
-
- const SkColor color = 0xff000000 | c;
-
- return sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeSize(size)),
- sksg::Color::Make(color));
-}
-
-sk_sp<sksg::RenderNode> AttachImageAsset(const json::ValueRef& jimage, AttachContext* ctx) {
- SkASSERT(jimage.isObject());
-
- const auto name = jimage["p"].toDefault(SkString()),
- path = jimage["u"].toDefault(SkString());
- if (name.isEmpty())
- return nullptr;
-
- // TODO: plumb resource paths explicitly to ResourceProvider?
- const auto resName = path.isEmpty() ? name : SkOSPath::Join(path.c_str(), name.c_str());
- const auto resStream = ctx->fResources.openStream(resName.c_str());
- if (!resStream || !resStream->hasLength()) {
- LOG("!! Could not load image resource: %s\n", resName.c_str());
- return nullptr;
- }
-
- // TODO: non-intrisic image sizing
- return sksg::Image::Make(
- SkImage::MakeFromEncoded(SkData::MakeFromStream(resStream.get(), resStream->getLength())));
-}
-
-sk_sp<sksg::RenderNode> AttachImageLayer(const json::ValueRef& jlayer, AttachContext* ctx,
- float*, float*) {
- SkASSERT(jlayer.isObject());
-
- return AttachAssetRef(jlayer, ctx, AttachImageAsset);
-}
-
-sk_sp<sksg::RenderNode> AttachNullLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
- SkASSERT(layer.isObject());
-
- // Null layers are used solely to drive dependent transforms,
- // but we use free-floating sksg::Matrices for that purpose.
- return nullptr;
-}
-
-sk_sp<sksg::RenderNode> AttachShapeLayer(const json::ValueRef& layer, AttachContext* ctx,
- float*, float*) {
- SkASSERT(layer.isObject());
-
- std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
- std::vector<GeometryEffectRec> geometryEffectStack;
- AttachShapeContext shapeCtx(ctx, &geometryStack, &geometryEffectStack, ctx->fAnimators.size());
- auto shapeNode = AttachShape(layer["shapes"], &shapeCtx);
-
- // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
- // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
- // due to attached animators. To avoid this, we track committed animators and discard the
- // orphans here.
- SkASSERT(shapeCtx.fCommittedAnimators <= ctx->fAnimators.size());
- ctx->fAnimators.resize(shapeCtx.fCommittedAnimators);
-
- return shapeNode;
-}
-
-sk_sp<sksg::RenderNode> AttachTextLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
- SkASSERT(layer.isObject());
-
- LOG("?? Text layer stub\n");
- return nullptr;
-}
-
-struct AttachLayerContext {
- AttachLayerContext(const json::ValueRef& jlayers, AttachContext* ctx)
- : fLayerList(jlayers), fCtx(ctx) {
- SkASSERT(fLayerList.isArray());
- }
-
- const json::ValueRef fLayerList;
- AttachContext* fCtx;
- SkTHashMap<int, sk_sp<sksg::Matrix>> fLayerMatrixMap;
- sk_sp<sksg::RenderNode> fCurrentMatte;
-
- sk_sp<sksg::Matrix> AttachLayerMatrix(const json::ValueRef& jlayer) {
- SkASSERT(jlayer.isObject());
-
- const auto layer_index = jlayer["ind"].toDefault<int>(-1);
- if (layer_index < 0)
- return nullptr;
-
- if (auto* m = fLayerMatrixMap.find(layer_index))
- return *m;
-
- return this->AttachLayerMatrixImpl(jlayer, layer_index);
- }
-
-private:
- sk_sp<sksg::Matrix> AttachParentLayerMatrix(const json::ValueRef& jlayer, int layer_index) {
- SkASSERT(jlayer.isObject());
-
- const auto parent_index = jlayer["parent"].toDefault<int>(-1);
- if (parent_index < 0 || parent_index == layer_index)
- return nullptr;
-
- if (auto* m = fLayerMatrixMap.find(parent_index))
- return *m;
-
- for (const json::ValueRef l : fLayerList) {
- if (l["ind"].toDefault<int>(-1) == parent_index) {
- return this->AttachLayerMatrixImpl(l, parent_index);
- }
- }
-
- return nullptr;
- }
-
- sk_sp<sksg::Matrix> AttachLayerMatrixImpl(const json::ValueRef& jlayer, int layer_index) {
- SkASSERT(!fLayerMatrixMap.find(layer_index));
-
- // Add a stub entry to break recursion cycles.
- fLayerMatrixMap.set(layer_index, nullptr);
-
- auto parent_matrix = this->AttachParentLayerMatrix(jlayer, layer_index);
-
- return *fLayerMatrixMap.set(layer_index, AttachMatrix(jlayer["ks"], fCtx, parent_matrix));
- }
-};
-
-SkBlendMode MaskBlendMode(char mode) {
- switch (mode) {
- case 'a': return SkBlendMode::kSrcOver; // Additive
- case 's': return SkBlendMode::kExclusion; // Subtract
- case 'i': return SkBlendMode::kDstIn; // Intersect
- case 'l': return SkBlendMode::kLighten; // Lighten
- case 'd': return SkBlendMode::kDarken; // Darken
- case 'f': return SkBlendMode::kDifference; // Difference
- default: break;
- }
-
- return SkBlendMode::kSrcOver;
-}
-
-sk_sp<sksg::RenderNode> AttachMask(const json::ValueRef& jmask,
- AttachContext* ctx,
- sk_sp<sksg::RenderNode> childNode) {
- if (!jmask.isArray())
- return childNode;
-
- struct MaskRecord {
- sk_sp<sksg::Path> mask_path;
- sk_sp<sksg::Color> mask_paint;
- };
-
- SkSTArray<4, MaskRecord, true> mask_stack;
-
- bool opaque_mask = true;
-
- for (const json::ValueRef m : jmask) {
- if (!m.isObject())
- continue;
-
- auto mask_path = AttachPath(m["pt"], ctx);
- if (!mask_path) {
- LogFail(m, "Could not parse mask path");
- continue;
- }
-
- mask_path->setFillType(m["inv"].toDefault(false)
- ? SkPath::kInverseWinding_FillType
- : SkPath::kWinding_FillType);
-
- SkString mode;
- if (!m["mode"].to(&mode) ||
- mode.size() != 1 ||
- !strcmp(mode.c_str(), "n")) { // "None" masks have no effect.
- continue;
- }
-
- auto mask_paint = sksg::Color::Make(SK_ColorBLACK);
- mask_paint->setAntiAlias(true);
- mask_paint->setBlendMode(MaskBlendMode(mode.c_str()[0]));
-
- const auto animator_count = ctx->fAnimators.size();
- BindProperty<ScalarValue>(m["o"], &ctx->fAnimators,
- [mask_paint](const ScalarValue& o) { mask_paint->setOpacity(o * 0.01f); });
-
- opaque_mask &= (animator_count == ctx->fAnimators.size() && mask_paint->getOpacity() >= 1);
-
- mask_stack.push_back({mask_path, mask_paint});
- }
-
- if (mask_stack.empty())
- return childNode;
-
- if (mask_stack.count() == 1 && opaque_mask) {
- // Single opaque mask => clip path.
- return sksg::ClipEffect::Make(std::move(childNode),
- std::move(mask_stack.front().mask_path),
- true);
- }
-
- auto mask_group = sksg::Group::Make();
- for (const auto& rec : mask_stack) {
- mask_group->addChild(sksg::Draw::Make(std::move(rec.mask_path),
- std::move(rec.mask_paint)));
-
- }
-
- return sksg::MaskEffect::Make(std::move(childNode), std::move(mask_group));
-}
-
-sk_sp<sksg::RenderNode> AttachLayer(const json::ValueRef& jlayer, AttachLayerContext* layerCtx) {
- if (!jlayer.isObject())
- return nullptr;
-
- using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const json::ValueRef&, AttachContext*,
- float* time_bias, float* time_scale);
- static constexpr LayerAttacher gLayerAttachers[] = {
- AttachCompLayer, // 'ty': 0
- AttachSolidLayer, // 'ty': 1
- AttachImageLayer, // 'ty': 2
- AttachNullLayer, // 'ty': 3
- AttachShapeLayer, // 'ty': 4
- AttachTextLayer, // 'ty': 5
- };
-
- int type = jlayer["ty"].toDefault<int>(-1);
- if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
- return nullptr;
- }
-
- sksg::AnimatorList layer_animators;
- AttachContext local_ctx = { layerCtx->fCtx->fResources,
- layerCtx->fCtx->fAssets,
- layerCtx->fCtx->fFrameRate,
- layer_animators};
-
- // Layer attachers may adjust these.
- float time_bias = 0,
- time_scale = 1;
-
- // Layer content.
- auto layer = gLayerAttachers[type](jlayer, &local_ctx, &time_bias, &time_scale);
-
- // Clip layers with explicit dimensions.
- float w = 0, h = 0;
- if (jlayer["w"].to(&w) && jlayer["h"].to(&h)) {
- layer = sksg::ClipEffect::Make(std::move(layer),
- sksg::Rect::Make(SkRect::MakeWH(w, h)),
- true);
- }
-
- // Optional layer mask.
- layer = AttachMask(jlayer["masksProperties"], &local_ctx, std::move(layer));
-
- // Optional layer transform.
- if (auto layerMatrix = layerCtx->AttachLayerMatrix(jlayer)) {
- layer = sksg::Transform::Make(std::move(layer), std::move(layerMatrix));
- }
-
- // Optional layer opacity.
- layer = AttachOpacity(jlayer["ks"], &local_ctx, std::move(layer));
-
- class LayerController final : public sksg::GroupAnimator {
- public:
- LayerController(sksg::AnimatorList&& layer_animators,
- sk_sp<sksg::OpacityEffect> controlNode,
- float in, float out,
- float time_bias, float time_scale)
- : INHERITED(std::move(layer_animators))
- , fControlNode(std::move(controlNode))
- , fIn(in)
- , fOut(out)
- , fTimeBias(time_bias)
- , fTimeScale(time_scale) {}
-
- void onTick(float t) override {
- const auto active = (t >= fIn && t <= fOut);
-
- // Keep the layer fully transparent except for its [in..out] lifespan.
- // (note: opacity == 0 disables rendering, while opacity == 1 is a noop)
- fControlNode->setOpacity(active ? 1 : 0);
-
- // Dispatch ticks only while active.
- if (active)
- this->INHERITED::onTick((t + fTimeBias) * fTimeScale);
- }
-
- private:
- const sk_sp<sksg::OpacityEffect> fControlNode;
- const float fIn,
- fOut,
- fTimeBias,
- fTimeScale;
-
- using INHERITED = sksg::GroupAnimator;
- };
-
- auto controller_node = sksg::OpacityEffect::Make(std::move(layer));
- const auto in = jlayer["ip"].toDefault(0.0f),
- out = jlayer["op"].toDefault(in);
-
- if (!jlayer["tm"].isNull()) {
- LogFail(jlayer["tm"], "Unsupported time remapping");
- }
-
- if (in >= out || !controller_node)
- return nullptr;
-
- layerCtx->fCtx->fAnimators.push_back(
- skstd::make_unique<LayerController>(std::move(layer_animators),
- controller_node,
- in,
- out,
- time_bias,
- time_scale));
-
- if (jlayer["td"].toDefault(false)) {
- // This layer is a matte. We apply it as a mask to the next layer.
- layerCtx->fCurrentMatte = std::move(controller_node);
- return nullptr;
- }
-
- if (layerCtx->fCurrentMatte) {
- // There is a pending matte. Apply and reset.
- static constexpr sksg::MaskEffect::Mode gMaskModes[] = {
- sksg::MaskEffect::Mode::kNormal, // tt: 1
- sksg::MaskEffect::Mode::kInvert, // tt: 2
- };
- const auto matteType = jlayer["tt"].toDefault<int>(1) - 1;
-
- if (matteType >= 0 && matteType < SkTo<int>(SK_ARRAY_COUNT(gMaskModes))) {
- return sksg::MaskEffect::Make(std::move(controller_node),
- std::move(layerCtx->fCurrentMatte),
- gMaskModes[matteType]);
- }
- layerCtx->fCurrentMatte.reset();
- }
-
- return std::move(controller_node);
-}
-
-sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef& comp, AttachContext* ctx) {
- if (!comp.isObject())
- return nullptr;
-
- const auto jlayers = comp["layers"];
- if (!jlayers.isArray())
- return nullptr;
-
- SkSTArray<16, sk_sp<sksg::RenderNode>, true> layers;
- AttachLayerContext layerCtx(jlayers, ctx);
-
- for (const json::ValueRef l : jlayers) {
- if (auto layer_fragment = AttachLayer(l, &layerCtx)) {
- layers.push_back(std::move(layer_fragment));
- }
- }
-
- if (layers.empty()) {
- return nullptr;
- }
-
- // Layers are painted in bottom->top order.
- auto comp_group = sksg::Group::Make();
- for (int i = layers.count() - 1; i >= 0; --i) {
- comp_group->addChild(std::move(layers[i]));
- }
-
- return std::move(comp_group);
-}
-
-} // namespace
-
-sk_sp<Animation> Animation::Make(SkStream* stream, const ResourceProvider& res, Stats* stats) {
- Stats stats_storage;
- if (!stats)
- stats = &stats_storage;
- memset(stats, 0, sizeof(struct Stats));
-
- if (!stream->hasLength()) {
- // TODO: handle explicit buffering?
- LOG("!! cannot parse streaming content\n");
- return nullptr;
- }
-
- stats->fJsonSize = stream->getLength();
- const auto t0 = SkTime::GetMSecs();
-
- const json::Document doc(stream);
- const auto json = doc.root();
- if (!json.isObject())
- return nullptr;
-
- const auto t1 = SkTime::GetMSecs();
- stats->fJsonParseTimeMS = t1 - t0;
-
- const auto version = json["v"].toDefault(SkString());
- const auto size = SkSize::Make(json["w"].toDefault(0.0f),
- json["h"].toDefault(0.0f));
- const auto fps = json["fr"].toDefault(-1.0f);
-
- if (size.isEmpty() || version.isEmpty() || fps <= 0) {
- LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
- version.c_str(), size.width(), size.height(), fps);
- return nullptr;
- }
-
- const auto anim =
- sk_sp<Animation>(new Animation(res, std::move(version), size, fps, json, stats));
- const auto t2 = SkTime::GetMSecs();
- stats->fSceneParseTimeMS = t2 - t1;
- stats->fTotalLoadTimeMS = t2 - t0;
-
- return anim;
-}
-
-sk_sp<Animation> Animation::MakeFromFile(const char path[], const ResourceProvider* res,
- Stats* stats) {
- class DirectoryResourceProvider final : public ResourceProvider {
- public:
- explicit DirectoryResourceProvider(SkString dir) : fDir(std::move(dir)) {}
-
- std::unique_ptr<SkStream> openStream(const char resource[]) const override {
- const auto resPath = SkOSPath::Join(fDir.c_str(), resource);
- return SkStream::MakeFromFile(resPath.c_str());
- }
-
- private:
- const SkString fDir;
- };
-
- const auto jsonStream = SkStream::MakeFromFile(path);
- if (!jsonStream)
- return nullptr;
-
- std::unique_ptr<ResourceProvider> defaultProvider;
- if (!res) {
- defaultProvider = skstd::make_unique<DirectoryResourceProvider>(SkOSPath::Dirname(path));
- }
-
- return Make(jsonStream.get(), res ? *res : *defaultProvider, stats);
-}
-
-Animation::Animation(const ResourceProvider& resources,
- SkString version, const SkSize& size, SkScalar fps, const json::ValueRef& json,
- Stats* stats)
- : fVersion(std::move(version))
- , fSize(size)
- , fFrameRate(fps)
- , fInPoint(json["ip"].toDefault(0.0f))
- , fOutPoint(SkTMax(json["op"].toDefault(SK_ScalarMax), fInPoint)) {
-
- AssetMap assets;
- for (const json::ValueRef asset : json["assets"]) {
- if (asset.isObject()) {
- assets.set(asset["id"].toDefault(SkString()), { asset, false });
- }
- }
-
- sksg::AnimatorList animators;
- AttachContext ctx = { resources, assets, fFrameRate, animators };
- auto root = AttachComposition(json, &ctx);
-
- stats->fAnimatorCount = animators.size();
-
- fScene = sksg::Scene::Make(std::move(root), std::move(animators));
-
- // In case the client calls render before the first tick.
- this->animationTick(0);
-}
-
-Animation::~Animation() = default;
-
-void Animation::setShowInval(bool show) {
- if (fScene) {
- fScene->setShowInval(show);
- }
-}
-
-void Animation::render(SkCanvas* canvas, const SkRect* dstR) const {
- 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::animationTick(SkMSec ms) {
- if (!fScene)
- return;
-
- // 't' in the BM model really means 'frame #'
- auto t = static_cast<float>(ms) * fFrameRate / 1000;
-
- t = fInPoint + std::fmod(t, fOutPoint - fInPoint);
-
- fScene->animate(t);
-}
-
-} // namespace skottie