blob: 1d26b08f32e160e1fbdd10734edeb01a6d28b0c6 [file] [log] [blame]
/*
* Copyright 2020 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/Adapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGTransform.h"
#include <vector>
namespace skottie {
namespace internal {
namespace {
class RepeaterAdapter final : public DiscardableAdapterBase<RepeaterAdapter, sksg::Group> {
public:
RepeaterAdapter(const skjson::ObjectValue& jrepeater,
const skjson::ObjectValue& jtransform,
const AnimationBuilder& abuilder,
sk_sp<sksg::RenderNode> repeater_node)
: fRepeaterNode(std::move(repeater_node))
, fComposite((ParseDefault(jrepeater["m"], 1) == 1) ? Composite::kAbove
: Composite::kBelow) {
this->bind(abuilder, jrepeater["c"], fCount);
this->bind(abuilder, jrepeater["o"], fOffset);
this->bind(abuilder, jtransform["a" ], fAnchorPoint);
this->bind(abuilder, jtransform["p" ], fPosition);
this->bind(abuilder, jtransform["s" ], fScale);
this->bind(abuilder, jtransform["r" ], fRotation);
this->bind(abuilder, jtransform["so"], fStartOpacity);
this->bind(abuilder, jtransform["eo"], fEndOpacity);
}
private:
void onSync() override {
static constexpr SkScalar kMaxCount = 512;
const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f);
const auto& compute_transform = [&] (size_t index) {
const auto t = fOffset + index;
// Position, scale & rotation are "scaled" by index/offset.
SkMatrix m = SkMatrix::Translate(-fAnchorPoint.x,
-fAnchorPoint.y);
m.postScale(std::pow(fScale.x * .01f, fOffset),
std::pow(fScale.y * .01f, fOffset));
m.postRotate(t * fRotation);
m.postTranslate(t * fPosition.x + fAnchorPoint.x,
t * fPosition.y + fAnchorPoint.y);
return m;
};
// TODO: start/end opacity support.
// TODO: we can avoid rebuilding all the fragments in most cases.
this->node()->clear();
for (size_t i = 0; i < count; ++i) {
const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1;
this->node()->addChild(sksg::TransformEffect::Make(fRepeaterNode,
compute_transform(insert_index)));
}
}
enum class Composite { kAbove, kBelow };
const sk_sp<sksg::RenderNode> fRepeaterNode;
const Composite fComposite;
// Repeater props
ScalarValue fCount = 0,
fOffset = 0;
// Transform props
Vec2Value fAnchorPoint = { 0, 0 },
fPosition = { 0, 0 },
fScale = { 100, 100 };
ScalarValue fRotation = 0,
fStartOpacity = 100,
fEndOpacity = 100;
};
} // namespace
std::vector<sk_sp<sksg::RenderNode>> ShapeBuilder::AttachRepeaterDrawEffect(
const skjson::ObjectValue& jrepeater,
const AnimationBuilder* abuilder,
std::vector<sk_sp<sksg::RenderNode>>&& draws) {
std::vector<sk_sp<sksg::RenderNode>> repeater_draws;
if (const skjson::ObjectValue* jtransform = jrepeater["tr"]) {
// We can skip the group if only one draw.
auto repeater_node = (draws.size() > 1) ? sksg::Group::Make(std::move(draws))
: std::move(draws[0]);
auto repeater_root =
abuilder->attachDiscardableAdapter<RepeaterAdapter>(jrepeater,
*jtransform,
*abuilder,
std::move(repeater_node));
repeater_draws.reserve(1);
repeater_draws.push_back(std::move(repeater_root));
} else {
repeater_draws = std::move(draws);
}
return repeater_draws;
}
} // namespace internal
} // namespace skottie