1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "modules/skottie/src/Adapter.h"
9#include "modules/skottie/src/SkottieJson.h"
10#include "modules/skottie/src/SkottiePriv.h"
11#include "modules/skottie/src/SkottieValue.h"
12#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
13#include "modules/sksg/include/SkSGDashEffect.h"
14#include "modules/sksg/include/SkSGPaint.h"
15
16namespace skottie {
17namespace internal {
18
19namespace {
20
21class FillStrokeAdapter final : public DiscardableAdapterBase<FillStrokeAdapter, sksg::PaintNode> {
22public:
23 enum class Type { kFill, kStroke };
24
25 FillStrokeAdapter(const skjson::ObjectValue& jpaint,
26 const AnimationBuilder& abuilder,
27 sk_sp<sksg::PaintNode> paint_node,
28 sk_sp<AnimatablePropertyContainer> gradient_adapter,
29 Type type)
30 : INHERITED(std::move(paint_node))
31 , fShaderType(gradient_adapter ? ShaderType::kGradient : ShaderType::kColor) {
32
33 this->attachDiscardableAdapter(std::move(gradient_adapter));
34
35 this->bind(abuilder, jpaint["o"], fOpacity);
36
37 this->node()->setAntiAlias(true);
38
39 if (type == Type::kStroke) {
40 this->bind(abuilder, jpaint["w"], fStrokeWidth);
41
42 this->node()->setStyle(SkPaint::kStroke_Style);
43 this->node()->setStrokeMiter(ParseDefault<SkScalar>(jpaint["ml"], 4.0f));
44
45 static constexpr SkPaint::Join gJoins[] = {
46 SkPaint::kMiter_Join,
47 SkPaint::kRound_Join,
48 SkPaint::kBevel_Join,
49 };
50 this->node()->setStrokeJoin(
51 gJoins[std::min<size_t>(ParseDefault<size_t>(jpaint["lj"], 1) - 1,
52 SK_ARRAY_COUNT(gJoins) - 1)]);
53
54 static constexpr SkPaint::Cap gCaps[] = {
55 SkPaint::kButt_Cap,
56 SkPaint::kRound_Cap,
57 SkPaint::kSquare_Cap,
58 };
59 this->node()->setStrokeCap(
60 gCaps[std::min<size_t>(ParseDefault<size_t>(jpaint["lc"], 1) - 1,
61 SK_ARRAY_COUNT(gCaps) - 1)]);
62 }
63
64 if (fShaderType == ShaderType::kColor) {
65 this->bind(abuilder, jpaint["c"], fColor);
66 }
67 }
68
69private:
70 void onSync() override {
71 this->node()->setOpacity(fOpacity * 0.01f);
72 this->node()->setStrokeWidth(fStrokeWidth);
73
74 if (fShaderType == ShaderType::kColor) {
75 auto* color_node = static_cast<sksg::Color*>(this->node().get());
76 color_node->setColor(fColor);
77 }
78 }
79
80 enum class ShaderType { kColor, kGradient };
81
82 const ShaderType fShaderType;
83
84 VectorValue fColor;
85 ScalarValue fOpacity = 100,
86 fStrokeWidth = 1;
87
88 using INHERITED = DiscardableAdapterBase<FillStrokeAdapter, sksg::PaintNode>;
89};
90
91class DashAdapter final : public DiscardableAdapterBase<DashAdapter, sksg::DashEffect> {
92public:
93 DashAdapter(const skjson::ArrayValue& jdash,
94 const AnimationBuilder& abuilder,
95 sk_sp<sksg::GeometryNode> geo)
96 : INHERITED(sksg::DashEffect::Make(std::move(geo))) {
97 SkASSERT(jdash.size() > 1);
98
99 // The dash is encoded as an arbitrary number of intervals (alternating dash/gap),
100 // plus a single trailing offset. Each value can be animated independently.
101 const auto interval_count = jdash.size() - 1;
102 fIntervals.resize(interval_count, 0);
103
104 for (size_t i = 0; i < jdash.size(); ++i) {
105 if (const skjson::ObjectValue* jint = jdash[i]) {
106 auto* target = i < interval_count
107 ? &fIntervals[i]
108 : &fOffset;
109 this->bind(abuilder, (*jint)["v"], target);
110 }
111 }
112 }
113
114private:
115 void onSync() override {
116 this->node()->setPhase(fOffset);
117 this->node()->setIntervals(fIntervals);
118 }
119
120 std::vector<ScalarValue> fIntervals;
121 ScalarValue fOffset = 0;
122
123 using INHERITED = DiscardableAdapterBase<DashAdapter, sksg::DashEffect>;
124};
125
126} // namespace
127
128sk_sp<sksg::PaintNode> ShapeBuilder::AttachFill(const skjson::ObjectValue& jpaint,
129 const AnimationBuilder* abuilder,
130 sk_sp<sksg::PaintNode> paint_node,
131 sk_sp<AnimatablePropertyContainer> gradient) {
132 return abuilder->attachDiscardableAdapter<FillStrokeAdapter>
133 (jpaint,
134 *abuilder,
135 std::move(paint_node),
136 std::move(gradient),
137 FillStrokeAdapter::Type::kFill);
138}
139
140sk_sp<sksg::PaintNode> ShapeBuilder::AttachStroke(const skjson::ObjectValue& jpaint,
141 const AnimationBuilder* abuilder,
142 sk_sp<sksg::PaintNode> paint_node,
143 sk_sp<AnimatablePropertyContainer> gradient) {
144 return abuilder->attachDiscardableAdapter<FillStrokeAdapter>
145 (jpaint,
146 *abuilder,
147 std::move(paint_node),
148 std::move(gradient),
149 FillStrokeAdapter::Type::kStroke);
150}
151
152sk_sp<sksg::PaintNode> ShapeBuilder::AttachColorFill(const skjson::ObjectValue& jpaint,
153 const AnimationBuilder* abuilder) {
154 auto color_node = sksg::Color::Make(SK_ColorBLACK);
155 abuilder->dispatchColorProperty(color_node);
156
157 return AttachFill(jpaint, abuilder, std::move(color_node));
158}
159
160sk_sp<sksg::PaintNode> ShapeBuilder::AttachColorStroke(const skjson::ObjectValue& jpaint,
161 const AnimationBuilder* abuilder) {
162 auto color_node = sksg::Color::Make(SK_ColorBLACK);
163 abuilder->dispatchColorProperty(color_node);
164
165 return AttachStroke(jpaint, abuilder, std::move(color_node));
166}
167
168std::vector<sk_sp<sksg::GeometryNode>> ShapeBuilder::AdjustStrokeGeometry(
169 const skjson::ObjectValue& jstroke,
170 const AnimationBuilder* abuilder,
171 std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
172
173 const skjson::ArrayValue* jdash = jstroke["d"];
174 if (jdash && jdash->size() > 1) {
175 for (size_t i = 0; i < geos.size(); ++i) {
176 geos[i] = abuilder->attachDiscardableAdapter<DashAdapter>(
177 *jdash, *abuilder, std::move(geos[i]));
178 }
179 }
180
181 return std::move(geos);
182}
183
184} // namespace internal
185} // namespace skottie
186