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