• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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 "include/core/SkM44.h"
9 #include "modules/skottie/src/Adapter.h"
10 #include "modules/skottie/src/SkottieJson.h"
11 #include "modules/skottie/src/SkottiePriv.h"
12 #include "modules/skottie/src/SkottieValue.h"
13 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
14 #include "modules/sksg/include/SkSGGeometryEffect.h"
15 #include "src/core/SkGeometry.h"
16 
17 #include <vector>
18 
19 namespace skottie::internal {
20 
21 namespace  {
22 
lerp(const SkPoint & p0,const SkPoint & p1,SkScalar t)23 static SkPoint lerp(const SkPoint& p0, const SkPoint& p1, SkScalar t) {
24     return p0 + (p1 - p0) * t;
25 }
26 
27 // Operates on the cubic representation of a shape.  Pulls vertices towards the shape center,
28 // and cubic control points away from the center.  The general shape center is the vertex average.
29 class PuckerBloatEffect final : public sksg::GeometryEffect {
30 public:
PuckerBloatEffect(sk_sp<sksg::GeometryNode> geo)31     explicit PuckerBloatEffect(sk_sp<sksg::GeometryNode> geo) : INHERITED({std::move(geo)}) {}
32 
33     // Fraction of the transition to center. I.e.
34     //
35     //     0 -> no effect
36     //     1 -> vertices collapsed to center
37     //
38     // Negative values are allowed (inverse direction), as are extranormal values.
39     SG_ATTRIBUTE(Amount, float, fAmount)
40 
41 private:
onRevalidateEffect(const sk_sp<GeometryNode> & geo)42     SkPath onRevalidateEffect(const sk_sp<GeometryNode>& geo) override {
43         struct CubicInfo {
44             SkPoint ctrl0, ctrl1, pt; // corresponding to SkPath::cubicTo() params, respectively.
45         };
46 
47         const auto input = geo->asPath();
48         if (SkScalarNearlyZero(fAmount)) {
49             return input;
50         }
51 
52         const auto input_bounds = input.computeTightBounds();
53         const SkPoint center{input_bounds.centerX(), input_bounds.centerY()};
54 
55         SkPath path;
56 
57         SkPoint contour_start = {0, 0};
58         std::vector<CubicInfo> cubics;
59 
60         auto commit_contour = [&]() {
61             path.moveTo(lerp(contour_start, center, fAmount));
62             for (const auto& c : cubics) {
63                 path.cubicTo(lerp(c.ctrl0, center, -fAmount),
64                              lerp(c.ctrl1, center, -fAmount),
65                              lerp(c.pt   , center,  fAmount));
66             }
67             path.close();
68 
69             cubics.clear();
70         };
71 
72         // Normalize all verbs to cubic representation.
73         SkPoint pts[4];
74         SkPath::Verb verb;
75         SkPath::Iter iter(input, true);
76         while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
77             switch (verb) {
78                 case SkPath::kMove_Verb:
79                     commit_contour();
80                     contour_start = pts[0];
81                     break;
82                 case SkPath::kLine_Verb: {
83                     // Empirically, straight lines are treated as cubics with control points
84                     // located length/100 away from extremities.
85                     static constexpr float kCtrlPosFraction = 1.f / 100;
86                     const auto line_start = pts[0],
87                                line_end   = pts[1];
88                     cubics.push_back({
89                             lerp(line_start, line_end,     kCtrlPosFraction),
90                             lerp(line_start, line_end, 1 - kCtrlPosFraction),
91                             line_end
92                     });
93                 } break;
94                 case SkPath::kQuad_Verb:
95                     SkConvertQuadToCubic(pts, pts);
96                     cubics.push_back({pts[1], pts[2], pts[3]});
97                     break;
98                 case SkPath::kConic_Verb: {
99                     // We should only ever encounter conics from circles/ellipses.
100                     SkASSERT(SkScalarNearlyEqual(iter.conicWeight(), SK_ScalarRoot2Over2));
101 
102                     // http://spencermortensen.com/articles/bezier-circle/
103                     static constexpr float kCubicCircleCoeff = 1 - 0.551915024494f;
104 
105                     const auto conic_start = cubics.empty() ? contour_start
106                                                             : cubics.back().pt,
107                                conic_end   = pts[2];
108 
109                     cubics.push_back({
110                         lerp(pts[1], conic_start, kCubicCircleCoeff),
111                         lerp(pts[1], conic_end  , kCubicCircleCoeff),
112                         conic_end
113                     });
114                 } break;
115                 case SkPath::kCubic_Verb:
116                     cubics.push_back({pts[1], pts[2], pts[3]});
117                     break;
118                 case SkPath::kClose_Verb:
119                     commit_contour();
120                     break;
121                 default:
122                     break;
123             }
124         }
125 
126         return path;
127     }
128 
129     float fAmount = 0;
130 
131     using INHERITED = sksg::GeometryEffect;
132 };
133 
134 class PuckerBloatAdapter final : public DiscardableAdapterBase<PuckerBloatAdapter,
135                                                                PuckerBloatEffect> {
136 public:
PuckerBloatAdapter(const skjson::ObjectValue & joffset,const AnimationBuilder & abuilder,sk_sp<sksg::GeometryNode> child)137     PuckerBloatAdapter(const skjson::ObjectValue& joffset,
138                        const AnimationBuilder& abuilder,
139                        sk_sp<sksg::GeometryNode> child)
140         : INHERITED(sk_make_sp<PuckerBloatEffect>(std::move(child))) {
141         this->bind(abuilder, joffset["a" ], fAmount);
142     }
143 
144 private:
onSync()145     void onSync() override {
146         // AE amount is percentage-based.
147         this->node()->setAmount(fAmount / 100);
148     }
149 
150     ScalarValue fAmount = 0;
151 
152     using INHERITED = DiscardableAdapterBase<PuckerBloatAdapter, PuckerBloatEffect>;
153 };
154 
155 } // namespace
156 
AttachPuckerBloatGeometryEffect(const skjson::ObjectValue & jround,const AnimationBuilder * abuilder,std::vector<sk_sp<sksg::GeometryNode>> && geos)157 std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AttachPuckerBloatGeometryEffect(
158         const skjson::ObjectValue& jround, const AnimationBuilder* abuilder,
159         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
160     std::vector<sk_sp<sksg::GeometryNode>> bloated;
161     bloated.reserve(geos.size());
162 
163     for (auto& g : geos) {
164         bloated.push_back(abuilder->attachDiscardableAdapter<PuckerBloatAdapter>
165                                         (jround, *abuilder, std::move(g)));
166     }
167 
168     return bloated;
169 }
170 
171 } // namespace skottie::internal
172