xref: /aosp_15_r20/external/skia/modules/skottie/src/layers/shapelayer/PuckerBloat.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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