[skottie] Initial camera support
Camera layers introduce a top-level 3d camera/view matrix based on their
transform properties:
* position - camera location
* point of interest (stored as anchor point by BM) - camera direction
* rotation - camera orientation
The perspective degree is controlled by a "zoom" camera property (which
corresponds to the view distance), and the composition dimensions.
Current limitations:
* single camera track/layer
* affects all layers (not just 3d-tagged layers)
* parent layer transforms are likely not applied correctly
Bug: skia:
Change-Id: Ifc1b8b699ff09fa13b4804d18546b444d02e81c2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/201651
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index ebdd7ed..d615611 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -117,12 +117,15 @@
sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
AnimatorScope* ascope,
- sk_sp<sksg::Transform> parent) const {
+ sk_sp<sksg::Transform> parent,
+ sk_sp<TransformAdapter3D> adapter) const {
static const VectorValue g_default_vec_0 = { 0, 0, 0},
g_default_vec_100 = {100, 100, 100};
- auto matrix = sksg::Matrix<SkMatrix44>::Make(SkMatrix::I());
- auto adapter = sk_make_sp<TransformAdapter3D>(matrix);
+ if (!adapter) {
+ // Default to TransformAdapter3D (we only use external adapters for cameras).
+ adapter = sk_make_sp<TransformAdapter3D>();
+ }
auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
[adapter](const VectorValue& a) {
@@ -165,7 +168,7 @@
// TODO: dispatch 3D transform properties
return (bound)
- ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix))
+ ? sksg::Transform::MakeConcat(std::move(parent), adapter->refTransform())
: parent;
}
@@ -266,13 +269,14 @@
sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
sk_sp<MarkerObserver> mobserver,
Animation::Builder::Stats* stats,
- float duration, float framerate)
+ const SkSize& size, float duration, float framerate)
: fResourceProvider(std::move(rp))
, fLazyFontMgr(std::move(fontmgr))
, fPropertyObserver(std::move(pobserver))
, fLogger(std::move(logger))
, fMarkerObserver(std::move(mobserver))
, fStats(stats)
+ , fSize(size)
, fDuration(duration)
, fFrameRate(framerate)
, fHasNontrivialBlending(false) {}
@@ -499,7 +503,7 @@
std::move(fPropertyObserver),
std::move(fLogger),
std::move(fMarkerObserver),
- &fStats, duration, fps);
+ &fStats, size, duration, fps);
auto scene = builder.parse(json);
const auto t2 = std::chrono::steady_clock::now();
diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp
index 1abfebc..ea69f44 100644
--- a/modules/skottie/src/SkottieAdapter.cpp
+++ b/modules/skottie/src/SkottieAdapter.cpp
@@ -7,6 +7,7 @@
#include "SkottieAdapter.h"
+#include "Sk3D.h"
#include "SkFont.h"
#include "SkMatrix.h"
#include "SkMatrix44.h"
@@ -75,11 +76,15 @@
fZ = v.size() > 2 ? v[2] : 0;
}
-TransformAdapter3D::TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)
- : fMatrixNode(std::move(matrix)) {}
+TransformAdapter3D::TransformAdapter3D()
+ : fMatrixNode(sksg::Matrix<SkMatrix44>::Make(SkMatrix::I())) {}
TransformAdapter3D::~TransformAdapter3D() = default;
+sk_sp<sksg::Transform> TransformAdapter3D::refTransform() const {
+ return fMatrixNode;
+}
+
SkMatrix44 TransformAdapter3D::totalMatrix() const {
SkMatrix44 t;
@@ -104,6 +109,62 @@
fMatrixNode->setMatrix(this->totalMatrix());
}
+CameraAdapter:: CameraAdapter(const SkSize& viewport_size)
+ : fViewportSize(viewport_size) {}
+
+CameraAdapter::~CameraAdapter() = default;
+
+SkMatrix44 CameraAdapter::totalMatrix() const {
+ // Camera parameters:
+ //
+ // * location -> position attribute
+ // * point of interest -> anchor point attribute
+ // * orientation -> rotation attribute
+ //
+ // Note: the orientation is specified post position/POI adjustment.
+ //
+ SkPoint3 pos = { this->getPosition().fX,
+ this->getPosition().fY,
+ -this->getPosition().fZ },
+ poi = { this->getAnchorPoint().fX,
+ this->getAnchorPoint().fY,
+ -this->getAnchorPoint().fZ },
+ up = { 0, 1, 0 };
+
+ SkMatrix44 cam_t;
+ Sk3LookAt(&cam_t, pos, poi, up);
+
+ {
+ SkMatrix44 rot;
+ rot.setRotateDegreesAbout(1, 0, 0, this->getRotation().fX);
+ cam_t.postConcat(rot);
+ rot.setRotateDegreesAbout(0, 1, 0, this->getRotation().fY);
+ cam_t.postConcat(rot);
+ rot.setRotateDegreesAbout(0, 0, 1, this->getRotation().fZ);
+ cam_t.postConcat(rot);
+ }
+
+ // View parameters:
+ //
+ // * size -> composition size (TODO: AE seems to base it on width only?)
+ // * distance -> "zoom" camera attribute
+ //
+ const auto view_size = SkTMax(fViewportSize.width(), fViewportSize.height()),
+ view_distance = this->getZoom(),
+ view_angle = std::atan(view_size * 0.5f / view_distance);
+
+ SkMatrix44 view_t;
+ Sk3Perspective(&view_t, 0, view_distance, 2 * view_angle);
+ view_t.postScale(view_size * 0.5f, view_size * 0.5f, 1);
+
+ SkMatrix44 t;
+ t.setTranslate(fViewportSize.width() * 0.5f, fViewportSize.height() * 0.5f, 0);
+ t.preConcat(view_t);
+ t.preConcat(cam_t);
+
+ return t;
+}
+
RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite)
: fRepeaterNode(repeater_node)
, fComposite(composite)
diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h
index 1138eb6..6452d8d 100644
--- a/modules/skottie/src/SkottieAdapter.h
+++ b/modules/skottie/src/SkottieAdapter.h
@@ -29,11 +29,16 @@
class RenderNode;
class RRect;
class TextBlob;
+class Transform;
class TransformEffect;
class TrimEffect;
};
+namespace skjson {
+ class ObjectValue;
+}
+
namespace skottie {
#define ADAPTER_PROPERTY(p_name, p_type, p_default) \
@@ -108,10 +113,10 @@
sk_sp<sksg::Matrix<SkMatrix>> fMatrixNode;
};
-class TransformAdapter3D final : public SkNVRefCnt<TransformAdapter3D> {
+class TransformAdapter3D : public SkRefCnt {
public:
- explicit TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>>);
- ~TransformAdapter3D();
+ TransformAdapter3D();
+ ~TransformAdapter3D() override;
struct Vec3 {
float fX, fY, fZ;
@@ -129,12 +134,32 @@
ADAPTER_PROPERTY(Rotation , Vec3, Vec3({ 0, 0, 0}))
ADAPTER_PROPERTY(Scale , Vec3, Vec3({100, 100, 100}))
- SkMatrix44 totalMatrix() const;
+ sk_sp<sksg::Transform> refTransform() const;
-private:
+protected:
void apply();
+private:
+ virtual SkMatrix44 totalMatrix() const;
+
sk_sp<sksg::Matrix<SkMatrix44>> fMatrixNode;
+
+ using INHERITED = SkRefCnt;
+};
+
+class CameraAdapter final : public TransformAdapter3D {
+public:
+ explicit CameraAdapter(const SkSize& viewport_size);
+ ~CameraAdapter() override;
+
+ ADAPTER_PROPERTY(Zoom, SkScalar, 0)
+
+private:
+ SkMatrix44 totalMatrix() const override;
+
+ const SkSize fViewportSize;
+
+ using INHERITED = TransformAdapter3D;
};
class RepeaterAdapter final : public SkNVRefCnt<RepeaterAdapter> {
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index 5cf304b..399f396 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -12,6 +12,7 @@
#include "SkImage.h"
#include "SkJSON.h"
#include "SkMakeUnique.h"
+#include "SkottieAdapter.h"
#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkParse.h"
@@ -171,6 +172,8 @@
return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode));
}
+static constexpr int kCameraLayerType = 13;
+
} // namespace
sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name,
@@ -406,9 +409,13 @@
AnimatorScope* fScope;
SkTHashMap<int, sk_sp<sksg::Transform>> fLayerMatrixMap;
sk_sp<sksg::RenderNode> fCurrentMatte;
+ sk_sp<sksg::Transform> fCameraTransform;
+
+ enum class TransformType { kLayer, kCamera };
sk_sp<sksg::Transform> attachLayerTransform(const skjson::ObjectValue& jlayer,
- const AnimationBuilder* abuilder) {
+ const AnimationBuilder* abuilder,
+ TransformType type = TransformType::kLayer) {
const auto layer_index = ParseDefault<int>(jlayer["ind"], -1);
if (layer_index < 0)
return nullptr;
@@ -416,7 +423,7 @@
if (auto* m = fLayerMatrixMap.find(layer_index))
return *m;
- return this->attachLayerTransformImpl(jlayer, abuilder, layer_index);
+ return this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index);
}
private:
@@ -434,16 +441,47 @@
if (!l) continue;
if (ParseDefault<int>((*l)["ind"], -1) == parent_index) {
- return this->attachLayerTransformImpl(*l, abuilder, parent_index);
+ const auto parent_type = ParseDefault<int>((*l)["ty"], -1) == kCameraLayerType
+ ? TransformType::kCamera
+ : TransformType::kLayer;
+ return this->attachLayerTransformImpl(*l, abuilder, parent_type, parent_index);
}
}
return nullptr;
}
+ sk_sp<sksg::Transform> attachTransformNode(const skjson::ObjectValue& jlayer,
+ const AnimationBuilder* abuilder,
+ sk_sp<sksg::Transform> parent_transform,
+ TransformType type) const {
+ const skjson::ObjectValue* jtransform = jlayer["ks"];
+ if (!jtransform) {
+ return nullptr;
+ }
+
+ if (type == TransformType::kCamera) {
+ auto camera_adapter = sk_make_sp<CameraAdapter>(abuilder->fSize);
+
+ abuilder->bindProperty<ScalarValue>(jlayer["pe"], fScope,
+ [camera_adapter] (const ScalarValue& pe) {
+ // 'pe' (perspective?) corresponds to AE's "zoom" camera property.
+ camera_adapter->setZoom(pe);
+ });
+
+ return abuilder->attachMatrix3D(*jtransform, fScope,
+ std::move(parent_transform),
+ std::move(camera_adapter));
+ }
+
+ return (ParseDefault<int>(jlayer["ddd"], 0) == 0)
+ ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_transform))
+ : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_transform));
+ }
+
sk_sp<sksg::Transform> attachLayerTransformImpl(const skjson::ObjectValue& jlayer,
const AnimationBuilder* abuilder,
- int layer_index) {
+ TransformType type, int layer_index) {
SkASSERT(!fLayerMatrixMap.find(layer_index));
// Add a stub entry to break recursion cycles.
@@ -451,14 +489,10 @@
auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index);
- if (const skjson::ObjectValue* jtransform = jlayer["ks"]) {
- auto transform_node = (ParseDefault<int>(jlayer["ddd"], 0) == 0)
- ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_matrix))
- : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_matrix));
-
- return *fLayerMatrixMap.set(layer_index, std::move(transform_node));
- }
- return nullptr;
+ return *fLayerMatrixMap.set(layer_index, this->attachTransformNode(jlayer,
+ abuilder,
+ std::move(parent_matrix),
+ type));
}
};
@@ -490,7 +524,21 @@
&AnimationBuilder::attachTextLayer, // 'ty': 5
};
- int type = ParseDefault<int>((*jlayer)["ty"], -1);
+ const auto type = ParseDefault<int>((*jlayer)["ty"], -1);
+
+ if (type == kCameraLayerType) {
+ // Camera layers are special: they don't build normal SG fragments, but drive a root-level
+ // transform.
+ if (layerCtx->fCameraTransform) {
+ this->log(Logger::Level::kWarning, jlayer, "Ignoring duplicate camera layer.");
+ } else {
+ layerCtx->fCameraTransform =
+ layerCtx->attachLayerTransform(*jlayer, this,
+ AttachLayerContext::TransformType::kCamera);
+ }
+ return nullptr;
+ }
+
if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
return nullptr;
}
@@ -593,9 +641,9 @@
return std::move(controller_node);
}
-sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& comp,
+sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& jcomp,
AnimatorScope* scope) const {
- const skjson::ArrayValue* jlayers = comp["layers"];
+ const skjson::ArrayValue* jlayers = jcomp["layers"];
if (!jlayers) return nullptr;
std::vector<sk_sp<sksg::RenderNode>> layers;
@@ -612,11 +660,22 @@
return nullptr;
}
- // Layers are painted in bottom->top order.
- std::reverse(layers.begin(), layers.end());
- layers.shrink_to_fit();
+ sk_sp<sksg::RenderNode> comp;
+ if (layers.size() == 1) {
+ comp = std::move(layers[0]);
+ } else {
+ // Layers are painted in bottom->top order.
+ std::reverse(layers.begin(), layers.end());
+ layers.shrink_to_fit();
+ comp = sksg::Group::Make(std::move(layers));
+ }
- return sksg::Group::Make(std::move(layers));
+ // Optional camera.
+ if (layerCtx.fCameraTransform) {
+ comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform));
+ }
+
+ return comp;
}
} // namespace internal
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 7736912..979f694 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -37,6 +37,9 @@
namespace skottie {
+class TransformAdapter2D;
+class TransformAdapter3D;
+
namespace internal {
using AnimatorScope = sksg::AnimatorList;
@@ -45,7 +48,8 @@
public:
AnimationBuilder(sk_sp<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
sk_sp<Logger>, sk_sp<MarkerObserver>,
- Animation::Builder::Stats*, float duration, float framerate);
+ Animation::Builder::Stats*, const SkSize& size,
+ float duration, float framerate);
std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
@@ -74,7 +78,8 @@
sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*,
sk_sp<sksg::Transform>) const;
sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*,
- sk_sp<sksg::Transform>) const;
+ sk_sp<sksg::Transform>,
+ sk_sp<TransformAdapter3D> = nullptr) const;
sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&, AnimatorScope*,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
@@ -174,6 +179,7 @@
sk_sp<Logger> fLogger;
sk_sp<MarkerObserver> fMarkerObserver;
Animation::Builder::Stats* fStats;
+ const SkSize fSize;
const float fDuration,
fFrameRate;
mutable const char* fPropertyObserverContext;