[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:
Florin Malita 2019-06-21 09:12:21 -04:00 committed by Skia Commit-Bot
parent d3494ede8d
commit d7b321afa2
5 changed files with 180 additions and 0 deletions

View File

@ -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",

View File

@ -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:

View File

@ -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&,

View 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

File diff suppressed because one or more lines are too long