blob: d36cf14ebd9f00ea00e9b3808213db170275da0b [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/effects/Effects.h"
#include "include/core/SkCanvas.h"
#include "include/effects/SkGradientShader.h"
#include "modules/skottie/src/Adapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGRenderNode.h"
#include "src/utils/SkJSON.h"
#include <cmath>
namespace skottie {
namespace internal {
namespace {
class RWipeRenderNode final : public sksg::CustomRenderNode {
public:
explicit RWipeRenderNode(sk_sp<sksg::RenderNode> layer)
: INHERITED({std::move(layer)}) {}
SG_ATTRIBUTE(Completion, float , fCompletion)
SG_ATTRIBUTE(StartAngle, float , fStartAngle)
SG_ATTRIBUTE(WipeCenter, SkPoint, fWipeCenter)
SG_ATTRIBUTE(Wipe , float , fWipe )
SG_ATTRIBUTE(Feather , float , fFeather )
protected:
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
SkASSERT(this->children().size() == 1ul);
const auto content_bounds = this->children()[0]->revalidate(ic, ctm);
if (fCompletion >= 100) {
return SkRect::MakeEmpty();
}
if (fCompletion <= 0) {
fMaskSigma = 0;
fMaskShader = nullptr;
} else {
fMaskSigma = std::max(fFeather, 0.0f) * kBlurSizeToSigma;
const auto t = fCompletion * 0.01f;
// Note: this could be simplified as a one-hard-stop gradient + local matrix
// (to apply rotation). Alas, local matrices are no longer supported in SkSG.
SkColor c0 = 0x00000000,
c1 = 0xffffffff;
auto sanitize_angle = [](float a) {
a = std::fmod(a, 360);
if (a < 0) {
a += 360;
}
return a;
};
auto a0 = sanitize_angle(fStartAngle - 90 + t * this->wipeAlignment()),
a1 = sanitize_angle(a0 + t * 360);
if (a0 > a1) {
std::swap(a0, a1);
std::swap(c0, c1);
}
const SkColor grad_colors[] = { c1, c0, c0, c1 };
const SkScalar grad_pos[] = { 0, 0, 1, 1 };
fMaskShader = SkGradientShader::MakeSweep(fWipeCenter.x(), fWipeCenter.y(),
grad_colors, grad_pos,
SK_ARRAY_COUNT(grad_colors),
SkTileMode::kClamp,
a0, a1, 0, nullptr);
// Edge feather requires a real blur.
if (fMaskSigma > 0) {
// TODO: this feature is disabled ATM.
}
}
return content_bounds;
}
void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
if (fCompletion >= 100) {
// Fully masked out.
return;
}
const auto local_ctx = ScopedRenderContext(canvas, ctx)
.modulateMaskShader(fMaskShader, canvas->getTotalMatrix());
this->children()[0]->render(canvas, local_ctx);
}
private:
float wipeAlignment() const {
switch (SkScalarRoundToInt(fWipe)) {
case 1: return 0.0f; // Clockwise
case 2: return -360.0f; // Counterclockwise
case 3: return -180.0f; // Both/center
default: break;
}
return 0.0f;
}
SkPoint fWipeCenter = { 0, 0 };
float fCompletion = 0,
fStartAngle = 0,
fWipe = 0,
fFeather = 0;
// Cached during revalidation.
sk_sp<SkShader> fMaskShader;
float fMaskSigma; // edge feather/blur
using INHERITED = sksg::CustomRenderNode;
};
class RadialWipeAdapter final : public DiscardableAdapterBase<RadialWipeAdapter, RWipeRenderNode> {
public:
RadialWipeAdapter(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer,
const AnimationBuilder& abuilder)
: INHERITED(sk_make_sp<RWipeRenderNode>(std::move(layer))) {
enum : size_t {
kCompletion_Index = 0,
kStartAngle_Index = 1,
kWipeCenter_Index = 2,
kWipe_Index = 3,
kFeather_Index = 4,
};
EffectBinder(jprops, abuilder, this)
.bind(kCompletion_Index, fCompletion)
.bind(kStartAngle_Index, fStartAngle)
.bind(kWipeCenter_Index, fWipeCenter)
.bind( kWipe_Index, fWipe )
.bind( kFeather_Index, fFeather );
}
private:
void onSync() override {
const auto& wiper = this->node();
wiper->setCompletion(fCompletion);
wiper->setStartAngle(fStartAngle);
wiper->setWipeCenter({fWipeCenter.x, fWipeCenter.y});
wiper->setWipe(fWipe);
wiper->setFeather(fFeather);
}
Vec2Value fWipeCenter = {0,0};
ScalarValue fCompletion = 0,
fStartAngle = 0,
fWipe = 0,
fFeather = 0;
using INHERITED = DiscardableAdapterBase<RadialWipeAdapter, RWipeRenderNode>;
};
} // namespace
sk_sp<sksg::RenderNode> EffectBuilder::attachRadialWipeEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
return fBuilder->attachDiscardableAdapter<RadialWipeAdapter>(jprops,
std::move(layer),
*fBuilder);
}
} // namespace internal
} // namespace skottie