aboutsummaryrefslogtreecommitdiffhomepage
path: root/experimental
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2018-01-19 15:07:29 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-01-19 20:44:56 +0000
commit16d0ad06b46841c78cce816406574314fab95c13 (patch)
treefdc41251d483c35440738e80d1f29d91c718897e /experimental
parenta99b39399522658d7e5ddee97d0e45aa9fceaf89 (diff)
[skottie,sksg] Improved shape group semantics
* paints also apply to preceding nested geometries * path effects also apply to preceding nested geometries TBR= Change-Id: Ic72f8d032fb5823f506ff688630b786a23219f20 Reviewed-on: https://skia-review.googlesource.com/97222 Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'experimental')
-rw-r--r--experimental/skottie/Skottie.cpp174
-rw-r--r--experimental/sksg/SkSGNode.h13
-rw-r--r--experimental/sksg/effects/SkSGTransform.h8
-rw-r--r--experimental/sksg/geometry/SkSGGeometryTransform.cpp49
-rw-r--r--experimental/sksg/geometry/SkSGGeometryTransform.h56
-rw-r--r--experimental/sksg/geometry/SkSGMerge.cpp1
-rw-r--r--experimental/sksg/geometry/SkSGTrimEffect.cpp2
7 files changed, 223 insertions, 80 deletions
diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp
index f8c5ea021c..696f619f83 100644
--- a/experimental/skottie/Skottie.cpp
+++ b/experimental/skottie/Skottie.cpp
@@ -21,6 +21,7 @@
#include "SkPoint.h"
#include "SkSGColor.h"
#include "SkSGDraw.h"
+#include "SkSGGeometryTransform.h"
#include "SkSGGradient.h"
#include "SkSGGroup.h"
#include "SkSGImage.h"
@@ -169,15 +170,8 @@ sk_sp<sksg::RenderNode> AttachOpacity(const Json::Value& jtransform, AttachConte
return opacityNode;
}
-sk_sp<sksg::RenderNode> AttachShape(const Json::Value&, AttachContext* ctx);
sk_sp<sksg::RenderNode> AttachComposition(const Json::Value&, AttachContext* ctx);
-sk_sp<sksg::RenderNode> AttachShapeGroup(const Json::Value& jgroup, AttachContext* ctx) {
- SkASSERT(jgroup.isObject());
-
- return AttachShape(jgroup["it"], ctx);
-}
-
sk_sp<sksg::GeometryNode> AttachPathGeometry(const Json::Value& jpath, AttachContext* ctx) {
SkASSERT(jpath.isObject());
@@ -502,11 +496,6 @@ static constexpr PaintAttacherT gPaintAttachers[] = {
AttachGradientStroke,
};
-using GroupAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*);
-static constexpr GroupAttacherT gGroupAttachers[] = {
- AttachShapeGroup,
-};
-
using GeometryEffectAttacherT =
std::vector<sk_sp<sksg::GeometryNode>> (*)(const Json::Value&,
AttachContext*,
@@ -535,7 +524,7 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
{ "el", ShapeType::kGeometry , 2 }, // ellipse -> AttachEllipseGeometry
{ "fl", ShapeType::kPaint , 0 }, // fill -> AttachColorFill
{ "gf", ShapeType::kPaint , 2 }, // gfill -> AttachGradientFill
- { "gr", ShapeType::kGroup , 0 }, // group -> AttachShapeGroup
+ { "gr", ShapeType::kGroup , 0 }, // group -> Inline handler
{ "gs", ShapeType::kPaint , 3 }, // gstroke -> AttachGradientStroke
{ "mm", ShapeType::kGeometryEffect, 0 }, // merge -> AttachMergeGeometryEffect
{ "rc", ShapeType::kGeometry , 1 }, // rrect -> AttachRRectGeometry
@@ -543,7 +532,7 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
{ "sr", ShapeType::kGeometry , 3 }, // polystar -> AttachPolyStarGeometry
{ "st", ShapeType::kPaint , 1 }, // stroke -> AttachColorStroke
{ "tm", ShapeType::kGeometryEffect, 1 }, // trim -> AttachTrimGeometryEffect
- { "tr", ShapeType::kTransform , 0 }, // transform -> In-place handler
+ { "tr", ShapeType::kTransform , 0 }, // transform -> Inline handler
};
if (!shape.isObject())
@@ -565,87 +554,136 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) {
return static_cast<const ShapeInfo*>(info);
}
-sk_sp<sksg::RenderNode> AttachShape(const Json::Value& shapeArray, AttachContext* ctx) {
- if (!shapeArray.isArray())
+struct GeometryEffectRec {
+ const Json::Value& fJson;
+ GeometryEffectAttacherT fAttach;
+};
+
+sk_sp<sksg::RenderNode> AttachShape(const Json::Value& jshape, AttachContext* ctx,
+ std::vector<sk_sp<sksg::GeometryNode>>* geometryStack,
+ std::vector<GeometryEffectRec>* geometryEffectStack) {
+ if (!jshape.isArray())
return nullptr;
- // (https://helpx.adobe.com/after-effects/using/overview-shape-layers-paths-vector.html#groups_and_render_order_for_shapes_and_shape_attributes)
- //
- // Render order for shapes within a shape layer
- //
- // The rules for rendering a shape layer are similar to the rules for rendering a composition
- // that contains nested compositions:
- //
- // * Within a group, the shape at the bottom of the Timeline panel stacking order is rendered
- // first.
- //
- // * All path operations within a group are performed before paint operations. This means,
- // for example, that the stroke follows the distortions in the path made by the Wiggle Paths
- // path operation. Path operations within a group are performed from top to bottom.
- //
- // * Paint operations within a group are performed from the bottom to the top in the Timeline
- // panel stacking order. This means, for example, that a stroke is rendered on top of
- // (in front of) a stroke that appears after it in the Timeline panel.
- //
- sk_sp<sksg::Group> shape_group = sksg::Group::Make();
- sk_sp<sksg::RenderNode> xformed_group = shape_group;
+ SkDEBUGCODE(const auto initialGeometryEffects = geometryEffectStack->size();)
- std::vector<sk_sp<sksg::GeometryNode>> geos;
- std::vector<sk_sp<sksg::RenderNode>> draws;
+ sk_sp<sksg::Group> shape_group = sksg::Group::Make();
+ sk_sp<sksg::RenderNode> shape_wrapper = shape_group;
+ sk_sp<sksg::Matrix> shape_matrix;
- for (const auto& s : shapeArray) {
+ struct ShapeRec {
+ const Json::Value& fJson;
+ const ShapeInfo& fInfo;
+ };
+
+ // First pass (bottom->top):
+ //
+ // * pick up the group transform and opacity
+ // * push local geometry effects onto the stack
+ // * 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];
const auto* info = FindShapeInfo(s);
if (!info) {
LogFail(s.isObject() ? s["ty"] : s, "Unknown shape");
continue;
}
+ recs.push_back({ s, *info });
+
switch (info->fShapeType) {
+ case ShapeType::kTransform:
+ if ((shape_matrix = AttachMatrix(s, ctx, nullptr))) {
+ shape_wrapper = sksg::Transform::Make(std::move(shape_wrapper), shape_matrix);
+ }
+ shape_wrapper = AttachOpacity(s, ctx, std::move(shape_wrapper));
+ break;
+ case ShapeType::kGeometryEffect:
+ SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
+ geometryEffectStack->push_back(
+ { s, gGeometryEffectAttachers[info->fAttacherIndex] });
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Second pass (top -> bottom, after 2x reverse):
+ //
+ // * track local geometry
+ // * emit local paints
+ //
+ std::vector<sk_sp<sksg::GeometryNode>> geos;
+ std::vector<sk_sp<sksg::RenderNode >> draws;
+ for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
+ switch (rec->fInfo.fShapeType) {
case ShapeType::kGeometry: {
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
- if (auto geo = gGeometryAttachers[info->fAttacherIndex](s, ctx)) {
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
+ if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, ctx)) {
geos.push_back(std::move(geo));
}
} break;
case ShapeType::kGeometryEffect: {
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
- geos = gGeometryEffectAttachers[info->fAttacherIndex](s, ctx, std::move(geos));
- } break;
- case ShapeType::kPaint: {
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
- if (auto paint = gPaintAttachers[info->fAttacherIndex](s, ctx)) {
- for (const auto& geo : geos) {
- draws.push_back(sksg::Draw::Make(geo, paint));
- }
- }
+ // Apply the current effect and pop from the stack.
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
+ geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
+ ctx,
+ std::move(geos));
+
+ SkASSERT(geometryEffectStack->back().fJson == rec->fJson);
+ SkASSERT(geometryEffectStack->back().fAttach ==
+ gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
+ geometryEffectStack->pop_back();
} break;
case ShapeType::kGroup: {
- SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGroupAttachers));
- if (auto group = gGroupAttachers[info->fAttacherIndex](s, ctx)) {
- draws.push_back(std::move(group));
+ if (auto subgroup = AttachShape(rec->fJson["it"], ctx, &geos, geometryEffectStack)) {
+ draws.push_back(std::move(subgroup));
}
} break;
- case ShapeType::kTransform: {
- // TODO: BM appears to transform the geometry, not the draw op itself.
- if (auto matrix = AttachMatrix(s, ctx, nullptr)) {
- xformed_group = sksg::Transform::Make(std::move(xformed_group),
- std::move(matrix));
+ case ShapeType::kPaint: {
+ SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
+ auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, ctx);
+ if (!paint || geos.empty())
+ break;
+
+ auto drawGeos = geos;
+
+ // Apply all pending effects from the stack.
+ for (auto it = geometryEffectStack->rbegin(); it != geometryEffectStack->rend(); ++it) {
+ drawGeos = it->fAttach(it->fJson, ctx, std::move(drawGeos));
}
- xformed_group = AttachOpacity(s, ctx, std::move(xformed_group));
+
+ // If we still have multiple geos, reduce using 'merge'.
+ auto geo = drawGeos.size() > 1
+ ? sksg::Merge::Make(std::move(drawGeos), sksg::Merge::Mode::kMerge)
+ : drawGeos[0];
+
+ SkASSERT(geo);
+ draws.push_back(sksg::Draw::Make(std::move(geo), std::move(paint)));
} break;
+ default:
+ break;
}
}
- if (draws.empty()) {
- return nullptr;
+ // By now we should have popped all local geometry effects.
+ SkASSERT(geometryEffectStack->size() == initialGeometryEffects);
+
+ // Push transformed local geometries to parent list, for subsequent paints.
+ for (const auto& geo : geos) {
+ geometryStack->push_back(shape_matrix
+ ? sksg::GeometryTransform::Make(std::move(geo), shape_matrix)
+ : std::move(geo));
}
- for (auto draw = draws.rbegin(); draw != draws.rend(); ++draw) {
- shape_group->addChild(std::move(*draw));
+ // Emit local draws reversed (bottom->top, per spec).
+ for (auto it = draws.rbegin(); it != draws.rend(); ++it) {
+ shape_group->addChild(std::move(*it));
}
- LOG("** Attached shape: %zd draws.\n", draws.size());
- return xformed_group;
+ return draws.empty() ? nullptr : shape_wrapper;
}
sk_sp<sksg::RenderNode> AttachCompLayer(const Json::Value& layer, AttachContext* ctx) {
@@ -739,7 +777,9 @@ sk_sp<sksg::RenderNode> AttachShapeLayer(const Json::Value& layer, AttachContext
LOG("** Attaching shape layer ind: %d\n", ParseInt(layer["ind"], 0));
- return AttachShape(layer["shapes"], ctx);
+ std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
+ std::vector<GeometryEffectRec> geometryEffectStack;
+ return AttachShape(layer["shapes"], ctx, &geometryStack, &geometryEffectStack);
}
sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext*) {
diff --git a/experimental/sksg/SkSGNode.h b/experimental/sksg/SkSGNode.h
index 37139339f2..5518984e51 100644
--- a/experimental/sksg/SkSGNode.h
+++ b/experimental/sksg/SkSGNode.h
@@ -70,6 +70,7 @@ private:
// TODO: too friendly, find another way.
friend class Draw;
friend class EffectNode;
+ friend class GeometryTransform;
friend class Group;
friend class MaskEffect;
friend class Matrix;
@@ -95,12 +96,12 @@ private:
};
// Helper for defining attribute getters/setters in subclasses.
-#define SG_ATTRIBUTE(attr_name, attr_type, attr_container) \
- attr_type get##attr_name() const { return attr_container; } \
- void set##attr_name(attr_type v) { \
- if (attr_container == v) return; \
- attr_container = v; \
- this->invalidate(); \
+#define SG_ATTRIBUTE(attr_name, attr_type, attr_container) \
+ const attr_type& get##attr_name() const { return attr_container; } \
+ void set##attr_name(const attr_type& v) { \
+ if (attr_container == v) return; \
+ attr_container = v; \
+ this->invalidate(); \
}
} // namespace sksg
diff --git a/experimental/sksg/effects/SkSGTransform.h b/experimental/sksg/effects/SkSGTransform.h
index 0694117a2a..6b7fbc010b 100644
--- a/experimental/sksg/effects/SkSGTransform.h
+++ b/experimental/sksg/effects/SkSGTransform.h
@@ -47,7 +47,7 @@ private:
/**
* Concrete Effect node, binding a Matrix to a RenderNode.
*/
-class Transform : public EffectNode {
+class Transform final : public EffectNode {
public:
static sk_sp<Transform> Make(sk_sp<RenderNode> child, sk_sp<Matrix> matrix) {
return child && matrix
@@ -64,14 +64,14 @@ public:
const sk_sp<Matrix>& getMatrix() const { return fMatrix; }
protected:
- Transform(sk_sp<RenderNode>, sk_sp<Matrix>);
-
void onRender(SkCanvas*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
private:
- sk_sp<Matrix> fMatrix;
+ Transform(sk_sp<RenderNode>, sk_sp<Matrix>);
+
+ const sk_sp<Matrix> fMatrix;
typedef EffectNode INHERITED;
};
diff --git a/experimental/sksg/geometry/SkSGGeometryTransform.cpp b/experimental/sksg/geometry/SkSGGeometryTransform.cpp
new file mode 100644
index 0000000000..14c37d98b9
--- /dev/null
+++ b/experimental/sksg/geometry/SkSGGeometryTransform.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "SkSGGeometryTransform.h"
+
+#include "SkCanvas.h"
+
+namespace sksg {
+
+GeometryTransform::GeometryTransform(sk_sp<GeometryNode> child, sk_sp<Matrix> matrix)
+ : fChild(std::move(child))
+ , fMatrix(std::move(matrix)) {
+ fChild->addInvalReceiver(this);
+ fMatrix->addInvalReceiver(this);
+}
+
+GeometryTransform::~GeometryTransform() {
+ fChild->removeInvalReceiver(this);
+ fMatrix->removeInvalReceiver(this);
+}
+
+SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ // We don't care about matrix reval results.
+ fMatrix->revalidate(ic, ctm);
+ const auto& m = fMatrix->getMatrix();
+
+ auto bounds = fChild->revalidate(ic, ctm);
+ fTransformed = fChild->asPath();
+ fTransformed.transform(m);
+
+ m.mapRect(&bounds);
+ return bounds;
+}
+
+SkPath GeometryTransform::onAsPath() const {
+ return fTransformed;
+}
+
+void GeometryTransform::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawPath(fTransformed, paint);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/geometry/SkSGGeometryTransform.h b/experimental/sksg/geometry/SkSGGeometryTransform.h
new file mode 100644
index 0000000000..31a3371408
--- /dev/null
+++ b/experimental/sksg/geometry/SkSGGeometryTransform.h
@@ -0,0 +1,56 @@
+/*
+ * 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 SkSGGeometryTransform_DEFINED
+#define SkSGGeometryTransform_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+#include "SkSGTransform.h"
+
+class SkMatrix;
+
+namespace sksg {
+
+/**
+ * Concrete Effect node, binding a Matrix to a GeometryNode.
+ */
+class GeometryTransform final : public GeometryNode {
+public:
+ static sk_sp<GeometryTransform> Make(sk_sp<GeometryNode> child, sk_sp<Matrix> matrix) {
+ return child && matrix
+ ? sk_sp<GeometryTransform>(new GeometryTransform(std::move(child), std::move(matrix)))
+ : nullptr;
+ }
+
+ static sk_sp<GeometryTransform> Make(sk_sp<GeometryNode> child, const SkMatrix& m) {
+ return Make(std::move(child), Matrix::Make(m));
+ }
+
+ ~GeometryTransform() override;
+
+ const sk_sp<Matrix>& getMatrix() const { return fMatrix; }
+
+protected:
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+private:
+ GeometryTransform(sk_sp<GeometryNode>, sk_sp<Matrix>);
+
+ const sk_sp<GeometryNode> fChild;
+ const sk_sp<Matrix> fMatrix;
+ SkPath fTransformed;
+
+ typedef GeometryNode INHERITED;
+};
+
+}
+
+#endif // SkSGGeometryTransform_DEFINED
diff --git a/experimental/sksg/geometry/SkSGMerge.cpp b/experimental/sksg/geometry/SkSGMerge.cpp
index f75945d85e..9c22e75a81 100644
--- a/experimental/sksg/geometry/SkSGMerge.cpp
+++ b/experimental/sksg/geometry/SkSGMerge.cpp
@@ -27,7 +27,6 @@ Merge::~Merge() {
}
void Merge::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
- SkASSERT(!this->hasInval());
canvas->drawPath(fMerged, paint);
}
diff --git a/experimental/sksg/geometry/SkSGTrimEffect.cpp b/experimental/sksg/geometry/SkSGTrimEffect.cpp
index 4c30389877..af0b640a4c 100644
--- a/experimental/sksg/geometry/SkSGTrimEffect.cpp
+++ b/experimental/sksg/geometry/SkSGTrimEffect.cpp
@@ -26,8 +26,6 @@ TrimEffect::~TrimEffect() {
// This is a quick hack to get something on the screen. What we really want here is to apply
// the geometry transformation and cache the result on revalidation. Or an SkTrimPathEffect.
void TrimEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
- SkASSERT(!this->hasInval());
-
SkASSERT(!paint.getPathEffect());
const auto path = fChild->asPath();