blob: f9f574172f89ecc0797286b16d33a423bac343a6 [file] [log] [blame]
Florin Malita094ccde2017-12-30 12:27:00 -05001/*
2 * Copyright 2017 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 "Skotty.h"
9
10#include "SkCanvas.h"
11#include "SkottyAnimator.h"
12#include "SkottyPriv.h"
13#include "SkottyProperties.h"
14#include "SkData.h"
15#include "SkMakeUnique.h"
16#include "SkPaint.h"
17#include "SkPath.h"
18#include "SkPoint.h"
19#include "SkSGColor.h"
20#include "SkSGDraw.h"
21#include "SkSGInvalidationController.h"
22#include "SkSGGroup.h"
23#include "SkSGPath.h"
24#include "SkSGTransform.h"
25#include "SkStream.h"
26#include "SkTArray.h"
27#include "SkTHash.h"
28
29#include <cmath>
30#include "stdlib.h"
31
32namespace skotty {
33
34namespace {
35
36using AssetMap = SkTHashMap<SkString, const Json::Value*>;
37
38struct AttachContext {
39 const AssetMap& fAssets;
40 SkTArray<std::unique_ptr<AnimatorBase>>& fAnimators;
41};
42
43bool LogFail(const Json::Value& json, const char* msg) {
44 const auto dump = json.toStyledString();
45 LOG("!! %s: %s", msg, dump.c_str());
46 return false;
47}
48
49// This is the workhorse for binding properties: depending on whether the property is animated,
50// it will either apply immediately or instantiate and attach a keyframe animator.
51template <typename ValueT, typename AttrT, typename NodeT, typename ApplyFuncT>
52bool AttachProperty(const Json::Value& jprop, AttachContext* ctx, const sk_sp<NodeT>& node,
53 ApplyFuncT&& apply) {
54 if (!jprop.isObject())
55 return false;
56
57 if (!ParseBool(jprop["a"], false)) {
58 // Static property.
59 ValueT val;
60 if (!ValueT::Parse(jprop["k"], &val)) {
61 return LogFail(jprop, "Could not parse static property");
62 }
63
64 apply(node, val.template as<AttrT>());
65 } else {
66 // Keyframe property.
67 using AnimatorT = Animator<ValueT, AttrT, NodeT>;
68 auto animator = AnimatorT::Make(jprop["k"], node, std::move(apply));
69
70 if (!animator) {
71 return LogFail(jprop, "Could not instantiate keyframe animator");
72 }
73
74 ctx->fAnimators.push_back(std::move(animator));
75 }
76
77 return true;
78}
79
80sk_sp<sksg::RenderNode> AttachTransform(const Json::Value& t, AttachContext* ctx,
81 sk_sp<sksg::RenderNode> wrapped_node) {
82 if (!t.isObject())
83 return wrapped_node;
84
85 auto xform = sk_make_sp<CompositeTransform>(wrapped_node);
86 auto anchor_attached = AttachProperty<VectorValue, SkPoint>(t["a"], ctx, xform,
87 [](const sk_sp<CompositeTransform>& node, const SkPoint& a) {
88 node->setAnchorPoint(a);
89 });
90 auto position_attached = AttachProperty<VectorValue, SkPoint>(t["p"], ctx, xform,
91 [](const sk_sp<CompositeTransform>& node, const SkPoint& p) {
92 node->setPosition(p);
93 });
94 auto scale_attached = AttachProperty<VectorValue, SkVector>(t["s"], ctx, xform,
95 [](const sk_sp<CompositeTransform>& node, const SkVector& s) {
96 node->setScale(s);
97 });
98 auto rotation_attached = AttachProperty<ScalarValue, SkScalar>(t["r"], ctx, xform,
99 [](const sk_sp<CompositeTransform>& node, SkScalar r) {
100 node->setRotation(r);
101 });
102 auto skew_attached = AttachProperty<ScalarValue, SkScalar>(t["sk"], ctx, xform,
103 [](const sk_sp<CompositeTransform>& node, SkScalar sk) {
104 node->setSkew(sk);
105 });
106 auto skewaxis_attached = AttachProperty<ScalarValue, SkScalar>(t["sa"], ctx, xform,
107 [](const sk_sp<CompositeTransform>& node, SkScalar sa) {
108 node->setSkewAxis(sa);
109 });
110
111 if (!anchor_attached &&
112 !position_attached &&
113 !scale_attached &&
114 !rotation_attached &&
115 !skew_attached &&
116 !skewaxis_attached) {
117 LogFail(t, "Could not parse transform");
118 return wrapped_node;
119 }
120
121 return xform->node();
122}
123
124sk_sp<sksg::RenderNode> AttachShape(const Json::Value&, AttachContext* ctx);
125sk_sp<sksg::RenderNode> AttachComposition(const Json::Value&, AttachContext* ctx);
126
127sk_sp<sksg::RenderNode> AttachShapeGroup(const Json::Value& jgroup, AttachContext* ctx) {
128 SkASSERT(jgroup.isObject());
129
130 return AttachShape(jgroup["it"], ctx);
131}
132
133sk_sp<sksg::GeometryNode> AttachPathGeometry(const Json::Value& jpath, AttachContext* ctx) {
134 SkASSERT(jpath.isObject());
135
136 auto path_node = sksg::Path::Make();
137 auto path_attached = AttachProperty<ShapeValue, SkPath>(jpath["ks"], ctx, path_node,
138 [](const sk_sp<sksg::Path>& node, const SkPath& p) { node->setPath(p); });
139
140 if (path_attached)
141 LOG("** Attached path geometry - verbs: %d\n", path_node->getPath().countVerbs());
142
143 return path_attached ? path_node : nullptr;
144}
145
146sk_sp<sksg::Color> AttachColorPaint(const Json::Value& obj, AttachContext* ctx) {
147 SkASSERT(obj.isObject());
148
149 auto color_node = sksg::Color::Make(SK_ColorBLACK);
150 color_node->setAntiAlias(true);
151
152 auto color_attached = AttachProperty<VectorValue, SkColor>(obj["c"], ctx, color_node,
153 [](const sk_sp<sksg::Color>& node, SkColor c) { node->setColor(c); });
154
155 return color_attached ? color_node : nullptr;
156}
157
158sk_sp<sksg::PaintNode> AttachFillPaint(const Json::Value& jfill, AttachContext* ctx) {
159 SkASSERT(jfill.isObject());
160
161 auto color = AttachColorPaint(jfill, ctx);
162 if (color) {
163 LOG("** Attached color fill: 0x%x\n", color->getColor());
164 }
165 return color;
166}
167
168sk_sp<sksg::PaintNode> AttachStrokePaint(const Json::Value& jstroke, AttachContext* ctx) {
169 SkASSERT(jstroke.isObject());
170
171 auto stroke_node = AttachColorPaint(jstroke, ctx);
172 if (!stroke_node)
173 return nullptr;
174
175 LOG("** Attached color stroke: 0x%x\n", stroke_node->getColor());
176
177 stroke_node->setStyle(SkPaint::kStroke_Style);
178
179 auto width_attached = AttachProperty<ScalarValue, SkScalar>(jstroke["w"], ctx, stroke_node,
180 [](const sk_sp<sksg::Color>& node, SkScalar width) { node->setStrokeWidth(width); });
181 if (!width_attached)
182 return nullptr;
183
184 stroke_node->setStrokeMiter(ParseScalar(jstroke["ml"], 4));
185
186 static constexpr SkPaint::Join gJoins[] = {
187 SkPaint::kMiter_Join,
188 SkPaint::kRound_Join,
189 SkPaint::kBevel_Join,
190 };
191 stroke_node->setStrokeJoin(gJoins[SkTPin<int>(ParseInt(jstroke["lj"], 1) - 1,
192 0, SK_ARRAY_COUNT(gJoins) - 1)]);
193
194 static constexpr SkPaint::Cap gCaps[] = {
195 SkPaint::kButt_Cap,
196 SkPaint::kRound_Cap,
197 SkPaint::kSquare_Cap,
198 };
199 stroke_node->setStrokeCap(gCaps[SkTPin<int>(ParseInt(jstroke["lc"], 1) - 1,
200 0, SK_ARRAY_COUNT(gCaps) - 1)]);
201
202 return stroke_node;
203}
204
205using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const Json::Value&, AttachContext*);
206static constexpr GeometryAttacherT gGeometryAttachers[] = {
207 AttachPathGeometry,
208};
209
210using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const Json::Value&, AttachContext*);
211static constexpr PaintAttacherT gPaintAttachers[] = {
212 AttachFillPaint,
213 AttachStrokePaint,
214};
215
216using GroupAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
217static constexpr GroupAttacherT gGroupAttachers[] = {
218 AttachShapeGroup,
219};
220
221enum class ShapeType {
222 kGeometry,
223 kPaint,
224 kGroup,
225};
226
227struct ShapeInfo {
228 const char* fTypeString;
229 ShapeType fShapeType;
230 uint32_t fAttacherIndex; // index into respective attacher tables
231};
232
233const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
234 static constexpr ShapeInfo gShapeInfo[] = {
235 { "fl", ShapeType::kPaint , 0 }, // fill -> AttachFillPaint
236 { "gr", ShapeType::kGroup , 0 }, // group -> AttachShapeGroup
237 { "sh", ShapeType::kGeometry, 0 }, // shape -> AttachPathGeometry
238 { "st", ShapeType::kPaint , 1 }, // stroke -> AttachStrokePaint
239 };
240
241 if (!shape.isObject())
242 return nullptr;
243
244 const auto& type = shape["ty"];
245 if (!type.isString())
246 return nullptr;
247
248 const auto* info = bsearch(type.asCString(),
249 gShapeInfo,
250 SK_ARRAY_COUNT(gShapeInfo),
251 sizeof(ShapeInfo),
252 [](const void* key, const void* info) {
253 return strcmp(static_cast<const char*>(key),
254 static_cast<const ShapeInfo*>(info)->fTypeString);
255 });
256
257 return static_cast<const ShapeInfo*>(info);
258}
259
260sk_sp<sksg::RenderNode> AttachShape(const Json::Value& shapeArray, AttachContext* ctx) {
261 if (!shapeArray.isArray())
262 return nullptr;
263
264 sk_sp<sksg::Group> shape_group = sksg::Group::Make();
265
266 SkSTArray<16, sk_sp<sksg::GeometryNode>, true> geos;
267 SkSTArray<16, sk_sp<sksg::PaintNode> , true> paints;
268
269 for (const auto& s : shapeArray) {
270 const auto* info = FindShapeInfo(s);
271 if (!info) {
272 LogFail(s.isObject() ? s["ty"] : s, "Unknown shape");
273 continue;
274 }
275
276 switch (info->fShapeType) {
277 case ShapeType::kGeometry: {
278 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
279 if (auto geo = gGeometryAttachers[info->fAttacherIndex](s, ctx)) {
280 geos.push_back(std::move(geo));
281 }
282 } break;
283 case ShapeType::kPaint: {
284 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
285 if (auto paint = gPaintAttachers[info->fAttacherIndex](s, ctx)) {
286 paints.push_back(std::move(paint));
287 }
288 } break;
289 case ShapeType::kGroup: {
290 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGroupAttachers));
291 if (auto group = gGroupAttachers[info->fAttacherIndex](s, ctx)) {
292 shape_group->addChild(std::move(group));
293 }
294 } break;
295 }
296 }
297
298 for (const auto& geo : geos) {
299 for (int i = paints.count() - 1; i >= 0; --i) {
300 shape_group->addChild(sksg::Draw::Make(geo, paints[i]));
301 }
302 }
303
304 LOG("** Attached shape - geometries: %d, paints: %d\n", geos.count(), paints.count());
305 return shape_group;
306}
307
308sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& layer, AttachContext* ctx) {
309 SkASSERT(layer.isObject());
310
311 auto refId = ParseString(layer["refId"], "");
312 if (refId.isEmpty()) {
313 LOG("!! Comp layer missing refId\n");
314 return nullptr;
315 }
316
317 const auto* comp = ctx->fAssets.find(refId);
318 if (!comp) {
319 LOG("!! Pre-comp not found: '%s'\n", refId.c_str());
320 return nullptr;
321 }
322
323 // TODO: cycle detection
324 return AttachComposition(**comp, ctx);
325}
326
327sk_sp<sksg::RenderNode> AttachSolidLayer(const Json::Value& layer, AttachContext*) {
328 SkASSERT(layer.isObject());
329
330 LOG("?? Solid layer stub\n");
331 return nullptr;
332}
333
334sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext*) {
335 SkASSERT(layer.isObject());
336
337 LOG("?? Image layer stub\n");
338 return nullptr;
339}
340
341sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*) {
342 SkASSERT(layer.isObject());
343
344 LOG("?? Null layer stub\n");
345 return nullptr;
346}
347
348sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext* ctx) {
349 SkASSERT(layer.isObject());
350
351 LOG("** Attaching shape layer ind: %d\n", ParseInt(layer["ind"], 0));
352
353 return AttachShape(layer["shapes"], ctx);
354}
355
356sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*) {
357 SkASSERT(layer.isObject());
358
359 LOG("?? Text layer stub\n");
360 return nullptr;
361}
362
363sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& layer, AttachContext* ctx) {
364 if (!layer.isObject())
365 return nullptr;
366
367 using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
368 static constexpr LayerAttacher gLayerAttachers[] = {
369 AttachCompLayer, // 'ty': 0
370 AttachSolidLayer, // 'ty': 1
371 AttachImageLayer, // 'ty': 2
372 AttachNullLayer, // 'ty': 3
373 AttachShapeLayer, // 'ty': 4
374 AttachTextLayer, // 'ty': 5
375 };
376
377 int type = ParseInt(layer["ty"], -1);
378 if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
379 return nullptr;
380 }
381
382 return AttachTransform(layer["ks"], ctx, gLayerAttachers[type](layer, ctx));
383}
384
385sk_sp<sksg::RenderNode> AttachComposition(const Json::Value& comp, AttachContext* ctx) {
386 if (!comp.isObject())
387 return nullptr;
388
389 LOG("** Attaching composition '%s'\n", ParseString(comp["id"], "").c_str());
390
391 auto comp_group = sksg::Group::Make();
392
393 for (const auto& l : comp["layers"]) {
394 if (auto layer_fragment = AttachLayer(l, ctx)) {
395 comp_group->addChild(std::move(layer_fragment));
396 }
397 }
398
399 return comp_group;
400}
401
402} // namespace
403
404std::unique_ptr<Animation> Animation::Make(SkStream* stream) {
405 if (!stream->hasLength()) {
406 // TODO: handle explicit buffering?
407 LOG("!! cannot parse streaming content\n");
408 return nullptr;
409 }
410
411 Json::Value json;
412 {
413 auto data = SkData::MakeFromStream(stream, stream->getLength());
414 if (!data) {
415 LOG("!! could not read stream\n");
416 return nullptr;
417 }
418
419 Json::Reader reader;
420
421 auto dataStart = static_cast<const char*>(data->data());
422 if (!reader.parse(dataStart, dataStart + data->size(), json, false) || !json.isObject()) {
423 LOG("!! failed to parse json: %s\n", reader.getFormattedErrorMessages().c_str());
424 return nullptr;
425 }
426 }
427
428 const auto version = ParseString(json["v"], "");
429 const auto size = SkSize::Make(ParseScalar(json["w"], -1), ParseScalar(json["h"], -1));
430 const auto fps = ParseScalar(json["fr"], -1);
431
432 if (size.isEmpty() || version.isEmpty() || fps < 0) {
433 LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
434 version.c_str(), size.width(), size.height(), fps);
435 return nullptr;
436 }
437
438 return std::unique_ptr<Animation>(new Animation(std::move(version), size, fps, json));
439}
440
441Animation::Animation(SkString version, const SkSize& size, SkScalar fps, const Json::Value& json)
442 : fVersion(std::move(version))
443 , fSize(size)
444 , fFrameRate(fps)
445 , fInPoint(ParseScalar(json["ip"], 0))
446 , fOutPoint(SkTMax(ParseScalar(json["op"], SK_ScalarMax), fInPoint)) {
447
448 AssetMap assets;
449 for (const auto& asset : json["assets"]) {
450 if (!asset.isObject()) {
451 continue;
452 }
453
454 assets.set(ParseString(asset["id"], ""), &asset);
455 }
456
457 AttachContext ctx = { assets, fAnimators };
458 fDom = AttachComposition(json, &ctx);
459
460 LOG("** Attached %d animators\n", fAnimators.count());
461}
462
463Animation::~Animation() = default;
464
465void Animation::render(SkCanvas* canvas) const {
466 if (!fDom)
467 return;
468
469 sksg::InvalidationController ic;
470 fDom->revalidate(&ic, SkMatrix::I());
471
472 // TODO: proper inval
473 fDom->render(canvas);
474
475 if (!fShowInval)
476 return;
477
478 SkPaint fill, stroke;
479 fill.setAntiAlias(true);
480 fill.setColor(0x40ff0000);
481 stroke.setAntiAlias(true);
482 stroke.setColor(0xffff0000);
483 stroke.setStyle(SkPaint::kStroke_Style);
484
485 for (const auto& r : ic) {
486 canvas->drawRect(r, fill);
487 canvas->drawRect(r, stroke);
488 }
489}
490
491void Animation::animationTick(SkMSec ms) {
492 // 't' in the BM model really means 'frame #'
493 auto t = static_cast<float>(ms) * fFrameRate / 1000;
494
495 t = fInPoint + std::fmod(t, fOutPoint - fInPoint);
496
497 // TODO: this can be optimized quite a bit with some sorting/state tracking.
498 for (const auto& a : fAnimators) {
499 a->tick(t);
500 }
501}
502
503} // namespace skotty