aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2018-05-04 15:10:54 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-05-04 19:58:13 +0000
commitfa7e9a813e44f283b14f686eeb857d0d00735811 (patch)
tree8524acb13c15e157b644379862292f0e2f910a40 /experimental
parent0b0d93dbe432b6de4fd8df9a84e2ba3f2cc5b07e (diff)
[skottie] Switch to RapidJSON
- pull latest RapidJSON under third_party/externals/rapidjson (note: and older RS version is already pulled as part of angle2, and it is also checked in G3) - add a thin Json porting layer (SkottieJson) to isolate RS idiosyncrasies - convert Skottie to use the new helpers - parse the DOM in-place (based on local experiments this is the fastest method) Ta-da: Skottie now parses JSON ~10x faster! Change-Id: Ida9099638f88ed025fee83055c8cd8680ee27176 Reviewed-on: https://skia-review.googlesource.com/125744 Commit-Queue: Florin Malita <fmalita@chromium.org> Reviewed-by: Mike Reed <reed@google.com>
Diffstat (limited to 'experimental')
-rw-r--r--experimental/skottie/Skottie.cpp236
-rw-r--r--experimental/skottie/Skottie.h11
-rw-r--r--experimental/skottie/SkottieAnimator.cpp61
-rw-r--r--experimental/skottie/SkottieAnimator.h6
-rw-r--r--experimental/skottie/SkottieJson.cpp255
-rw-r--r--experimental/skottie/SkottieJson.h76
-rw-r--r--experimental/skottie/SkottieParser.cpp148
-rw-r--r--experimental/skottie/SkottieParser.h28
8 files changed, 476 insertions, 345 deletions
diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp
index c4e964d4ee..99549bfccd 100644
--- a/experimental/skottie/Skottie.cpp
+++ b/experimental/skottie/Skottie.cpp
@@ -8,10 +8,9 @@
#include "Skottie.h"
#include "SkCanvas.h"
-#include "SkJSONCPP.h"
#include "SkottieAdapter.h"
#include "SkottieAnimator.h"
-#include "SkottieParser.h"
+#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkData.h"
#include "SkImage.h"
@@ -53,7 +52,7 @@ namespace skottie {
namespace {
-using AssetMap = SkTHashMap<SkString, const Json::Value*>;
+using AssetMap = SkTHashMap<SkString, json::ValueRef>;
struct AttachContext {
const ResourceProvider& fResources;
@@ -62,13 +61,13 @@ struct AttachContext {
sksg::AnimatorList& fAnimators;
};
-bool LogFail(const Json::Value& json, const char* msg) {
- const auto dump = json.toStyledString();
+bool LogFail(const json::ValueRef& json, const char* msg) {
+ const auto dump = json.toString();
LOG("!! %s: %s", msg, dump.c_str());
return false;
}
-sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx,
+sk_sp<sksg::Matrix> AttachMatrix(const json::ValueRef& t, AttachContext* ctx,
sk_sp<sksg::Matrix> parentMatrix) {
if (!t.isObject())
return nullptr;
@@ -88,13 +87,13 @@ sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx,
adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
});
- auto* jrotation = &t["r"];
- if (jrotation->isNull()) {
+ auto jrotation = t["r"];
+ if (jrotation.isNull()) {
// 3d rotations have separate rx,ry,rz components. While we don't fully support them,
// we can still make use of rz.
- jrotation = &t["rz"];
+ jrotation = t["rz"];
}
- auto rotation_attached = BindProperty<ScalarValue>(*jrotation, &ctx->fAnimators,
+ auto rotation_attached = BindProperty<ScalarValue>(jrotation, &ctx->fAnimators,
[adapter](const ScalarValue& r) {
adapter->setRotation(r);
});
@@ -120,7 +119,7 @@ sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx,
return matrix;
}
-sk_sp<sksg::RenderNode> AttachOpacity(const Json::Value& jtransform, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachOpacity(const json::ValueRef& jtransform, AttachContext* ctx,
sk_sp<sksg::RenderNode> childNode) {
if (!jtransform.isObject() || !childNode)
return childNode;
@@ -129,8 +128,8 @@ sk_sp<sksg::RenderNode> AttachOpacity(const Json::Value& jtransform, AttachConte
// nodes for the extremely common case of static opaciy == 100.
const auto& opacity = jtransform["o"];
if (opacity.isObject() &&
- !ParseDefault(opacity["a"], true) &&
- ParseDefault(opacity["k"], -1) == 100) {
+ !opacity["a"].toDefault(true) &&
+ opacity["k"].toDefault<int>(-1) == 100) {
// Ignoring static full opacity.
return childNode;
}
@@ -145,9 +144,9 @@ sk_sp<sksg::RenderNode> AttachOpacity(const Json::Value& jtransform, AttachConte
return std::move(opacityNode);
}
-sk_sp<sksg::RenderNode> AttachComposition(const Json::Value&, AttachContext* ctx);
+sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef&, AttachContext* ctx);
-sk_sp<sksg::Path> AttachPath(const Json::Value& jpath, AttachContext* ctx) {
+sk_sp<sksg::Path> AttachPath(const json::ValueRef& jpath, AttachContext* ctx) {
auto path_node = sksg::Path::Make();
return BindProperty<ShapeValue>(jpath, &ctx->fAnimators,
[path_node](const ShapeValue& p) {
@@ -157,13 +156,13 @@ sk_sp<sksg::Path> AttachPath(const Json::Value& jpath, AttachContext* ctx) {
: nullptr;
}
-sk_sp<sksg::GeometryNode> AttachPathGeometry(const Json::Value& jpath, AttachContext* ctx) {
+sk_sp<sksg::GeometryNode> AttachPathGeometry(const json::ValueRef& jpath, AttachContext* ctx) {
SkASSERT(jpath.isObject());
return AttachPath(jpath["ks"], ctx);
}
-sk_sp<sksg::GeometryNode> AttachRRectGeometry(const Json::Value& jrect, AttachContext* ctx) {
+sk_sp<sksg::GeometryNode> AttachRRectGeometry(const json::ValueRef& jrect, AttachContext* ctx) {
SkASSERT(jrect.isObject());
auto rect_node = sksg::RRect::Make();
@@ -189,7 +188,7 @@ sk_sp<sksg::GeometryNode> AttachRRectGeometry(const Json::Value& jrect, AttachCo
return std::move(rect_node);
}
-sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const Json::Value& jellipse, AttachContext* ctx) {
+sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const json::ValueRef& jellipse, AttachContext* ctx) {
SkASSERT(jellipse.isObject());
auto rect_node = sksg::RRect::Make();
@@ -213,7 +212,7 @@ sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const Json::Value& jellipse, Att
return std::move(rect_node);
}
-sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const Json::Value& jstar, AttachContext* ctx) {
+sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const json::ValueRef& jstar, AttachContext* ctx) {
SkASSERT(jstar.isObject());
static constexpr PolyStarAdapter::Type gTypes[] = {
@@ -221,7 +220,7 @@ sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const Json::Value& jstar, Attac
PolyStarAdapter::Type::kPoly, // "sy": 2
};
- const auto type = ParseDefault(jstar["sy"], 0) - 1;
+ const auto type = jstar["sy"].toDefault<int>(0) - 1;
if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gTypes))) {
LogFail(jstar, "Unknown polystar type");
return nullptr;
@@ -262,7 +261,7 @@ sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const Json::Value& jstar, Attac
return std::move(path_node);
}
-sk_sp<sksg::Color> AttachColor(const Json::Value& obj, AttachContext* ctx) {
+sk_sp<sksg::Color> AttachColor(const json::ValueRef& obj, AttachContext* ctx) {
SkASSERT(obj.isObject());
auto color_node = sksg::Color::Make(SK_ColorBLACK);
@@ -274,21 +273,21 @@ sk_sp<sksg::Color> AttachColor(const Json::Value& obj, AttachContext* ctx) {
return color_attached ? color_node : nullptr;
}
-sk_sp<sksg::Gradient> AttachGradient(const Json::Value& obj, AttachContext* ctx) {
+sk_sp<sksg::Gradient> AttachGradient(const json::ValueRef& obj, AttachContext* ctx) {
SkASSERT(obj.isObject());
const auto& stops = obj["g"];
if (!stops.isObject())
return nullptr;
- const auto stopCount = ParseDefault(stops["p"], -1);
+ const auto stopCount = stops["p"].toDefault<int>(-1);
if (stopCount < 0)
return nullptr;
sk_sp<sksg::Gradient> gradient_node;
sk_sp<GradientAdapter> adapter;
- if (ParseDefault(obj["t"], 1) == 1) {
+ if (obj["t"].toDefault<int>(1) == 1) {
auto linear_node = sksg::LinearGradient::Make();
adapter = sk_make_sp<LinearGradientAdapter>(linear_node, stopCount);
gradient_node = std::move(linear_node);
@@ -316,7 +315,7 @@ sk_sp<sksg::Gradient> AttachGradient(const Json::Value& obj, AttachContext* ctx)
return gradient_node;
}
-sk_sp<sksg::PaintNode> AttachPaint(const Json::Value& jpaint, AttachContext* ctx,
+sk_sp<sksg::PaintNode> AttachPaint(const json::ValueRef& jpaint, AttachContext* ctx,
sk_sp<sksg::PaintNode> paint_node) {
if (paint_node) {
paint_node->setAntiAlias(true);
@@ -331,7 +330,7 @@ sk_sp<sksg::PaintNode> AttachPaint(const Json::Value& jpaint, AttachContext* ctx
return paint_node;
}
-sk_sp<sksg::PaintNode> AttachStroke(const Json::Value& jstroke, AttachContext* ctx,
+sk_sp<sksg::PaintNode> AttachStroke(const json::ValueRef& jstroke, AttachContext* ctx,
sk_sp<sksg::PaintNode> stroke_node) {
SkASSERT(jstroke.isObject());
@@ -347,14 +346,14 @@ sk_sp<sksg::PaintNode> AttachStroke(const Json::Value& jstroke, AttachContext* c
if (!width_attached)
return nullptr;
- stroke_node->setStrokeMiter(ParseDefault(jstroke["ml"], 4.0f));
+ stroke_node->setStrokeMiter(jstroke["ml"].toDefault(4.0f));
static constexpr SkPaint::Join gJoins[] = {
SkPaint::kMiter_Join,
SkPaint::kRound_Join,
SkPaint::kBevel_Join,
};
- stroke_node->setStrokeJoin(gJoins[SkTPin<int>(ParseDefault(jstroke["lj"], 1) - 1,
+ stroke_node->setStrokeJoin(gJoins[SkTPin<int>(jstroke["lj"].toDefault<int>(1) - 1,
0, SK_ARRAY_COUNT(gJoins) - 1)]);
static constexpr SkPaint::Cap gCaps[] = {
@@ -362,38 +361,38 @@ sk_sp<sksg::PaintNode> AttachStroke(const Json::Value& jstroke, AttachContext* c
SkPaint::kRound_Cap,
SkPaint::kSquare_Cap,
};
- stroke_node->setStrokeCap(gCaps[SkTPin<int>(ParseDefault(jstroke["lc"], 1) - 1,
+ stroke_node->setStrokeCap(gCaps[SkTPin<int>(jstroke["lc"].toDefault<int>(1) - 1,
0, SK_ARRAY_COUNT(gCaps) - 1)]);
return stroke_node;
}
-sk_sp<sksg::PaintNode> AttachColorFill(const Json::Value& jfill, AttachContext* ctx) {
+sk_sp<sksg::PaintNode> AttachColorFill(const json::ValueRef& jfill, AttachContext* ctx) {
SkASSERT(jfill.isObject());
return AttachPaint(jfill, ctx, AttachColor(jfill, ctx));
}
-sk_sp<sksg::PaintNode> AttachGradientFill(const Json::Value& jfill, AttachContext* ctx) {
+sk_sp<sksg::PaintNode> AttachGradientFill(const json::ValueRef& jfill, AttachContext* ctx) {
SkASSERT(jfill.isObject());
return AttachPaint(jfill, ctx, AttachGradient(jfill, ctx));
}
-sk_sp<sksg::PaintNode> AttachColorStroke(const Json::Value& jstroke, AttachContext* ctx) {
+sk_sp<sksg::PaintNode> AttachColorStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
SkASSERT(jstroke.isObject());
return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachColor(jstroke, ctx)));
}
-sk_sp<sksg::PaintNode> AttachGradientStroke(const Json::Value& jstroke, AttachContext* ctx) {
+sk_sp<sksg::PaintNode> AttachGradientStroke(const json::ValueRef& jstroke, AttachContext* ctx) {
SkASSERT(jstroke.isObject());
return AttachStroke(jstroke, ctx, AttachPaint(jstroke, ctx, AttachGradient(jstroke, ctx)));
}
std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
- const Json::Value& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+ const json::ValueRef& jmerge, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
std::vector<sk_sp<sksg::GeometryNode>> merged;
static constexpr sksg::Merge::Mode gModes[] = {
@@ -404,7 +403,7 @@ std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
sksg::Merge::Mode::kXOR , // "mm": 5
};
- const auto mode = gModes[SkTPin<int>(ParseDefault(jmerge["mm"], 1) - 1,
+ const auto mode = gModes[SkTPin<int>(jmerge["mm"].toDefault(1) - 1,
0, SK_ARRAY_COUNT(gModes) - 1)];
merged.push_back(sksg::Merge::Make(std::move(geos), mode));
@@ -412,14 +411,14 @@ std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
}
std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
- const Json::Value& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+ const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
enum class Mode {
kMerged, // "m": 1
kSeparate, // "m": 2
} gModes[] = { Mode::kMerged, Mode::kSeparate };
- const auto mode = gModes[SkTPin<int>(ParseDefault(jtrim["m"], 1) - 1,
+ const auto mode = gModes[SkTPin<int>(jtrim["m"].toDefault(1) - 1,
0, SK_ARRAY_COUNT(gModes) - 1)];
std::vector<sk_sp<sksg::GeometryNode>> inputs;
@@ -454,7 +453,7 @@ std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
}
std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
- const Json::Value& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
+ const json::ValueRef& jtrim, AttachContext* ctx, std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
std::vector<sk_sp<sksg::GeometryNode>> rounded;
rounded.reserve(geos.size());
@@ -472,7 +471,7 @@ std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
return rounded;
}
-using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const Json::Value&, AttachContext*);
+using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const json::ValueRef&, AttachContext*);
static constexpr GeometryAttacherT gGeometryAttachers[] = {
AttachPathGeometry,
AttachRRectGeometry,
@@ -480,7 +479,7 @@ static constexpr GeometryAttacherT gGeometryAttachers[] = {
AttachPolystarGeometry,
};
-using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const Json::Value&, AttachContext*);
+using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const json::ValueRef&, AttachContext*);
static constexpr PaintAttacherT gPaintAttachers[] = {
AttachColorFill,
AttachColorStroke,
@@ -489,7 +488,7 @@ static constexpr PaintAttacherT gPaintAttachers[] = {
};
using GeometryEffectAttacherT =
- std::vector<sk_sp<sksg::GeometryNode>> (*)(const Json::Value&,
+ std::vector<sk_sp<sksg::GeometryNode>> (*)(const json::ValueRef&,
AttachContext*,
std::vector<sk_sp<sksg::GeometryNode>>&&);
static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
@@ -512,7 +511,7 @@ struct ShapeInfo {
uint32_t fAttacherIndex; // index into respective attacher tables
};
-const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
+const ShapeInfo* FindShapeInfo(const json::ValueRef& shape) {
static constexpr ShapeInfo gShapeInfo[] = {
{ "el", ShapeType::kGeometry , 2 }, // ellipse -> AttachEllipseGeometry
{ "fl", ShapeType::kPaint , 0 }, // fill -> AttachColorFill
@@ -532,11 +531,11 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
if (!shape.isObject())
return nullptr;
- const auto& type = shape["ty"];
- if (!type.isString())
+ const auto type = shape["ty"].toDefault(SkString());
+ if (type.isEmpty())
return nullptr;
- const auto* info = bsearch(type.asCString(),
+ const auto* info = bsearch(type.c_str(),
gShapeInfo,
SK_ARRAY_COUNT(gShapeInfo),
sizeof(ShapeInfo),
@@ -549,7 +548,7 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
}
struct GeometryEffectRec {
- const Json::Value& fJson;
+ const json::ValueRef fJson;
GeometryEffectAttacherT fAttach;
};
@@ -569,7 +568,7 @@ struct AttachShapeContext {
size_t fCommittedAnimators;
};
-sk_sp<sksg::RenderNode> AttachShape(const Json::Value& jshape, AttachShapeContext* shapeCtx) {
+sk_sp<sksg::RenderNode> AttachShape(const json::ValueRef& jshape, AttachShapeContext* shapeCtx) {
if (!jshape.isArray())
return nullptr;
@@ -580,7 +579,7 @@ sk_sp<sksg::RenderNode> AttachShape(const Json::Value& jshape, AttachShapeContex
sk_sp<sksg::Matrix> shape_matrix;
struct ShapeRec {
- const Json::Value& fJson;
+ const json::ValueRef fJson;
const ShapeInfo& fInfo;
};
@@ -591,8 +590,8 @@ sk_sp<sksg::RenderNode> AttachShape(const Json::Value& jshape, AttachShapeContex
// * store recs for next pass
//
std::vector<ShapeRec> recs;
- for (Json::ArrayIndex i = 0; i < jshape.size(); ++i) {
- const auto& s = jshape[jshape.size() - 1 - i];
+ for (size_t i = 0; i < jshape.size(); ++i) {
+ const auto s = jshape[jshape.size() - 1 - i];
const auto* info = FindShapeInfo(s);
if (!info) {
LogFail(s.isObject() ? s["ty"] : s, "Unknown shape");
@@ -765,18 +764,18 @@ sk_sp<sksg::RenderNode> AttachNestedAnimation(const char* path, AttachContext* c
return sk_make_sp<SkottieSGAdapter>(std::move(animation));
}
-sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& jlayer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachCompLayer(const json::ValueRef& jlayer, AttachContext* ctx,
float* time_bias, float* time_scale) {
SkASSERT(jlayer.isObject());
SkString refId;
- if (!Parse(jlayer["refId"], &refId) || refId.isEmpty()) {
+ if (!jlayer["refId"].to(&refId) || refId.isEmpty()) {
LOG("!! Comp layer missing refId\n");
return nullptr;
}
- const auto start_time = ParseDefault(jlayer["st"], 0.0f),
- stretch_time = ParseDefault(jlayer["sr"], 1.0f);
+ const auto start_time = jlayer["st"].toDefault(0.0f),
+ stretch_time = jlayer["sr"].toDefault(1.0f);
*time_bias = -start_time;
*time_scale = 1 / stretch_time;
@@ -795,16 +794,16 @@ sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& jlayer, AttachContext
}
// TODO: cycle detection
- return AttachComposition(**comp, ctx);
+ return AttachComposition(*comp, ctx);
}
-sk_sp<sksg::RenderNode> AttachSolidLayer(const Json::Value& jlayer, AttachContext*,
+sk_sp<sksg::RenderNode> AttachSolidLayer(const json::ValueRef& jlayer, AttachContext*,
float*, float*) {
SkASSERT(jlayer.isObject());
- const auto size = SkSize::Make(ParseDefault(jlayer["sw"], 0.0f),
- ParseDefault(jlayer["sh"], 0.0f));
- const auto hex = ParseDefault(jlayer["sc"], SkString());
+ const auto size = SkSize::Make(jlayer["sw"].toDefault(0.0f),
+ jlayer["sh"].toDefault(0.0f));
+ const auto hex = jlayer["sc"].toDefault(SkString());
uint32_t c;
if (size.isEmpty() ||
!hex.startsWith("#") ||
@@ -819,11 +818,11 @@ sk_sp<sksg::RenderNode> AttachSolidLayer(const Json::Value& jlayer, AttachContex
sksg::Color::Make(color));
}
-sk_sp<sksg::RenderNode> AttachImageAsset(const Json::Value& jimage, AttachContext* ctx) {
+sk_sp<sksg::RenderNode> AttachImageAsset(const json::ValueRef& jimage, AttachContext* ctx) {
SkASSERT(jimage.isObject());
- const auto name = ParseDefault(jimage["p"], SkString()),
- path = ParseDefault(jimage["u"], SkString());
+ const auto name = jimage["p"].toDefault(SkString()),
+ path = jimage["u"].toDefault(SkString());
if (name.isEmpty())
return nullptr;
@@ -840,12 +839,12 @@ sk_sp<sksg::RenderNode> AttachImageAsset(const Json::Value& jimage, AttachContex
SkImage::MakeFromEncoded(SkData::MakeFromStream(resStream.get(), resStream->getLength())));
}
-sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachImageLayer(const json::ValueRef& layer, AttachContext* ctx,
float*, float*) {
SkASSERT(layer.isObject());
SkString refId;
- if (!Parse(layer["refId"], &refId) || refId.isEmpty()) {
+ if (!layer["refId"].to(&refId) || refId.isEmpty()) {
LOG("!! Image layer missing refId\n");
return nullptr;
}
@@ -856,10 +855,10 @@ sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext
return nullptr;
}
- return AttachImageAsset(**jimage, ctx);
+ return AttachImageAsset(*jimage, ctx);
}
-sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*, float*, float*) {
+sk_sp<sksg::RenderNode> AttachNullLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
SkASSERT(layer.isObject());
// Null layers are used solely to drive dependent transforms,
@@ -867,7 +866,7 @@ sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*
return nullptr;
}
-sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext* ctx,
+sk_sp<sksg::RenderNode> AttachShapeLayer(const json::ValueRef& layer, AttachContext* ctx,
float*, float*) {
SkASSERT(layer.isObject());
@@ -886,7 +885,7 @@ sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext
return shapeNode;
}
-sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*, float*, float*) {
+sk_sp<sksg::RenderNode> AttachTextLayer(const json::ValueRef& layer, AttachContext*, float*, float*) {
SkASSERT(layer.isObject());
LOG("?? Text layer stub\n");
@@ -894,42 +893,40 @@ sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*
}
struct AttachLayerContext {
- AttachLayerContext(const Json::Value& jlayers, AttachContext* ctx)
+ AttachLayerContext(const json::ValueRef& jlayers, AttachContext* ctx)
: fLayerList(jlayers), fCtx(ctx) {}
- const Json::Value& fLayerList;
+ const json::ValueRef fLayerList;
AttachContext* fCtx;
SkTHashMap<int, sk_sp<sksg::Matrix>> fLayerMatrixMap;
sk_sp<sksg::RenderNode> fCurrentMatte;
- sk_sp<sksg::Matrix> AttachParentLayerMatrix(const Json::Value& jlayer) {
+ sk_sp<sksg::Matrix> AttachParentLayerMatrix(const json::ValueRef& jlayer) {
SkASSERT(jlayer.isObject());
SkASSERT(fLayerList.isArray());
- const auto parent_index = ParseDefault(jlayer["parent"], -1);
+ const auto parent_index = jlayer["parent"].toDefault<int>(-1);
if (parent_index < 0)
return nullptr;
if (auto* m = fLayerMatrixMap.find(parent_index))
return *m;
- for (const auto& l : fLayerList) {
- if (!l.isObject()) {
- continue;
- }
-
- if (ParseDefault(l["ind"], -1) == parent_index) {
- return this->AttachLayerMatrix(l);
+ sk_sp<sksg::Matrix> matrix;
+ for (const json::ValueRef l : fLayerList) {
+ if (l["ind"].toDefault<int>(-1) == parent_index) {
+ matrix = this->AttachLayerMatrix(l);
+ break;
}
}
return nullptr;
}
- sk_sp<sksg::Matrix> AttachLayerMatrix(const Json::Value& jlayer) {
+ sk_sp<sksg::Matrix> AttachLayerMatrix(const json::ValueRef& jlayer) {
SkASSERT(jlayer.isObject());
- const auto layer_index = ParseDefault(jlayer["ind"], -1);
+ const auto layer_index = jlayer["ind"].toDefault<int>(-1);
if (layer_index < 0)
return nullptr;
@@ -962,7 +959,7 @@ SkBlendMode MaskBlendMode(char mode) {
return SkBlendMode::kSrcOver;
}
-sk_sp<sksg::RenderNode> AttachMask(const Json::Value& jmask,
+sk_sp<sksg::RenderNode> AttachMask(const json::ValueRef& jmask,
AttachContext* ctx,
sk_sp<sksg::RenderNode> childNode) {
if (!jmask.isArray())
@@ -977,7 +974,7 @@ sk_sp<sksg::RenderNode> AttachMask(const Json::Value& jmask,
bool opaque_mask = true;
- for (const auto& m : jmask) {
+ for (const json::ValueRef m : jmask) {
if (!m.isObject())
continue;
@@ -987,12 +984,12 @@ sk_sp<sksg::RenderNode> AttachMask(const Json::Value& jmask,
continue;
}
- mask_path->setFillType(ParseDefault(m["inv"], false)
+ mask_path->setFillType(m["inv"].toDefault(false)
? SkPath::kInverseWinding_FillType
: SkPath::kWinding_FillType);
SkString mode;
- if (!Parse(m["mode"], &mode) ||
+ if (!m["mode"].to(&mode) ||
mode.size() != 1 ||
!strcmp(mode.c_str(), "n")) { // "None" masks have no effect.
continue;
@@ -1031,12 +1028,11 @@ sk_sp<sksg::RenderNode> AttachMask(const Json::Value& jmask,
return sksg::MaskEffect::Make(std::move(childNode), std::move(mask_group));
}
-sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
- AttachLayerContext* layerCtx) {
+sk_sp<sksg::RenderNode> AttachLayer(const json::ValueRef& jlayer, AttachLayerContext* layerCtx) {
if (!jlayer.isObject())
return nullptr;
- using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*,
+ using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const json::ValueRef&, AttachContext*,
float* time_bias, float* time_scale);
static constexpr LayerAttacher gLayerAttachers[] = {
AttachCompLayer, // 'ty': 0
@@ -1047,7 +1043,7 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
AttachTextLayer, // 'ty': 5
};
- int type = ParseDefault(jlayer["ty"], -1);
+ int type = jlayer["ty"].toDefault<int>(-1);
if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
return nullptr;
}
@@ -1066,8 +1062,8 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
auto layer = gLayerAttachers[type](jlayer, &local_ctx, &time_bias, &time_scale);
// Clip layers with explicit dimensions.
- float w, h;
- if (Parse(jlayer["w"], &w) && Parse(jlayer["h"], &h)) {
+ float w = 0, h = 0;
+ if (jlayer["w"].to(&w) && jlayer["h"].to(&h)) {
layer = sksg::ClipEffect::Make(std::move(layer),
sksg::Rect::Make(SkRect::MakeWH(w, h)),
true);
@@ -1120,8 +1116,8 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
};
auto controller_node = sksg::OpacityEffect::Make(std::move(layer));
- const auto in = ParseDefault(jlayer["ip"], 0.0f),
- out = ParseDefault(jlayer["op"], in);
+ const auto in = jlayer["ip"].toDefault(0.0f),
+ out = jlayer["op"].toDefault(in);
if (!jlayer["tm"].isNull()) {
LogFail(jlayer["tm"], "Unsupported time remapping");
@@ -1138,7 +1134,7 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
time_bias,
time_scale));
- if (ParseDefault(jlayer["td"], false)) {
+ if (jlayer["td"].toDefault(false)) {
// This layer is a matte. We apply it as a mask to the next layer.
layerCtx->fCurrentMatte = std::move(controller_node);
return nullptr;
@@ -1150,7 +1146,7 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
sksg::MaskEffect::Mode::kNormal, // tt: 1
sksg::MaskEffect::Mode::kInvert, // tt: 2
};
- const auto matteType = ParseDefault(jlayer["tt"], 1) - 1;
+ const auto matteType = jlayer["tt"].toDefault<int>(1) - 1;
if (matteType >= 0 && matteType < SkTo<int>(SK_ARRAY_COUNT(gMaskModes))) {
return sksg::MaskEffect::Make(std::move(controller_node),
@@ -1163,18 +1159,18 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer,
return std::move(controller_node);
}
-sk_sp<sksg::RenderNode> AttachComposition(const Json::Value& comp, AttachContext* ctx) {
+sk_sp<sksg::RenderNode> AttachComposition(const json::ValueRef& comp, AttachContext* ctx) {
if (!comp.isObject())
return nullptr;
- const auto& jlayers = comp["layers"];
+ const auto jlayers = comp["layers"];
if (!jlayers.isArray())
return nullptr;
SkSTArray<16, sk_sp<sksg::RenderNode>, true> layers;
AttachLayerContext layerCtx(jlayers, ctx);
- for (const auto& l : jlayers) {
+ for (const json::ValueRef l : jlayers) {
if (auto layer_fragment = AttachLayer(l, &layerCtx)) {
layers.push_back(std::move(layer_fragment));
}
@@ -1207,33 +1203,21 @@ sk_sp<Animation> Animation::Make(SkStream* stream, const ResourceProvider& res,
return nullptr;
}
+ stats->fJsonSize = stream->getLength();
const auto t0 = SkTime::GetMSecs();
- Json::Value json;
- {
- auto data = SkData::MakeFromStream(stream, stream->getLength());
- if (!data) {
- LOG("!! could not read stream\n");
- return nullptr;
- }
- stats->fJsonSize = data->size();
-
- Json::Reader reader;
-
- auto dataStart = static_cast<const char*>(data->data());
- if (!reader.parse(dataStart, dataStart + data->size(), json, false) || !json.isObject()) {
- LOG("!! failed to parse json: %s\n", reader.getFormattedErrorMessages().c_str());
- return nullptr;
- }
- }
+ const json::Document doc(stream);
+ const auto json = doc.root();
+ if (!json.isObject())
+ return nullptr;
const auto t1 = SkTime::GetMSecs();
stats->fJsonParseTimeMS = t1 - t0;
- const auto version = ParseDefault(json["v"], SkString());
- const auto size = SkSize::Make(ParseDefault(json["w"], 0.0f),
- ParseDefault(json["h"], 0.0f));
- const auto fps = ParseDefault(json["fr"], -1.0f);
+ const auto version = json["v"].toDefault(SkString());
+ const auto size = SkSize::Make(json["w"].toDefault(0.0f),
+ json["h"].toDefault(0.0f));
+ const auto fps = json["fr"].toDefault(-1.0f);
if (size.isEmpty() || version.isEmpty() || fps <= 0) {
LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)",
@@ -1278,21 +1262,19 @@ sk_sp<Animation> Animation::MakeFromFile(const char path[], const ResourceProvid
}
Animation::Animation(const ResourceProvider& resources,
- SkString version, const SkSize& size, SkScalar fps, const Json::Value& json,
+ SkString version, const SkSize& size, SkScalar fps, const json::ValueRef& json,
Stats* stats)
: fVersion(std::move(version))
, fSize(size)
, fFrameRate(fps)
- , fInPoint(ParseDefault(json["ip"], 0.0f))
- , fOutPoint(SkTMax(ParseDefault(json["op"], SK_ScalarMax), fInPoint)) {
+ , fInPoint(json["ip"].toDefault(0.0f))
+ , fOutPoint(SkTMax(json["op"].toDefault(SK_ScalarMax), fInPoint)) {
AssetMap assets;
- for (const auto& asset : json["assets"]) {
- if (!asset.isObject()) {
- continue;
+ for (const json::ValueRef asset : json["assets"]) {
+ if (asset.isObject()) {
+ assets.set(asset["id"].toDefault(SkString()), asset);
}
-
- assets.set(ParseDefault(asset["id"], SkString()), &asset);
}
sksg::AnimatorList animators;
diff --git a/experimental/skottie/Skottie.h b/experimental/skottie/Skottie.h
index 44541fd81b..275127aa4e 100644
--- a/experimental/skottie/Skottie.h
+++ b/experimental/skottie/Skottie.h
@@ -11,8 +11,6 @@
#include "SkRefCnt.h"
#include "SkSize.h"
#include "SkString.h"
-#include "SkTArray.h"
-#include "SkTHash.h"
#include "SkTypes.h"
#include <memory>
@@ -21,12 +19,12 @@ class SkCanvas;
struct SkRect;
class SkStream;
-namespace Json { class Value; }
-
namespace sksg { class Scene; }
namespace skottie {
+namespace json { class ValueRef; }
+
class ResourceProvider : public SkNoncopyable {
public:
virtual ~ResourceProvider() = default;
@@ -63,9 +61,8 @@ public:
void setShowInval(bool show);
private:
- Animation(const ResourceProvider&,
- SkString ver, const SkSize& size, SkScalar fps,
- const Json::Value&, Stats*);
+ Animation(const ResourceProvider&, SkString ver, const SkSize& size, SkScalar fps,
+ const json::ValueRef&, Stats*);
SkString fVersion;
SkSize fSize;
diff --git a/experimental/skottie/SkottieAnimator.cpp b/experimental/skottie/SkottieAnimator.cpp
index 34803e05b5..d8cc6e7769 100644
--- a/experimental/skottie/SkottieAnimator.cpp
+++ b/experimental/skottie/SkottieAnimator.cpp
@@ -8,9 +8,9 @@
#include "SkottieAnimator.h"
#include "SkCubicMap.h"
-#include "SkJSONCPP.h"
+#include "SkottieJson.h"
#include "SkottieValue.h"
-#include "SkottieParser.h"
+#include "SkString.h"
#include "SkTArray.h"
#include <memory>
@@ -21,9 +21,9 @@ namespace {
#define LOG SkDebugf
-bool LogFail(const Json::Value& json, const char* msg) {
- const auto dump = json.toStyledString();
- LOG("!! %s: %s", msg, dump.c_str());
+bool LogFail(const json::ValueRef& json, const char* msg) {
+ const auto dump = json.toString();
+ LOG("!! %s: %s\n", msg, dump.c_str());
return false;
}
@@ -67,20 +67,19 @@ protected:
: SkTPin(fCubicMaps[rec.cmidx].computeYFromX(lt), 0.0f, 1.0f);
}
- virtual int parseValue(const Json::Value&) = 0;
+ virtual int parseValue(const json::ValueRef&) = 0;
- void parseKeyFrames(const Json::Value& jframes) {
+ void parseKeyFrames(const json::ValueRef& jframes) {
if (!jframes.isArray())
return;
- for (const auto& jframe : jframes) {
+ for (const json::ValueRef jframe : jframes) {
if (!jframe.isObject())
continue;
float t0;
- if (!Parse(jframe["t"], &t0)) {
+ if (!jframe["t"].to(&t0))
continue;
- }
if (!fRecs.empty()) {
if (fRecs.back().t1 >= t0) {
@@ -93,25 +92,23 @@ protected:
}
const auto vidx0 = this->parseValue(jframe["s"]);
- if (vidx0 < 0) {
+ if (vidx0 < 0)
continue;
- }
// Defaults for constant frames.
int vidx1 = vidx0, cmidx = -1;
- if (!ParseDefault(jframe["h"], false)) {
+ if (!jframe["h"].toDefault(false)) {
// Regular frame, requires an end value.
vidx1 = this->parseValue(jframe["e"]);
- if (vidx1 < 0) {
+ if (vidx1 < 0)
continue;
- }
// default is linear lerp
static constexpr SkPoint kDefaultC0 = { 0, 0 },
kDefaultC1 = { 1, 1 };
- const auto c0 = ParseDefault(jframe["i"], kDefaultC0),
- c1 = ParseDefault(jframe["o"], kDefaultC1);
+ const auto c0 = jframe["i"].toDefault(kDefaultC0),
+ c1 = jframe["o"].toDefault(kDefaultC1);
if (c0 != kDefaultC0 || c1 != kDefaultC1) {
// TODO: is it worth de-duping these?
@@ -181,7 +178,7 @@ private:
template <typename T>
class KeyframeAnimator final : public KeyframeAnimatorBase {
public:
- static std::unique_ptr<KeyframeAnimator> Make(const Json::Value& jframes,
+ static std::unique_ptr<KeyframeAnimator> Make(const json::ValueRef& jframes,
std::function<void(const T&)>&& apply) {
std::unique_ptr<KeyframeAnimator> animator(new KeyframeAnimator(jframes, std::move(apply)));
if (!animator->count())
@@ -199,15 +196,15 @@ protected:
}
private:
- KeyframeAnimator(const Json::Value& jframes,
+ KeyframeAnimator(const json::ValueRef& jframes,
std::function<void(const T&)>&& apply)
: fApplyFunc(std::move(apply)) {
this->parseKeyFrames(jframes);
}
- int parseValue(const Json::Value& jv) override {
+ int parseValue(const json::ValueRef& jv) override {
T val;
- if (!Parse(jv, &val) || (!fVs.empty() &&
+ if (!jv.to(&val) || (!fVs.empty() &&
ValueTraits<T>::Cardinality(val) != ValueTraits<T>::Cardinality(fVs.back()))) {
return -1;
}
@@ -241,21 +238,21 @@ private:
};
template <typename T>
-static inline bool BindPropertyImpl(const Json::Value& jprop,
+static inline bool BindPropertyImpl(const json::ValueRef& jprop,
sksg::AnimatorList* animators,
std::function<void(const T&)>&& apply,
const T* noop = nullptr) {
if (!jprop.isObject())
return false;
- const auto& jpropA = jprop["a"];
- const auto& jpropK = jprop["k"];
+ const auto jpropA = jprop["a"];
+ const auto jpropK = jprop["k"];
// Older Json versions don't have an "a" animation marker.
// For those, we attempt to parse both ways.
- if (!ParseDefault(jpropA, false)) {
+ if (!jpropA.toDefault(false)) {
T val;
- if (Parse<T>(jpropK, &val)) {
+ if (jpropK.to<T>(&val)) {
// Static property.
if (noop && val == *noop)
return false;
@@ -283,7 +280,7 @@ static inline bool BindPropertyImpl(const Json::Value& jprop,
class SplitPointAnimator final : public sksg::Animator {
public:
- static std::unique_ptr<SplitPointAnimator> Make(const Json::Value& jprop,
+ static std::unique_ptr<SplitPointAnimator> Make(const json::ValueRef& jprop,
std::function<void(const VectorValue&)>&& apply,
const VectorValue*) {
if (!jprop.isObject())
@@ -337,7 +334,7 @@ private:
using INHERITED = sksg::Animator;
};
-bool BindSplitPositionProperty(const Json::Value& jprop,
+bool BindSplitPositionProperty(const json::ValueRef& jprop,
sksg::AnimatorList* animators,
std::function<void(const VectorValue&)>&& apply,
const VectorValue* noop) {
@@ -352,7 +349,7 @@ bool BindSplitPositionProperty(const Json::Value& jprop,
} // namespace
template <>
-bool BindProperty(const Json::Value& jprop,
+bool BindProperty(const json::ValueRef& jprop,
sksg::AnimatorList* animators,
std::function<void(const ScalarValue&)>&& apply,
const ScalarValue* noop) {
@@ -360,17 +357,17 @@ bool BindProperty(const Json::Value& jprop,
}
template <>
-bool BindProperty(const Json::Value& jprop,
+bool BindProperty(const json::ValueRef& jprop,
sksg::AnimatorList* animators,
std::function<void(const VectorValue&)>&& apply,
const VectorValue* noop) {
- return ParseDefault(jprop["s"], false)
+ return jprop["s"].toDefault<bool>(false)
? BindSplitPositionProperty(jprop, animators, std::move(apply), noop)
: BindPropertyImpl(jprop, animators, std::move(apply), noop);
}
template <>
-bool BindProperty(const Json::Value& jprop,
+bool BindProperty(const json::ValueRef& jprop,
sksg::AnimatorList* animators,
std::function<void(const ShapeValue&)>&& apply,
const ShapeValue* noop) {
diff --git a/experimental/skottie/SkottieAnimator.h b/experimental/skottie/SkottieAnimator.h
index 77be06b809..6dc8f6c759 100644
--- a/experimental/skottie/SkottieAnimator.h
+++ b/experimental/skottie/SkottieAnimator.h
@@ -12,14 +12,14 @@
#include <functional>
-namespace Json { class Value; }
-
namespace skottie {
+namespace json { class ValueRef; }
+
// This is the workhorse for property binding: depending on whether the property is animated,
// it will either apply immediately or instantiate and attach a keyframe animator.
template <typename T>
-bool BindProperty(const Json::Value&,
+bool BindProperty(const json::ValueRef&,
sksg::AnimatorList*,
std::function<void(const T&)>&&,
const T* noop = nullptr);
diff --git a/experimental/skottie/SkottieJson.cpp b/experimental/skottie/SkottieJson.cpp
new file mode 100644
index 0000000000..7b2c6ae985
--- /dev/null
+++ b/experimental/skottie/SkottieJson.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkottieJson.h"
+
+#include "SkData.h"
+#include "SkScalar.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkottieValue.h"
+
+#include "rapidjson/error/en.h"
+#include "rapidjson/prettywriter.h"
+#include "rapidjson/stringbuffer.h"
+
+#include <vector>
+
+namespace skottie {
+
+namespace json {
+
+template <>
+bool json::ValueRef::to<SkScalar>(SkScalar* v) const {
+ if (!fValue) return false;
+
+ // Some versions wrap values as single-element arrays.
+ if (fValue->IsArray() && fValue->Size() == 1) {
+ return json::ValueRef(fValue->operator[](0)).to(v);
+ }
+
+ if (!fValue->IsNumber())
+ return false;
+
+ *v = static_cast<SkScalar>(fValue->GetDouble());
+
+ return true;
+}
+
+template <>
+bool json::ValueRef::to<bool>(bool* v) const {
+ if (!fValue) return false;
+
+ switch(fValue->GetType()) {
+ case rapidjson::kNumberType:
+ *v = SkToBool(fValue->GetDouble());
+ return true;
+ case rapidjson::kFalseType:
+ case rapidjson::kTrueType:
+ *v = fValue->GetBool();
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+template <>
+bool json::ValueRef::to<int>(int* v) const {
+ if (!fValue || !fValue->IsInt())
+ return false;
+
+ *v = fValue->GetInt();
+
+ return true;
+}
+
+template <>
+bool json::ValueRef::to<SkString>(SkString* v) const {
+ if (!fValue || !fValue->IsString())
+ return false;
+
+ v->set(fValue->GetString());
+
+ return true;
+}
+
+template <>
+bool json::ValueRef::to<SkPoint>(SkPoint* v) const {
+ if (!fValue || !fValue->IsObject())
+ return false;
+
+ const auto jvx = ValueRef(fValue->operator[]("x")),
+ jvy = ValueRef(fValue->operator[]("y"));
+
+ // Some BM versions seem to store x/y as single-element arrays.
+ return json::ValueRef(jvx.isArray() ? jvx.operator[](size_t(0)) : jvx).to(&v->fX)
+ && json::ValueRef(jvy.isArray() ? jvy.operator[](size_t(0)) : jvy).to(&v->fY);
+}
+
+template <>
+bool json::ValueRef::to<std::vector<float>>(std::vector<float>* v) const {
+ if (!fValue || !fValue->IsArray())
+ return false;
+
+ v->resize(fValue->Size());
+ for (size_t i = 0; i < fValue->Size(); ++i) {
+ if (!ValueRef(fValue->operator[](i)).to(v->data() + i)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+bool ParsePointVec(const rapidjson::Value& jv, std::vector<SkPoint>* pts) {
+ if (!jv.IsArray())
+ return false;
+
+ pts->clear();
+ pts->reserve(jv.Size());
+
+ std::vector<float> vec;
+ for (size_t i = 0; i < jv.Size(); ++i) {
+ if (!ValueRef(jv[i]).to(&vec) || vec.size() != 2)
+ return false;
+ pts->push_back(SkPoint::Make(vec[0], vec[1]));
+ }
+
+ return true;
+}
+
+} // namespace
+
+template <>
+bool json::ValueRef::to<ShapeValue>(ShapeValue* v) const {
+ SkASSERT(v->fVertices.empty());
+
+ if (!fValue)
+ return false;
+
+ // Some versions wrap values as single-element arrays.
+ if (fValue->IsArray() && fValue->Size() == 1) {
+ return json::ValueRef(fValue->operator[](0)).to(v);
+ }
+
+ std::vector<SkPoint> inPts, // Cubic Bezier "in" control points, relative to vertices.
+ outPts, // Cubic Bezier "out" control points, relative to vertices.
+ verts; // Cubic Bezier vertices.
+
+ if (!fValue->IsObject() ||
+ !ParsePointVec(fValue->operator[]("i"), &inPts) ||
+ !ParsePointVec(fValue->operator[]("o"), &outPts) ||
+ !ParsePointVec(fValue->operator[]("v"), &verts) ||
+ inPts.size() != outPts.size() ||
+ inPts.size() != verts.size()) {
+
+ return false;
+ }
+
+ v->fVertices.reserve(inPts.size());
+ for (size_t i = 0; i < inPts.size(); ++i) {
+ v->fVertices.push_back(BezierVertex({inPts[i], outPts[i], verts[i]}));
+ }
+ v->fClosed = json::ValueRef(fValue->operator[]("c")).toDefault<bool>(false);
+
+ return true;
+}
+
+size_t ValueRef::size() const {
+ return this->isArray() ? fValue->Size() : 0;
+}
+
+ValueRef ValueRef::operator[](size_t i) const {
+ return this->isArray() ? ValueRef(fValue->operator[](i)) : ValueRef();
+}
+
+ValueRef ValueRef::operator[](const char* key) const {
+ if (!this->isObject())
+ return ValueRef();
+
+ const auto m = fValue->FindMember(key);
+ return m == fValue->MemberEnd() ? ValueRef() : ValueRef(m->value);
+}
+
+const rapidjson::Value* ValueRef::begin() const {
+ if (this->isArray()) {
+ return fValue->Begin();
+ }
+ if (this->isObject()) {
+ return &fValue->MemberBegin()->value;
+ }
+
+ return nullptr;
+}
+
+const rapidjson::Value* ValueRef::end() const {
+ if (this->isArray()) {
+ return fValue->End();
+ }
+ if (this->isObject()) {
+ return &fValue->MemberEnd()->value;
+ }
+
+ return nullptr;
+}
+
+SkString json::ValueRef::toString() const {
+#ifdef SK_DEBUG
+ rapidjson::StringBuffer buf;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buf);
+ fValue->Accept(writer);
+
+ return SkString(buf.GetString());
+#else
+ return SkString();
+#endif // SK_DEBUG
+}
+
+Document::Document(SkStream* stream) {
+ if (!stream->hasLength()) {
+ SkDebugf("!! unsupported unseekable json stream\n");
+ return;
+ }
+
+ // RapidJSON provides three DOM-builder approaches:
+ //
+ // 1) in-place : all data buffered, constructs the DOM in-place -- this is the fastest
+ // 2) from buffer: all data buffered, copies to DOM -- this is slightly slower
+ // 3) from stream: streamed data, reads/copies to DOM -- this is *significantly* slower
+ //
+ // We like fast, so #1 it is.
+
+ // The buffer needs to be C-string.
+ const auto size = stream->getLength();
+ fData = SkData::MakeUninitialized(size + 1);
+ if (stream->read(fData->writable_data(), size) < size) {
+ SkDebugf("!! could not read JSON stream\n");
+ return;
+ }
+
+ auto data = static_cast<char*>(fData->writable_data());
+ data[size] = '\0';
+
+ fDocument.ParseInsitu(data);
+
+#ifdef SK_DEBUG
+ if (fDocument.HasParseError()) {
+ SkDebugf("!! failed to parse json: %s\n",
+ rapidjson::GetParseError_En(fDocument.GetParseError()));
+ }
+#endif
+}
+
+} // namespace json
+
+} // namespace skottie
diff --git a/experimental/skottie/SkottieJson.h b/experimental/skottie/SkottieJson.h
new file mode 100644
index 0000000000..76e17c610e
--- /dev/null
+++ b/experimental/skottie/SkottieJson.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkottieJson_DEFINED
+#define SkottieJson_DEFINED
+
+#include "SkRefCnt.h"
+
+#include "rapidjson/document.h"
+
+class SkData;
+class SkStream;
+class SkString;
+
+namespace skottie {
+
+namespace json {
+
+class ValueRef {
+public:
+ ValueRef() : fValue(nullptr) {}
+ ValueRef(const rapidjson::Value& v) : fValue(v.IsNull() ? nullptr : &v) {}
+
+ bool isNull() const { return !fValue; }
+ bool isObject() const { return fValue && fValue->IsObject(); }
+ bool isArray() const { return fValue && fValue->IsArray(); }
+
+ template <typename T>
+ bool to(T*) const;
+
+ template <typename T>
+ T toDefault(const T& defaultValue) const {
+ T v;
+ if (!this->to<T>(&v)) {
+ v = defaultValue;
+ }
+ return v;
+ }
+
+ size_t size() const;
+ ValueRef operator[](size_t i) const;
+ ValueRef operator[](const char* key) const;
+
+ bool operator==(const ValueRef& other) const { return fValue == other.fValue; }
+ bool operator!=(const ValueRef& other) const { return !(*this == other); }
+
+ const rapidjson::Value* begin() const;
+ const rapidjson::Value* end() const;
+
+ SkString toString() const;
+
+private:
+ const rapidjson::Value* fValue;
+};
+
+// Container for the json DOM
+class Document {
+public:
+ explicit Document(SkStream*);
+
+ ValueRef root() const { return fDocument; }
+
+private:
+ sk_sp<SkData> fData; // raw data
+ rapidjson::Document fDocument; // in-place json DOM
+};
+
+} // namespace json
+
+} // namespace skottie
+
+#endif // SkottieJson_DEFINED
diff --git a/experimental/skottie/SkottieParser.cpp b/experimental/skottie/SkottieParser.cpp
deleted file mode 100644
index 36690036d2..0000000000
--- a/experimental/skottie/SkottieParser.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkottieParser.h"
-
-#include "SkJSONCPP.h"
-#include "SkScalar.h"
-#include "SkPath.h"
-#include "SkPoint.h"
-#include "SkString.h"
-#include "SkottieValue.h"
-
-#include <vector>
-
-namespace skottie {
-
-template <>
-bool Parse<SkScalar>(const Json::Value& jv, SkScalar* v) {
- // Some versions wrap values as single-element arrays.
- if (jv.isArray() && jv.size() == 1) {
- return Parse(jv[0], v);
- }
-
- if (jv.isNull() || !jv.isConvertibleTo(Json::realValue))
- return false;
-
- *v = jv.asFloat();
-
- return true;
-}
-
-template <>
-bool Parse<bool>(const Json::Value& jv, bool* v) {
- if (jv.isNull() || !jv.isConvertibleTo(Json::booleanValue))
- return false;
-
- *v = jv.asBool();
-
- return true;
-}
-
-template <>
-bool Parse<int>(const Json::Value& jv, int* v) {
- if (jv.isNull() || !jv.isConvertibleTo(Json::intValue))
- return false;
-
- *v = jv.asInt();
-
- return true;
-}
-
-template <>
-bool Parse<SkString>(const Json::Value& jv, SkString* v) {
- if (jv.isNull() || !jv.isConvertibleTo(Json::stringValue))
- return false;
-
- v->set(jv.isString() ? jv.asCString() : jv.asString().c_str());
-
- return true;
-}
-
-template <>
-bool Parse<SkPoint>(const Json::Value& jv, SkPoint* v) {
- if (!jv.isObject())
- return false;
-
- const auto& jvx = jv["x"],
- jvy = jv["y"];
-
- // Some BM versions seem to store x/y as single-element arrays.
- return Parse(jvx.isArray() ? jvx[0] : jvx, &v->fX)
- && Parse(jvy.isArray() ? jvy[0] : jvy, &v->fY);
-}
-
-template <>
-bool Parse<std::vector<float>>(const Json::Value& jv, std::vector<float>* v) {
- if (!jv.isArray())
- return false;
-
- v->resize(jv.size());
-
- for (Json::ArrayIndex i = 0; i < jv.size(); ++i) {
- if (!Parse(jv[i], v->data() + i)) {
- return false;
- }
- }
-
- return true;
-}
-
-namespace {
-
-bool ParsePointVec(const Json::Value& jv, std::vector<SkPoint>* pts) {
- if (!jv.isArray())
- return false;
-
- pts->clear();
- pts->reserve(jv.size());
-
- std::vector<float> vec;
- for (Json::ArrayIndex i = 0; i < jv.size(); ++i) {
- if (!Parse(jv[i], &vec) || vec.size() != 2)
- return false;
- pts->push_back(SkPoint::Make(vec[0], vec[1]));
- }
-
- return true;
-}
-
-} // namespace
-
-template <>
-bool Parse<ShapeValue>(const Json::Value& jv, ShapeValue* v) {
- SkASSERT(v->fVertices.empty());
-
- // Some versions wrap values as single-element arrays.
- if (jv.isArray() && jv.size() == 1) {
- return Parse(jv[0], v);
- }
-
- std::vector<SkPoint> inPts, // Cubic Bezier "in" control points, relative to vertices.
- outPts, // Cubic Bezier "out" control points, relative to vertices.
- verts; // Cubic Bezier vertices.
-
- if (!jv.isObject() ||
- !ParsePointVec(jv["i"], &inPts) ||
- !ParsePointVec(jv["o"], &outPts) ||
- !ParsePointVec(jv["v"], &verts) ||
- inPts.size() != outPts.size() ||
- inPts.size() != verts.size()) {
-
- return false;
- }
-
- v->fVertices.reserve(inPts.size());
- for (size_t i = 0; i < inPts.size(); ++i) {
- v->fVertices.push_back(BezierVertex({inPts[i], outPts[i], verts[i]}));
- }
- v->fClosed = ParseDefault(jv["c"], false);
-
- return true;
-}
-
-} // nasmespace skottie
diff --git a/experimental/skottie/SkottieParser.h b/experimental/skottie/SkottieParser.h
deleted file mode 100644
index b805484cd0..0000000000
--- a/experimental/skottie/SkottieParser.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkottieParser_DEFINED
-#define SkottieParser_DEFINED
-
-namespace Json { class Value; }
-
-namespace skottie {
-
-template <typename T>
-bool Parse(const Json::Value&, T*);
-
-template <typename T>
-static inline T ParseDefault(const Json::Value& jv, const T& defaultValue) {
- T v;
- if (!Parse<T>(jv, &v))
- v = defaultValue;
- return v;
-}
-
-} // nasmespace skottie
-
-#endif // SkottieParser_DEFINED