Flutter Engine
The Flutter Engine
ImageFilterDAGSlide.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2019 Google LLC
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
11#include "include/core/SkFont.h"
18#include "include/core/SkRect.h"
25#include "tools/ToolUtils.h"
27#include "tools/viewer/Slide.h"
28
29namespace {
30
31struct FilterNode {
32 // Pointer to the actual filter in the DAG, so it still contains its input filters and
33 // may be used as an input in an earlier node. Null when this represents the "source" input
35
36 // FilterNodes wrapping each of fFilter's inputs. Leaf node when fInputNodes is empty.
37 std::vector<FilterNode> fInputNodes;
38
39 // Distance from root filter
40 int fDepth;
41
42 // The source content rect (this is the same for all nodes, but is stored here for convenience)
44 // The mapping for the filter dag (same for all nodes, but stored here for convenience)
45 skif::Mapping fMapping;
46
47 // Cached reverse bounds using device-space clip bounds (e.g. no local bounds hint passed to
48 // saveLayer). This represents the layer calculated in SkCanvas for the filtering.
49 skif::LayerSpace<SkIRect> fUnhintedLayerBounds;
50
51 // Cached input bounds using the local draw bounds (e.g. saveLayer with a bounds rect, or
52 // an auto-layer for a draw with image filter). This represents the layer bounds up to this
53 // point of the DAG.
54 skif::LayerSpace<SkIRect> fHintedLayerBounds;
55
56 // Cached output bounds based on local draw bounds. This represents the output up to this
57 // point of the DAG.
58 skif::LayerSpace<SkIRect> fOutputBounds;
59
60 FilterNode(const SkImageFilter* filter,
61 const skif::Mapping& mapping,
63 int depth)
64 : fFilter(sk_ref_sp(filter))
65 , fDepth(depth)
66 , fContent(content)
67 , fMapping(mapping) {
68 this->computeInputBounds();
69 this->computeOutputBounds();
70 if (fFilter) {
71 fInputNodes.reserve(fFilter->countInputs());
72 for (int i = 0; i < fFilter->countInputs(); ++i) {
73 fInputNodes.emplace_back(fFilter->getInput(i), mapping, content, depth + 1);
74 }
75 }
76 }
77
78private:
79 void computeOutputBounds() {
80 if (fFilter) {
81 // For visualization purposes, we want the output bounds in layer space, before it's
82 // been transformed to device space. To achieve that, we mock a new mapping with the
83 // identity matrix transform.
84 skif::Mapping layerOnly{fMapping.layerMatrix()};
85 std::optional<skif::DeviceSpace<SkIRect>> pseudoDeviceBounds =
86 as_IFB(fFilter)->getOutputBounds(layerOnly, fContent);
87 // Since layerOnly's device matrix is I, this is effectively a cast to layer space
88 if (pseudoDeviceBounds) {
89 fOutputBounds = layerOnly.deviceToLayer(*pseudoDeviceBounds);
90 } else {
91 // Skip drawing infinite output bounds
92 fOutputBounds = skif::LayerSpace<SkIRect>::Empty();
93 }
94 } else {
95 fOutputBounds = fMapping.paramToLayer(fContent).roundOut();
96 }
97
98 // Fill in children
99 for (size_t i = 0; i < fInputNodes.size(); ++i) {
100 fInputNodes[i].computeOutputBounds();
101 }
102 }
103
104 void computeInputBounds() {
105 // As a proxy for what the base device had, use the content rect mapped to device space
106 // (e.g. clipRect() was called with the same coords prior to the draw).
107 skif::DeviceSpace<SkIRect> targetOutput(fMapping.totalMatrix()
108 .mapRect(SkRect(fContent))
109 .roundOut());
110
111 if (fFilter) {
112 fHintedLayerBounds = as_IFB(fFilter)->getInputBounds(fMapping, targetOutput, fContent);
113 fUnhintedLayerBounds = as_IFB(fFilter)->getInputBounds(fMapping, targetOutput, {});
114 } else {
115 fHintedLayerBounds = fMapping.paramToLayer(fContent).roundOut();
116 fUnhintedLayerBounds = fMapping.deviceToLayer(targetOutput);
117 }
118 }
119};
120
121} // anonymous namespace
122
123static FilterNode build_dag(const SkMatrix& ctm, const SkRect& rect,
124 const SkImageFilter* rootFilter) {
125 // Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
127 skif::ParameterSpace<SkPoint> center({rect.centerX(), rect.centerY()});
128 skif::Mapping mapping;
129 SkAssertResult(mapping.decomposeCTM(ctm, rootFilter, center));
130 return FilterNode(rootFilter, mapping, content, 0);
131}
132
133static void draw_node(SkCanvas* canvas, const FilterNode& node) {
134 canvas->clear(SK_ColorTRANSPARENT);
135
136 SkPaint filterPaint;
137 filterPaint.setImageFilter(node.fFilter);
138
139 SkRect content = SkRect(node.fContent);
141 static const SkColor kColors[2] = {SK_ColorGREEN, SK_ColorWHITE};
142 SkPoint points[2] = { {content.fLeft + 15.f, content.fTop + 15.f},
143 {content.fRight - 15.f, content.fBottom - 15.f} };
146
148 line.setStrokeWidth(0.f);
150
151 canvas->save();
152 canvas->concat(node.fMapping.layerToDevice());
153 canvas->save();
154 canvas->concat(node.fMapping.layerMatrix());
155
156 canvas->saveLayer(&content, &filterPaint);
157 canvas->drawRect(content, paint);
158 canvas->restore(); // Completes the image filter
159
160 // Draw content-rect bounds
161 line.setColor(SK_ColorBLACK);
162 canvas->drawRect(content, line);
163
164 // Bounding boxes have all been mapped by the layer matrix from local to layer space, so undo
165 // the layer matrix, leaving just the device matrix.
166 canvas->restore();
167
168 // The hinted bounds of the layer saved for the filtering
169 line.setColor(SK_ColorRED);
170 canvas->drawRect(SkRect::Make(SkIRect(node.fHintedLayerBounds)).makeOutset(3.f, 3.f), line);
171 // The bounds of the layer if there was no local content hint
172 line.setColor(SK_ColorGREEN);
173 canvas->drawRect(SkRect::Make(SkIRect(node.fUnhintedLayerBounds)).makeOutset(2.f, 2.f), line);
174
175 // The output bounds in layer space
176 line.setColor(SK_ColorBLUE);
177 canvas->drawRect(SkRect::Make(SkIRect(node.fOutputBounds)).makeOutset(1.f, 1.f), line);
178 // Device-space bounding box of the output bounds (e.g. what legacy DAG manipulation via
179 // MatrixTransform would produce).
180 static const SkScalar kDashParams[] = {6.f, 12.f};
181 line.setPathEffect(SkDashPathEffect::Make(kDashParams, 2, 0.f));
182 SkRect devOutputBounds = SkRect::Make(SkIRect(node.fMapping.layerToDevice(node.fOutputBounds)));
183 canvas->restore(); // undoes device matrix
184 canvas->drawRect(devOutputBounds, line);
185}
186
187static constexpr float kLineHeight = 16.f;
188static constexpr float kLineInset = 8.f;
189
190static float print_matrix(SkCanvas* canvas, const char* prefix, const SkMatrix& matrix,
191 float x, float y, const SkFont& font, const SkPaint& paint) {
192 canvas->drawString(prefix, x, y, font, paint);
193 y += kLineHeight;
194 for (int i = 0; i < 3; ++i) {
195 SkString row;
196 row.appendf("[%.2f %.2f %.2f]",
197 matrix.get(i * 3), matrix.get(i * 3 + 1), matrix.get(i * 3 + 2));
198 canvas->drawString(row, x, y, font, paint);
199 y += kLineHeight;
200 }
201 return y;
202}
203
204static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
205 float x, float y, const SkFont& font, const SkPaint& paint) {
206 canvas->drawString(prefix, x, y, font, paint);
207 y += kLineHeight;
208 SkString sz;
209 sz.appendf("%d x %d", rect.width(), rect.height());
210 canvas->drawString(sz, x, y, font, paint);
211 return y + kLineHeight;
212}
213
214static float print_info(SkCanvas* canvas, const FilterNode& node) {
217 text.setAntiAlias(true);
218
219 float y = kLineHeight;
220 if (node.fFilter) {
221 canvas->drawString(node.fFilter->getTypeName(), kLineInset, y, font, text);
222 y += kLineHeight;
223 if (node.fDepth == 0) {
224 // The mapping is the same for all nodes, so only print at the root
225 y = print_matrix(canvas, "Param->Layer", node.fMapping.layerMatrix(),
226 kLineInset, y, font, text);
227 y = print_matrix(canvas,
228 "Layer->Device",
229 node.fMapping.layerToDevice(),
231 y,
232 font,
233 text);
234 }
235
236 y = print_size(canvas, "Layer Size", SkIRect(node.fUnhintedLayerBounds),
237 kLineInset, y, font, text);
238 y = print_size(canvas, "Layer Size (hinted)", SkIRect(node.fHintedLayerBounds),
239 kLineInset, y, font, text);
240 } else {
241 canvas->drawString("Source Input", kLineInset, y, font, text);
242 y += kLineHeight;
243 }
244
245 return y;
246}
247
248// Returns bottom edge in pixels that the subtree reached in canvas
249static float draw_dag(SkCanvas* canvas, SkSurface* nodeSurface, const FilterNode& node) {
250 // First capture the results of the node, into nodeSurface
251 draw_node(nodeSurface->getCanvas(), node);
252 sk_sp<SkImage> nodeResults = nodeSurface->makeImageSnapshot();
253
254 // Fill in background of the filter node with a checkerboard
255 canvas->save();
256 canvas->clipRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height()));
258 canvas->restore();
259
260 // Display filtered results in current canvas' location (assumed CTM is set for this node)
261 canvas->drawImage(nodeResults, 0, 0);
262
264 line.setAntiAlias(true);
266 line.setStrokeWidth(3.f);
267
268 // Text info
269 canvas->save();
270 canvas->translate(0, nodeResults->height());
271 float textHeight = print_info(canvas, node);
272 canvas->restore();
273
274 // Border around filtered results + text info
275 canvas->drawRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height() + textHeight),
276 line);
277
278 static const float kPad = 20.f;
279 float x = nodeResults->width() + kPad;
280 float y = 0;
281 for (size_t i = 0; i < node.fInputNodes.size(); ++i) {
282 // Line connecting this node to its child
283 canvas->drawLine(nodeResults->width(), 0.5f * nodeResults->height(), // right of node
284 x, y + 0.5f * nodeResults->height(), line); // left of child
285 canvas->save();
286 canvas->translate(x, y);
287 y += draw_dag(canvas, nodeSurface, node.fInputNodes[i]);
288 canvas->restore();
289 }
290 return std::max(y, nodeResults->height() + textHeight + kPad);
291}
292
293static void draw_dag(SkCanvas* canvas, SkImageFilter* filter,
294 const SkRect& rect, const SkISize& surfaceSize) {
295 // Get the current CTM, which includes all the viewer's UI modifications, which we want to
296 // pass into our mock canvases for each DAG node.
297 SkMatrix ctm = canvas->getTotalMatrix();
298
299 canvas->save();
300 // Reset the matrix so that the DAG layout and instructional text is fixed to the window.
301 canvas->resetMatrix();
302
303 // Process the image filter DAG to display intermediate results later on, which will apply the
304 // provided CTM during draw_node calls.
305 FilterNode dag = build_dag(ctm, rect, filter);
306
307 sk_sp<SkSurface> nodeSurface =
308 canvas->makeSurface(canvas->imageInfo().makeDimensions(surfaceSize));
309 draw_dag(canvas, nodeSurface.get(), dag);
310
311 canvas->restore();
312}
313
315public:
316 ImageFilterDAGSlide() { fName = "ImageFilterDAG"; }
317
318 void draw(SkCanvas* canvas) override {
319 // Make a large DAG
320 // /--- Color Filter <---- Blur <--- Offset
321 // Merge <
322 // \--- Blur <--- Drop Shadow
324 10.f, 5.f, 3.f, 3.f, SK_ColorBLACK, nullptr);
325 sk_sp<SkImageFilter> blur1 = SkImageFilters::Blur(2.f, 2.f, std::move(drop2));
326
327 sk_sp<SkImageFilter> offset3 = SkImageFilters::Offset(-5.f, -5.f, nullptr);
328 sk_sp<SkImageFilter> blur2 = SkImageFilters::Blur(4.f, 4.f, std::move(offset3));
331
332 sk_sp<SkImageFilter> merge0 = SkImageFilters::Merge(std::move(blur1), std::move(cf1));
333
334 draw_dag(canvas, merge0.get(), kFilterRect, kFilterSurfaceSize);
335 }
336
337 // We want to use the viewer calculated CTM in the mini surfaces used per DAG node. The rotation
338 // matrix viewer calculates is based on the slide content size.
339 SkISize getDimensions() const override { return kFilterSurfaceSize; }
340
341private:
342 static constexpr SkRect kFilterRect = SkRect::MakeXYWH(20.f, 20.f, 60.f, 60.f);
343 static constexpr SkISize kFilterSurfaceSize = SkISize::Make(
344 2 * (kFilterRect.fRight + kFilterRect.fLeft),
345 2 * (kFilterRect.fBottom + kFilterRect.fTop));
346
347};
348
349DEF_SLIDE(return new ImageFilterDAGSlide();)
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
static const int points[]
static FilterNode build_dag(const SkMatrix &ctm, const SkRect &rect, const SkImageFilter *rootFilter)
static float print_matrix(SkCanvas *canvas, const char *prefix, const SkMatrix &matrix, float x, float y, const SkFont &font, const SkPaint &paint)
static constexpr float kLineHeight
static float print_size(SkCanvas *canvas, const char *prefix, const SkIRect &rect, float x, float y, const SkFont &font, const SkPaint &paint)
static float print_info(SkCanvas *canvas, const FilterNode &node)
static void draw_node(SkCanvas *canvas, const FilterNode &node)
static constexpr float kLineInset
static float draw_dag(SkCanvas *canvas, SkSurface *nodeSurface, const FilterNode &node)
@ kModulate
r = s*d
constexpr SkColor SK_ColorLTGRAY
Definition: SkColor.h:118
uint32_t SkColor
Definition: SkColor.h:37
constexpr SkColor SK_ColorTRANSPARENT
Definition: SkColor.h:99
constexpr SkColor SK_ColorGRAY
Definition: SkColor.h:113
constexpr SkColor SK_ColorBLUE
Definition: SkColor.h:135
constexpr SkColor SK_ColorRED
Definition: SkColor.h:126
constexpr SkColor SK_ColorBLACK
Definition: SkColor.h:103
constexpr SkColor SK_ColorGREEN
Definition: SkColor.h:131
constexpr SkColor SK_ColorWHITE
Definition: SkColor.h:122
static SkImageFilter_Base * as_IFB(SkImageFilter *filter)
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
#define DEF_SLIDE(code)
Definition: Slide.h:25
static SkScalar center(float pos0, float pos1)
constexpr int kPad
void draw(SkCanvas *canvas) override
SkISize getDimensions() const override
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition: SkCanvas.cpp:496
void drawRect(const SkRect &rect, const SkPaint &paint)
Definition: SkCanvas.cpp:1673
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
Definition: SkCanvas.cpp:1361
void restore()
Definition: SkCanvas.cpp:461
void translate(SkScalar dx, SkScalar dy)
Definition: SkCanvas.cpp:1278
sk_sp< SkSurface > makeSurface(const SkImageInfo &info, const SkSurfaceProps *props=nullptr)
Definition: SkCanvas.cpp:1195
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
Definition: SkCanvas.cpp:2700
void clear(SkColor color)
Definition: SkCanvas.h:1199
SkMatrix getTotalMatrix() const
Definition: SkCanvas.cpp:1629
void resetMatrix()
Definition: SkCanvas.cpp:1355
int save()
Definition: SkCanvas.cpp:447
void concat(const SkMatrix &matrix)
Definition: SkCanvas.cpp:1318
void drawString(const char str[], SkScalar x, SkScalar y, const SkFont &font, const SkPaint &paint)
Definition: SkCanvas.h:1803
SkImageInfo imageInfo() const
Definition: SkCanvas.cpp:1206
void drawImage(const SkImage *image, SkScalar left, SkScalar top)
Definition: SkCanvas.h:1528
static sk_sp< SkColorFilter > Blend(const SkColor4f &c, sk_sp< SkColorSpace >, SkBlendMode mode)
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
Definition: SkFont.h:35
static sk_sp< SkShader > MakeLinear(const SkPoint pts[2], const SkColor colors[], const SkScalar pos[], int count, SkTileMode mode, uint32_t flags=0, const SkMatrix *localMatrix=nullptr)
std::optional< skif::DeviceSpace< SkIRect > > getOutputBounds(const skif::Mapping &mapping, const skif::ParameterSpace< SkRect > &contentBounds) const
skif::LayerSpace< SkIRect > getInputBounds(const skif::Mapping &mapping, const skif::DeviceSpace< SkIRect > &desiredOutput, std::optional< skif::ParameterSpace< SkRect > > knownContentBounds) const
static sk_sp< SkImageFilter > ColorFilter(sk_sp< SkColorFilter > cf, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > DropShadow(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, SkColor color, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Merge(sk_sp< SkImageFilter > *const filters, int count, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Blur(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Offset(SkScalar dx, SkScalar dy, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
int width() const
Definition: SkImage.h:285
int height() const
Definition: SkImage.h:291
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition: SkMatrix.cpp:1141
void setImageFilter(sk_sp< SkImageFilter > imageFilter)
@ kStroke_Style
set to stroke geometry
Definition: SkPaint.h:194
void void void appendf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:550
SkCanvas * getCanvas()
Definition: SkSurface.cpp:82
sk_sp< SkImage > makeImageSnapshot()
Definition: SkSurface.cpp:90
Definition: Slide.h:29
SkString fName
Definition: Slide.h:54
T * get() const
Definition: SkRefCnt.h:303
const SkMatrix & deviceToLayer() const
SkMatrix totalMatrix() const
const SkMatrix & layerMatrix() const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
const Paint & paint
Definition: color_source.cc:38
float SkScalar
Definition: extension.cpp:12
static float max(float r, float g, float b)
Definition: hsl.cpp:49
std::u16string text
union flutter::testing::@2836::KeyboardChange::@76 content
double y
double x
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SK_API sk_sp< SkShader > Empty()
void draw_checkerboard(SkCanvas *canvas, SkColor c1, SkColor c2, int size)
Definition: ToolUtils.cpp:174
sk_sp< SkTypeface > DefaultTypeface()
const DlColor kColors[]
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
font
Font Metadata and Metrics.
Definition: SkRect.h:32
Definition: SkSize.h:16
static constexpr SkISize Make(int32_t w, int32_t h)
Definition: SkSize.h:20
SkImageInfo makeDimensions(SkISize newSize) const
Definition: SkImageInfo.h:454
static SkRect Make(const SkISize &size)
Definition: SkRect.h:669
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
SkRect makeOutset(float dx, float dy) const
Definition: SkRect.h:1002
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
static constexpr SkRect MakeWH(float w, float h)
Definition: SkRect.h:609
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15