blob: 7a8e9e35c4c35c264fbbb9dbc07c042ee42607a3 [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
Florin Malitadacc02b2017-12-31 09:12:31 -0500221using TransformAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*,
222 sk_sp<sksg::RenderNode>);
223static constexpr TransformAttacherT gTransformAttachers[] = {
224 AttachTransform,
225};
226
Florin Malita094ccde2017-12-30 12:27:00 -0500227enum class ShapeType {
228 kGeometry,
229 kPaint,
230 kGroup,
Florin Malitadacc02b2017-12-31 09:12:31 -0500231 kTransform,
Florin Malita094ccde2017-12-30 12:27:00 -0500232};
233
234struct ShapeInfo {
235 const char* fTypeString;
236 ShapeType fShapeType;
237 uint32_t fAttacherIndex; // index into respective attacher tables
238};
239
240const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
241 static constexpr ShapeInfo gShapeInfo[] = {
Florin Malitadacc02b2017-12-31 09:12:31 -0500242 { "fl", ShapeType::kPaint , 0 }, // fill -> AttachFillPaint
243 { "gr", ShapeType::kGroup , 0 }, // group -> AttachShapeGroup
244 { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry
245 { "st", ShapeType::kPaint , 1 }, // stroke -> AttachStrokePaint
246 { "tr", ShapeType::kTransform, 0 }, // transform -> AttachTransform
Florin Malita094ccde2017-12-30 12:27:00 -0500247 };
248
249 if (!shape.isObject())
250 return nullptr;
251
252 const auto& type = shape["ty"];
253 if (!type.isString())
254 return nullptr;
255
256 const auto* info = bsearch(type.asCString(),
257 gShapeInfo,
258 SK_ARRAY_COUNT(gShapeInfo),
259 sizeof(ShapeInfo),
260 [](const void* key, const void* info) {
261 return strcmp(static_cast<const char*>(key),
262 static_cast<const ShapeInfo*>(info)->fTypeString);
263 });
264
265 return static_cast<const ShapeInfo*>(info);
266}
267
268sk_sp<sksg::RenderNode> AttachShape(const Json::Value& shapeArray, AttachContext* ctx) {
269 if (!shapeArray.isArray())
270 return nullptr;
271
Florin Malitadacc02b2017-12-31 09:12:31 -0500272 sk_sp<sksg::Group> shape_group = sksg::Group::Make();
273 sk_sp<sksg::RenderNode> xformed_group = shape_group;
Florin Malita094ccde2017-12-30 12:27:00 -0500274
275 SkSTArray<16, sk_sp<sksg::GeometryNode>, true> geos;
276 SkSTArray<16, sk_sp<sksg::PaintNode> , true> paints;
277
278 for (const auto& s : shapeArray) {
279 const auto* info = FindShapeInfo(s);
280 if (!info) {
281 LogFail(s.isObject() ? s["ty"] : s, "Unknown shape");
282 continue;
283 }
284
285 switch (info->fShapeType) {
286 case ShapeType::kGeometry: {
287 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
288 if (auto geo = gGeometryAttachers[info->fAttacherIndex](s, ctx)) {
289 geos.push_back(std::move(geo));
290 }
291 } break;
292 case ShapeType::kPaint: {
293 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
294 if (auto paint = gPaintAttachers[info->fAttacherIndex](s, ctx)) {
295 paints.push_back(std::move(paint));
296 }
297 } break;
298 case ShapeType::kGroup: {
299 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGroupAttachers));
300 if (auto group = gGroupAttachers[info->fAttacherIndex](s, ctx)) {
301 shape_group->addChild(std::move(group));
302 }
303 } break;
Florin Malitadacc02b2017-12-31 09:12:31 -0500304 case ShapeType::kTransform: {
305 // TODO: BM appears to transform the grometry, not the draw op itself.
306 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gTransformAttachers));
307 xformed_group = gTransformAttachers[info->fAttacherIndex](s, ctx, xformed_group);
308 } break;
Florin Malita094ccde2017-12-30 12:27:00 -0500309 }
310 }
311
312 for (const auto& geo : geos) {
313 for (int i = paints.count() - 1; i >= 0; --i) {
314 shape_group->addChild(sksg::Draw::Make(geo, paints[i]));
315 }
316 }
317
318 LOG("** Attached shape - geometries: %d, paints: %d\n", geos.count(), paints.count());
Florin Malitadacc02b2017-12-31 09:12:31 -0500319 return xformed_group;
Florin Malita094ccde2017-12-30 12:27:00 -0500320}
321
322sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& layer, AttachContext* ctx) {
323 SkASSERT(layer.isObject());
324
325 auto refId = ParseString(layer["refId"], "");
326 if (refId.isEmpty()) {
327 LOG("!! Comp layer missing refId\n");
328 return nullptr;
329 }
330
331 const auto* comp = ctx->fAssets.find(refId);
332 if (!comp) {
333 LOG("!! Pre-comp not found: '%s'\n", refId.c_str());
334 return nullptr;
335 }
336
337 // TODO: cycle detection
338 return AttachComposition(**comp, ctx);
339}
340
341sk_sp<sksg::RenderNode> AttachSolidLayer(const Json::Value& layer, AttachContext*) {
342 SkASSERT(layer.isObject());
343
344 LOG("?? Solid layer stub\n");
345 return nullptr;
346}
347
348sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext*) {
349 SkASSERT(layer.isObject());
350
351 LOG("?? Image layer stub\n");
352 return nullptr;
353}
354
355sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*) {
356 SkASSERT(layer.isObject());
357
358 LOG("?? Null layer stub\n");
359 return nullptr;
360}
361
362sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext* ctx) {
363 SkASSERT(layer.isObject());
364
365 LOG("** Attaching shape layer ind: %d\n", ParseInt(layer["ind"], 0));
366
367 return AttachShape(layer["shapes"], ctx);
368}
369
370sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*) {
371 SkASSERT(layer.isObject());
372
373 LOG("?? Text layer stub\n");
374 return nullptr;
375}
376
377sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& layer, AttachContext* ctx) {
378 if (!layer.isObject())
379 return nullptr;
380
381 using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
382 static constexpr LayerAttacher gLayerAttachers[] = {
383 AttachCompLayer, // 'ty': 0
384 AttachSolidLayer, // 'ty': 1
385 AttachImageLayer, // 'ty': 2
386 AttachNullLayer, // 'ty': 3
387 AttachShapeLayer, // 'ty': 4
388 AttachTextLayer, // 'ty': 5
389 };
390
391 int type = ParseInt(layer["ty"], -1);
392 if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
393 return nullptr;
394 }
395
396 return AttachTransform(layer["ks"], ctx, gLayerAttachers[type](layer, ctx));
397}
398
399sk_sp<sksg::RenderNode> AttachComposition(const Json::Value& comp, AttachContext* ctx) {
400 if (!comp.isObject())
401 return nullptr;
402
403 LOG("** Attaching composition '%s'\n", ParseString(comp["id"], "").c_str());
404
405 auto comp_group = sksg::Group::Make();
406
407 for (const auto& l : comp["layers"]) {
408 if (auto layer_fragment = AttachLayer(l, ctx)) {
409 comp_group->addChild(std::move(layer_fragment));
410 }
411 }
412
413 return comp_group;
414}
415
416} // namespace
417
418std::unique_ptr<Animation> Animation::Make(SkStream* stream) {
419 if (!stream->hasLength()) {
420 // TODO: handle explicit buffering?
421 LOG("!! cannot parse streaming content\n");
422 return nullptr;
423 }
424
425 Json::Value json;
426 {
427 auto data = SkData::MakeFromStream(stream, stream->getLength());
428 if (!data) {
429 LOG("!! could not read stream\n");
430 return nullptr;
431 }
432
433 Json::Reader reader;
434
435 auto dataStart = static_cast<const char*>(data->data());
436 if (!reader.parse(dataStart, dataStart + data->size(), json, false) || !json.isObject()) {
437 LOG("!! failed to parse json: %s\n", reader.getFormattedErrorMessages().c_str());
438 return nullptr;
439 }
440 }
441
442 const auto version = ParseString(json["v"], "");
443 const auto size = SkSize::Make(ParseScalar(json["w"], -1), ParseScalar(json["h"], -1));
444 const auto fps = ParseScalar(json["fr"], -1);
445
446 if (size.isEmpty() || version.isEmpty() || fps < 0) {
447 LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
448 version.c_str(), size.width(), size.height(), fps);
449 return nullptr;
450 }
451
452 return std::unique_ptr<Animation>(new Animation(std::move(version), size, fps, json));
453}
454
455Animation::Animation(SkString version, const SkSize& size, SkScalar fps, const Json::Value& json)
456 : fVersion(std::move(version))
457 , fSize(size)
458 , fFrameRate(fps)
459 , fInPoint(ParseScalar(json["ip"], 0))
460 , fOutPoint(SkTMax(ParseScalar(json["op"], SK_ScalarMax), fInPoint)) {
461
462 AssetMap assets;
463 for (const auto& asset : json["assets"]) {
464 if (!asset.isObject()) {
465 continue;
466 }
467
468 assets.set(ParseString(asset["id"], ""), &asset);
469 }
470
471 AttachContext ctx = { assets, fAnimators };
472 fDom = AttachComposition(json, &ctx);
473
474 LOG("** Attached %d animators\n", fAnimators.count());
475}
476
477Animation::~Animation() = default;
478
479void Animation::render(SkCanvas* canvas) const {
480 if (!fDom)
481 return;
482
483 sksg::InvalidationController ic;
484 fDom->revalidate(&ic, SkMatrix::I());
485
486 // TODO: proper inval
487 fDom->render(canvas);
488
489 if (!fShowInval)
490 return;
491
492 SkPaint fill, stroke;
493 fill.setAntiAlias(true);
494 fill.setColor(0x40ff0000);
495 stroke.setAntiAlias(true);
496 stroke.setColor(0xffff0000);
497 stroke.setStyle(SkPaint::kStroke_Style);
498
499 for (const auto& r : ic) {
500 canvas->drawRect(r, fill);
501 canvas->drawRect(r, stroke);
502 }
503}
504
505void Animation::animationTick(SkMSec ms) {
506 // 't' in the BM model really means 'frame #'
507 auto t = static_cast<float>(ms) * fFrameRate / 1000;
508
509 t = fInPoint + std::fmod(t, fOutPoint - fInPoint);
510
511 // TODO: this can be optimized quite a bit with some sorting/state tracking.
512 for (const auto& a : fAnimators) {
513 a->tick(t);
514 }
515}
516
517} // namespace skotty