[skottie] Add support for keyframed text nodes

-- introduce a new animatable value (TextValue)

 -- introduce a new adapter (TextAdapter) to translate Lottie text props to SG text props

 -- use existing animated property-bind machinery and the new constructs when parsing text layers

Change-Id: Ibbfb69daf5b0a3c9a5ce8d1ccdeedca5b5d0fa6f
Reviewed-on: https://skia-review.googlesource.com/149266
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2018-08-26 22:06:55 -04:00 committed by Skia Commit-Bot
parent adb4bfef0c
commit 9402c7dc37
8 changed files with 235 additions and 90 deletions

View File

@ -10,9 +10,13 @@
#include "SkMatrix.h"
#include "SkPath.h"
#include "SkRRect.h"
#include "SkSGColor.h"
#include "SkSGDraw.h"
#include "SkSGGradient.h"
#include "SkSGGroup.h"
#include "SkSGPath.h"
#include "SkSGRect.h"
#include "SkSGText.h"
#include "SkSGTransform.h"
#include "SkSGTrimEffect.h"
#include "SkTo.h"
@ -169,4 +173,75 @@ void TrimEffectAdapter::apply() {
fTrimEffect->setMode(mode);
}
TextAdapter::TextAdapter(sk_sp<sksg::Group> root)
: fRoot(std::move(root))
, fTextNode(sksg::Text::Make(nullptr, SkString()))
, fFillColor(sksg::Color::Make(SK_ColorTRANSPARENT))
, fStrokeColor(sksg::Color::Make(SK_ColorTRANSPARENT))
, fFillNode(sksg::Draw::Make(fTextNode, fFillColor))
, fStrokeNode(sksg::Draw::Make(fTextNode, fStrokeColor))
, fHadFill(false)
, fHadStroke(false) {
// Build a SG fragment with the following general format:
//
// [Group]
// [Draw]
// [FillPaint]
// [Text]*
// [Draw]
// [StrokePaint]
// [Text]*
//
// * where the text node is shared
fTextNode->setFlags(fTextNode->getFlags() |
SkPaint::kAntiAlias_Flag |
SkPaint::kSubpixelText_Flag);
fTextNode->setHinting(SkPaint::kNo_Hinting);
fStrokeColor->setStyle(SkPaint::kStroke_Style);
}
void TextAdapter::apply() {
// Push text props to the scene graph.
fTextNode->setTypeface(fText.fTypeface);
fTextNode->setText(fText.fText);
fTextNode->setSize(fText.fTextSize);
fTextNode->setAlign(fText.fAlign);
fFillColor->setColor(fText.fFillColor);
fStrokeColor->setColor(fText.fStrokeColor);
fStrokeColor->setStrokeWidth(fText.fStrokeWidth);
// Turn the state transition into a tri-state value:
// -1: detach node
// 0: no change
// 1: attach node
const auto fill_change = SkToInt(fText.fHasFill) - SkToInt(fHadFill);
const auto stroke_change = SkToInt(fText.fHasStroke) - SkToInt(fHadStroke);
// Sync SG topology.
if (fill_change || stroke_change) {
// This is trickier than it should be because sksg::Group only allows adding children
// in paint-order.
if (stroke_change < 0 || (fHadStroke && fill_change > 0)) {
fRoot->removeChild(fStrokeNode);
}
if (fill_change < 0) {
fRoot->removeChild(fFillNode);
} else if (fill_change > 0) {
fRoot->addChild(fFillNode);
}
if (stroke_change > 0 || (fHadStroke && fill_change > 0)) {
fRoot->addChild(fStrokeNode);
}
}
// Track current state.
fHadFill = fText.fHasFill;
fHadStroke = fText.fHasStroke;
}
} // namespace skottie

View File

@ -15,12 +15,16 @@
namespace sksg {
class Color;
class Draw;
class Gradient;
class Group;
class LinearGradient;
class Matrix;
class Path;
class RadialGradient;
class RRect;
class Text;
class TrimEffect;
};
@ -156,6 +160,30 @@ private:
using INHERITED = SkRefCnt;
};
class TextAdapter final : public SkRefCnt {
public:
explicit TextAdapter(sk_sp<sksg::Group> root);
ADAPTER_PROPERTY(Text, TextValue, TextValue())
const sk_sp<sksg::Group>& root() const { return fRoot; }
private:
void apply();
sk_sp<sksg::Group> fRoot;
sk_sp<sksg::Text> fTextNode;
sk_sp<sksg::Color> fFillColor,
fStrokeColor;
sk_sp<sksg::Draw> fFillNode,
fStrokeNode;
bool fHadFill : 1, // - state cached from the prev apply()
fHadStroke : 1; // /
using INHERITED = SkRefCnt;
};
#undef ADAPTER_PROPERTY
} // namespace skottie

View File

@ -389,5 +389,13 @@ bool AnimationBuilder::bindProperty(const skjson::Value& jv,
return BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
}
template <>
bool AnimationBuilder::bindProperty(const skjson::Value& jv,
AnimatorScope* ascope,
std::function<void(const TextValue&)>&& apply,
const TextValue* noop) const {
return BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
}
} // namespace internal
} // namespace skottie

View File

@ -49,6 +49,8 @@ public:
std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
sk_sp<SkTypeface> findFont(const SkString& name) const;
// This is the workhorse for property binding: depending on whether the property is animated,
// it will either apply immediately or instantiate and attach a keyframe animator.
template <typename T>

View File

@ -9,6 +9,7 @@
#include "SkFontMgr.h"
#include "SkMakeUnique.h"
#include "SkottieAdapter.h"
#include "SkottieJson.h"
#include "SkottieValue.h"
#include "SkSGColor.h"
@ -205,6 +206,15 @@ void AnimationBuilder::parseFonts(const skjson::ObjectValue* jfonts,
}
}
sk_sp<SkTypeface> AnimationBuilder::findFont(const SkString& font_name) const {
if (const auto* font = fFonts.find(font_name)) {
return font->fTypeface;
}
LOG("!! Unknown font: \"%s\"\n", font_name.c_str());
return nullptr;
}
sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer,
AnimatorScope* ascope) const {
// General text node format:
@ -245,93 +255,19 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectVa
LOG("?? Unsupported animated text properties.\n");
}
// TODO: The "d" node is keyframed, not static. Add a new animated value type and parse as such.
const skjson::ObjectValue* jd = (*jt)["d"];
const skjson::ArrayValue* jk = jd
? (*jd)["k"].operator const skjson::ArrayValue*() : nullptr;
const skjson::ObjectValue* jv0 = jk && jk->size() == 1
? (*jk)[0].operator const skjson::ObjectValue*() : nullptr;
const skjson::ObjectValue* jprops = jv0
? (*jv0)["s"].operator const skjson::ObjectValue*() : nullptr;
if (!jprops) {
LogJSON(*jt, "!! Unexpected text property");
if (!jd) {
return nullptr;
}
const skjson::StringValue* font_name = (*jprops)["f"];
const skjson::StringValue* text = (*jprops)["t"];
const skjson::NumberValue* text_size = (*jprops)["s"];
if (!font_name || !text || !text_size) {
LogJSON(*jprops, "!! Invalid text properties");
return nullptr;
}
auto text_root = sksg::Group::Make();
auto adapter = sk_make_sp<TextAdapter>(text_root);
const auto* font = fFonts.find(SkString(font_name->begin(), font_name->size()));
if (!font) {
LOG("!! Unknown font: \"%s\"\n", font_name->begin());
return nullptr;
}
this->bindProperty<TextValue>(*jd, ascope, [adapter] (const TextValue& txt) {
adapter->setText(txt);
});
static constexpr SkPaint::Align gAlignMap[] = {
SkPaint::kLeft_Align, // 'j': 0
SkPaint::kRight_Align, // 'j': 1
SkPaint::kCenter_Align // 'j': 2
};
const auto align = gAlignMap[SkTMin<size_t>(ParseDefault<size_t>((*jprops)["j"], 0),
SK_ARRAY_COUNT(gAlignMap))];
// Emit a SG fragment with the following general format:
//
// [Group]
// [Draw]
// [FillPaint]
// [Text]
// [Draw]
// [StrokePaint]
// [Text]
//
auto text_node = sksg::Text::Make(font->fTypeface, SkString(text->begin(), text->size()));
text_node->setFlags(text_node->getFlags() |
SkPaint::kAntiAlias_Flag |
SkPaint::kSubpixelText_Flag);
text_node->setSize(**text_size);
text_node->setAlign(align);
text_node->setHinting(SkPaint::kNo_Hinting);
const auto parse_color = [](const skjson::ArrayValue* jcolor) -> sk_sp<sksg::Color> {
VectorValue color_vec;
if (!jcolor || !Parse(*jcolor, &color_vec)) {
return nullptr;
}
auto paint = sksg::Color::Make(ValueTraits<VectorValue>::As<SkColor>(color_vec));
return paint;
};
auto fill_paint = parse_color((*jprops)["fc"]),
stroke_paint = parse_color((*jprops)["sc"]);
auto fill_node = sksg::Draw::Make(text_node, fill_paint),
stroke_node = sksg::Draw::Make(text_node, stroke_paint);
if (!stroke_node) {
return std::move(fill_node);
}
stroke_paint->setStyle(SkPaint::kStroke_Style);
stroke_paint->setStrokeWidth(ParseDefault((*jprops)["sw"], 0.0f));
if (!fill_node) {
return std::move(stroke_node);
}
// Fill & stroke
auto group_node = sksg::Group::Make();
group_node->addChild(std::move(fill_node));
group_node->addChild(std::move(stroke_node));
return std::move(group_node);
return std::move(text_root);
}
} // namespace internal

View File

@ -234,4 +234,69 @@ SkPath ValueTraits<ShapeValue>::As<SkPath>(const ShapeValue& shape) {
return path;
}
template <>
bool ValueTraits<TextValue>::FromJSON(const skjson::Value& jv,
const internal::AnimationBuilder* abuilder,
TextValue* v) {
const skjson::ObjectValue* jtxt = jv;
if (!jtxt) {
return false;
}
const skjson::StringValue* font_name = (*jtxt)["f"];
const skjson::StringValue* text = (*jtxt)["t"];
const skjson::NumberValue* text_size = (*jtxt)["s"];
if (!font_name || !text || !text_size ||
!(v->fTypeface = abuilder->findFont(SkString(font_name->begin(), font_name->size())))) {
return false;
}
v->fText.set(text->begin(), text->size());
v->fTextSize = **text_size;
static constexpr SkPaint::Align gAlignMap[] = {
SkPaint::kLeft_Align, // 'j': 0
SkPaint::kRight_Align, // 'j': 1
SkPaint::kCenter_Align // 'j': 2
};
v->fAlign = gAlignMap[SkTMin<size_t>(ParseDefault<size_t>((*jtxt)["j"], 0),
SK_ARRAY_COUNT(gAlignMap))];
const auto& parse_color = [] (const skjson::ArrayValue* jcolor,
const internal::AnimationBuilder* abuilder,
SkColor* c) {
if (!jcolor) {
return false;
}
VectorValue color_vec;
if (!ValueTraits<VectorValue>::FromJSON(*jcolor, abuilder, &color_vec)) {
return false;
}
*c = ValueTraits<VectorValue>::As<SkColor>(color_vec);
return true;
};
v->fHasFill = parse_color((*jtxt)["fc"], abuilder, &v->fFillColor);
v->fHasStroke = parse_color((*jtxt)["sc"], abuilder, &v->fStrokeColor);
if (v->fHasStroke) {
v->fStrokeWidth = ParseDefault((*jtxt)["s"], 0.0f);
}
return true;
}
template <>
bool ValueTraits<TextValue>::CanLerp(const TextValue&, const TextValue&) {
// Text values are never interpolated, but we pretend that they could be.
return true;
}
template <>
void ValueTraits<TextValue>::Lerp(const TextValue& v0, const TextValue&, float, TextValue* result) {
// Text value keyframes are treated as selectors, not as interpolated values.
*result = v0;
}
} // namespace skottie

View File

@ -8,8 +8,12 @@
#ifndef SkottieValue_DEFINED
#define SkottieValue_DEFINED
#include "SkColor.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkScalar.h"
#include "SkString.h"
#include "SkTypeface.h"
#include <vector>
@ -65,6 +69,32 @@ struct ShapeValue {
bool operator!=(const ShapeValue& other) const { return !(*this == other); }
};
struct TextValue {
sk_sp<SkTypeface> fTypeface;
SkString fText;
float fTextSize = 0,
fStrokeWidth = 0;
SkPaint::Align fAlign = SkPaint::kLeft_Align;
SkColor fFillColor = SK_ColorTRANSPARENT,
fStrokeColor = SK_ColorTRANSPARENT;
bool fHasFill : 1,
fHasStroke : 1;
bool operator==(const TextValue& other) const {
return fTypeface == other.fTypeface
&& fText == other.fText
&& fTextSize == other.fTextSize
&& fStrokeWidth == other.fStrokeWidth
&& fAlign == other.fAlign
&& fFillColor == other.fFillColor
&& fStrokeColor == other.fStrokeColor
&& fHasFill == other.fHasFill
&& fHasStroke == other.fHasStroke;
}
bool operator!=(const TextValue& other) const { return !(*this == other); }
};
} // namespace skottie
#endif // SkottieValue_DEFINED

View File

@ -29,14 +29,15 @@ public:
static sk_sp<Text> Make(sk_sp<SkTypeface> tf, const SkString& text);
~Text() override;
SG_ATTRIBUTE(Text , SkString , fText )
SG_ATTRIBUTE(Flags , uint32_t , fFlags )
SG_ATTRIBUTE(Position, SkPoint , fPosition)
SG_ATTRIBUTE(Size , SkScalar , fSize )
SG_ATTRIBUTE(ScaleX , SkScalar , fScaleX )
SG_ATTRIBUTE(SkewX , SkScalar , fSkewX )
SG_ATTRIBUTE(Align , SkPaint::Align , fAlign )
SG_ATTRIBUTE(Hinting , SkPaint::Hinting, fHinting )
SG_ATTRIBUTE(Typeface, sk_sp<SkTypeface>, fTypeface)
SG_ATTRIBUTE(Text , SkString , fText )
SG_ATTRIBUTE(Flags , uint32_t , fFlags )
SG_ATTRIBUTE(Position, SkPoint , fPosition)
SG_ATTRIBUTE(Size , SkScalar , fSize )
SG_ATTRIBUTE(ScaleX , SkScalar , fScaleX )
SG_ATTRIBUTE(SkewX , SkScalar , fSkewX )
SG_ATTRIBUTE(Align , SkPaint::Align , fAlign )
SG_ATTRIBUTE(Hinting , SkPaint::Hinting , fHinting )
// TODO: add shaping functionality.
@ -52,7 +53,7 @@ private:
SkPoint alignedPosition(SkScalar advance) const;
const sk_sp<SkTypeface> fTypeface;
sk_sp<SkTypeface> fTypeface;
SkString fText;
uint32_t fFlags = SkPaintDefaults_Flags;
SkPoint fPosition = SkPoint::Make(0, 0);