123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- /*
- * Copyright 2019 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
- #include "modules/skottie/src/SkottiePriv.h"
- #include "modules/skottie/src/SkottieAdapter.h"
- #include "modules/skottie/src/SkottieJson.h"
- #include "modules/skottie/src/effects/Effects.h"
- #include "modules/skottie/src/effects/MotionBlurEffect.h"
- #include "modules/sksg/include/SkSGClipEffect.h"
- #include "modules/sksg/include/SkSGDraw.h"
- #include "modules/sksg/include/SkSGGroup.h"
- #include "modules/sksg/include/SkSGMaskEffect.h"
- #include "modules/sksg/include/SkSGMerge.h"
- #include "modules/sksg/include/SkSGPaint.h"
- #include "modules/sksg/include/SkSGPath.h"
- #include "modules/sksg/include/SkSGRect.h"
- #include "modules/sksg/include/SkSGRenderEffect.h"
- #include "modules/sksg/include/SkSGRenderNode.h"
- #include "modules/sksg/include/SkSGTransform.h"
- namespace skottie {
- namespace internal {
- namespace {
- static constexpr int kNullLayerType = 3;
- static constexpr int kCameraLayerType = 13;
- struct MaskInfo {
- SkBlendMode fBlendMode; // used when masking with layers/blending
- sksg::Merge::Mode fMergeMode; // used when clipping
- bool fInvertGeometry;
- };
- const MaskInfo* GetMaskInfo(char mode) {
- static constexpr MaskInfo k_add_info =
- { SkBlendMode::kSrcOver , sksg::Merge::Mode::kUnion , false };
- static constexpr MaskInfo k_int_info =
- { SkBlendMode::kSrcIn , sksg::Merge::Mode::kIntersect , false };
- // AE 'subtract' is the same as 'intersect' + inverted geometry
- // (draws the opacity-adjusted paint *outside* the shape).
- static constexpr MaskInfo k_sub_info =
- { SkBlendMode::kSrcIn , sksg::Merge::Mode::kIntersect , true };
- static constexpr MaskInfo k_dif_info =
- { SkBlendMode::kDifference, sksg::Merge::Mode::kDifference, false };
- switch (mode) {
- case 'a': return &k_add_info;
- case 'f': return &k_dif_info;
- case 'i': return &k_int_info;
- case 's': return &k_sub_info;
- default: break;
- }
- return nullptr;
- }
- sk_sp<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask,
- const AnimationBuilder* abuilder,
- AnimatorScope* ascope,
- sk_sp<sksg::RenderNode> childNode) {
- if (!jmask) return childNode;
- struct MaskRecord {
- sk_sp<sksg::Path> mask_path; // for clipping and masking
- sk_sp<sksg::Color> mask_paint; // for masking
- sk_sp<sksg::BlurImageFilter> mask_blur; // for masking
- sksg::Merge::Mode merge_mode; // for clipping
- };
- SkSTArray<4, MaskRecord, true> mask_stack;
- bool has_effect = false;
- auto blur_effect = sksg::BlurImageFilter::Make();
- for (const skjson::ObjectValue* m : *jmask) {
- if (!m) continue;
- const skjson::StringValue* jmode = (*m)["mode"];
- if (!jmode || jmode->size() != 1) {
- abuilder->log(Logger::Level::kError, &(*m)["mode"], "Invalid mask mode.");
- continue;
- }
- const auto mode = *jmode->begin();
- if (mode == 'n') {
- // "None" masks have no effect.
- continue;
- }
- const auto* mask_info = GetMaskInfo(mode);
- if (!mask_info) {
- abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported mask mode: '%c'.", mode);
- continue;
- }
- auto mask_path = abuilder->attachPath((*m)["pt"], ascope);
- if (!mask_path) {
- abuilder->log(Logger::Level::kError, m, "Could not parse mask path.");
- continue;
- }
- // "inv" is cumulative with mask info fInvertGeometry
- const auto inverted =
- (mask_info->fInvertGeometry != ParseDefault<bool>((*m)["inv"], false));
- mask_path->setFillType(inverted ? SkPath::kInverseWinding_FillType
- : SkPath::kWinding_FillType);
- auto mask_paint = sksg::Color::Make(SK_ColorBLACK);
- mask_paint->setAntiAlias(true);
- // First mask in the stack initializes the mask buffer.
- mask_paint->setBlendMode(mask_stack.empty() ? SkBlendMode::kSrc
- : mask_info->fBlendMode);
- has_effect |= abuilder->bindProperty<ScalarValue>((*m)["o"], ascope,
- [mask_paint](const ScalarValue& o) {
- mask_paint->setOpacity(o * 0.01f);
- }, 100.0f);
- static const VectorValue default_feather = { 0, 0 };
- if (abuilder->bindProperty<VectorValue>((*m)["f"], ascope,
- [blur_effect](const VectorValue& feather) {
- // Close enough to AE.
- static constexpr SkScalar kFeatherToSigma = 0.38f;
- auto sX = feather.size() > 0 ? feather[0] * kFeatherToSigma : 0,
- sY = feather.size() > 1 ? feather[1] * kFeatherToSigma : 0;
- blur_effect->setSigma({ sX, sY });
- }, default_feather)) {
- has_effect = true;
- mask_stack.push_back({ mask_path,
- mask_paint,
- std::move(blur_effect),
- mask_info->fMergeMode});
- blur_effect = sksg::BlurImageFilter::Make();
- } else {
- mask_stack.push_back({mask_path, mask_paint, nullptr, mask_info->fMergeMode});
- }
- }
- if (mask_stack.empty())
- return childNode;
- // If the masks are fully opaque, we can clip.
- if (!has_effect) {
- sk_sp<sksg::GeometryNode> clip_node;
- if (mask_stack.count() == 1) {
- // Single path -> just clip.
- clip_node = std::move(mask_stack.front().mask_path);
- } else {
- // Multiple clip paths -> merge.
- std::vector<sksg::Merge::Rec> merge_recs;
- merge_recs.reserve(SkToSizeT(mask_stack.count()));
- for (auto& mask : mask_stack) {
- const auto mode = merge_recs.empty() ? sksg::Merge::Mode::kMerge : mask.merge_mode;
- merge_recs.push_back({std::move(mask.mask_path), mode});
- }
- clip_node = sksg::Merge::Make(std::move(merge_recs));
- }
- return sksg::ClipEffect::Make(std::move(childNode), std::move(clip_node), true);
- }
- const auto make_mask = [](const MaskRecord& rec) {
- auto mask = sksg::Draw::Make(std::move(rec.mask_path),
- std::move(rec.mask_paint));
- // Optional mask blur (feather).
- return sksg::ImageFilterEffect::Make(std::move(mask), std::move(rec.mask_blur));
- };
- sk_sp<sksg::RenderNode> maskNode;
- if (mask_stack.count() == 1) {
- // no group needed for single mask
- maskNode = make_mask(mask_stack.front());
- } else {
- std::vector<sk_sp<sksg::RenderNode>> masks;
- masks.reserve(SkToSizeT(mask_stack.count()));
- for (auto& rec : mask_stack) {
- masks.push_back(make_mask(rec));
- }
- maskNode = sksg::Group::Make(std::move(masks));
- }
- return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode));
- }
- class LayerController final : public sksg::Animator {
- public:
- LayerController(sksg::AnimatorList&& layer_animators,
- sk_sp<sksg::RenderNode> layer,
- size_t tanim_count, float in, float out)
- : fLayerAnimators(std::move(layer_animators))
- , fLayerNode(std::move(layer))
- , fTransformAnimatorsCount(tanim_count)
- , fIn(in)
- , fOut(out) {}
- protected:
- void onTick(float t) override {
- const auto active = (t >= fIn && t < fOut);
- if (fLayerNode) {
- fLayerNode->setVisible(active);
- }
- // When active, dispatch ticks to all layer animators.
- // When inactive, we must still dispatch ticks to the layer transform animators
- // (active child layers depend on transforms being updated).
- const auto dispatch_count = active ? fLayerAnimators.size()
- : fTransformAnimatorsCount;
- for (size_t i = 0; i < dispatch_count; ++i) {
- fLayerAnimators[i]->tick(t);
- }
- }
- private:
- const sksg::AnimatorList fLayerAnimators;
- const sk_sp<sksg::RenderNode> fLayerNode;
- const size_t fTransformAnimatorsCount;
- const float fIn,
- fOut;
- };
- class MotionBlurController final : public sksg::Animator {
- public:
- explicit MotionBlurController(sk_sp<MotionBlurEffect> mbe)
- : fMotionBlurEffect(std::move(mbe)) {}
- protected:
- // When motion blur is present, time ticks are not passed to layer animators
- // but to the motion blur effect. The effect then drives the animators/scene-graph
- // during reval and render phases.
- void onTick(float t) override {
- fMotionBlurEffect->setT(t);
- }
- private:
- const sk_sp<MotionBlurEffect> fMotionBlurEffect;
- };
- } // namespace
- AnimationBuilder::AttachLayerContext::AttachLayerContext(const skjson::ArrayValue& jlayers)
- : fLayerList(jlayers) {}
- AnimationBuilder::AttachLayerContext::~AttachLayerContext() = default;
- AnimationBuilder::AttachLayerContext::TransformRec
- AnimationBuilder::AttachLayerContext::attachLayerTransform(const skjson::ObjectValue& jlayer,
- const AnimationBuilder* abuilder,
- TransformType type) {
- TransformRec result;
- const auto layer_index = ParseDefault<int>(jlayer["ind"], -1);
- if (layer_index >= 0) {
- auto* rec = fLayerTransformMap.find(layer_index);
- if (!rec) {
- rec = this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index);
- }
- SkASSERT(rec);
- // Note: the transform animator scope is *moved* to the result, because
- // we want the animators transferred to the LayerController.
- //
- // This is safe because a) the scope is not used internally, and
- // b) there is exactly one attachLayerTransform call per layer.
- // The transform node OTOH may be used at a later time for parenting.
- result.fTransformNode = rec->fTransformNode;
- result.fTransformScope = std::move(rec->fTransformScope);
- }
- return result;
- }
- sk_sp<sksg::Transform>
- AnimationBuilder::AttachLayerContext::attachParentLayerTransform(const skjson::ObjectValue& jlayer,
- const AnimationBuilder* abuilder,
- int layer_index) {
- const auto parent_index = ParseDefault<int>(jlayer["parent"], -1);
- if (parent_index < 0 || parent_index == layer_index)
- return nullptr;
- if (const auto* rec = fLayerTransformMap.find(parent_index))
- return rec->fTransformNode;
- for (const skjson::ObjectValue* l : fLayerList) {
- if (!l) continue;
- if (ParseDefault<int>((*l)["ind"], -1) == 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)->fTransformNode;
- }
- }
- return nullptr;
- }
- sk_sp<sksg::Transform>
- AnimationBuilder::AttachLayerContext::attachTransformNode(const skjson::ObjectValue& jlayer,
- const AnimationBuilder* abuilder,
- AnimatorScope* ascope,
- 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"], ascope,
- [camera_adapter] (const ScalarValue& pe) {
- // 'pe' (perspective?) corresponds to AE's "zoom" camera property.
- camera_adapter->setZoom(pe);
- });
- // parent_transform applies to the camera itself => it pre-composes inverted to the
- // camera/view/adapter transform.
- //
- // T_camera' = T_camera x Inv(parent_transform)
- //
- parent_transform = sksg::Transform::MakeInverse(std::move(parent_transform));
- return abuilder->attachMatrix3D(*jtransform, ascope,
- std::move(parent_transform),
- std::move(camera_adapter),
- true); // pre-compose parent
- }
- return (ParseDefault<int>(jlayer["ddd"], 0) == 0)
- ? abuilder->attachMatrix2D(*jtransform, ascope, std::move(parent_transform))
- : abuilder->attachMatrix3D(*jtransform, ascope, std::move(parent_transform));
- }
- AnimationBuilder::AttachLayerContext::TransformRec*
- AnimationBuilder::AttachLayerContext::attachLayerTransformImpl(const skjson::ObjectValue& jlayer,
- const AnimationBuilder* abuilder,
- TransformType type,
- int layer_index) {
- SkASSERT(!fLayerTransformMap.find(layer_index));
- // Add a stub entry to break recursion cycles.
- fLayerTransformMap.set(layer_index, { nullptr, {} });
- auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index);
- AnimatorScope ascope;
- auto transform = this->attachTransformNode(jlayer,
- abuilder,
- &ascope,
- std::move(parent_matrix),
- type);
- return fLayerTransformMap.set(layer_index, { std::move(transform), std::move(ascope) });
- }
- bool AnimationBuilder::AttachLayerContext::hasMotionBlur(const skjson::ObjectValue& jlayer) const {
- return fMotionBlurSamples > 1
- && fMotionBlurAngle > 0
- && ParseDefault(jlayer["mb"], false);
- }
- sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue* jlayer,
- AnimatorScope* ascope,
- AttachLayerContext* layerCtx) const {
- if (!jlayer) {
- return nullptr;
- }
- LayerInfo layer_info = {
- fSize,
- ParseDefault<float>((*jlayer)["ip"], 0.0f),
- ParseDefault<float>((*jlayer)["op"], 0.0f),
- };
- if (layer_info.fInPoint >= layer_info.fOutPoint) {
- this->log(Logger::Level::kError, nullptr,
- "Invalid layer in/out points: %f/%f.", layer_info.fInPoint, layer_info.fOutPoint);
- return nullptr;
- }
- const AutoPropertyTracker apt(this, *jlayer);
- using LayerBuilder = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
- LayerInfo*,
- AnimatorScope*) const;
- // AE is annoyingly inconsistent in how effects interact with layer transforms: depending on
- // the layer type, effects are applied before or after the content is transformed.
- //
- // Empirically, pre-rendered layers (for some loose meaning of "pre-rendered") are in the
- // former category (effects are subject to transformation), while the remaining types are in
- // the latter.
- enum : uint32_t {
- kTransformEffects = 1, // The layer transform also applies to its effects.
- };
- static constexpr struct {
- LayerBuilder fBuilder;
- uint32_t fFlags;
- } gLayerBuildInfo[] = {
- { &AnimationBuilder::attachPrecompLayer, kTransformEffects }, // 'ty': 0 -> precomp
- { &AnimationBuilder::attachSolidLayer , kTransformEffects }, // 'ty': 1 -> solid
- { &AnimationBuilder::attachImageLayer , kTransformEffects }, // 'ty': 2 -> image
- { &AnimationBuilder::attachNullLayer , 0 }, // 'ty': 3 -> null
- { &AnimationBuilder::attachShapeLayer , 0 }, // 'ty': 4 -> shape
- { &AnimationBuilder::attachTextLayer , 0 }, // 'ty': 5 -> text
- };
- const auto type = ParseDefault<int>((*jlayer)["ty"], -1);
- if ((type < 0) ||
- (type >= SkTo<int>(SK_ARRAY_COUNT(gLayerBuildInfo)) && type != kCameraLayerType)) {
- return nullptr;
- }
- // Optional layer transform.
- const auto transform_type = (type == kCameraLayerType)
- ? AttachLayerContext::TransformType::kCamera
- : AttachLayerContext::TransformType::kLayer;
- auto layer_transform_rec = layerCtx->attachLayerTransform(*jlayer, this, transform_type);
- 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.");
- return nullptr;
- }
- layerCtx->fCameraTransform = layer_transform_rec.fTransformNode;
- }
- AnimatorScope layer_animators = std::move(layer_transform_rec.fTransformScope);
- const auto transform_animator_count = layer_animators.size();
- const auto is_hidden = ParseDefault<bool>((*jlayer)["hd"], false) || type == kCameraLayerType;
- const auto& build_info = gLayerBuildInfo[is_hidden ? kNullLayerType : type];
- // Build the layer content fragment.
- auto layer = (this->*(build_info.fBuilder))(*jlayer, &layer_info, &layer_animators);
- // Clip layers with explicit dimensions.
- float w = 0, h = 0;
- if (Parse<float>((*jlayer)["w"], &w) && Parse<float>((*jlayer)["h"], &h)) {
- layer = sksg::ClipEffect::Make(std::move(layer),
- sksg::Rect::Make(SkRect::MakeWH(w, h)),
- true);
- }
- // Optional layer mask.
- layer = AttachMask((*jlayer)["masksProperties"], this, &layer_animators, std::move(layer));
- // Does the transform apply to effects also?
- // (AE quirk: it doesn't - except for solid layers)
- const auto transform_effects = (build_info.fFlags & kTransformEffects);
- // Attach the transform before effects, when needed.
- if (layer_transform_rec.fTransformNode && !transform_effects) {
- layer = sksg::TransformEffect::Make(std::move(layer), layer_transform_rec.fTransformNode);
- }
- // Optional layer effects.
- if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
- layer = EffectBuilder(this, layer_info.fSize, &layer_animators)
- .attachEffects(*jeffects, std::move(layer));
- }
- // Attach the transform after effects, when needed.
- if (layer_transform_rec.fTransformNode && transform_effects) {
- layer = sksg::TransformEffect::Make(std::move(layer),
- std::move(layer_transform_rec.fTransformNode));
- }
- // Optional layer opacity.
- // TODO: de-dupe this "ks" lookup with matrix above.
- if (const skjson::ObjectValue* jtransform = (*jlayer)["ks"]) {
- layer = this->attachOpacity(*jtransform, &layer_animators, std::move(layer));
- }
- // Optional blend mode.
- layer = this->attachBlendMode(*jlayer, std::move(layer));
- const auto has_animators = !layer_animators.empty();
- sk_sp<sksg::Animator> controller = sk_make_sp<LayerController>(std::move(layer_animators),
- layer,
- transform_animator_count,
- layer_info.fInPoint,
- layer_info.fOutPoint);
- // Optional motion blur.
- if (has_animators && layerCtx->hasMotionBlur(*jlayer)) {
- SkASSERT(layerCtx->fMotionBlurAngle >= 0);
- // Wrap both the layer node and the controller.
- auto motion_blur = MotionBlurEffect::Make(std::move(controller), std::move(layer),
- layerCtx->fMotionBlurSamples,
- layerCtx->fMotionBlurAngle,
- layerCtx->fMotionBlurPhase);
- controller = sk_make_sp<MotionBlurController>(motion_blur);
- layer = std::move(motion_blur);
- }
- ascope->push_back(std::move(controller));
- if (!layer) {
- return nullptr;
- }
- if (ParseDefault<bool>((*jlayer)["td"], false)) {
- // This layer is a matte. We apply it as a mask to the next layer.
- layerCtx->fCurrentMatte = std::move(layer);
- return nullptr;
- }
- if (layerCtx->fCurrentMatte) {
- // There is a pending matte. Apply and reset.
- static constexpr sksg::MaskEffect::Mode gMaskModes[] = {
- sksg::MaskEffect::Mode::kNormal, // tt: 1
- sksg::MaskEffect::Mode::kInvert, // tt: 2
- };
- const auto matteType = ParseDefault<size_t>((*jlayer)["tt"], 1) - 1;
- if (matteType < SK_ARRAY_COUNT(gMaskModes)) {
- return sksg::MaskEffect::Make(std::move(layer),
- std::move(layerCtx->fCurrentMatte),
- gMaskModes[matteType]);
- }
- layerCtx->fCurrentMatte.reset();
- }
- return layer;
- }
- } // namespace internal
- } // namespace skottie
|