/*
* 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 "SkottiePriv.h"
#include "SkJSON.h"
#include "SkottieAdapter.h"
#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkSGColor.h"
#include "SkSGRenderEffect.h"
#include "SkSGColorFilter.h"
namespace skottie {
namespace internal {
namespace {
sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kMapBlackTo_Index = 0,
kMapWhiteTo_Index = 1,
kAmount_Index = 2,
// kOpacity_Index = 3, // currently unused (not exported)
kMax_Index = kAmount_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color0_prop = jprops[kMapBlackTo_Index];
const skjson::ObjectValue* color1_prop = jprops[kMapWhiteTo_Index];
const skjson::ObjectValue* amount_prop = jprops[ kAmount_Index];
if (!color0_prop || !color1_prop || !amount_prop) {
return nullptr;
}
auto tint_node =
sksg::GradientColorFilter::Make(std::move(layer),
abuilder->attachColor(*color0_prop, ascope, "v"),
abuilder->attachColor(*color1_prop, ascope, "v"));
if (!tint_node) {
return nullptr;
}
abuilder->bindProperty<ScalarValue>((*amount_prop)["v"], ascope,
[tint_node](const ScalarValue& w) {
tint_node->setWeight(w / 100); // 100-based
});
return std::move(tint_node);
}
sk_sp<sksg::RenderNode> AttachTritoneLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kHiColor_Index = 0,
kMiColor_Index = 1,
kLoColor_Index = 2,
kBlendAmount_Index = 3,
kMax_Index = kBlendAmount_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* hicolor_prop = jprops[ kHiColor_Index];
const skjson::ObjectValue* micolor_prop = jprops[ kMiColor_Index];
const skjson::ObjectValue* locolor_prop = jprops[ kLoColor_Index];
const skjson::ObjectValue* blend_prop = jprops[kBlendAmount_Index];
if (!hicolor_prop || !micolor_prop || !locolor_prop || !blend_prop) {
return nullptr;
}
auto tritone_node =
sksg::GradientColorFilter::Make(std::move(layer), {
abuilder->attachColor(*locolor_prop, ascope, "v"),
abuilder->attachColor(*micolor_prop, ascope, "v"),
abuilder->attachColor(*hicolor_prop, ascope, "v") });
if (!tritone_node) {
return nullptr;
}
abuilder->bindProperty<ScalarValue>((*blend_prop)["v"], ascope,
[tritone_node](const ScalarValue& w) {
tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
});
return std::move(tritone_node);
}
sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kFillMask_Index = 0,
kAllMasks_Index = 1,
kColor_Index = 2,
kInvert_Index = 3,
kHFeather_Index = 4,
kVFeather_Index = 5,
kOpacity_Index = 6,
kMax_Index = kOpacity_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color_prop = jprops[ kColor_Index];
const skjson::ObjectValue* opacity_prop = jprops[kOpacity_Index];
if (!color_prop || !opacity_prop) {
return nullptr;
}
sk_sp<sksg::Color> color_node = abuilder->attachColor(*color_prop, ascope, "v");
if (!color_node) {
return nullptr;
}
abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
[color_node](const ScalarValue& o) {
const auto c = color_node->getColor();
const auto a = sk_float_round2int_no_saturate(SkTPin(o, 0.0f, 1.0f) * 255);
color_node->setColor(SkColorSetA(c, a));
});
return sksg::ModeColorFilter::Make(std::move(layer),
std::move(color_node),
SkBlendMode::kSrcIn);
}
sk_sp<sksg::RenderNode> AttachDropShadowLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kShadowColor_Index = 0,
kOpacity_Index = 1,
kDirection_Index = 2,
kDistance_Index = 3,
kSoftness_Index = 4,
kShadowOnly_Index = 5,
kMax_Index = kShadowOnly_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color_prop = jprops[kShadowColor_Index];
const skjson::ObjectValue* opacity_prop = jprops[ kOpacity_Index];
const skjson::ObjectValue* direction_prop = jprops[ kDirection_Index];
const skjson::ObjectValue* distance_prop = jprops[ kDistance_Index];
const skjson::ObjectValue* softness_prop = jprops[ kSoftness_Index];
const skjson::ObjectValue* shadow_only_prop = jprops[ kShadowOnly_Index];
if (!color_prop ||
!opacity_prop ||
!direction_prop ||
!distance_prop ||
!softness_prop ||
!shadow_only_prop) {
return nullptr;
}
auto shadow_effect = sksg::DropShadowImageFilter::Make();
auto shadow_adapter = sk_make_sp<DropShadowEffectAdapter>(shadow_effect);
abuilder->bindProperty<VectorValue>((*color_prop)["v"], ascope,
[shadow_adapter](const VectorValue& c) {
shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
});
abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& o) {
shadow_adapter->setOpacity(o);
});
abuilder->bindProperty<ScalarValue>((*direction_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDirection(d);
});
abuilder->bindProperty<ScalarValue>((*distance_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDistance(d);
});
abuilder->bindProperty<ScalarValue>((*softness_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setSoftness(s);
});
abuilder->bindProperty<ScalarValue>((*shadow_only_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setShadowOnly(SkToBool(s));
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(shadow_effect));
}
sk_sp<sksg::RenderNode> AttachGaussianBlurLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kBlurriness_Index = 0,
kDimensions_Index = 1,
kRepeatEdge_Index = 2,
kMax_Index = kRepeatEdge_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* blurriness_prop = jprops[kBlurriness_Index];
const skjson::ObjectValue* dimensions_prop = jprops[kDimensions_Index];
const skjson::ObjectValue* repeatedge_prop = jprops[kRepeatEdge_Index];
if (!blurriness_prop || !dimensions_prop || !repeatedge_prop) {
return nullptr;
}
auto blur_effect = sksg::BlurImageFilter::Make();
auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
abuilder->bindProperty<ScalarValue>((*blurriness_prop)["v"], ascope,
[blur_addapter](const ScalarValue& b) {
blur_addapter->setBlurriness(b);
});
abuilder->bindProperty<ScalarValue>((*dimensions_prop)["v"], ascope,
[blur_addapter](const ScalarValue& d) {
blur_addapter->setDimensions(d);
});
abuilder->bindProperty<ScalarValue>((*repeatedge_prop)["v"], ascope,
[blur_addapter](const ScalarValue& r) {
blur_addapter->setRepeatEdge(r);
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(blur_effect));
}
} // namespace
sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::ArrayValue& jeffects,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) const {
if (!layer) {
return nullptr;
}
enum : int32_t {
kTint_Effect = 20,
kFill_Effect = 21,
kTritone_Effect = 23,
kDropShadow_Effect = 25,
kGaussianBlur_Effect = 29,
};
for (const skjson::ObjectValue* jeffect : jeffects) {
if (!jeffect) {
continue;
}
const skjson::ArrayValue* jprops = (*jeffect)["ef"];
if (!jprops) {
continue;
}
switch (const auto ty = ParseDefault<int>((*jeffect)["ty"], -1)) {
case kTint_Effect:
layer = AttachTintLayerEffect(*jprops, this, ascope, std::move(layer));
break;
case kFill_Effect:
layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
break;
case kTritone_Effect:
layer = AttachTritoneLayerEffect(*jprops, this, ascope, std::move(layer));
break;
case kDropShadow_Effect:
layer = AttachDropShadowLayerEffect(*jprops, this, ascope, std::move(layer));
break;
case kGaussianBlur_Effect:
layer = AttachGaussianBlurLayerEffect(*jprops, this, ascope, std::move(layer));
break;
default:
this->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
break;
}
if (!layer) {
this->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
return nullptr;
}
}
return layer;
}
} // namespace internal
} // namespace skottie