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
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkColor.h"
10 #include "include/core/SkColorFilter.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkImageFilter.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkPaint.h"
16 #include "include/core/SkPathEffect.h"
17 #include "include/core/SkPoint.h"
18 #include "include/core/SkRect.h"
19 #include "include/core/SkSurface.h"
20 #include "include/effects/SkDashPathEffect.h"
21 #include "include/effects/SkGradientShader.h"
22 #include "include/effects/SkImageFilters.h"
23 #include "src/core/SkImageFilter_Base.h"
24 #include "src/core/SkSpecialImage.h"
25 #include "tools/ToolUtils.h"
26 #include "tools/fonts/FontToolUtils.h"
27 #include "tools/viewer/Slide.h"
28
29 namespace {
30
31 struct 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
34 sk_sp<SkImageFilter> fFilter;
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)
43 skif::ParameterSpace<SkRect> fContent;
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
FilterNode__anondab24eb60111::FilterNode60 FilterNode(const SkImageFilter* filter,
61 const skif::Mapping& mapping,
62 const skif::ParameterSpace<SkRect>& content,
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
78 private:
computeOutputBounds__anondab24eb60111::FilterNode79 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
computeInputBounds__anondab24eb60111::FilterNode104 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
build_dag(const SkMatrix & ctm,const SkRect & rect,const SkImageFilter * rootFilter)123 static FilterNode build_dag(const SkMatrix& ctm, const SkRect& rect,
124 const SkImageFilter* rootFilter) {
125 // Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
126 skif::ParameterSpace<SkRect> content(rect);
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
draw_node(SkCanvas * canvas,const FilterNode & node)133 static 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);
140 SkPaint paint;
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} };
144 paint.setShader(SkGradientShader::MakeLinear(points, kColors, nullptr, std::size(kColors),
145 SkTileMode::kRepeat));
146
147 SkPaint line;
148 line.setStrokeWidth(0.f);
149 line.setStyle(SkPaint::kStroke_Style);
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
187 static constexpr float kLineHeight = 16.f;
188 static constexpr float kLineInset = 8.f;
189
print_matrix(SkCanvas * canvas,const char * prefix,const SkMatrix & matrix,float x,float y,const SkFont & font,const SkPaint & paint)190 static 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
print_size(SkCanvas * canvas,const char * prefix,const SkIRect & rect,float x,float y,const SkFont & font,const SkPaint & paint)204 static 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
print_info(SkCanvas * canvas,const FilterNode & node)214 static float print_info(SkCanvas* canvas, const FilterNode& node) {
215 SkFont font(ToolUtils::DefaultTypeface(), 12);
216 SkPaint text;
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(),
230 kLineInset,
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
draw_dag(SkCanvas * canvas,SkSurface * nodeSurface,const FilterNode & node)249 static 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()));
257 ToolUtils::draw_checkerboard(canvas, SK_ColorGRAY, SK_ColorLTGRAY, 10);
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
263 SkPaint line;
264 line.setAntiAlias(true);
265 line.setStyle(SkPaint::kStroke_Style);
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
draw_dag(SkCanvas * canvas,SkImageFilter * filter,const SkRect & rect,const SkISize & surfaceSize)293 static 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
314 class ImageFilterDAGSlide : public Slide {
315 public:
ImageFilterDAGSlide()316 ImageFilterDAGSlide() { fName = "ImageFilterDAG"; }
317
draw(SkCanvas * canvas)318 void draw(SkCanvas* canvas) override {
319 // Make a large DAG
320 // /--- Color Filter <---- Blur <--- Offset
321 // Merge <
322 // \--- Blur <--- Drop Shadow
323 sk_sp<SkImageFilter> drop2 = SkImageFilters::DropShadow(
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));
329 sk_sp<SkImageFilter> cf1 = SkImageFilters::ColorFilter(
330 SkColorFilters::Blend(SK_ColorGRAY, SkBlendMode::kModulate), std::move(blur2));
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.
getDimensions() const339 SkISize getDimensions() const override { return kFilterSurfaceSize; }
340
341 private:
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
349 DEF_SLIDE(return new ImageFilterDAGSlide();)
350