Flutter Engine
The Flutter Engine
DisplacementMapEffect.cpp
Go to the documentation of this file.
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
14#include "include/core/SkRect.h"
19#include "include/core/SkSize.h"
33#include "src/utils/SkJSON.h"
34
35#include <algorithm>
36#include <cmath>
37#include <cstdio>
38#include <tuple>
39#include <utility>
40#include <vector>
41
42struct SkPoint;
43
44namespace sksg {
45class InvalidationController;
46}
47
48namespace skottie::internal {
49namespace {
50
51// AE's displacement map effect [1] is somewhat similar to SVG's feDisplacementMap [2]. Main
52// differences:
53//
54// - more selector options: full/half/off, luminance, hue/saturation/lightness
55// - the scale factor is anisotropic (independent x/y values)
56// - displacement coverage is restricted to non-transparent source for some selectors
57// (specifically: r, g, b, h, s, l).
58//
59// [1] https://helpx.adobe.com/after-effects/using/distort-effects.html#displacement_map_effect
60// [2] https://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement
61
62// |selector_matrix| and |selector_offset| are set up to select and scale the x/y displacement
63// in R/G, and the x/y coverage modulation in B/A.
64static constexpr char gDisplacementSkSL[] =
65 "uniform shader child;"
66 "uniform shader displ;"
67
68 "uniform half4x4 selector_matrix;"
69 "uniform half4 selector_offset;"
70
71 "half4 main(float2 xy) {"
72 "half4 d = displ.eval(xy);"
73
74 "d = selector_matrix*unpremul(d) + selector_offset;"
75
76 "return child.eval(xy + d.xy*d.zw);"
77 "}"
78;
79
80static sk_sp<SkRuntimeEffect> displacement_effect_singleton() {
81 static const SkRuntimeEffect* effect =
83 if (0 && !effect) {
84 auto err = SkRuntimeEffect::MakeForShader(SkString(gDisplacementSkSL)).errorText;
85 printf("!!! %s\n", err.c_str());
86 }
87 SkASSERT(effect);
88
89 return sk_ref_sp(effect);
90}
91
92class DisplacementNode final : public sksg::CustomRenderNode {
93public:
94 ~DisplacementNode() override {
95 this->unobserveInval(fDisplSource);
96 }
97
99 const SkSize& child_size,
100 sk_sp<RenderNode> displ,
101 const SkSize& displ_size) {
102 if (!child || !displ) {
103 return nullptr;
104 }
105
106 return sk_sp<DisplacementNode>(new DisplacementNode(std::move(child), child_size,
107 std::move(displ), displ_size));
108 }
109
110 enum class Pos : unsigned {
111 kCenter,
112 kStretch,
113 kTile,
114
115 kLast = kTile,
116 };
117
118 enum class Selector : unsigned {
119 kR,
120 kG,
121 kB,
122 kA,
123 kLuminance,
124 kHue,
125 kLightness,
127 kFull,
128 kHalf,
129 kOff,
130
131 kLast = kOff,
132 };
133
134 SG_ATTRIBUTE(Scale , SkV2 , fScale )
135 SG_ATTRIBUTE(ChildTileMode, SkTileMode, fChildTileMode )
136 SG_ATTRIBUTE(Pos , Pos , fPos )
137 SG_ATTRIBUTE(XSelector , Selector , fXSelector )
138 SG_ATTRIBUTE(YSelector , Selector , fYSelector )
139 SG_ATTRIBUTE(ExpandBounds , bool , fExpandBounds )
140
141private:
142 DisplacementNode(sk_sp<RenderNode> child, const SkSize& child_size,
143 sk_sp<RenderNode> displ, const SkSize& displ_size)
144 : INHERITED({std::move(child)})
145 , fDisplSource(std::move(displ))
146 , fDisplSize(displ_size)
147 , fChildSize(child_size)
148 {
149 this->observeInval(fDisplSource);
150 }
151
152 struct SelectorCoeffs {
153 float dr, dg, db, da, d_offset, // displacement contribution
154 c_scale, c_offset; // coverage as a function of alpha
155 };
156
157 static SelectorCoeffs Coeffs(Selector sel) {
158 // D = displacement input
159 // C = displacement coverage
160 static constexpr SelectorCoeffs gCoeffs[] = {
161 { 1,0,0,0,0, 1,0 }, // kR: D = r, C = a
162 { 0,1,0,0,0, 1,0 }, // kG: D = g, C = a
163 { 0,0,1,0,0, 1,0 }, // kB: D = b, C = a
164 { 0,0,0,1,0, 0,1 }, // kA: D = a, C = 1.0
166 // kLuminance: D = lum(rgb), C = a
167 { 1,0,0,0,0, 0,1 }, // kH: D = h, C = 1.0 (HSLA)
168 { 0,1,0,0,0, 0,1 }, // kL: D = l, C = 1.0 (HSLA)
169 { 0,0,1,0,0, 0,1 }, // kS: D = s, C = 1.0 (HSLA)
170 { 0,0,0,0,1, 0,1 }, // kFull: D = 1.0, C = 1.0
171 { 0,0,0,0,.5f, 0,1 }, // kHalf: D = 0.5, C = 1.0
172 { 0,0,0,0,0, 0,1 }, // kOff: D = 0.0, C = 1.0
173 };
174
175 const auto i = static_cast<size_t>(sel);
176 SkASSERT(i < std::size(gCoeffs));
177
178 return gCoeffs[i];
179 }
180
181 static bool IsConst(Selector s) {
182 return s == Selector::kFull
183 || s == Selector::kHalf
184 || s == Selector::kOff;
185 }
186
187 sk_sp<SkShader> buildEffectShader(sksg::InvalidationController* ic, const SkMatrix& ctm) {
188 // AE quirk: combining two const/generated modes does not displace - we need at
189 // least one non-const selector to trigger the effect. *shrug*
190 if ((IsConst(fXSelector) && IsConst(fYSelector)) ||
191 (SkScalarNearlyZero(fScale.x) && SkScalarNearlyZero(fScale.y))) {
192 return nullptr;
193 }
194
195 auto get_content_picture = [](const sk_sp<sksg::RenderNode>& node,
196 sksg::InvalidationController* ic, const SkMatrix& ctm) {
197 if (!node) {
198 return sk_sp<SkPicture>(nullptr);
199 }
200
201 const auto bounds = node->revalidate(ic, ctm);
202
203 SkPictureRecorder recorder;
204 node->render(recorder.beginRecording(bounds));
205 return recorder.finishRecordingAsPicture();
206 };
207
208 const auto child_content = get_content_picture(this->children()[0], ic, ctm),
209 displ_content = get_content_picture(fDisplSource, ic, ctm);
210 if (!child_content || !displ_content) {
211 return nullptr;
212 }
213
214 const auto child_tile = SkRect::MakeSize(fChildSize);
215 auto child_shader = child_content->makeShader(fChildTileMode,
216 fChildTileMode,
218 nullptr,
219 &child_tile);
220
221 const auto displ_tile = SkRect::MakeSize(fDisplSize);
222 const auto displ_mode = this->displacementTileMode();
223 const auto displ_matrix = this->displacementMatrix();
224 auto displ_shader = displ_content->makeShader(displ_mode,
225 displ_mode,
227 &displ_matrix,
228 &displ_tile);
229
230 SkRuntimeShaderBuilder builder(displacement_effect_singleton());
231 builder.child("child") = std::move(child_shader);
232 builder.child("displ") = std::move(displ_shader);
233
234 const auto xc = Coeffs(fXSelector),
235 yc = Coeffs(fYSelector);
236
237 const auto s = fScale * 2;
238
239 const float selector_m[] = {
240 xc.dr*s.x, yc.dr*s.y, 0, 0,
241 xc.dg*s.x, yc.dg*s.y, 0, 0,
242 xc.db*s.x, yc.db*s.y, 0, 0,
243 xc.da*s.x, yc.da*s.y, xc.c_scale, yc.c_scale,
244
245 // │ │ │ └──── A -> vertical modulation
246 // │ │ └──────────────── B -> horizontal modulation
247 // │ └──────────────────────────────── G -> vertical displacement
248 // └─────────────────────────────────────────── R -> horizontal displacement
249 };
250 const float selector_o[] = {
251 (xc.d_offset - .5f) * s.x,
252 (yc.d_offset - .5f) * s.y,
253 xc.c_offset,
254 yc.c_offset,
255 };
256
257 builder.uniform("selector_matrix") = selector_m;
258 builder.uniform("selector_offset") = selector_o;
259
260 // TODO: RGB->HSL stage
261 return builder.makeShader();
262 }
263
264 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
265 fEffectShader = this->buildEffectShader(ic, ctm);
266
267 auto bounds = this->children()[0]->revalidate(ic, ctm);
268 if (fExpandBounds) {
269 // Expand the bounds to accommodate max displacement (which is |fScale|).
270 bounds.outset(std::abs(fScale.x), std::abs(fScale.y));
271 }
272
273 return bounds;
274 }
275
276 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
277 if (!fEffectShader) {
278 // no displacement effect - just render the content
279 this->children()[0]->render(canvas, ctx);
280 return;
281 }
282
283 auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
284 canvas->getTotalMatrix(),
285 true);
286 SkPaint shader_paint;
287 shader_paint.setShader(fEffectShader);
288
289 canvas->drawRect(this->bounds(), shader_paint);
290 }
291
292 SkTileMode displacementTileMode() const {
293 return fPos == Pos::kTile
296 }
297
298 SkMatrix displacementMatrix() const {
299 switch (fPos) {
300 case Pos::kCenter: return SkMatrix::Translate(
301 (fChildSize.fWidth - fDisplSize.fWidth ) / 2,
302 (fChildSize.fHeight - fDisplSize.fHeight) / 2);
303 case Pos::kStretch: return SkMatrix::Scale(
304 fChildSize.fWidth / fDisplSize.fWidth,
305 fChildSize.fHeight / fDisplSize.fHeight);
306 case Pos::kTile: return SkMatrix::I();
307 }
309 }
310
311 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
312
313 const sk_sp<sksg::RenderNode> fDisplSource;
314 const SkSize fDisplSize,
315 fChildSize;
316
317 // Cached top-level shader
318 sk_sp<SkShader> fEffectShader;
319
320 SkV2 fScale = { 0, 0 };
321 SkTileMode fChildTileMode = SkTileMode::kDecal;
322 Pos fPos = Pos::kCenter;
323 Selector fXSelector = Selector::kR,
324 fYSelector = Selector::kR;
325 bool fExpandBounds = false;
326
328};
329
330class DisplacementMapAdapter final : public DiscardableAdapterBase<DisplacementMapAdapter,
331 DisplacementNode> {
332public:
333 DisplacementMapAdapter(const skjson::ArrayValue& jprops,
334 const AnimationBuilder* abuilder,
336 : INHERITED(std::move(node)) {
337 EffectBinder(jprops, *abuilder, this)
338 .bind(kUseForHorizontal_Index, fHorizontalSelector)
339 .bind(kMaxHorizontal_Index , fMaxHorizontal )
340 .bind(kUseForVertical_Index , fVerticalSelector )
341 .bind(kMaxVertical_Index , fMaxVertical )
342 .bind(kMapBehavior_Index , fMapBehavior )
343 .bind(kEdgeBehavior_Index , fEdgeBehavior )
344 .bind(kExpandOutput_Index , fExpandOutput );
345 }
346
347 static std::tuple<sk_sp<sksg::RenderNode>, SkSize> GetDisplacementSource(
348 const skjson::ArrayValue& jprops,
349 const EffectBuilder* ebuilder) {
350
351 if (const skjson::ObjectValue* jv = EffectBuilder::GetPropValue(jprops, kMapLayer_Index)) {
352 auto* map_builder = ebuilder->getLayerBuilder(ParseDefault((*jv)["k"], -1));
353 if (map_builder) {
354 return std::make_tuple(map_builder->contentTree(), map_builder->size());
355 }
356 }
357
358 return std::make_tuple<sk_sp<sksg::RenderNode>, SkSize>(nullptr, {0,0});
359 }
360
361private:
362 enum : size_t {
363 kMapLayer_Index = 0,
364 kUseForHorizontal_Index = 1,
365 kMaxHorizontal_Index = 2,
366 kUseForVertical_Index = 3,
367 kMaxVertical_Index = 4,
368 kMapBehavior_Index = 5,
369 kEdgeBehavior_Index = 6,
370 kExpandOutput_Index = 7,
371 };
372
373 template <typename E>
374 E ToEnum(float v) {
375 // map one-based float "enums" to real enum types
376 const auto uv = std::min(static_cast<unsigned>(v) - 1,
377 static_cast<unsigned>(E::kLast));
378
379 return static_cast<E>(uv);
380 }
381
382 void onSync() override {
383 if (!this->node()) {
384 return;
385 }
386
387 this->node()->setScale({fMaxHorizontal, fMaxVertical});
388 this->node()->setChildTileMode(fEdgeBehavior != 0 ? SkTileMode::kRepeat
390
391 this->node()->setPos(ToEnum<DisplacementNode::Pos>(fMapBehavior));
392 this->node()->setXSelector(ToEnum<DisplacementNode::Selector>(fHorizontalSelector));
393 this->node()->setYSelector(ToEnum<DisplacementNode::Selector>(fVerticalSelector));
394 this->node()->setExpandBounds(fExpandOutput != 0);
395 }
396
397 ScalarValue fHorizontalSelector = 0,
398 fVerticalSelector = 0,
399 fMaxHorizontal = 0,
400 fMaxVertical = 0,
401 fMapBehavior = 0,
402 fEdgeBehavior = 0,
403 fExpandOutput = 0;
404
405 using INHERITED = DiscardableAdapterBase<DisplacementMapAdapter, DisplacementNode>;
406};
407
408} // namespace
409
410sk_sp<sksg::RenderNode> EffectBuilder::attachDisplacementMapEffect(
411 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
412 auto [ displ, displ_size ] = DisplacementMapAdapter::GetDisplacementSource(jprops, this);
413
414 auto displ_node = DisplacementNode::Make(layer, fLayerSize, std::move(displ), displ_size);
415
416 if (!displ_node) {
417 return layer;
418 }
419
420 return fBuilder->attachDiscardableAdapter<DisplacementMapAdapter>(jprops,
421 fBuilder,
422 std::move(displ_node));
423}
424
425} // namespace skottie::internal
float c_scale
float c_offset
float d_offset
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SK_LUM_COEFF_B
Definition: SkColorData.h:112
#define SK_LUM_COEFF_R
Definition: SkColorData.h:110
#define SK_LUM_COEFF_G
Definition: SkColorData.h:111
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
#define SG_ATTRIBUTE(attr_name, attr_type, attr_container)
Definition: SkSGNode.h:100
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:101
SkTileMode
Definition: SkTileMode.h:13
void drawRect(const SkRect &rect, const SkPaint &paint)
Definition: SkCanvas.cpp:1673
SkMatrix getTotalMatrix() const
Definition: SkCanvas.cpp:1629
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition: SkMatrix.h:75
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition: SkMatrix.h:91
static const SkMatrix & I()
Definition: SkMatrix.cpp:1544
void setShader(sk_sp< SkShader > shader)
SkCanvas * beginRecording(const SkRect &bounds, sk_sp< SkBBoxHierarchy > bbh)
sk_sp< SkPicture > finishRecordingAsPicture()
static Result MakeForShader(SkString sksl, const Options &)
T * release()
Definition: SkRefCnt.h:324
void attachDiscardableAdapter(sk_sp< T > adapter) const
Definition: SkottiePriv.h:139
static const skjson::Value & GetPropValue(const skjson::ArrayValue &jprops, size_t prop_index)
Definition: Effects.cpp:181
const SkRect & revalidate(InvalidationController *, const SkMatrix &)
Definition: SkSGNode.cpp:134
void render(SkCanvas *, const RenderContext *=nullptr) const
struct MyStruct s
static float min(float r, float g, float b)
Definition: hsl.cpp:48
Definition: dart.idl:629
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
Optional< SkRect > bounds
Definition: SkRecords.h:189
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
const auto kB
const auto kA
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
@ kSaturation
saturation of source with hue and luminosity of destination
@ kHue
hue of source with saturation and luminosity of destination
SkScalar ScalarValue
Definition: SkottieValue.h:22
T ParseDefault(const skjson::Value &v, const T &defaultValue)
Definition: SkottieJson.h:23
Definition: Skottie.h:32
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707
Definition: ref_ptr.h:256
static constexpr SkRect MakeSize(const SkSize &size)
Definition: SkRect.h:633
sk_sp< SkRuntimeEffect > effect
Definition: SkSize.h:52
SkScalar fHeight
Definition: SkSize.h:54
SkScalar fWidth
Definition: SkSize.h:53
Definition: SkM44.h:19
float x
Definition: SkM44.h:20
float y
Definition: SkM44.h:20