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 | |
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: |
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: |
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: |
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: |
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 | |
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 | |