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