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