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/private/SkTPin.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/SkSGGradient.h"
15#include "modules/sksg/include/SkSGPaint.h"
16
17namespace skottie {
18namespace internal {
19
20namespace  {
21
22class GradientAdapter final : public AnimatablePropertyContainer {
23public:
24    static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
25                                       const AnimationBuilder& abuilder) {
26        const skjson::ObjectValue* jstops = jgrad["g"];
27        if (!jstops)
28            return nullptr;
29
30        const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
31        if (stopCount < 0)
32            return nullptr;
33
34        const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
35                                                                  : Type::kRadial;
36        auto gradient_node = (type == Type::kLinear)
37                ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
38                : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
39
40        return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
41                                                          type,
42                                                          SkToSizeT(stopCount),
43                                                          jgrad, *jstops, abuilder));
44    }
45
46    const sk_sp<sksg::Gradient>& node() const { return fGradient; }
47
48private:
49    enum class Type { kLinear, kRadial };
50
51    GradientAdapter(sk_sp<sksg::Gradient> gradient,
52                    Type type,
53                    size_t stop_count,
54                    const skjson::ObjectValue& jgrad,
55                    const skjson::ObjectValue& jstops,
56                    const AnimationBuilder& abuilder)
57        : fGradient(std::move(gradient))
58        , fType(type)
59        , fStopCount(stop_count) {
60        this->bind(abuilder,  jgrad["s"], fStartPoint);
61        this->bind(abuilder,  jgrad["e"], fEndPoint  );
62        this->bind(abuilder, jstops["k"], fStops     );
63    }
64
65    void onSync() override {
66        const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y},
67                   e_point = SkPoint{  fEndPoint.x,   fEndPoint.y};
68
69        switch (fType) {
70        case Type::kLinear: {
71            auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
72            grad->setStartPoint(s_point);
73            grad->setEndPoint(e_point);
74
75            break;
76        }
77        case Type::kRadial: {
78            auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
79            grad->setStartCenter(s_point);
80            grad->setEndCenter(s_point);
81            grad->setStartRadius(0);
82            grad->setEndRadius(SkPoint::Distance(s_point, e_point));
83
84            break;
85        }
86        }
87
88        // Gradient color stops are specified as a consolidated float vector holding:
89        //
90        //   a) an (optional) array of color/RGB stop records (t, r, g, b)
91        //
92        // followed by
93        //
94        //   b) an (optional) array of opacity/alpha stop records (t, a)
95        //
96        struct   ColorRec { float t, r, g, b; };
97        struct OpacityRec { float t, a;       };
98
99        // The number of color records is explicit (fColorStopCount),
100        // while the number of opacity stops is implicit (based on the size of fStops).
101        //
102        // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
103        const auto c_count = fStopCount,
104                   c_size  = c_count * 4,
105                   o_count = (fStops.size() - c_size) / 2;
106        if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
107            // apply() may get called before the stops are set, so only log when we have some stops.
108            if (!fStops.empty()) {
109                SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
110            }
111            return;
112        }
113
114        const auto* c_rec = c_count > 0
115                ? reinterpret_cast<const ColorRec*>(fStops.data())
116                : nullptr;
117        const auto* o_rec = o_count > 0
118                ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
119                : nullptr;
120        const auto* c_end = c_rec + c_count;
121        const auto* o_end = o_rec + o_count;
122
123        sksg::Gradient::ColorStop current_stop = {
124            0.0f, {
125                c_rec ? c_rec->r : 0,
126                c_rec ? c_rec->g : 0,
127                c_rec ? c_rec->b : 0,
128                o_rec ? o_rec->a : 1,
129        }};
130
131        std::vector<sksg::Gradient::ColorStop> stops;
132        stops.reserve(c_count);
133
134        // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
135        while (c_rec || o_rec) {
136            // After exhausting one of color recs / opacity recs, continue propagating the last
137            // computed values (as if they were specified at the current position).
138            const auto& cs = c_rec
139                    ? *c_rec
140                    : ColorRec{ o_rec->t,
141                                current_stop.fColor.fR,
142                                current_stop.fColor.fG,
143                                current_stop.fColor.fB };
144            const auto& os = o_rec
145                    ? *o_rec
146                    : OpacityRec{ c_rec->t, current_stop.fColor.fA };
147
148            // Compute component lerp coefficients based on the relative position of the stops
149            // being considered. The idea is to select the smaller-pos stop, use its own properties
150            // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
151            // larger-pos stop against the previously computed gradient stop values.
152            const auto     c_pos = std::max(cs.t, current_stop.fPosition),
153                           o_pos = std::max(os.t, current_stop.fPosition),
154                       c_pos_rel = c_pos - current_stop.fPosition,
155                       o_pos_rel = o_pos - current_stop.fPosition,
156                             t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
157                             t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
158
159            auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
160
161            current_stop = {
162                    std::min(c_pos, o_pos),
163                    {
164                        lerp(current_stop.fColor.fR, cs.r, t_c ),
165                        lerp(current_stop.fColor.fG, cs.g, t_c ),
166                        lerp(current_stop.fColor.fB, cs.b, t_c ),
167                        lerp(current_stop.fColor.fA, os.a, t_o)
168                    }
169            };
170            stops.push_back(current_stop);
171
172            // Consume one of, or both (for coincident positions) color/opacity stops.
173            if (c_pos <= o_pos) {
174                c_rec = next_rec<ColorRec>(c_rec, c_end);
175            }
176            if (o_pos <= c_pos) {
177                o_rec = next_rec<OpacityRec>(o_rec, o_end);
178            }
179        }
180
181        stops.shrink_to_fit();
182        fGradient->setColorStops(std::move(stops));
183    }
184
185private:
186    template <typename T>
187    const T* next_rec(const T* rec, const T* end_rec) const {
188        if (!rec) return nullptr;
189
190        SkASSERT(rec < end_rec);
191        rec++;
192
193        return rec < end_rec ? rec : nullptr;
194    }
195
196    const sk_sp<sksg::Gradient> fGradient;
197    const Type                  fType;
198    const size_t                fStopCount;
199
200    VectorValue  fStops;
201    Vec2Value    fStartPoint = {0,0},
202                 fEndPoint   = {0,0};
203};
204
205} // namespace
206
207sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad,
208                                                        const AnimationBuilder* abuilder) {
209    auto adapter = GradientAdapter::Make(jgrad, *abuilder);
210
211    return adapter
212            ? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
213            : nullptr;
214}
215
216sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad,
217                                                          const AnimationBuilder* abuilder) {
218    auto adapter = GradientAdapter::Make(jgrad, *abuilder);
219
220    return adapter
221            ? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
222            : nullptr;
223}
224
225} // namespace internal
226} // namespace skottie
227