[skottie] Radial swipe effect
Implement radial wipe with a sweep gradient shader mask filter. The implementation is slightly convoluted because edge feathering requires a real blur, which in turn requires content layer isolation. So there are two distinct operation modes: - no feather -> draw the content directly into the dest buffer, with the mask filter deferred in SG context - feather -> draw the content into a separate layer, then blend (dstOut) the composed blur+shader mask on top Change-Id: I253701aff42db8010ce463762252c262e2c5d92b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222596 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
d3494ede8d
commit
d7b321afa2
@ -36,6 +36,7 @@ skia_skottie_sources = [
|
||||
"$_src/effects/LevelsEffect.cpp",
|
||||
"$_src/effects/LinearWipeEffect.cpp",
|
||||
"$_src/effects/MotionTileEffect.cpp",
|
||||
"$_src/effects/RadialWipeEffect.cpp",
|
||||
"$_src/effects/TintEffect.cpp",
|
||||
"$_src/effects/TransformEffect.cpp",
|
||||
"$_src/effects/TritoneEffect.cpp",
|
||||
|
@ -27,6 +27,7 @@ EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectVal
|
||||
kFill_Effect = 21,
|
||||
kTritone_Effect = 23,
|
||||
kDropShadow_Effect = 25,
|
||||
kRadialWipe_Effect = 26,
|
||||
kGaussianBlur_Effect = 29,
|
||||
};
|
||||
|
||||
@ -41,6 +42,8 @@ EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectVal
|
||||
return &EffectBuilder::attachTritoneEffect;
|
||||
case kDropShadow_Effect:
|
||||
return &EffectBuilder::attachDropShadowEffect;
|
||||
case kRadialWipe_Effect:
|
||||
return &EffectBuilder::attachRadialWipeEffect;
|
||||
case kGaussianBlur_Effect:
|
||||
return &EffectBuilder::attachGaussianBlurEffect;
|
||||
default:
|
||||
|
@ -38,6 +38,8 @@ private:
|
||||
sk_sp<sksg::RenderNode>) const;
|
||||
sk_sp<sksg::RenderNode> attachMotionTileEffect (const skjson::ArrayValue&,
|
||||
sk_sp<sksg::RenderNode>) const;
|
||||
sk_sp<sksg::RenderNode> attachRadialWipeEffect (const skjson::ArrayValue&,
|
||||
sk_sp<sksg::RenderNode>) const;
|
||||
sk_sp<sksg::RenderNode> attachTintEffect (const skjson::ArrayValue&,
|
||||
sk_sp<sksg::RenderNode>) const;
|
||||
sk_sp<sksg::RenderNode> attachTransformEffect (const skjson::ArrayValue&,
|
||||
|
173
modules/skottie/src/effects/RadialWipeEffect.cpp
Normal file
173
modules/skottie/src/effects/RadialWipeEffect.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 "include/effects/SkShaderMaskFilter.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;
|
||||
fMaskFilter = nullptr;
|
||||
} else {
|
||||
static constexpr float kFeatherToSigma = 0.3f; // close enough to AE
|
||||
fMaskSigma = std::max(fFeather, 0.0f) * kFeatherToSigma;
|
||||
|
||||
// The gradient is inverted between non-blurred and blurred (latter requires dstOut).
|
||||
const SkColor c0 = fMaskSigma > 0 ? 0xffffffff : 0x00000000,
|
||||
c1 = 0xffffffff - c0;
|
||||
auto t = fCompletion * 0.01f;
|
||||
|
||||
const SkColor grad_colors[] = { c0, c1 };
|
||||
const SkScalar grad_pos[] = { t, t };
|
||||
|
||||
SkMatrix lm;
|
||||
lm.setRotate(fStartAngle - 90 + t * this->wipeAlignment(),
|
||||
fWipeCenter.x(), fWipeCenter.y());
|
||||
|
||||
fMaskFilter = SkShaderMaskFilter::Make(
|
||||
SkGradientShader::MakeSweep(fWipeCenter.x(), fWipeCenter.y(),
|
||||
grad_colors, grad_pos,
|
||||
SK_ARRAY_COUNT(grad_colors), 0, &lm));
|
||||
|
||||
// Edge feather requires a real blur.
|
||||
if (fMaskSigma > 0) {
|
||||
fMaskFilter = SkMaskFilter::MakeCompose(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
|
||||
fMaskSigma),
|
||||
std::move(fMaskFilter));
|
||||
}
|
||||
}
|
||||
|
||||
return content_bounds;
|
||||
}
|
||||
|
||||
void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
|
||||
if (fCompletion >= 100) {
|
||||
// Fully masked out.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fMaskSigma) {
|
||||
// No mask filter, or a shader-only mask filter: we can draw the content directly.
|
||||
const auto local_ctx = ScopedRenderContext(canvas, ctx)
|
||||
.modulateMaskFilter(fMaskFilter, canvas->getTotalMatrix());
|
||||
this->children()[0]->render(canvas, local_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Blurred mask filters require a separate layer.
|
||||
SkAutoCanvasRestore acr(canvas, false);
|
||||
canvas->saveLayer(this->bounds(), nullptr);
|
||||
|
||||
this->children()[0]->render(canvas, ctx);
|
||||
|
||||
// Outset the mask to clip-out any edge blur.
|
||||
const auto mask_bounds = this->bounds().makeOutset(fMaskSigma * 3, fMaskSigma * 3);
|
||||
|
||||
SkPaint mask_paint;
|
||||
mask_paint.setBlendMode(SkBlendMode::kDstOut);
|
||||
mask_paint.setMaskFilter(fMaskFilter);
|
||||
canvas->drawRect(mask_bounds, mask_paint);
|
||||
}
|
||||
|
||||
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<SkMaskFilter> fMaskFilter;
|
||||
float fMaskSigma; // edge feather/blur
|
||||
|
||||
using INHERITED = sksg::CustomRenderNode;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
sk_sp<sksg::RenderNode> EffectBuilder::attachRadialWipeEffect(const skjson::ArrayValue& jprops,
|
||||
sk_sp<sksg::RenderNode> layer) const {
|
||||
enum : size_t {
|
||||
kCompletion_Index = 0,
|
||||
kStartAngle_Index = 1,
|
||||
kWipeCenter_Index = 2,
|
||||
kWipe_Index = 3,
|
||||
kFeather_Index = 4,
|
||||
};
|
||||
|
||||
auto wiper = sk_make_sp<RWipeRenderNode>(std::move(layer));
|
||||
|
||||
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index), fScope,
|
||||
[wiper](const ScalarValue& c) {
|
||||
wiper->setCompletion(c);
|
||||
});
|
||||
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kStartAngle_Index), fScope,
|
||||
[wiper](const ScalarValue& sa) {
|
||||
wiper->setStartAngle(sa);
|
||||
});
|
||||
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kWipeCenter_Index), fScope,
|
||||
[wiper](const VectorValue& c) {
|
||||
wiper->setWipeCenter(ValueTraits<VectorValue>::As<SkPoint>(c));
|
||||
});
|
||||
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWipe_Index), fScope,
|
||||
[wiper](const ScalarValue& w) {
|
||||
wiper->setWipe(w);
|
||||
});
|
||||
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index), fScope,
|
||||
[wiper](const ScalarValue& f) {
|
||||
wiper->setFeather(f);
|
||||
});
|
||||
|
||||
return std::move(wiper);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace skottie
|
1
resources/skottie/skottie-radial-wipe-effect.json
Normal file
1
resources/skottie/skottie-radial-wipe-effect.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user