/* * 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 "include/private/SkTPin.h" #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/SkSGRenderNode.h" #include namespace skottie { namespace internal { namespace { class RepeaterRenderNode final : public sksg::CustomRenderNode { public: enum class CompositeMode { kBelow, kAbove }; RepeaterRenderNode(std::vector>&& children, CompositeMode mode) : INHERITED(std::move(children)) , fMode(mode) {} SG_ATTRIBUTE(Count , size_t, fCount ) SG_ATTRIBUTE(Offset , float , fOffset ) SG_ATTRIBUTE(AnchorPoint , SkV2 , fAnchorPoint ) SG_ATTRIBUTE(Position , SkV2 , fPosition ) SG_ATTRIBUTE(Scale , SkV2 , fScale ) SG_ATTRIBUTE(Rotation , float , fRotation ) SG_ATTRIBUTE(StartOpacity, float , fStartOpacity) SG_ATTRIBUTE(EndOpacity , float , fEndOpacity ) private: const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing SkMatrix instanceTransform(size_t i) const { const auto t = fOffset + i; // Position, scale & rotation are "scaled" by index/offset. return SkMatrix::Translate(t * fPosition.x + fAnchorPoint.x, t * fPosition.y + fAnchorPoint.y) * SkMatrix::RotateDeg(t * fRotation) * SkMatrix::Scale(std::pow(fScale.x, t), std::pow(fScale.y, t)) * SkMatrix::Translate(-fAnchorPoint.x, -fAnchorPoint.y); } SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override { fChildrenBounds = SkRect::MakeEmpty(); for (const auto& child : this->children()) { fChildrenBounds.join(child->revalidate(ic, ctm)); } auto bounds = SkRect::MakeEmpty(); for (size_t i = 0; i < fCount; ++i) { bounds.join(this->instanceTransform(i).mapRect(fChildrenBounds)); } return bounds; } void onRender(SkCanvas* canvas, const RenderContext* ctx) const override { // To cover the full opacity range, the denominator below should be (fCount - 1). // Interstingly, that's not what AE does. Off-by-one bug? const auto dOpacity = fCount > 1 ? (fEndOpacity - fStartOpacity) / fCount : 0.0f; for (size_t i = 0; i < fCount; ++i) { const auto render_index = fMode == CompositeMode::kAbove ? i : fCount - i - 1; const auto opacity = fStartOpacity + dOpacity * render_index; if (opacity <= 0) { continue; } SkAutoCanvasRestore acr(canvas, true); canvas->concat(this->instanceTransform(render_index)); const auto& children = this->children(); const auto local_ctx = ScopedRenderContext(canvas, ctx) .modulateOpacity(opacity) .setIsolation(fChildrenBounds, canvas->getTotalMatrix(), children.size() > 1); for (const auto& child : children) { child->render(canvas, local_ctx); } } } const CompositeMode fMode; SkRect fChildrenBounds = SkRect::MakeEmpty(); // cached size_t fCount = 0; float fOffset = 0, fRotation = 0, fStartOpacity = 1, fEndOpacity = 1; SkV2 fAnchorPoint = {0,0}, fPosition = {0,0}, fScale = {1,1}; using INHERITED = sksg::CustomRenderNode; }; class RepeaterAdapter final : public DiscardableAdapterBase { public: RepeaterAdapter(const skjson::ObjectValue& jrepeater, const skjson::ObjectValue& jtransform, const AnimationBuilder& abuilder, std::vector>&& draws) : INHERITED(sk_make_sp(std::move(draws), (ParseDefault(jrepeater["m"], 1) == 1) ? RepeaterRenderNode::CompositeMode::kBelow : RepeaterRenderNode::CompositeMode::kAbove)) { 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 = 1024; this->node()->setCount(static_cast(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f)); this->node()->setOffset(fOffset); this->node()->setAnchorPoint(fAnchorPoint); this->node()->setPosition(fPosition); this->node()->setScale(fScale * 0.01f); this->node()->setRotation(fRotation); this->node()->setStartOpacity(SkTPin(fStartOpacity * 0.01f, 0.0f, 1.0f)); this->node()->setEndOpacity (SkTPin(fEndOpacity * 0.01f, 0.0f, 1.0f)); } // 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; using INHERITED = DiscardableAdapterBase; }; } // namespace std::vector> ShapeBuilder::AttachRepeaterDrawEffect( const skjson::ObjectValue& jrepeater, const AnimationBuilder* abuilder, std::vector>&& draws) { std::vector> repeater_draws; if (const skjson::ObjectValue* jtransform = jrepeater["tr"]) { // input draws are in top->bottom order - reverse for paint order std::reverse(draws.begin(), draws.end()); repeater_draws.reserve(1); repeater_draws.push_back( abuilder->attachDiscardableAdapter(jrepeater, *jtransform, *abuilder, std::move(draws))); } else { repeater_draws = std::move(draws); } return repeater_draws; } } // namespace internal } // namespace skottie