xref: /aosp_15_r20/external/skia/modules/skottie/src/effects/VenetianBlindsEffect.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2019 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkGradientShader.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottieValue.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/effects/Effects.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGRenderNode.h"
22*c8dee2aaSAndroid Build Coastguard Worker 
23*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
24*c8dee2aaSAndroid Build Coastguard Worker #include <array>
25*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
26*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
27*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker namespace skjson {
30*c8dee2aaSAndroid Build Coastguard Worker class ArrayValue;
31*c8dee2aaSAndroid Build Coastguard Worker }
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker namespace skottie {
34*c8dee2aaSAndroid Build Coastguard Worker namespace internal {
35*c8dee2aaSAndroid Build Coastguard Worker 
36*c8dee2aaSAndroid Build Coastguard Worker namespace  {
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker class VenetianBlindsAdapter final : public MaskShaderEffectBase {
39*c8dee2aaSAndroid Build Coastguard Worker public:
Make(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const SkSize & layer_size,const AnimationBuilder * abuilder)40*c8dee2aaSAndroid Build Coastguard Worker     static sk_sp<VenetianBlindsAdapter> Make(const skjson::ArrayValue& jprops,
41*c8dee2aaSAndroid Build Coastguard Worker                                              sk_sp<sksg::RenderNode> layer,
42*c8dee2aaSAndroid Build Coastguard Worker                                              const SkSize& layer_size,
43*c8dee2aaSAndroid Build Coastguard Worker                                              const AnimationBuilder* abuilder) {
44*c8dee2aaSAndroid Build Coastguard Worker         return sk_sp<VenetianBlindsAdapter>(
45*c8dee2aaSAndroid Build Coastguard Worker                     new VenetianBlindsAdapter(jprops, std::move(layer), layer_size, abuilder));
46*c8dee2aaSAndroid Build Coastguard Worker     }
47*c8dee2aaSAndroid Build Coastguard Worker 
48*c8dee2aaSAndroid Build Coastguard Worker private:
VenetianBlindsAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const SkSize & ls,const AnimationBuilder * abuilder)49*c8dee2aaSAndroid Build Coastguard Worker     VenetianBlindsAdapter(const skjson::ArrayValue& jprops,
50*c8dee2aaSAndroid Build Coastguard Worker                           sk_sp<sksg::RenderNode> layer, const SkSize& ls,
51*c8dee2aaSAndroid Build Coastguard Worker                           const AnimationBuilder* abuilder)
52*c8dee2aaSAndroid Build Coastguard Worker         : INHERITED(std::move(layer), ls) {
53*c8dee2aaSAndroid Build Coastguard Worker         enum : size_t {
54*c8dee2aaSAndroid Build Coastguard Worker             kCompletion_Index = 0,
55*c8dee2aaSAndroid Build Coastguard Worker              kDirection_Index = 1,
56*c8dee2aaSAndroid Build Coastguard Worker                  kWidth_Index = 2,
57*c8dee2aaSAndroid Build Coastguard Worker                kFeather_Index = 3,
58*c8dee2aaSAndroid Build Coastguard Worker         };
59*c8dee2aaSAndroid Build Coastguard Worker 
60*c8dee2aaSAndroid Build Coastguard Worker         EffectBinder(jprops, *abuilder, this)
61*c8dee2aaSAndroid Build Coastguard Worker                 .bind(kCompletion_Index, fCompletion)
62*c8dee2aaSAndroid Build Coastguard Worker                 .bind( kDirection_Index, fDirection )
63*c8dee2aaSAndroid Build Coastguard Worker                 .bind(     kWidth_Index, fWidth     )
64*c8dee2aaSAndroid Build Coastguard Worker                 .bind(   kFeather_Index, fFeather   );
65*c8dee2aaSAndroid Build Coastguard Worker     }
66*c8dee2aaSAndroid Build Coastguard Worker 
onMakeMask() const67*c8dee2aaSAndroid Build Coastguard Worker     MaskInfo onMakeMask() const override {
68*c8dee2aaSAndroid Build Coastguard Worker         if (fCompletion >= 100) {
69*c8dee2aaSAndroid Build Coastguard Worker             // The layer is fully disabled.
70*c8dee2aaSAndroid Build Coastguard Worker             // TODO: fix layer controller visibility clash and pass a null shader instead.
71*c8dee2aaSAndroid Build Coastguard Worker             return { SkShaders::Color(SK_ColorTRANSPARENT), false };
72*c8dee2aaSAndroid Build Coastguard Worker         }
73*c8dee2aaSAndroid Build Coastguard Worker 
74*c8dee2aaSAndroid Build Coastguard Worker         if (fCompletion <= 0) {
75*c8dee2aaSAndroid Build Coastguard Worker             // The layer is fully visible (no mask).
76*c8dee2aaSAndroid Build Coastguard Worker             return { nullptr, true };
77*c8dee2aaSAndroid Build Coastguard Worker         }
78*c8dee2aaSAndroid Build Coastguard Worker 
79*c8dee2aaSAndroid Build Coastguard Worker         static constexpr float kFeatherSigmaFactor = 3.0f,
80*c8dee2aaSAndroid Build Coastguard Worker                                        kMinFeather = 0.5f; // for soft gradient edges
81*c8dee2aaSAndroid Build Coastguard Worker 
82*c8dee2aaSAndroid Build Coastguard Worker         const auto t = fCompletion * 0.01f,
83*c8dee2aaSAndroid Build Coastguard Worker                 size = std::max(1.0f, fWidth),
84*c8dee2aaSAndroid Build Coastguard Worker                angle = SkDegreesToRadians(-fDirection),
85*c8dee2aaSAndroid Build Coastguard Worker              feather = std::max(fFeather * kFeatherSigmaFactor, kMinFeather),
86*c8dee2aaSAndroid Build Coastguard Worker                   df = feather / size, // feather distance in normalized stop space
87*c8dee2aaSAndroid Build Coastguard Worker                  df0 = 0.5f * std::min(df,     t),
88*c8dee2aaSAndroid Build Coastguard Worker                  df1 = 0.5f * std::min(df, 1 - t);
89*c8dee2aaSAndroid Build Coastguard Worker 
90*c8dee2aaSAndroid Build Coastguard Worker         // In its simplest form, the Venetian Blinds effect is a single-step gradient
91*c8dee2aaSAndroid Build Coastguard Worker         // repeating along the direction vector.
92*c8dee2aaSAndroid Build Coastguard Worker         //
93*c8dee2aaSAndroid Build Coastguard Worker         // To avoid an expensive blur pass, we emulate the feather property by softening
94*c8dee2aaSAndroid Build Coastguard Worker         // the gradient edges:
95*c8dee2aaSAndroid Build Coastguard Worker         //
96*c8dee2aaSAndroid Build Coastguard Worker         //  1.0 [                                 |       -------       ]
97*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |      /       \      ]
98*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |     /         \     ]
99*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |    /           \    ]
100*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |   /             \   ]
101*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |  /               \  ]
102*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 | /                 \ ]
103*c8dee2aaSAndroid Build Coastguard Worker         //      [                                 |/                   \]
104*c8dee2aaSAndroid Build Coastguard Worker         //  0.5 [                                 |                     ]
105*c8dee2aaSAndroid Build Coastguard Worker         //      [\                               /|                     ]
106*c8dee2aaSAndroid Build Coastguard Worker         //      [ \                             / |                     ]
107*c8dee2aaSAndroid Build Coastguard Worker         //      [  \                           /  |                     ]
108*c8dee2aaSAndroid Build Coastguard Worker         //      [   \                         /   |                     ]
109*c8dee2aaSAndroid Build Coastguard Worker         //      [    \                       /    |                     ]
110*c8dee2aaSAndroid Build Coastguard Worker         //      [     \                     /     |                     ]
111*c8dee2aaSAndroid Build Coastguard Worker         //      [      \                   /      |                     ]
112*c8dee2aaSAndroid Build Coastguard Worker         //  0.0 [       -------------------       |                     ]
113*c8dee2aaSAndroid Build Coastguard Worker         //
114*c8dee2aaSAndroid Build Coastguard Worker         //      ^       ^                 ^       ^       ^     ^       ^
115*c8dee2aaSAndroid Build Coastguard Worker         //      0      fp0               fp1      T      fp2   fp3      1
116*c8dee2aaSAndroid Build Coastguard Worker         //
117*c8dee2aaSAndroid Build Coastguard Worker         //      |       |                 |       |       |     |       |
118*c8dee2aaSAndroid Build Coastguard Worker         //      |< df0 >|                 |< df0 >|< df1 >|     |< df1 >|
119*c8dee2aaSAndroid Build Coastguard Worker         //
120*c8dee2aaSAndroid Build Coastguard Worker         //  ... df     >|                 |<      df     >|     |<      df ...
121*c8dee2aaSAndroid Build Coastguard Worker         //
122*c8dee2aaSAndroid Build Coastguard Worker         // Note 1: fp0-fp1 and/or fp2-fp3 can collapse when df is large enough.
123*c8dee2aaSAndroid Build Coastguard Worker         //
124*c8dee2aaSAndroid Build Coastguard Worker         // Note 2: G(fp0) == G(fp1) and G(fp2) == G(fp3), whether collapsed or not.
125*c8dee2aaSAndroid Build Coastguard Worker         //
126*c8dee2aaSAndroid Build Coastguard Worker         // Note 3: to minimize the number of gradient stops, we can shift the gradient by -df0
127*c8dee2aaSAndroid Build Coastguard Worker         //         (such that fp0 aligns with 0/pts[0]).
128*c8dee2aaSAndroid Build Coastguard Worker 
129*c8dee2aaSAndroid Build Coastguard Worker         // Gradient value at fp0/fp1, fp2/fp3.
130*c8dee2aaSAndroid Build Coastguard Worker         // Note: g01 > 0 iff fp0-fp1 is collapsed and g23 < 1 iff fp2-fp3 is collapsed
131*c8dee2aaSAndroid Build Coastguard Worker         const auto g01 = std::max(0.0f, 0.5f * (1 + sk_ieee_float_divide(0 - t, df))),
132*c8dee2aaSAndroid Build Coastguard Worker                    g23 = std::min(1.0f, 0.5f * (1 + sk_ieee_float_divide(1 - t, df)));
133*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(0 <= g01 && g01 <= g23 && g23 <= 1);
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker         const SkColor c01 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g01 * 0xff)),
136*c8dee2aaSAndroid Build Coastguard Worker                       c23 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g23 * 0xff)),
137*c8dee2aaSAndroid Build Coastguard Worker                  colors[] = { c01, c23, c23, c01 };
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker         const SkScalar pos[] = {
140*c8dee2aaSAndroid Build Coastguard Worker          // 0,              // fp0
141*c8dee2aaSAndroid Build Coastguard Worker             t - df0 - df0,  // fp1
142*c8dee2aaSAndroid Build Coastguard Worker             t + df1 - df0,  // fp2
143*c8dee2aaSAndroid Build Coastguard Worker             1 - df1 - df0,  // fp3
144*c8dee2aaSAndroid Build Coastguard Worker             1,
145*c8dee2aaSAndroid Build Coastguard Worker         };
146*c8dee2aaSAndroid Build Coastguard Worker         static_assert(std::size(colors) == std::size(pos), "");
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker         const auto center = SkPoint::Make(0.5f * this->layerSize().width(),
149*c8dee2aaSAndroid Build Coastguard Worker                                           0.5f * this->layerSize().height()),
150*c8dee2aaSAndroid Build Coastguard Worker                  grad_vec = SkVector::Make( size * std::cos(angle),
151*c8dee2aaSAndroid Build Coastguard Worker                                            -size * std::sin(angle));
152*c8dee2aaSAndroid Build Coastguard Worker 
153*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint pts[] = {
154*c8dee2aaSAndroid Build Coastguard Worker             center + grad_vec * (df0 + 0),
155*c8dee2aaSAndroid Build Coastguard Worker             center + grad_vec * (df0 + 1),
156*c8dee2aaSAndroid Build Coastguard Worker         };
157*c8dee2aaSAndroid Build Coastguard Worker 
158*c8dee2aaSAndroid Build Coastguard Worker         return {
159*c8dee2aaSAndroid Build Coastguard Worker             SkGradientShader::MakeLinear(pts, colors, pos, std::size(colors),
160*c8dee2aaSAndroid Build Coastguard Worker                                          SkTileMode::kRepeat),
161*c8dee2aaSAndroid Build Coastguard Worker             true
162*c8dee2aaSAndroid Build Coastguard Worker         };
163*c8dee2aaSAndroid Build Coastguard Worker     }
164*c8dee2aaSAndroid Build Coastguard Worker 
165*c8dee2aaSAndroid Build Coastguard Worker     ScalarValue fCompletion = 0,
166*c8dee2aaSAndroid Build Coastguard Worker                 fDirection  = 0,
167*c8dee2aaSAndroid Build Coastguard Worker                 fWidth      = 0,
168*c8dee2aaSAndroid Build Coastguard Worker                 fFeather    = 0;
169*c8dee2aaSAndroid Build Coastguard Worker 
170*c8dee2aaSAndroid Build Coastguard Worker     using INHERITED = MaskShaderEffectBase;
171*c8dee2aaSAndroid Build Coastguard Worker };
172*c8dee2aaSAndroid Build Coastguard Worker 
173*c8dee2aaSAndroid Build Coastguard Worker } // namespace
174*c8dee2aaSAndroid Build Coastguard Worker 
attachVenetianBlindsEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const175*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode> EffectBuilder::attachVenetianBlindsEffect(
176*c8dee2aaSAndroid Build Coastguard Worker         const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
177*c8dee2aaSAndroid Build Coastguard Worker     return fBuilder->attachDiscardableAdapter<VenetianBlindsAdapter>(jprops,
178*c8dee2aaSAndroid Build Coastguard Worker                                                                      std::move(layer),
179*c8dee2aaSAndroid Build Coastguard Worker                                                                      fLayerSize,
180*c8dee2aaSAndroid Build Coastguard Worker                                                                      fBuilder);
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker 
183*c8dee2aaSAndroid Build Coastguard Worker } // namespace internal
184*c8dee2aaSAndroid Build Coastguard Worker } // namespace skottie
185