xref: /aosp_15_r20/external/skia/modules/skottie/src/layers/shapelayer/Gradient.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/SkColor.h"
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkPoint.h"
11 #include "include/core/SkRefCnt.h"
12 #include "include/core/SkScalar.h"
13 #include "include/private/base/SkAssert.h"
14 #include "include/private/base/SkDebug.h"
15 #include "include/private/base/SkFloatingPoint.h"
16 #include "include/private/base/SkTPin.h"
17 #include "include/private/base/SkTo.h"
18 #include "modules/skottie/src/SkottieJson.h"
19 #include "modules/skottie/src/SkottieValue.h"
20 #include "modules/skottie/src/animator/Animator.h"
21 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
22 #include "modules/sksg/include/SkSGGradient.h"
23 #include "modules/sksg/include/SkSGPaint.h"
24 #include "modules/sksg/include/SkSGRenderEffect.h"
25 #include "src/utils/SkJSON.h"
26 
27 #include <algorithm>
28 #include <cstddef>
29 #include <utility>
30 #include <vector>
31 
32 namespace skottie {
33 namespace internal {
34 class AnimationBuilder;
35 
36 namespace  {
37 
38 class GradientAdapter final : public AnimatablePropertyContainer {
39 public:
Make(const skjson::ObjectValue & jgrad,const AnimationBuilder & abuilder)40     static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
41                                        const AnimationBuilder& abuilder) {
42         const skjson::ObjectValue* jstops = jgrad["g"];
43         if (!jstops)
44             return nullptr;
45 
46         const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
47         if (stopCount < 0)
48             return nullptr;
49 
50         const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
51                                                                   : Type::kRadial;
52         auto gradient_node = (type == Type::kLinear)
53                 ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
54                 : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
55 
56         return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
57                                                           type,
58                                                           SkToSizeT(stopCount),
59                                                           jgrad, *jstops, abuilder));
60     }
61 
node() const62     const sk_sp<sksg::Gradient>& node() const { return fGradient; }
63 
64 private:
65     enum class Type { kLinear, kRadial };
66 
GradientAdapter(sk_sp<sksg::Gradient> gradient,Type type,size_t stop_count,const skjson::ObjectValue & jgrad,const skjson::ObjectValue & jstops,const AnimationBuilder & abuilder)67     GradientAdapter(sk_sp<sksg::Gradient> gradient,
68                     Type type,
69                     size_t stop_count,
70                     const skjson::ObjectValue& jgrad,
71                     const skjson::ObjectValue& jstops,
72                     const AnimationBuilder& abuilder)
73         : fGradient(std::move(gradient))
74         , fType(type)
75         , fStopCount(stop_count) {
76         this->bind(abuilder,  jgrad["s"], fStartPoint     );
77         this->bind(abuilder,  jgrad["e"], fEndPoint       );
78         this->bind(abuilder,  jgrad["h"], fHighlightLength);
79         this->bind(abuilder,  jgrad["a"], fHighlightAngle );
80         this->bind(abuilder, jstops["k"], fStops          );
81     }
82 
onSync()83     void onSync() override {
84         const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y},
85                    e_point = SkPoint{  fEndPoint.x,   fEndPoint.y};
86 
87         switch (fType) {
88         case Type::kLinear: {
89             auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
90             grad->setStartPoint(s_point);
91             grad->setEndPoint(e_point);
92 
93             break;
94         }
95         case Type::kRadial: {
96             // The highlight parameters control the location of the actual gradient start point
97             // (equivalent to SVG's radial gradient focal point
98             //  https://www.w3.org/TR/SVG11/pservers.html#RadialGradients)
99             //
100             //   - highlight length determines the position along the |s_point -> e_point| vector,
101             //     where 0% corresponds to s_point and 100% corresponds to e_point.
102             //   - highlight angle rotates the point around s_point
103             //
104             const SkPoint rotated_e_point =
105                     SkMatrix::RotateDeg(fHighlightAngle, s_point).mapPoint(e_point);
106 
107             // The valid range for length is [-100% .. 100%], where negative values mirror the
108             // positive interval relative to s_point.
109             //
110             // Edge case: for exactly -100% and 100%, the focal point lies on the end circle and
111             // this triggers specific SVG behavior (see
112             // SkConicalGrdient::FocalData::isFocalOnCircle and friends), which does not match
113             // AE's semantics.  To avoid that, we clamp by an epsilon value.
114             const float eps = SK_ScalarNearlyZero * 2,
115                       h_len = SkTPin(fHighlightLength * 0.01f, -1 + eps, 1 - eps);
116 
117             const SkPoint focal_point = s_point + (rotated_e_point - s_point) * h_len;
118 
119             auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
120             grad->setStartCenter(focal_point);
121             grad->setEndCenter(s_point);
122             grad->setStartRadius(0);
123             grad->setEndRadius(SkPoint::Distance(s_point, rotated_e_point));
124 
125             break;
126         }
127         }
128 
129         // Gradient color stops are specified as a consolidated float vector holding:
130         //
131         //   a) an (optional) array of color/RGB stop records (t, r, g, b)
132         //
133         // followed by
134         //
135         //   b) an (optional) array of opacity/alpha stop records (t, a)
136         //
137         struct   ColorRec { float t, r, g, b; };
138         struct OpacityRec { float t, a;       };
139 
140         // The number of color records is explicit (fColorStopCount),
141         // while the number of opacity stops is implicit (based on the size of fStops).
142         //
143         // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
144         const auto c_count = fStopCount,
145                    c_size  = c_count * 4,
146                    o_count = (fStops.size() - c_size) / 2;
147         if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
148             // apply() may get called before the stops are set, so only log when we have some stops.
149             if (!fStops.empty()) {
150                 SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
151             }
152             return;
153         }
154 
155         const auto* c_rec = c_count > 0
156                 ? reinterpret_cast<const ColorRec*>(fStops.data())
157                 : nullptr;
158         const auto* o_rec = o_count > 0
159                 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
160                 : nullptr;
161         const auto* c_end = c_rec + c_count;
162         const auto* o_end = o_rec + o_count;
163 
164         sksg::Gradient::ColorStop current_stop = {
165             0.0f, {
166                 c_rec ? c_rec->r : 0,
167                 c_rec ? c_rec->g : 0,
168                 c_rec ? c_rec->b : 0,
169                 o_rec ? o_rec->a : 1,
170         }};
171 
172         std::vector<sksg::Gradient::ColorStop> stops;
173         stops.reserve(c_count);
174 
175         // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
176         while (c_rec || o_rec) {
177             // After exhausting one of color recs / opacity recs, continue propagating the last
178             // computed values (as if they were specified at the current position).
179             const auto& cs = c_rec
180                     ? *c_rec
181                     : ColorRec{ o_rec->t,
182                                 current_stop.fColor.fR,
183                                 current_stop.fColor.fG,
184                                 current_stop.fColor.fB };
185             const auto& os = o_rec
186                     ? *o_rec
187                     : OpacityRec{ c_rec->t, current_stop.fColor.fA };
188 
189             // Compute component lerp coefficients based on the relative position of the stops
190             // being considered. The idea is to select the smaller-pos stop, use its own properties
191             // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
192             // larger-pos stop against the previously computed gradient stop values.
193             const auto     c_pos = std::max(cs.t, current_stop.fPosition),
194                            o_pos = std::max(os.t, current_stop.fPosition),
195                        c_pos_rel = c_pos - current_stop.fPosition,
196                        o_pos_rel = o_pos - current_stop.fPosition,
197                              t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
198                              t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
199 
200             auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
201 
202             current_stop = {
203                     std::min(c_pos, o_pos),
204                     {
205                         lerp(current_stop.fColor.fR, cs.r, t_c ),
206                         lerp(current_stop.fColor.fG, cs.g, t_c ),
207                         lerp(current_stop.fColor.fB, cs.b, t_c ),
208                         lerp(current_stop.fColor.fA, os.a, t_o)
209                     }
210             };
211             stops.push_back(current_stop);
212 
213             // Consume one of, or both (for coincident positions) color/opacity stops.
214             if (c_pos <= o_pos) {
215                 c_rec = next_rec<ColorRec>(c_rec, c_end);
216             }
217             if (o_pos <= c_pos) {
218                 o_rec = next_rec<OpacityRec>(o_rec, o_end);
219             }
220         }
221 
222         stops.shrink_to_fit();
223         fGradient->setColorStops(std::move(stops));
224     }
225 
226 private:
227     template <typename T>
next_rec(const T * rec,const T * end_rec) const228     const T* next_rec(const T* rec, const T* end_rec) const {
229         if (!rec) return nullptr;
230 
231         SkASSERT(rec < end_rec);
232         rec++;
233 
234         return rec < end_rec ? rec : nullptr;
235     }
236 
237     const sk_sp<sksg::Gradient> fGradient;
238     const Type                  fType;
239     const size_t                fStopCount;
240 
241     VectorValue  fStops;
242     Vec2Value    fStartPoint = {0,0},
243                  fEndPoint   = {0,0};
244     float        fHighlightLength = 0,
245                  fHighlightAngle  = 0;
246 };
247 
248 } // namespace
249 
AttachGradientFill(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)250 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad,
251                                                         const AnimationBuilder* abuilder) {
252     auto adapter = GradientAdapter::Make(jgrad, *abuilder);
253 
254     return adapter
255             ? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
256             : nullptr;
257 }
258 
AttachGradientStroke(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)259 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad,
260                                                           const AnimationBuilder* abuilder) {
261     auto adapter = GradientAdapter::Make(jgrad, *abuilder);
262 
263     return adapter
264             ? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
265             : nullptr;
266 }
267 
268 } // namespace internal
269 } // namespace skottie
270