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