blob: 3efad4276b1642bbb4199816542cdeb0213a3d17 [file] [log] [blame]
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/SkottiePriv.h"
#include "include/core/SkCanvas.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGTransform.h"
#include <algorithm>
namespace skottie {
namespace internal {
sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name) const {
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());
}
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
const auto local_scope =
ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
canvas->getTotalMatrix(),
true);
fAnimation->render(canvas);
}
private:
const sk_sp<Animation> fAnimation;
};
class SkottieAnimatorAdapter final : public sksg::Animator {
public:
SkottieAnimatorAdapter(sk_sp<Animation> animation, float time_scale)
: fAnimation(std::move(animation))
, fTimeScale(time_scale) {
SkASSERT(fAnimation);
}
protected:
void onTick(float t) {
// TODO: we prolly need more sophisticated timeline mapping for nested animations.
fAnimation->seek(t * fTimeScale);
}
private:
const sk_sp<Animation> fAnimation;
const float fTimeScale;
};
const auto data = fResourceProvider->load("", name);
if (!data) {
this->log(Logger::Level::kError, nullptr, "Could not load: %s.", name);
return nullptr;
}
auto animation = Animation::Builder()
.setResourceProvider(fResourceProvider)
.setFontManager(fLazyFontMgr.getMaybeNull())
.make(static_cast<const char*>(data->data()), data->size());
if (!animation) {
this->log(Logger::Level::kError, nullptr, "Could not parse nested animation: %s.", name);
return nullptr;
}
fCurrentAnimatorScope->push_back(
sk_make_sp<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration));
return sk_make_sp<SkottieSGAdapter>(std::move(animation));
}
sk_sp<sksg::RenderNode> AnimationBuilder::attachAssetRef(
const skjson::ObjectValue& jlayer,
const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>& func) const {
const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString());
if (refId.isEmpty()) {
this->log(Logger::Level::kError, nullptr, "Layer missing refId.");
return nullptr;
}
if (refId.startsWith("$")) {
return this->attachNestedAnimation(refId.c_str() + 1);
}
const auto* asset_info = fAssets.find(refId);
if (!asset_info) {
this->log(Logger::Level::kError, nullptr, "Asset not found: '%s'.", refId.c_str());
return nullptr;
}
if (asset_info->fIsAttaching) {
this->log(Logger::Level::kError, nullptr,
"Asset cycle detected for: '%s'", refId.c_str());
return nullptr;
}
asset_info->fIsAttaching = true;
auto asset = func(*asset_info->fAsset);
asset_info->fIsAttaching = false;
return asset;
}
sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(
const skjson::ObjectValue& jcomp) const {
const skjson::ArrayValue* jlayers = jcomp["layers"];
if (!jlayers) return nullptr;
std::vector<sk_sp<sksg::RenderNode>> layers;
AttachLayerContext layerCtx(*jlayers);
// Optional motion blur params.
if (const skjson::ObjectValue* jmb = jcomp["mb"]) {
static constexpr size_t kMaxSamplesPerFrame = 64;
layerCtx.fMotionBlurSamples = std::min(ParseDefault<size_t>((*jmb)["spf"], 1ul),
kMaxSamplesPerFrame);
layerCtx.fMotionBlurAngle = SkTPin(ParseDefault((*jmb)["sa"], 0.0f), 0.0f, 720.0f);
layerCtx.fMotionBlurPhase = SkTPin(ParseDefault((*jmb)["sp"], 0.0f), -360.0f, 360.0f);
}
layers.reserve(jlayers->size());
for (const auto& l : *jlayers) {
if (auto layer = this->attachLayer(l, &layerCtx)) {
layers.push_back(std::move(layer));
}
}
if (layers.empty()) {
return nullptr;
}
sk_sp<sksg::RenderNode> comp;
if (layers.size() == 1) {
comp = std::move(layers[0]);
} else {
// Layers are painted in bottom->top order.
std::reverse(layers.begin(), layers.end());
layers.shrink_to_fit();
comp = sksg::Group::Make(std::move(layers));
}
// Optional camera.
if (layerCtx.fCameraTransform) {
comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform));
}
return comp;
}
} // namespace internal
} // namespace skottie