blob: 47ff8281830d976f89866c9afcc657c5ede4d3f1 [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"
Florin Malitae6345d92018-01-03 23:37:54 -050023#include "SkSGMerge.h"
Florin Malita094ccde2017-12-30 12:27:00 -050024#include "SkSGPath.h"
Florin Malita2e1d7e22018-01-02 10:40:00 -050025#include "SkSGRect.h"
Florin Malita094ccde2017-12-30 12:27:00 -050026#include "SkSGTransform.h"
27#include "SkStream.h"
28#include "SkTArray.h"
29#include "SkTHash.h"
30
31#include <cmath>
Florin Malitae6345d92018-01-03 23:37:54 -050032#include <vector>
33
Florin Malita094ccde2017-12-30 12:27:00 -050034#include "stdlib.h"
35
36namespace skotty {
37
38namespace {
39
40using AssetMap = SkTHashMap<SkString, const Json::Value*>;
41
42struct AttachContext {
43 const AssetMap& fAssets;
44 SkTArray<std::unique_ptr<AnimatorBase>>& fAnimators;
45};
46
47bool LogFail(const Json::Value& json, const char* msg) {
48 const auto dump = json.toStyledString();
49 LOG("!! %s: %s", msg, dump.c_str());
50 return false;
51}
52
53// This is the workhorse for binding properties: depending on whether the property is animated,
54// it will either apply immediately or instantiate and attach a keyframe animator.
55template <typename ValueT, typename AttrT, typename NodeT, typename ApplyFuncT>
56bool AttachProperty(const Json::Value& jprop, AttachContext* ctx, const sk_sp<NodeT>& node,
57 ApplyFuncT&& apply) {
58 if (!jprop.isObject())
59 return false;
60
61 if (!ParseBool(jprop["a"], false)) {
62 // Static property.
63 ValueT val;
64 if (!ValueT::Parse(jprop["k"], &val)) {
65 return LogFail(jprop, "Could not parse static property");
66 }
67
68 apply(node, val.template as<AttrT>());
69 } else {
70 // Keyframe property.
71 using AnimatorT = Animator<ValueT, AttrT, NodeT>;
72 auto animator = AnimatorT::Make(jprop["k"], node, std::move(apply));
73
74 if (!animator) {
75 return LogFail(jprop, "Could not instantiate keyframe animator");
76 }
77
78 ctx->fAnimators.push_back(std::move(animator));
79 }
80
81 return true;
82}
83
84sk_sp<sksg::RenderNode> AttachTransform(const Json::Value& t, AttachContext* ctx,
85 sk_sp<sksg::RenderNode> wrapped_node) {
Florin Malita2e1d7e22018-01-02 10:40:00 -050086 if (!t.isObject() || !wrapped_node)
Florin Malita094ccde2017-12-30 12:27:00 -050087 return wrapped_node;
88
89 auto xform = sk_make_sp<CompositeTransform>(wrapped_node);
90 auto anchor_attached = AttachProperty<VectorValue, SkPoint>(t["a"], ctx, xform,
91 [](const sk_sp<CompositeTransform>& node, const SkPoint& a) {
92 node->setAnchorPoint(a);
93 });
94 auto position_attached = AttachProperty<VectorValue, SkPoint>(t["p"], ctx, xform,
95 [](const sk_sp<CompositeTransform>& node, const SkPoint& p) {
96 node->setPosition(p);
97 });
98 auto scale_attached = AttachProperty<VectorValue, SkVector>(t["s"], ctx, xform,
99 [](const sk_sp<CompositeTransform>& node, const SkVector& s) {
100 node->setScale(s);
101 });
102 auto rotation_attached = AttachProperty<ScalarValue, SkScalar>(t["r"], ctx, xform,
103 [](const sk_sp<CompositeTransform>& node, SkScalar r) {
104 node->setRotation(r);
105 });
106 auto skew_attached = AttachProperty<ScalarValue, SkScalar>(t["sk"], ctx, xform,
107 [](const sk_sp<CompositeTransform>& node, SkScalar sk) {
108 node->setSkew(sk);
109 });
110 auto skewaxis_attached = AttachProperty<ScalarValue, SkScalar>(t["sa"], ctx, xform,
111 [](const sk_sp<CompositeTransform>& node, SkScalar sa) {
112 node->setSkewAxis(sa);
113 });
114
115 if (!anchor_attached &&
116 !position_attached &&
117 !scale_attached &&
118 !rotation_attached &&
119 !skew_attached &&
120 !skewaxis_attached) {
121 LogFail(t, "Could not parse transform");
122 return wrapped_node;
123 }
124
125 return xform->node();
126}
127
128sk_sp<sksg::RenderNode> AttachShape(const Json::Value&, AttachContext* ctx);
129sk_sp<sksg::RenderNode> AttachComposition(const Json::Value&, AttachContext* ctx);
130
131sk_sp<sksg::RenderNode> AttachShapeGroup(const Json::Value& jgroup, AttachContext* ctx) {
132 SkASSERT(jgroup.isObject());
133
134 return AttachShape(jgroup["it"], ctx);
135}
136
137sk_sp<sksg::GeometryNode> AttachPathGeometry(const Json::Value& jpath, AttachContext* ctx) {
138 SkASSERT(jpath.isObject());
139
140 auto path_node = sksg::Path::Make();
141 auto path_attached = AttachProperty<ShapeValue, SkPath>(jpath["ks"], ctx, path_node,
142 [](const sk_sp<sksg::Path>& node, const SkPath& p) { node->setPath(p); });
143
144 if (path_attached)
145 LOG("** Attached path geometry - verbs: %d\n", path_node->getPath().countVerbs());
146
147 return path_attached ? path_node : nullptr;
148}
149
Florin Malita2e1d7e22018-01-02 10:40:00 -0500150sk_sp<sksg::GeometryNode> AttachRRectGeometry(const Json::Value& jrect, AttachContext* ctx) {
151 SkASSERT(jrect.isObject());
152
153 auto rect_node = sksg::RRect::Make();
154 auto composite = sk_make_sp<CompositeRRect>(rect_node);
155
156 auto p_attached = AttachProperty<VectorValue, SkPoint>(jrect["p"], ctx, composite,
157 [](const sk_sp<CompositeRRect>& node, const SkPoint& pos) { node->setPosition(pos); });
158 auto s_attached = AttachProperty<VectorValue, SkSize>(jrect["s"], ctx, composite,
159 [](const sk_sp<CompositeRRect>& node, const SkSize& sz) { node->setSize(sz); });
160 auto r_attached = AttachProperty<ScalarValue, SkScalar>(jrect["r"], ctx, composite,
161 [](const sk_sp<CompositeRRect>& node, SkScalar radius) { node->setRadius(radius); });
162
163 if (!p_attached && !s_attached && !r_attached) {
164 return nullptr;
165 }
166
167 return rect_node;
168}
169
Florin Malita094ccde2017-12-30 12:27:00 -0500170sk_sp<sksg::Color> AttachColorPaint(const Json::Value& obj, AttachContext* ctx) {
171 SkASSERT(obj.isObject());
172
173 auto color_node = sksg::Color::Make(SK_ColorBLACK);
174 color_node->setAntiAlias(true);
175
176 auto color_attached = AttachProperty<VectorValue, SkColor>(obj["c"], ctx, color_node,
177 [](const sk_sp<sksg::Color>& node, SkColor c) { node->setColor(c); });
178
179 return color_attached ? color_node : nullptr;
180}
181
182sk_sp<sksg::PaintNode> AttachFillPaint(const Json::Value& jfill, AttachContext* ctx) {
183 SkASSERT(jfill.isObject());
184
185 auto color = AttachColorPaint(jfill, ctx);
186 if (color) {
187 LOG("** Attached color fill: 0x%x\n", color->getColor());
188 }
189 return color;
190}
191
192sk_sp<sksg::PaintNode> AttachStrokePaint(const Json::Value& jstroke, AttachContext* ctx) {
193 SkASSERT(jstroke.isObject());
194
195 auto stroke_node = AttachColorPaint(jstroke, ctx);
196 if (!stroke_node)
197 return nullptr;
198
199 LOG("** Attached color stroke: 0x%x\n", stroke_node->getColor());
200
201 stroke_node->setStyle(SkPaint::kStroke_Style);
202
203 auto width_attached = AttachProperty<ScalarValue, SkScalar>(jstroke["w"], ctx, stroke_node,
204 [](const sk_sp<sksg::Color>& node, SkScalar width) { node->setStrokeWidth(width); });
205 if (!width_attached)
206 return nullptr;
207
208 stroke_node->setStrokeMiter(ParseScalar(jstroke["ml"], 4));
209
210 static constexpr SkPaint::Join gJoins[] = {
211 SkPaint::kMiter_Join,
212 SkPaint::kRound_Join,
213 SkPaint::kBevel_Join,
214 };
215 stroke_node->setStrokeJoin(gJoins[SkTPin<int>(ParseInt(jstroke["lj"], 1) - 1,
216 0, SK_ARRAY_COUNT(gJoins) - 1)]);
217
218 static constexpr SkPaint::Cap gCaps[] = {
219 SkPaint::kButt_Cap,
220 SkPaint::kRound_Cap,
221 SkPaint::kSquare_Cap,
222 };
223 stroke_node->setStrokeCap(gCaps[SkTPin<int>(ParseInt(jstroke["lc"], 1) - 1,
224 0, SK_ARRAY_COUNT(gCaps) - 1)]);
225
226 return stroke_node;
227}
228
Florin Malitae6345d92018-01-03 23:37:54 -0500229std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
230 const Json::Value& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
231 std::vector<sk_sp<sksg::GeometryNode>> merged;
232
233 static constexpr sksg::Merge::Mode gModes[] = {
234 sksg::Merge::Mode::kMerge, // "mm": 1
235 sksg::Merge::Mode::kUnion, // "mm": 2
236 sksg::Merge::Mode::kDifference, // "mm": 3
237 sksg::Merge::Mode::kIntersect, // "mm": 4
238 sksg::Merge::Mode::kXOR , // "mm": 5
239 };
240
241 const auto mode = gModes[SkTPin<int>(ParseInt(jmerge["mm"], 1) - 1, 0, SK_ARRAY_COUNT(gModes))];
242 merged.push_back(sksg::Merge::Make(std::move(geos), mode));
243
244 LOG("** Attached merge path effect, mode: %d\n", mode);
245
246 return merged;
247}
248
Florin Malita094ccde2017-12-30 12:27:00 -0500249using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const Json::Value&, AttachContext*);
250static constexpr GeometryAttacherT gGeometryAttachers[] = {
251 AttachPathGeometry,
Florin Malita2e1d7e22018-01-02 10:40:00 -0500252 AttachRRectGeometry,
Florin Malita094ccde2017-12-30 12:27:00 -0500253};
254
255using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const Json::Value&, AttachContext*);
256static constexpr PaintAttacherT gPaintAttachers[] = {
257 AttachFillPaint,
258 AttachStrokePaint,
259};
260
261using GroupAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
262static constexpr GroupAttacherT gGroupAttachers[] = {
263 AttachShapeGroup,
264};
265
Florin Malitadacc02b2017-12-31 09:12:31 -0500266using TransformAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*,
267 sk_sp<sksg::RenderNode>);
268static constexpr TransformAttacherT gTransformAttachers[] = {
269 AttachTransform,
270};
271
Florin Malitae6345d92018-01-03 23:37:54 -0500272using GeometryEffectAttacherT =
273 std::vector<sk_sp<sksg::GeometryNode>> (*)(const Json::Value&,
274 AttachContext*,
275 std::vector<sk_sp<sksg::GeometryNode>>&&);
276static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
277 AttachMergeGeometryEffect,
278};
279
Florin Malita094ccde2017-12-30 12:27:00 -0500280enum class ShapeType {
281 kGeometry,
Florin Malitae6345d92018-01-03 23:37:54 -0500282 kGeometryEffect,
Florin Malita094ccde2017-12-30 12:27:00 -0500283 kPaint,
284 kGroup,
Florin Malitadacc02b2017-12-31 09:12:31 -0500285 kTransform,
Florin Malita094ccde2017-12-30 12:27:00 -0500286};
287
288struct ShapeInfo {
289 const char* fTypeString;
290 ShapeType fShapeType;
291 uint32_t fAttacherIndex; // index into respective attacher tables
292};
293
294const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
295 static constexpr ShapeInfo gShapeInfo[] = {
Florin Malitae6345d92018-01-03 23:37:54 -0500296 { "fl", ShapeType::kPaint , 0 }, // fill -> AttachFillPaint
297 { "gr", ShapeType::kGroup , 0 }, // group -> AttachShapeGroup
298 { "mm", ShapeType::kGeometryEffect, 0 }, // merge -> AttachMergeGeometryEffect
299 { "rc", ShapeType::kGeometry , 1 }, // shape -> AttachRRectGeometry
300 { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry
301 { "st", ShapeType::kPaint , 1 }, // stroke -> AttachStrokePaint
302 { "tr", ShapeType::kTransform , 0 }, // transform -> AttachTransform
Florin Malita094ccde2017-12-30 12:27:00 -0500303 };
304
305 if (!shape.isObject())
306 return nullptr;
307
308 const auto& type = shape["ty"];
309 if (!type.isString())
310 return nullptr;
311
312 const auto* info = bsearch(type.asCString(),
313 gShapeInfo,
314 SK_ARRAY_COUNT(gShapeInfo),
315 sizeof(ShapeInfo),
316 [](const void* key, const void* info) {
317 return strcmp(static_cast<const char*>(key),
318 static_cast<const ShapeInfo*>(info)->fTypeString);
319 });
320
321 return static_cast<const ShapeInfo*>(info);
322}
323
324sk_sp<sksg::RenderNode> AttachShape(const Json::Value& shapeArray, AttachContext* ctx) {
325 if (!shapeArray.isArray())
326 return nullptr;
327
Florin Malita2a8275b2018-01-02 12:52:43 -0500328 // (https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vector.html#groups_and_render_order_for_shapes_and_shape_attributes)
329 //
330 // Render order for shapes within a shape layer
331 //
332 // The rules for rendering a shape layer are similar to the rules for rendering a composition
333 // that contains nested compositions:
334 //
335 // * Within a group, the shape at the bottom of the Timeline panel stacking order is rendered
336 // first.
337 //
338 // * All path operations within a group are performed before paint operations. This means,
339 // for example, that the stroke follows the distortions in the path made by the Wiggle Paths
340 // path operation. Path operations within a group are performed from top to bottom.
341 //
342 // * Paint operations within a group are performed from the bottom to the top in the Timeline
343 // panel stacking order. This means, for example, that a stroke is rendered on top of
344 // (in front of) a stroke that appears after it in the Timeline panel.
345 //
Florin Malitadacc02b2017-12-31 09:12:31 -0500346 sk_sp<sksg::Group> shape_group = sksg::Group::Make();
347 sk_sp<sksg::RenderNode> xformed_group = shape_group;
Florin Malita094ccde2017-12-30 12:27:00 -0500348
Florin Malitae6345d92018-01-03 23:37:54 -0500349 std::vector<sk_sp<sksg::GeometryNode>> geos;
350 std::vector<sk_sp<sksg::RenderNode>> draws;
Florin Malita094ccde2017-12-30 12:27:00 -0500351
352 for (const auto& s : shapeArray) {
353 const auto* info = FindShapeInfo(s);
354 if (!info) {
355 LogFail(s.isObject() ? s["ty"] : s, "Unknown shape");
356 continue;
357 }
358
359 switch (info->fShapeType) {
360 case ShapeType::kGeometry: {
361 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
362 if (auto geo = gGeometryAttachers[info->fAttacherIndex](s, ctx)) {
363 geos.push_back(std::move(geo));
364 }
365 } break;
Florin Malitae6345d92018-01-03 23:37:54 -0500366 case ShapeType::kGeometryEffect: {
367 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
368 geos = gGeometryEffectAttachers[info->fAttacherIndex](s, ctx, std::move(geos));
369 } break;
Florin Malita094ccde2017-12-30 12:27:00 -0500370 case ShapeType::kPaint: {
371 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
372 if (auto paint = gPaintAttachers[info->fAttacherIndex](s, ctx)) {
Florin Malita2a8275b2018-01-02 12:52:43 -0500373 for (const auto& geo : geos) {
374 draws.push_back(sksg::Draw::Make(geo, paint));
375 }
Florin Malita094ccde2017-12-30 12:27:00 -0500376 }
377 } break;
378 case ShapeType::kGroup: {
379 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGroupAttachers));
380 if (auto group = gGroupAttachers[info->fAttacherIndex](s, ctx)) {
Florin Malita2a8275b2018-01-02 12:52:43 -0500381 draws.push_back(std::move(group));
Florin Malita094ccde2017-12-30 12:27:00 -0500382 }
383 } break;
Florin Malitadacc02b2017-12-31 09:12:31 -0500384 case ShapeType::kTransform: {
Florin Malita2a8275b2018-01-02 12:52:43 -0500385 // TODO: BM appears to transform the geometry, not the draw op itself.
Florin Malitadacc02b2017-12-31 09:12:31 -0500386 SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gTransformAttachers));
387 xformed_group = gTransformAttachers[info->fAttacherIndex](s, ctx, xformed_group);
388 } break;
Florin Malita094ccde2017-12-30 12:27:00 -0500389 }
390 }
391
Florin Malita2a8275b2018-01-02 12:52:43 -0500392 if (draws.empty()) {
393 return nullptr;
Florin Malita094ccde2017-12-30 12:27:00 -0500394 }
395
Florin Malitae6345d92018-01-03 23:37:54 -0500396 for (auto draw = draws.rbegin(); draw != draws.rend(); ++draw) {
397 shape_group->addChild(std::move(*draw));
Florin Malita2a8275b2018-01-02 12:52:43 -0500398 }
399
Florin Malitae6345d92018-01-03 23:37:54 -0500400 LOG("** Attached shape: %zd draws.\n", draws.size());
Florin Malitadacc02b2017-12-31 09:12:31 -0500401 return xformed_group;
Florin Malita094ccde2017-12-30 12:27:00 -0500402}
403
404sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& layer, AttachContext* ctx) {
405 SkASSERT(layer.isObject());
406
407 auto refId = ParseString(layer["refId"], "");
408 if (refId.isEmpty()) {
409 LOG("!! Comp layer missing refId\n");
410 return nullptr;
411 }
412
413 const auto* comp = ctx->fAssets.find(refId);
414 if (!comp) {
415 LOG("!! Pre-comp not found: '%s'\n", refId.c_str());
416 return nullptr;
417 }
418
419 // TODO: cycle detection
420 return AttachComposition(**comp, ctx);
421}
422
423sk_sp<sksg::RenderNode> AttachSolidLayer(const Json::Value& layer, AttachContext*) {
424 SkASSERT(layer.isObject());
425
426 LOG("?? Solid layer stub\n");
427 return nullptr;
428}
429
430sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext*) {
431 SkASSERT(layer.isObject());
432
433 LOG("?? Image layer stub\n");
434 return nullptr;
435}
436
437sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*) {
438 SkASSERT(layer.isObject());
439
440 LOG("?? Null layer stub\n");
441 return nullptr;
442}
443
444sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext* ctx) {
445 SkASSERT(layer.isObject());
446
447 LOG("** Attaching shape layer ind: %d\n", ParseInt(layer["ind"], 0));
448
449 return AttachShape(layer["shapes"], ctx);
450}
451
452sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*) {
453 SkASSERT(layer.isObject());
454
455 LOG("?? Text layer stub\n");
456 return nullptr;
457}
458
459sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& layer, AttachContext* ctx) {
460 if (!layer.isObject())
461 return nullptr;
462
463 using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
464 static constexpr LayerAttacher gLayerAttachers[] = {
465 AttachCompLayer, // 'ty': 0
466 AttachSolidLayer, // 'ty': 1
467 AttachImageLayer, // 'ty': 2
468 AttachNullLayer, // 'ty': 3
469 AttachShapeLayer, // 'ty': 4
470 AttachTextLayer, // 'ty': 5
471 };
472
473 int type = ParseInt(layer["ty"], -1);
474 if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
475 return nullptr;
476 }
477
478 return AttachTransform(layer["ks"], ctx, gLayerAttachers[type](layer, ctx));
479}
480
481sk_sp<sksg::RenderNode> AttachComposition(const Json::Value& comp, AttachContext* ctx) {
482 if (!comp.isObject())
483 return nullptr;
484
Florin Malita2a8275b2018-01-02 12:52:43 -0500485 SkSTArray<16, sk_sp<sksg::RenderNode>, true> layers;
Florin Malita094ccde2017-12-30 12:27:00 -0500486
487 for (const auto& l : comp["layers"]) {
488 if (auto layer_fragment = AttachLayer(l, ctx)) {
Florin Malita2a8275b2018-01-02 12:52:43 -0500489 layers.push_back(std::move(layer_fragment));
Florin Malita094ccde2017-12-30 12:27:00 -0500490 }
491 }
492
Florin Malita2a8275b2018-01-02 12:52:43 -0500493 if (layers.empty()) {
494 return nullptr;
495 }
496
497 // Layers are painted in bottom->top order.
498 auto comp_group = sksg::Group::Make();
499 for (int i = layers.count() - 1; i >= 0; --i) {
500 comp_group->addChild(std::move(layers[i]));
501 }
502
503 LOG("** Attached composition '%s': %d layers.\n",
504 ParseString(comp["id"], "").c_str(), layers.count());
505
Florin Malita094ccde2017-12-30 12:27:00 -0500506 return comp_group;
507}
508
509} // namespace
510
511std::unique_ptr<Animation> Animation::Make(SkStream* stream) {
512 if (!stream->hasLength()) {
513 // TODO: handle explicit buffering?
514 LOG("!! cannot parse streaming content\n");
515 return nullptr;
516 }
517
518 Json::Value json;
519 {
520 auto data = SkData::MakeFromStream(stream, stream->getLength());
521 if (!data) {
522 LOG("!! could not read stream\n");
523 return nullptr;
524 }
525
526 Json::Reader reader;
527
528 auto dataStart = static_cast<const char*>(data->data());
529 if (!reader.parse(dataStart, dataStart + data->size(), json, false) || !json.isObject()) {
530 LOG("!! failed to parse json: %s\n", reader.getFormattedErrorMessages().c_str());
531 return nullptr;
532 }
533 }
534
535 const auto version = ParseString(json["v"], "");
536 const auto size = SkSize::Make(ParseScalar(json["w"], -1), ParseScalar(json["h"], -1));
537 const auto fps = ParseScalar(json["fr"], -1);
538
539 if (size.isEmpty() || version.isEmpty() || fps < 0) {
540 LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
541 version.c_str(), size.width(), size.height(), fps);
542 return nullptr;
543 }
544
545 return std::unique_ptr<Animation>(new Animation(std::move(version), size, fps, json));
546}
547
548Animation::Animation(SkString version, const SkSize& size, SkScalar fps, const Json::Value& json)
549 : fVersion(std::move(version))
550 , fSize(size)
551 , fFrameRate(fps)
552 , fInPoint(ParseScalar(json["ip"], 0))
553 , fOutPoint(SkTMax(ParseScalar(json["op"], SK_ScalarMax), fInPoint)) {
554
555 AssetMap assets;
556 for (const auto& asset : json["assets"]) {
557 if (!asset.isObject()) {
558 continue;
559 }
560
561 assets.set(ParseString(asset["id"], ""), &asset);
562 }
563
564 AttachContext ctx = { assets, fAnimators };
565 fDom = AttachComposition(json, &ctx);
566
567 LOG("** Attached %d animators\n", fAnimators.count());
568}
569
570Animation::~Animation() = default;
571
572void Animation::render(SkCanvas* canvas) const {
573 if (!fDom)
574 return;
575
576 sksg::InvalidationController ic;
577 fDom->revalidate(&ic, SkMatrix::I());
578
579 // TODO: proper inval
580 fDom->render(canvas);
581
582 if (!fShowInval)
583 return;
584
585 SkPaint fill, stroke;
586 fill.setAntiAlias(true);
587 fill.setColor(0x40ff0000);
588 stroke.setAntiAlias(true);
589 stroke.setColor(0xffff0000);
590 stroke.setStyle(SkPaint::kStroke_Style);
591
592 for (const auto& r : ic) {
593 canvas->drawRect(r, fill);
594 canvas->drawRect(r, stroke);
595 }
596}
597
598void Animation::animationTick(SkMSec ms) {
599 // 't' in the BM model really means 'frame #'
600 auto t = static_cast<float>(ms) * fFrameRate / 1000;
601
602 t = fInPoint + std::fmod(t, fOutPoint - fInPoint);
603
604 // TODO: this can be optimized quite a bit with some sorting/state tracking.
605 for (const auto& a : fAnimators) {
606 a->tick(t);
607 }
608}
609
610} // namespace skotty