1 /*
2 * Copyright 2018 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 "SkottiePriv.h"
9
10 #include "SkJSON.h"
11 #include "SkottieAdapter.h"
12 #include "SkottieJson.h"
13 #include "SkottieValue.h"
14 #include "SkSGColor.h"
15 #include "SkSGRenderEffect.h"
16 #include "SkSGColorFilter.h"
17
18 namespace skottie {
19 namespace internal {
20
21 namespace {
22
AttachTintLayerEffect(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer)23 sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
24 const AnimationBuilder* abuilder,
25 AnimatorScope* ascope,
26 sk_sp<sksg::RenderNode> layer) {
27 enum : size_t {
28 kMapBlackTo_Index = 0,
29 kMapWhiteTo_Index = 1,
30 kAmount_Index = 2,
31 // kOpacity_Index = 3, // currently unused (not exported)
32
33 kMax_Index = kAmount_Index,
34 };
35
36 if (jprops.size() <= kMax_Index) {
37 return nullptr;
38 }
39
40 const skjson::ObjectValue* color0_prop = jprops[kMapBlackTo_Index];
41 const skjson::ObjectValue* color1_prop = jprops[kMapWhiteTo_Index];
42 const skjson::ObjectValue* amount_prop = jprops[ kAmount_Index];
43
44 if (!color0_prop || !color1_prop || !amount_prop) {
45 return nullptr;
46 }
47
48 auto tint_node =
49 sksg::GradientColorFilter::Make(std::move(layer),
50 abuilder->attachColor(*color0_prop, ascope, "v"),
51 abuilder->attachColor(*color1_prop, ascope, "v"));
52 if (!tint_node) {
53 return nullptr;
54 }
55
56 abuilder->bindProperty<ScalarValue>((*amount_prop)["v"], ascope,
57 [tint_node](const ScalarValue& w) {
58 tint_node->setWeight(w / 100); // 100-based
59 });
60
61 return std::move(tint_node);
62 }
63
AttachTritoneLayerEffect(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer)64 sk_sp<sksg::RenderNode> AttachTritoneLayerEffect(const skjson::ArrayValue& jprops,
65 const AnimationBuilder* abuilder,
66 AnimatorScope* ascope,
67 sk_sp<sksg::RenderNode> layer) {
68 enum : size_t {
69 kHiColor_Index = 0,
70 kMiColor_Index = 1,
71 kLoColor_Index = 2,
72 kBlendAmount_Index = 3,
73
74 kMax_Index = kBlendAmount_Index,
75 };
76
77 if (jprops.size() <= kMax_Index) {
78 return nullptr;
79 }
80
81 const skjson::ObjectValue* hicolor_prop = jprops[ kHiColor_Index];
82 const skjson::ObjectValue* micolor_prop = jprops[ kMiColor_Index];
83 const skjson::ObjectValue* locolor_prop = jprops[ kLoColor_Index];
84 const skjson::ObjectValue* blend_prop = jprops[kBlendAmount_Index];
85
86 if (!hicolor_prop || !micolor_prop || !locolor_prop || !blend_prop) {
87 return nullptr;
88 }
89
90 auto tritone_node =
91 sksg::GradientColorFilter::Make(std::move(layer), {
92 abuilder->attachColor(*locolor_prop, ascope, "v"),
93 abuilder->attachColor(*micolor_prop, ascope, "v"),
94 abuilder->attachColor(*hicolor_prop, ascope, "v") });
95 if (!tritone_node) {
96 return nullptr;
97 }
98
99 abuilder->bindProperty<ScalarValue>((*blend_prop)["v"], ascope,
100 [tritone_node](const ScalarValue& w) {
101 tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
102 });
103
104 return std::move(tritone_node);
105 }
106
AttachFillLayerEffect(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer)107 sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
108 const AnimationBuilder* abuilder,
109 AnimatorScope* ascope,
110 sk_sp<sksg::RenderNode> layer) {
111 enum : size_t {
112 kFillMask_Index = 0,
113 kAllMasks_Index = 1,
114 kColor_Index = 2,
115 kInvert_Index = 3,
116 kHFeather_Index = 4,
117 kVFeather_Index = 5,
118 kOpacity_Index = 6,
119
120 kMax_Index = kOpacity_Index,
121 };
122
123 if (jprops.size() <= kMax_Index) {
124 return nullptr;
125 }
126
127 const skjson::ObjectValue* color_prop = jprops[ kColor_Index];
128 const skjson::ObjectValue* opacity_prop = jprops[kOpacity_Index];
129 if (!color_prop || !opacity_prop) {
130 return nullptr;
131 }
132
133 sk_sp<sksg::Color> color_node = abuilder->attachColor(*color_prop, ascope, "v");
134 if (!color_node) {
135 return nullptr;
136 }
137
138 abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
139 [color_node](const ScalarValue& o) {
140 const auto c = color_node->getColor();
141 const auto a = sk_float_round2int_no_saturate(SkTPin(o, 0.0f, 1.0f) * 255);
142 color_node->setColor(SkColorSetA(c, a));
143 });
144
145 return sksg::ModeColorFilter::Make(std::move(layer),
146 std::move(color_node),
147 SkBlendMode::kSrcIn);
148 }
149
AttachDropShadowLayerEffect(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer)150 sk_sp<sksg::RenderNode> AttachDropShadowLayerEffect(const skjson::ArrayValue& jprops,
151 const AnimationBuilder* abuilder,
152 AnimatorScope* ascope,
153 sk_sp<sksg::RenderNode> layer) {
154 enum : size_t {
155 kShadowColor_Index = 0,
156 kOpacity_Index = 1,
157 kDirection_Index = 2,
158 kDistance_Index = 3,
159 kSoftness_Index = 4,
160 kShadowOnly_Index = 5,
161
162 kMax_Index = kShadowOnly_Index,
163 };
164
165 if (jprops.size() <= kMax_Index) {
166 return nullptr;
167 }
168
169 const skjson::ObjectValue* color_prop = jprops[kShadowColor_Index];
170 const skjson::ObjectValue* opacity_prop = jprops[ kOpacity_Index];
171 const skjson::ObjectValue* direction_prop = jprops[ kDirection_Index];
172 const skjson::ObjectValue* distance_prop = jprops[ kDistance_Index];
173 const skjson::ObjectValue* softness_prop = jprops[ kSoftness_Index];
174 const skjson::ObjectValue* shadow_only_prop = jprops[ kShadowOnly_Index];
175
176 if (!color_prop ||
177 !opacity_prop ||
178 !direction_prop ||
179 !distance_prop ||
180 !softness_prop ||
181 !shadow_only_prop) {
182 return nullptr;
183 }
184
185 auto shadow_effect = sksg::DropShadowImageFilter::Make();
186 auto shadow_adapter = sk_make_sp<DropShadowEffectAdapter>(shadow_effect);
187
188 abuilder->bindProperty<VectorValue>((*color_prop)["v"], ascope,
189 [shadow_adapter](const VectorValue& c) {
190 shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
191 });
192 abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
193 [shadow_adapter](const ScalarValue& o) {
194 shadow_adapter->setOpacity(o);
195 });
196 abuilder->bindProperty<ScalarValue>((*direction_prop)["v"], ascope,
197 [shadow_adapter](const ScalarValue& d) {
198 shadow_adapter->setDirection(d);
199 });
200 abuilder->bindProperty<ScalarValue>((*distance_prop)["v"], ascope,
201 [shadow_adapter](const ScalarValue& d) {
202 shadow_adapter->setDistance(d);
203 });
204 abuilder->bindProperty<ScalarValue>((*softness_prop)["v"], ascope,
205 [shadow_adapter](const ScalarValue& s) {
206 shadow_adapter->setSoftness(s);
207 });
208 abuilder->bindProperty<ScalarValue>((*shadow_only_prop)["v"], ascope,
209 [shadow_adapter](const ScalarValue& s) {
210 shadow_adapter->setShadowOnly(SkToBool(s));
211 });
212
213 return sksg::ImageFilterEffect::Make(std::move(layer), std::move(shadow_effect));
214 }
215
AttachGaussianBlurLayerEffect(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer)216 sk_sp<sksg::RenderNode> AttachGaussianBlurLayerEffect(const skjson::ArrayValue& jprops,
217 const AnimationBuilder* abuilder,
218 AnimatorScope* ascope,
219 sk_sp<sksg::RenderNode> layer) {
220 enum : size_t {
221 kBlurriness_Index = 0,
222 kDimensions_Index = 1,
223 kRepeatEdge_Index = 2,
224
225 kMax_Index = kRepeatEdge_Index,
226 };
227
228 if (jprops.size() <= kMax_Index) {
229 return nullptr;
230 }
231
232 const skjson::ObjectValue* blurriness_prop = jprops[kBlurriness_Index];
233 const skjson::ObjectValue* dimensions_prop = jprops[kDimensions_Index];
234 const skjson::ObjectValue* repeatedge_prop = jprops[kRepeatEdge_Index];
235
236 if (!blurriness_prop || !dimensions_prop || !repeatedge_prop) {
237 return nullptr;
238 }
239
240 auto blur_effect = sksg::BlurImageFilter::Make();
241 auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
242
243 abuilder->bindProperty<ScalarValue>((*blurriness_prop)["v"], ascope,
244 [blur_addapter](const ScalarValue& b) {
245 blur_addapter->setBlurriness(b);
246 });
247 abuilder->bindProperty<ScalarValue>((*dimensions_prop)["v"], ascope,
248 [blur_addapter](const ScalarValue& d) {
249 blur_addapter->setDimensions(d);
250 });
251 abuilder->bindProperty<ScalarValue>((*repeatedge_prop)["v"], ascope,
252 [blur_addapter](const ScalarValue& r) {
253 blur_addapter->setRepeatEdge(r);
254 });
255
256 return sksg::ImageFilterEffect::Make(std::move(layer), std::move(blur_effect));
257 }
258
259 } // namespace
260
attachLayerEffects(const skjson::ArrayValue & jeffects,AnimatorScope * ascope,sk_sp<sksg::RenderNode> layer) const261 sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::ArrayValue& jeffects,
262 AnimatorScope* ascope,
263 sk_sp<sksg::RenderNode> layer) const {
264 if (!layer) {
265 return nullptr;
266 }
267
268 enum : int32_t {
269 kTint_Effect = 20,
270 kFill_Effect = 21,
271 kTritone_Effect = 23,
272 kDropShadow_Effect = 25,
273 kGaussianBlur_Effect = 29,
274 };
275
276 for (const skjson::ObjectValue* jeffect : jeffects) {
277 if (!jeffect) {
278 continue;
279 }
280
281 const skjson::ArrayValue* jprops = (*jeffect)["ef"];
282 if (!jprops) {
283 continue;
284 }
285
286 switch (const auto ty = ParseDefault<int>((*jeffect)["ty"], -1)) {
287 case kTint_Effect:
288 layer = AttachTintLayerEffect(*jprops, this, ascope, std::move(layer));
289 break;
290 case kFill_Effect:
291 layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
292 break;
293 case kTritone_Effect:
294 layer = AttachTritoneLayerEffect(*jprops, this, ascope, std::move(layer));
295 break;
296 case kDropShadow_Effect:
297 layer = AttachDropShadowLayerEffect(*jprops, this, ascope, std::move(layer));
298 break;
299 case kGaussianBlur_Effect:
300 layer = AttachGaussianBlurLayerEffect(*jprops, this, ascope, std::move(layer));
301 break;
302 default:
303 this->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
304 break;
305 }
306
307 if (!layer) {
308 this->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
309 return nullptr;
310 }
311 }
312
313 return layer;
314 }
315
316 } // namespace internal
317 } // namespace skottie
318