From c353ee211fc99c0bf2035f9e77f87fd67b3c19c5 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Mon, 30 Apr 2018 21:49:41 -0400 Subject: [skottie] Power-reduce paths (cubicTo -> lineTo) For straight lines, Lottie exports control points conincident with the vertices. We can detect this case and emit more efficient lineTo's. One wrinkle: we can only apply this power-reduction post-interpolation (otherwise the path verbs and point count would not be guaranteed to match). Hence we store explicit shape data and defer the SkPath conversion. TBR= Change-Id: I7818be464eabee6096d2078440843243a55c6e98 Reviewed-on: https://skia-review.googlesource.com/124800 Reviewed-by: Florin Malita Commit-Queue: Florin Malita --- experimental/skottie/Skottie.cpp | 4 +- experimental/skottie/SkottieAnimator.cpp | 37 +------------ experimental/skottie/SkottieParser.cpp | 26 +++------ experimental/skottie/SkottieValue.cpp | 93 ++++++++++++++++++++++++++++++-- experimental/skottie/SkottieValue.h | 34 +++++++++++- 5 files changed, 134 insertions(+), 60 deletions(-) (limited to 'experimental') diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp index bb98ad75f0..1b5886eee0 100644 --- a/experimental/skottie/Skottie.cpp +++ b/experimental/skottie/Skottie.cpp @@ -149,7 +149,9 @@ sk_sp AttachComposition(const Json::Value&, AttachContext* ctx sk_sp AttachPath(const Json::Value& jpath, AttachContext* ctx) { auto path_node = sksg::Path::Make(); return BindProperty(jpath, &ctx->fAnimators, - [path_node](const ShapeValue& p) { path_node->setPath(p); }) + [path_node](const ShapeValue& p) { + path_node->setPath(ValueTraits::As(p)); + }) ? path_node : nullptr; } diff --git a/experimental/skottie/SkottieAnimator.cpp b/experimental/skottie/SkottieAnimator.cpp index 5f8d0db89e..34803e05b5 100644 --- a/experimental/skottie/SkottieAnimator.cpp +++ b/experimental/skottie/SkottieAnimator.cpp @@ -27,41 +27,6 @@ bool LogFail(const Json::Value& json, const char* msg) { return false; } -template -static inline T lerp(const T&, const T&, float); - -template <> -ScalarValue lerp(const ScalarValue& v0, const ScalarValue& v1, float t) { - SkASSERT(t >= 0 && t <= 1); - return v0 + (v1 - v0) * t; -} - -template <> -VectorValue lerp(const VectorValue& v0, const VectorValue& v1, float t) { - SkASSERT(v0.size() == v1.size()); - - VectorValue v; - v.reserve(v0.size()); - - for (size_t i = 0; i < v0.size(); ++i) { - v.push_back(lerp(v0[i], v1[i], t)); - } - - return v; -} - -template <> -ShapeValue lerp(const ShapeValue& v0, const ShapeValue& v1, float t) { - SkASSERT(t >= 0 && t <= 1); - SkASSERT(v1.isInterpolatable(v0)); - - ShapeValue v; - SkAssertResult(v1.interpolate(v0, t, &v)); - v.setIsVolatile(true); - - return v; -} - class KeyframeAnimatorBase : public sksg::Animator { public: int count() const { return fRecs.count(); } @@ -264,7 +229,7 @@ private: const auto lt = this->localT(rec, t); const auto& v0 = fVs[rec.vidx0]; const auto& v1 = fVs[rec.vidx1]; - *v = lerp(v0, v1, lt); + *v = ValueTraits::Lerp(v0, v1, lt); } } diff --git a/experimental/skottie/SkottieParser.cpp b/experimental/skottie/SkottieParser.cpp index 24603bf828..36690036d2 100644 --- a/experimental/skottie/SkottieParser.cpp +++ b/experimental/skottie/SkottieParser.cpp @@ -12,6 +12,7 @@ #include "SkPath.h" #include "SkPoint.h" #include "SkString.h" +#include "SkottieValue.h" #include @@ -113,8 +114,8 @@ bool ParsePointVec(const Json::Value& jv, std::vector* pts) { } // namespace template <> -bool Parse(const Json::Value& jv, SkPath* v) { - SkASSERT(v->isEmpty()); +bool Parse(const Json::Value& jv, ShapeValue* v) { + SkASSERT(v->fVertices.empty()); // Some versions wrap values as single-element arrays. if (jv.isArray() && jv.size() == 1) { @@ -135,24 +136,11 @@ bool Parse(const Json::Value& jv, SkPath* v) { return false; } - if (!verts.empty()) { - v->moveTo(verts.front()); - } - - const auto& addCubic = [&](size_t from, size_t to) { - v->cubicTo(verts[from] + outPts[from], - verts[to] + inPts[to], - verts[to]); - }; - - for (size_t i = 1; i < verts.size(); ++i) { - addCubic(i - 1, i); - } - - if (!verts.empty() && ParseDefault(jv["c"], false)) { - addCubic(verts.size() - 1, 0); - v->close(); + 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; } diff --git a/experimental/skottie/SkottieValue.cpp b/experimental/skottie/SkottieValue.cpp index 386c8ab623..edfa891aa1 100644 --- a/experimental/skottie/SkottieValue.cpp +++ b/experimental/skottie/SkottieValue.cpp @@ -8,6 +8,7 @@ #include "SkottieValue.h" #include "SkColor.h" +#include "SkNx.h" #include "SkPoint.h" #include "SkSize.h" @@ -18,6 +19,12 @@ size_t ValueTraits::Cardinality(const ScalarValue&) { return 1; } +template <> +ScalarValue ValueTraits::Lerp(const ScalarValue& v0, const ScalarValue& v1, float t) { + SkASSERT(t >= 0 && t <= 1); + return v0 + (v1 - v0) * t; +} + template <> template <> SkScalar ValueTraits::As(const ScalarValue& v) { @@ -29,6 +36,20 @@ size_t ValueTraits::Cardinality(const VectorValue& vec) { return vec.size(); } +template <> +VectorValue ValueTraits::Lerp(const VectorValue& v0, const VectorValue& v1, float t) { + SkASSERT(v0.size() == v1.size()); + + VectorValue v; + v.reserve(v0.size()); + + for (size_t i = 0; i < v0.size(); ++i) { + v.push_back(ValueTraits::Lerp(v0[i], v1[i], t)); + } + + return v; +} + template <> template <> SkColor ValueTraits::As(const VectorValue& v) { @@ -61,13 +82,79 @@ SkSize ValueTraits::As(const VectorValue& vec) { } template <> -size_t ValueTraits::Cardinality(const ShapeValue& path) { - return SkTo(path.countVerbs()); +size_t ValueTraits::Cardinality(const ShapeValue& shape) { + return shape.fVertices.size(); +} + +static SkPoint lerp_point(const SkPoint& v0, const SkPoint& v1, const Sk2f& t) { + const auto v2f0 = Sk2f::Load(&v0), + v2f1 = Sk2f::Load(&v1); + + SkPoint v; + (v2f0 + (v2f1 - v2f0) * t).store(&v); + + return v; +} + +template <> +ShapeValue ValueTraits::Lerp(const ShapeValue& v0, const ShapeValue& v1, float t) { + SkASSERT(t >= 0 && t <= 1); + SkASSERT(v0.fVertices.size() == v1.fVertices.size()); + SkASSERT(v0.fClosed == v1.fClosed); + + ShapeValue v; + v.fClosed = v0.fClosed; + v.fVolatile = true; // interpolated values are volatile + + const auto t2f = Sk2f(t); + v.fVertices.reserve(v0.fVertices.size()); + + for (size_t i = 0; i < v0.fVertices.size(); ++i) { + v.fVertices.emplace_back(BezierVertex({ + lerp_point(v0.fVertices[i].fInPoint , v1.fVertices[i].fInPoint , t2f), + lerp_point(v0.fVertices[i].fOutPoint, v1.fVertices[i].fOutPoint, t2f), + lerp_point(v0.fVertices[i].fVertex , v1.fVertices[i].fVertex , t2f) + })); + } + + return v; } template <> template <> -SkPath ValueTraits::As(const ShapeValue& path) { +SkPath ValueTraits::As(const ShapeValue& shape) { + SkPath path; + + if (!shape.fVertices.empty()) { + path.moveTo(shape.fVertices.front().fVertex); + } + + const auto& addCubic = [&](size_t from, size_t to) { + const auto c0 = shape.fVertices[from].fVertex + shape.fVertices[from].fOutPoint, + c1 = shape.fVertices[to].fVertex + shape.fVertices[to].fInPoint; + + if (c0 == shape.fVertices[from].fVertex && + c1 == shape.fVertices[to].fVertex) { + // If the control points are coincident, we can power-reduce to a straight line. + // TODO: we could also do that when the controls are on the same line as the + // vertices, but it's unclear how common that case is. + path.lineTo(shape.fVertices[to].fVertex); + } else { + path.cubicTo(c0, c1, shape.fVertices[to].fVertex); + } + }; + + for (size_t i = 1; i < shape.fVertices.size(); ++i) { + addCubic(i - 1, i); + } + + if (!shape.fVertices.empty() && shape.fClosed) { + addCubic(shape.fVertices.size() - 1, 0); + path.close(); + } + + path.setIsVolatile(shape.fVolatile); + return path; } diff --git a/experimental/skottie/SkottieValue.h b/experimental/skottie/SkottieValue.h index 6d6d94a7a3..cfdbd7aba7 100644 --- a/experimental/skottie/SkottieValue.h +++ b/experimental/skottie/SkottieValue.h @@ -21,11 +21,43 @@ struct ValueTraits { template static U As(const T&); + + static T Lerp(const T&, const T&, float); }; using ScalarValue = SkScalar; using VectorValue = std::vector; -using ShapeValue = SkPath; + +struct BezierVertex { + SkPoint fInPoint, // "in" control point, relative to the vertex + fOutPoint, // "out" control point, relative to the vertex + fVertex; + + bool operator==(const BezierVertex& other) const { + return fInPoint == other.fInPoint + && fOutPoint == other.fOutPoint + && fVertex == other.fVertex; + } + + bool operator!=(const BezierVertex& other) const { return !(*this == other); } +}; + +struct ShapeValue { + std::vector fVertices; + bool fClosed : 1, + fVolatile : 1; + + ShapeValue() : fClosed(false), fVolatile(false) {} + ShapeValue(const ShapeValue&) = default; + ShapeValue(ShapeValue&&) = default; + ShapeValue& operator=(const ShapeValue&) = default; + + bool operator==(const ShapeValue& other) const { + return fVertices == other.fVertices && fClosed == other.fClosed; + } + + bool operator!=(const ShapeValue& other) const { return !(*this == other); } +}; } // namespace skottie -- cgit v1.2.3