xref: /aosp_15_r20/external/skia/tools/viewer/ImageFilterDAGSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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