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