1/*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "modules/skottie/src/effects/Effects.h"
9
10#include "include/effects/SkGradientShader.h"
11#include "include/effects/SkShaderMaskFilter.h"
12#include "modules/skottie/src/SkottieValue.h"
13#include "modules/sksg/include/SkSGRenderEffect.h"
14#include "src/utils/SkJSON.h"
15
16#include <cmath>
17#include <utility>
18
19namespace skottie {
20namespace internal {
21
22namespace {
23
24class LinearWipeAdapter final : public MaskShaderEffectBase {
25public:
26 static sk_sp<LinearWipeAdapter> Make(const skjson::ArrayValue& jprops,
27 sk_sp<sksg::RenderNode> layer,
28 const SkSize& layer_size,
29 const AnimationBuilder* abuilder) {
30 return sk_sp<LinearWipeAdapter>(new LinearWipeAdapter(jprops,
31 std::move(layer),
32 layer_size,
33 abuilder));
34 }
35
36private:
37 LinearWipeAdapter(const skjson::ArrayValue& jprops,
38 sk_sp<sksg::RenderNode> layer,
39 const SkSize& layer_size,
40 const AnimationBuilder* abuilder)
41 : INHERITED(std::move(layer), layer_size) {
42 enum : size_t {
43 kCompletion_Index = 0,
44 kAngle_Index = 1,
45 kFeather_Index = 2,
46 };
47
48 EffectBinder(jprops, *abuilder, this)
49 .bind(kCompletion_Index, fCompletion)
50 .bind( kAngle_Index, fAngle )
51 .bind( kFeather_Index, fFeather );
52 }
53
54 MaskInfo onMakeMask() const override {
55 if (fCompletion >= 100) {
56 // The layer is fully disabled.
57 // TODO: fix layer controller visibility clash and pass a null shader instead.
58 return { SkShaders::Color(SK_ColorTRANSPARENT), false };
59 }
60
61 if (fCompletion <= 0) {
62 // The layer is fully visible (no mask).
63 return { nullptr, true };
64 }
65
66 const auto t = SkTPin(fCompletion * 0.01f, 0.0f, 1.0f),
67 feather = std::max(fFeather, 0.0f),
68 angle = SkDegreesToRadians(90 - fAngle),
69 cos_ = std::cos(angle),
70 sin_ = std::sin(angle);
71
72 // Select the correct diagonal vector depending on quadrant.
73 const SkVector angle_v = {cos_, sin_},
74 diag_v = {std::copysign(this->layerSize().width() , cos_),
75 std::copysign(this->layerSize().height(), sin_)};
76
77 // The transition length is the projection of the diagonal onto the angle vector.
78 const auto len = SkVector::DotProduct(diag_v, angle_v);
79
80 // Pad the gradient segment to accommodate optional feather ramps at both extremities.
81 const auto grad_len = len + feather * 2;
82 const SkVector grad_v = angle_v * grad_len,
83 adjusted_grad_v = { grad_v.fX, -grad_v.fY }, // Y flipped for drawing space.
84 center_v = {0.5f * this->layerSize().width(),
85 0.5f * this->layerSize().height()};
86
87 // Gradient start/end points:
88 const SkPoint pts[] = {
89 center_v - adjusted_grad_v * 0.5f,
90 center_v + adjusted_grad_v * 0.5f,
91 };
92
93 static constexpr SkColor colors[] = { 0x00000000,
94 0xffffffff };
95
96 // To emulate the feather effect, we distance the color stops to generate
97 // a linear transition/ramp. For t == 0 the ramp should be completely outside/before
98 // the transition domain, and for t == 1 it should be completely outside/after.
99 //
100 // [0 ................... |len|]
101 //
102 // [0 <feather_ramp> [ ] <feather_ramp> |grad_len|]
103 const auto adjusted_t = t * (len + feather) / grad_len;
104 const SkScalar pos[] = { adjusted_t,
105 adjusted_t + feather / grad_len };
106
107 return { SkGradientShader::MakeLinear(pts, colors, pos, 2, SkTileMode::kClamp), true };
108 }
109
110 ScalarValue fCompletion = 0,
111 fAngle = 0,
112 fFeather = 0;
113
114 using INHERITED = MaskShaderEffectBase;
115};
116
117} // namespace
118
119sk_sp<sksg::RenderNode> EffectBuilder::attachLinearWipeEffect(const skjson::ArrayValue& jprops,
120 sk_sp<sksg::RenderNode> layer) const {
121 return fBuilder->attachDiscardableAdapter<LinearWipeAdapter>(jprops,
122 std::move(layer),
123 fLayerSize,
124 fBuilder);
125}
126
127} // namespace internal
128} // namespace skottie
129
130