• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/SkMatrixPriv.h"
25 #include "src/core/SkSpecialImage.h"
26 #include "tools/ToolUtils.h"
27 #include "tools/fonts/FontToolUtils.h"
28 #include "tools/viewer/Slide.h"
29 
30 namespace {
31 
32 struct FilterNode {
33     // Pointer to the actual filter in the DAG, so it still contains its input filters and
34     // may be used as an input in an earlier node. Null when this represents the "source" input
35     sk_sp<SkImageFilter> fFilter;
36 
37     // FilterNodes wrapping each of fFilter's inputs. Leaf node when fInputNodes is empty.
38     std::vector<FilterNode> fInputNodes;
39 
40     // Distance from root filter
41     int fDepth;
42 
43     // The source content rect (this is the same for all nodes, but is stored here for convenience)
44     skif::ParameterSpace<SkRect> fContent;
45     // The mapping for the filter dag (same for all nodes, but stored here for convenience)
46     skif::Mapping fMapping;
47 
48     // Cached reverse bounds using device-space clip bounds (e.g. no local bounds hint passed to
49     // saveLayer). This represents the layer calculated in SkCanvas for the filtering.
50     skif::LayerSpace<SkIRect> fUnhintedLayerBounds;
51 
52     // Cached input bounds using the local draw bounds (e.g. saveLayer with a bounds rect, or
53     // an auto-layer for a draw with image filter). This represents the layer bounds up to this
54     // point of the DAG.
55     skif::LayerSpace<SkIRect> fHintedLayerBounds;
56 
57     // Cached output bounds based on local draw bounds. This represents the output up to this
58     // point of the DAG.
59     skif::LayerSpace<SkIRect> fOutputBounds;
60 
FilterNode__anonf0dd09360111::FilterNode61     FilterNode(const SkImageFilter* filter,
62                const skif::Mapping& mapping,
63                const skif::ParameterSpace<SkRect>& content,
64                int depth)
65             : fFilter(sk_ref_sp(filter))
66             , fDepth(depth)
67             , fContent(content)
68             , fMapping(mapping) {
69         this->computeInputBounds();
70         this->computeOutputBounds();
71         if (fFilter) {
72             fInputNodes.reserve(fFilter->countInputs());
73             for (int i = 0; i < fFilter->countInputs(); ++i) {
74                 fInputNodes.emplace_back(fFilter->getInput(i), mapping, content, depth + 1);
75             }
76         }
77     }
78 
79 private:
computeOutputBounds__anonf0dd09360111::FilterNode80     void computeOutputBounds() {
81         if (fFilter) {
82             // For visualization purposes, we want the output bounds in layer space, before it's
83             // been transformed to device space. To achieve that, we mock a new mapping with the
84             // identity matrix transform.
85             skif::Mapping layerOnly{fMapping.layerMatrix()};
86             std::optional<skif::DeviceSpace<SkIRect>> pseudoDeviceBounds =
87                     as_IFB(fFilter)->getOutputBounds(layerOnly, fContent);
88             // Since layerOnly's device matrix is I, this is effectively a cast to layer space
89             if (pseudoDeviceBounds) {
90                 fOutputBounds = layerOnly.deviceToLayer(*pseudoDeviceBounds);
91             } else {
92                 // Skip drawing infinite output bounds
93                 fOutputBounds = skif::LayerSpace<SkIRect>::Empty();
94             }
95         } else {
96             fOutputBounds = fMapping.paramToLayer(fContent).roundOut();
97         }
98 
99         // Fill in children
100         for (size_t i = 0; i < fInputNodes.size(); ++i) {
101             fInputNodes[i].computeOutputBounds();
102         }
103     }
104 
computeInputBounds__anonf0dd09360111::FilterNode105     void computeInputBounds() {
106         // As a proxy for what the base device had, use the content rect mapped to device space
107         // (e.g. clipRect() was called with the same coords prior to the draw).
108         skif::DeviceSpace<SkIRect> targetOutput(SkMatrixPriv::MapRect(fMapping.totalMatrix(),
109                                                                       SkRect(fContent))
110                                                              .roundOut());
111 
112         if (fFilter) {
113             fHintedLayerBounds = as_IFB(fFilter)->getInputBounds(fMapping, targetOutput, fContent);
114             fUnhintedLayerBounds = as_IFB(fFilter)->getInputBounds(fMapping, targetOutput, {});
115         } else {
116             fHintedLayerBounds = fMapping.paramToLayer(fContent).roundOut();
117             fUnhintedLayerBounds = fMapping.deviceToLayer(targetOutput);
118         }
119     }
120 };
121 
122 } // anonymous namespace
123 
build_dag(const SkM44 & ctm,const SkRect & rect,const SkImageFilter * rootFilter)124 static FilterNode build_dag(const SkM44& ctm, const SkRect& rect,
125                             const SkImageFilter* rootFilter) {
126     // Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
127     skif::ParameterSpace<SkRect> content(rect);
128     skif::ParameterSpace<SkPoint> center({rect.centerX(), rect.centerY()});
129     skif::Mapping mapping;
130     SkAssertResult(mapping.decomposeCTM(ctm, rootFilter, center));
131     return FilterNode(rootFilter, mapping, content, 0);
132 }
133 
draw_node(SkCanvas * canvas,const FilterNode & node)134 static void draw_node(SkCanvas* canvas, const FilterNode& node) {
135     canvas->clear(SK_ColorTRANSPARENT);
136 
137     SkPaint filterPaint;
138     filterPaint.setImageFilter(node.fFilter);
139 
140     SkRect content = SkRect(node.fContent);
141     SkPaint paint;
142     static const SkColor kColors[2] = {SK_ColorGREEN, SK_ColorWHITE};
143     SkPoint points[2] = { {content.fLeft + 15.f, content.fTop + 15.f},
144                           {content.fRight - 15.f, content.fBottom - 15.f} };
145     paint.setShader(SkGradientShader::MakeLinear(points, kColors, nullptr, std::size(kColors),
146                                                  SkTileMode::kRepeat));
147 
148     SkPaint line;
149     line.setStrokeWidth(0.f);
150     line.setStyle(SkPaint::kStroke_Style);
151 
152     canvas->save();
153     canvas->concat(node.fMapping.layerToDevice());
154     canvas->save();
155     canvas->concat(node.fMapping.layerMatrix());
156 
157     canvas->saveLayer(&content, &filterPaint);
158     canvas->drawRect(content, paint);
159     canvas->restore(); // Completes the image filter
160 
161     // Draw content-rect bounds
162     line.setColor(SK_ColorBLACK);
163     canvas->drawRect(content, line);
164 
165     // Bounding boxes have all been mapped by the layer matrix from local to layer space, so undo
166     // the layer matrix, leaving just the device matrix.
167     canvas->restore();
168 
169     // The hinted bounds of the layer saved for the filtering
170     line.setColor(SK_ColorRED);
171     canvas->drawRect(SkRect::Make(SkIRect(node.fHintedLayerBounds)).makeOutset(3.f, 3.f), line);
172     // The bounds of the layer if there was no local content hint
173     line.setColor(SK_ColorGREEN);
174     canvas->drawRect(SkRect::Make(SkIRect(node.fUnhintedLayerBounds)).makeOutset(2.f, 2.f), line);
175 
176     // The output bounds in layer space
177     line.setColor(SK_ColorBLUE);
178     canvas->drawRect(SkRect::Make(SkIRect(node.fOutputBounds)).makeOutset(1.f, 1.f), line);
179     // Device-space bounding box of the output bounds (e.g. what legacy DAG manipulation via
180     // MatrixTransform would produce).
181     static const SkScalar kDashParams[] = {6.f, 12.f};
182     line.setPathEffect(SkDashPathEffect::Make(kDashParams, 2, 0.f));
183     SkRect devOutputBounds = SkRect::Make(SkIRect(node.fMapping.layerToDevice(node.fOutputBounds)));
184     canvas->restore(); // undoes device matrix
185     canvas->drawRect(devOutputBounds, line);
186 }
187 
188 static constexpr float kLineHeight = 16.f;
189 static constexpr float kLineInset = 8.f;
190 
print_matrix(SkCanvas * canvas,const char * prefix,const SkMatrix & matrix,float x,float y,const SkFont & font,const SkPaint & paint)191 static float print_matrix(SkCanvas* canvas, const char* prefix, const SkMatrix& matrix,
192                          float x, float y, const SkFont& font, const SkPaint& paint) {
193     canvas->drawString(prefix, x, y, font, paint);
194     y += kLineHeight;
195     for (int i = 0; i < 3; ++i) {
196         SkString row;
197         row.appendf("[%.2f %.2f %.2f]",
198                     matrix.get(i * 3), matrix.get(i * 3 + 1), matrix.get(i * 3 + 2));
199         canvas->drawString(row, x, y, font, paint);
200         y += kLineHeight;
201     }
202     return y;
203 }
204 
print_size(SkCanvas * canvas,const char * prefix,const SkIRect & rect,float x,float y,const SkFont & font,const SkPaint & paint)205 static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
206                        float x, float y, const SkFont& font, const SkPaint& paint) {
207     canvas->drawString(prefix, x, y, font, paint);
208     y += kLineHeight;
209     SkString sz;
210     sz.appendf("%d x %d", rect.width(), rect.height());
211     canvas->drawString(sz, x, y, font, paint);
212     return y + kLineHeight;
213 }
214 
print_info(SkCanvas * canvas,const FilterNode & node)215 static float print_info(SkCanvas* canvas, const FilterNode& node) {
216     SkFont font(ToolUtils::DefaultTypeface(), 12);
217     SkPaint text;
218     text.setAntiAlias(true);
219 
220     float y = kLineHeight;
221     if (node.fFilter) {
222         canvas->drawString(node.fFilter->getTypeName(), kLineInset, y, font, text);
223         y += kLineHeight;
224         if (node.fDepth == 0) {
225             // The mapping is the same for all nodes, so only print at the root
226             y = print_matrix(canvas,
227                              "Param->Layer",
228                              node.fMapping.layerMatrix().asM33(),
229                              kLineInset,
230                              y,
231                              font,
232                              text);
233             y = print_matrix(canvas,
234                              "Layer->Device",
235                              node.fMapping.layerToDevice().asM33(),
236                              kLineInset,
237                              y,
238                              font,
239                              text);
240         }
241 
242         y = print_size(canvas, "Layer Size", SkIRect(node.fUnhintedLayerBounds),
243                        kLineInset, y, font, text);
244         y = print_size(canvas, "Layer Size (hinted)", SkIRect(node.fHintedLayerBounds),
245                        kLineInset, y, font, text);
246     } else {
247         canvas->drawString("Source Input", kLineInset, y, font, text);
248         y += kLineHeight;
249     }
250 
251     return y;
252 }
253 
254 // Returns bottom edge in pixels that the subtree reached in canvas
draw_dag(SkCanvas * canvas,SkSurface * nodeSurface,const FilterNode & node)255 static float draw_dag(SkCanvas* canvas, SkSurface* nodeSurface, const FilterNode& node) {
256     // First capture the results of the node, into nodeSurface
257     draw_node(nodeSurface->getCanvas(), node);
258     sk_sp<SkImage> nodeResults = nodeSurface->makeImageSnapshot();
259 
260     // Fill in background of the filter node with a checkerboard
261     canvas->save();
262     canvas->clipRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height()));
263     ToolUtils::draw_checkerboard(canvas, SK_ColorGRAY, SK_ColorLTGRAY, 10);
264     canvas->restore();
265 
266     // Display filtered results in current canvas' location (assumed CTM is set for this node)
267     canvas->drawImage(nodeResults, 0, 0);
268 
269     SkPaint line;
270     line.setAntiAlias(true);
271     line.setStyle(SkPaint::kStroke_Style);
272     line.setStrokeWidth(3.f);
273 
274     // Text info
275     canvas->save();
276     canvas->translate(0, nodeResults->height());
277     float textHeight = print_info(canvas, node);
278     canvas->restore();
279 
280     // Border around filtered results + text info
281     canvas->drawRect(SkRect::MakeWH(nodeResults->width(), nodeResults->height() + textHeight),
282                      line);
283 
284     static const float kPad = 20.f;
285     float x = nodeResults->width() + kPad;
286     float y = 0;
287     for (size_t i = 0; i < node.fInputNodes.size(); ++i) {
288         // Line connecting this node to its child
289         canvas->drawLine(nodeResults->width(), 0.5f * nodeResults->height(), // right of node
290                          x, y + 0.5f * nodeResults->height(), line);         // left of child
291         canvas->save();
292         canvas->translate(x, y);
293         y += draw_dag(canvas, nodeSurface, node.fInputNodes[i]);
294         canvas->restore();
295     }
296     return std::max(y, nodeResults->height() + textHeight + kPad);
297 }
298 
draw_dag(SkCanvas * canvas,SkImageFilter * filter,const SkRect & rect,const SkISize & surfaceSize)299 static void draw_dag(SkCanvas* canvas, SkImageFilter* filter,
300                      const SkRect& rect, const SkISize& surfaceSize) {
301     // Get the current CTM, which includes all the viewer's UI modifications, which we want to
302     // pass into our mock canvases for each DAG node.
303     SkM44 ctm = canvas->getLocalToDevice();
304 
305     canvas->save();
306     // Reset the matrix so that the DAG layout and instructional text is fixed to the window.
307     canvas->resetMatrix();
308 
309     // Process the image filter DAG to display intermediate results later on, which will apply the
310     // provided CTM during draw_node calls.
311     FilterNode dag = build_dag(ctm, rect, filter);
312 
313     sk_sp<SkSurface> nodeSurface =
314             canvas->makeSurface(canvas->imageInfo().makeDimensions(surfaceSize));
315     draw_dag(canvas, nodeSurface.get(), dag);
316 
317     canvas->restore();
318 }
319 
320 class ImageFilterDAGSlide : public Slide {
321 public:
ImageFilterDAGSlide()322     ImageFilterDAGSlide() { fName = "ImageFilterDAG"; }
323 
draw(SkCanvas * canvas)324     void draw(SkCanvas* canvas) override {
325         // Make a large DAG
326         //        /--- Color Filter <---- Blur <--- Offset
327         // Merge <
328         //        \--- Blur <--- Drop Shadow
329         sk_sp<SkImageFilter> drop2 = SkImageFilters::DropShadow(
330                 10.f, 5.f, 3.f, 3.f, SK_ColorBLACK, nullptr);
331         sk_sp<SkImageFilter> blur1 = SkImageFilters::Blur(2.f, 2.f, std::move(drop2));
332 
333         sk_sp<SkImageFilter> offset3 = SkImageFilters::Offset(-5.f, -5.f, nullptr);
334         sk_sp<SkImageFilter> blur2 = SkImageFilters::Blur(4.f, 4.f, std::move(offset3));
335         sk_sp<SkImageFilter> cf1 = SkImageFilters::ColorFilter(
336                 SkColorFilters::Blend(SK_ColorGRAY, SkBlendMode::kModulate), std::move(blur2));
337 
338         sk_sp<SkImageFilter> merge0 = SkImageFilters::Merge(std::move(blur1), std::move(cf1));
339 
340         draw_dag(canvas, merge0.get(), kFilterRect, kFilterSurfaceSize);
341     }
342 
343     // We want to use the viewer calculated CTM in the mini surfaces used per DAG node. The rotation
344     // matrix viewer calculates is based on the slide content size.
getDimensions() const345     SkISize getDimensions() const override { return kFilterSurfaceSize; }
346 
347 private:
348     static constexpr SkRect kFilterRect = SkRect::MakeXYWH(20.f, 20.f, 60.f, 60.f);
349     static constexpr SkISize kFilterSurfaceSize = SkISize::Make(
350             2 * (kFilterRect.fRight + kFilterRect.fLeft),
351             2 * (kFilterRect.fBottom + kFilterRect.fTop));
352 
353 };
354 
355 DEF_SLIDE(return new ImageFilterDAGSlide();)
356