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