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/SkPathBuilder.h"
9 #include "modules/skottie/src/SkottieJson.h"
10 #include "modules/skottie/src/SkottieValue.h"
11 #include "modules/skottie/src/animator/Animator.h"
12 #include "modules/skottie/src/animator/VectorKeyframeAnimator.h"
13 
14 namespace skottie {
15 
16 // Shapes (paths) are encoded as a vector of floats.  For each vertex, we store 6 floats:
17 //
18 //   - vertex point      (2 floats)
19 //   - in-tangent point  (2 floats)
20 //   - out-tangent point (2 floats)
21 //
22 // Additionally, we store one trailing "closed shape" flag - e.g.
23 //
24 //  [ v0.x, v0.y, v0_in.x, v0_in.y, v0_out.x, v0_out.y, ... , closed_flag ]
25 //
26 enum ShapeEncodingInfo : size_t {
27             kX_Index = 0,
28             kY_Index = 1,
29           kInX_Index = 2,
30           kInY_Index = 3,
31          kOutX_Index = 4,
32          kOutY_Index = 5,
33 
34     kFloatsPerVertex = 6
35 };
36 
shape_encoding_len(size_t vertex_count)37 static size_t shape_encoding_len(size_t vertex_count) {
38     return vertex_count * kFloatsPerVertex + 1;
39 }
40 
41 // Some versions wrap shape values as single-element arrays.
shape_root(const skjson::Value & jv)42 static const skjson::ObjectValue* shape_root(const skjson::Value& jv) {
43     if (const skjson::ArrayValue* av = jv) {
44         if (av->size() == 1) {
45             return (*av)[0];
46         }
47     }
48 
49     return jv;
50 }
51 
parse_encoding_len(const skjson::Value & jv,size_t * len)52 static bool parse_encoding_len(const skjson::Value& jv, size_t* len) {
53     if (const auto* jshape = shape_root(jv)) {
54         if (const skjson::ArrayValue* jvs = (*jshape)["v"]) {
55             *len = shape_encoding_len(jvs->size());
56             return true;
57         }
58     }
59     return false;
60 }
61 
parse_encoding_data(const skjson::Value & jv,size_t data_len,float data[])62 static bool parse_encoding_data(const skjson::Value& jv, size_t data_len, float data[]) {
63     const auto* jshape = shape_root(jv);
64     if (!jshape) {
65         return false;
66     }
67 
68     // vertices are required, in/out tangents are optional
69     const skjson::ArrayValue* jvs = (*jshape)["v"]; // vertex points
70     const skjson::ArrayValue* jis = (*jshape)["i"]; // in-tangent points
71     const skjson::ArrayValue* jos = (*jshape)["o"]; // out-tangent points
72 
73     if (!jvs || data_len != shape_encoding_len(jvs->size())) {
74         return false;
75     }
76 
77     auto parse_point = [](const skjson::ArrayValue* ja, size_t i, float* x, float* y) {
78         SkASSERT(ja);
79         const skjson::ArrayValue* jpt = (*ja)[i];
80 
81         if (!jpt || jpt->size() != 2ul) {
82             return false;
83         }
84 
85         return Parse((*jpt)[0], x) && Parse((*jpt)[1], y);
86     };
87 
88     auto parse_optional_point = [&parse_point](const skjson::ArrayValue* ja, size_t i,
89                                                float* x, float* y) {
90         if (!ja || i >= ja->size()) {
91             // default control point
92             *x = *y = 0;
93             return true;
94         }
95 
96         return parse_point(*ja, i, x, y);
97     };
98 
99     for (size_t i = 0; i < jvs->size(); ++i) {
100         float* dst = data + i * kFloatsPerVertex;
101         SkASSERT(dst + kFloatsPerVertex <= data + data_len);
102 
103         if (!parse_point         (jvs, i, dst +    kX_Index, dst +    kY_Index) ||
104             !parse_optional_point(jis, i, dst +  kInX_Index, dst +  kInY_Index) ||
105             !parse_optional_point(jos, i, dst + kOutX_Index, dst + kOutY_Index)) {
106             return false;
107         }
108     }
109 
110     // "closed" flag
111     data[data_len - 1] = ParseDefault<bool>((*jshape)["c"], false);
112 
113     return true;
114 }
115 
operator SkPath() const116 ShapeValue::operator SkPath() const {
117     const auto vertex_count = this->size() / kFloatsPerVertex;
118 
119     SkPathBuilder path;
120 
121     if (vertex_count) {
122         // conservatively assume all cubics
123         path.incReserve(1 + SkToInt(vertex_count * 3));
124 
125         // Move to first vertex.
126         path.moveTo((*this)[kX_Index], (*this)[kY_Index]);
127     }
128 
129     auto addCubic = [&](size_t from_vertex, size_t to_vertex) {
130         const auto from_index = kFloatsPerVertex * from_vertex,
131                      to_index = kFloatsPerVertex *   to_vertex;
132 
133         const SkPoint p0 = SkPoint{ (*this)[from_index +    kX_Index],
134                                     (*this)[from_index +    kY_Index] },
135                       p1 = SkPoint{ (*this)[  to_index +    kX_Index],
136                                     (*this)[  to_index +    kY_Index] },
137                       c0 = SkPoint{ (*this)[from_index + kOutX_Index],
138                                     (*this)[from_index + kOutY_Index] } + p0,
139                       c1 = SkPoint{ (*this)[  to_index +  kInX_Index],
140                                     (*this)[  to_index +  kInY_Index] } + p1;
141 
142         if (c0 == p0 && c1 == p1) {
143             // If the control points are coincident, we can power-reduce to a straight line.
144             // TODO: we could also do that when the controls are on the same line as the
145             //       vertices, but it's unclear how common that case is.
146             path.lineTo(p1);
147         } else {
148             path.cubicTo(c0, c1, p1);
149         }
150     };
151 
152     for (size_t i = 1; i < vertex_count; ++i) {
153         addCubic(i - 1, i);
154     }
155 
156     // Close the path with an extra cubic, if needed.
157     if (vertex_count && this->back() != 0) {
158         addCubic(vertex_count - 1, 0);
159         path.close();
160     }
161 
162     return path.detach();
163 }
164 
165 namespace internal {
166 
167 template <>
bind(const AnimationBuilder & abuilder,const skjson::ObjectValue * jprop,ShapeValue * v)168 bool AnimatablePropertyContainer::bind<ShapeValue>(const AnimationBuilder& abuilder,
169                                                   const skjson::ObjectValue* jprop,
170                                                   ShapeValue* v) {
171     VectorKeyframeAnimatorBuilder builder(v, parse_encoding_len, parse_encoding_data);
172 
173     return this->bindImpl(abuilder, jprop, builder);
174 }
175 
176 } // namespace internal
177 
178 } // namespace skottie
179