Coverage Report

Created: 2021-08-22 09:07

/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