/src/skia/modules/skottie/src/layers/shapelayer/PuckerBloat.cpp
Line | Count | Source (jump to first uncovered line) |
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 | 104k | static SkPoint lerp(const SkPoint& p0, const SkPoint& p1, SkScalar t) { |
24 | 104k | return p0 + (p1 - p0) * t; |
25 | 104k | } |
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 | 19.2k | 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 | 18.2k | SkPath onRevalidateEffect(const sk_sp<GeometryNode>& geo) override { |
43 | 18.2k | struct CubicInfo { |
44 | 18.2k | SkPoint ctrl0, ctrl1, pt; // corresponding to SkPath::cubicTo() params, respectively. |
45 | 18.2k | }; |
46 | | |
47 | 18.2k | const auto input = geo->asPath(); |
48 | 18.2k | if (SkScalarNearlyZero(fAmount)) { |
49 | 14.9k | return input; |
50 | 14.9k | } |
51 | | |
52 | 3.33k | const auto input_bounds = input.computeTightBounds(); |
53 | 3.33k | const SkPoint center{input_bounds.centerX(), input_bounds.centerY()}; |
54 | | |
55 | 3.33k | SkPath path; |
56 | | |
57 | 3.33k | SkPoint contour_start = {0, 0}; |
58 | 3.33k | std::vector<CubicInfo> cubics; |
59 | | |
60 | 6.46k | auto commit_contour = [&]() { |
61 | 6.46k | path.moveTo(lerp(contour_start, center, fAmount)); |
62 | 26.3k | for (const auto& c : cubics) { |
63 | 26.3k | path.cubicTo(lerp(c.ctrl0, center, -fAmount), |
64 | 26.3k | lerp(c.ctrl1, center, -fAmount), |
65 | 26.3k | lerp(c.pt , center, fAmount)); |
66 | 26.3k | } |
67 | 6.46k | path.close(); |
68 | | |
69 | 6.46k | cubics.clear(); |
70 | 6.46k | }; |
71 | | |
72 | | // Normalize all verbs to cubic representation. |
73 | 3.33k | SkPoint pts[4]; |
74 | 3.33k | SkPath::Verb verb; |
75 | 3.33k | SkPath::Iter iter(input, true); |
76 | 36.1k | while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { |
77 | 32.7k | switch (verb) { |
78 | 3.23k | case SkPath::kMove_Verb: |
79 | 3.23k | commit_contour(); |
80 | 3.23k | contour_start = pts[0]; |
81 | 3.23k | break; |
82 | 7.71k | case SkPath::kLine_Verb: { |
83 | | // Empirically, straight lines are treated as cubics with control points |
84 | | // located length/100 away from extremities. |
85 | 7.71k | static constexpr float kCtrlPosFraction = 1.f / 100; |
86 | 7.71k | const auto line_start = pts[0], |
87 | 7.71k | line_end = pts[1]; |
88 | 7.71k | cubics.push_back({ |
89 | 7.71k | lerp(line_start, line_end, kCtrlPosFraction), |
90 | 7.71k | lerp(line_start, line_end, 1 - kCtrlPosFraction), |
91 | 7.71k | line_end |
92 | 7.71k | }); |
93 | 7.71k | } break; |
94 | 11.4k | case SkPath::kQuad_Verb: |
95 | 11.4k | SkConvertQuadToCubic(pts, pts); |
96 | 11.4k | cubics.push_back({pts[1], pts[2], pts[3]}); |
97 | 11.4k | break; |
98 | 1.77k | case SkPath::kConic_Verb: { |
99 | | // We should only ever encounter conics from circles/ellipses. |
100 | 1.77k | SkASSERT(SkScalarNearlyEqual(iter.conicWeight(), SK_ScalarRoot2Over2)); |
101 | | |
102 | | // http://spencermortensen.com/articles/bezier-circle/ |
103 | 1.77k | static constexpr float kCubicCircleCoeff = 1 - 0.551915024494f; |
104 | | |
105 | 315 | const auto conic_start = cubics.empty() ? contour_start |
106 | 1.46k | : cubics.back().pt, |
107 | 1.77k | conic_end = pts[2]; |
108 | | |
109 | 1.77k | cubics.push_back({ |
110 | 1.77k | lerp(pts[1], conic_start, kCubicCircleCoeff), |
111 | 1.77k | lerp(pts[1], conic_end , kCubicCircleCoeff), |
112 | 1.77k | conic_end |
113 | 1.77k | }); |
114 | 1.77k | } break; |
115 | 5.38k | case SkPath::kCubic_Verb: |
116 | 5.38k | cubics.push_back({pts[1], pts[2], pts[3]}); |
117 | 5.38k | break; |
118 | 3.23k | case SkPath::kClose_Verb: |
119 | 3.23k | commit_contour(); |
120 | 3.23k | break; |
121 | 0 | default: |
122 | 0 | break; |
123 | 32.7k | } |
124 | 32.7k | } |
125 | | |
126 | 3.33k | return path; |
127 | 3.33k | } |
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 | 19.2k | : INHERITED(sk_make_sp<PuckerBloatEffect>(std::move(child))) { |
141 | 19.2k | this->bind(abuilder, joffset["a" ], fAmount); |
142 | 19.2k | } |
143 | | |
144 | | private: |
145 | 19.2k | void onSync() override { |
146 | | // AE amount is percentage-based. |
147 | 19.2k | this->node()->setAmount(fAmount / 100); |
148 | 19.2k | } |
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 | 1.69k | std::vector<sk_sp<sksg::GeometryNode>>&& geos) { |
160 | 1.69k | std::vector<sk_sp<sksg::GeometryNode>> bloated; |
161 | 1.69k | bloated.reserve(geos.size()); |
162 | | |
163 | 19.2k | for (auto& g : geos) { |
164 | 19.2k | bloated.push_back(abuilder->attachDiscardableAdapter<PuckerBloatAdapter> |
165 | 19.2k | (jround, *abuilder, std::move(g))); |
166 | 19.2k | } |
167 | | |
168 | 1.69k | return bloated; |
169 | 1.69k | } |
170 | | |
171 | | } // namespace skottie::internal |