xref: /aosp_15_r20/external/skia/tools/viewer/FilterBoundsSlide.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/SkBitmap.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkPathEffect.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRect.h"
17 #include "include/effects/SkDashPathEffect.h"
18 #include "include/effects/SkGradientShader.h"
19 #include "include/effects/SkImageFilters.h"
20 #include "src/core/SkImageFilterTypes.h"
21 #include "src/core/SkImageFilter_Base.h"
22 #include "src/core/SkMatrixPriv.h"
23 #include "src/core/SkRectPriv.h"
24 #include "tools/ToolUtils.h"
25 #include "tools/fonts/FontToolUtils.h"
26 #include "tools/viewer/Slide.h"
27 
28 static constexpr float kLineHeight = 16.f;
29 static constexpr float kLineInset = 8.f;
30 
print_size(SkCanvas * canvas,const char * prefix,std::optional<SkIRect> rect,float x,float y,const SkFont & font,const SkPaint & paint)31 static float print_size(SkCanvas* canvas, const char* prefix,
32                         std::optional<SkIRect> rect,
33                         float x, float y, const SkFont& font, const SkPaint& paint) {
34     canvas->drawString(prefix, x, y, font, paint);
35     y += kLineHeight;
36     SkString sz;
37     if (rect) {
38         sz.appendf("%d x %d", rect->width(), rect->height());
39     } else {
40         sz.appendf("infinite");
41     }
42     canvas->drawString(sz, x, y, font, paint);
43     return y + kLineHeight;
44 }
45 
print_info(SkCanvas * canvas,const skif::LayerSpace<SkIRect> & layerContentBounds,const skif::DeviceSpace<SkIRect> & outputBounds,std::optional<skif::DeviceSpace<SkIRect>> hintedOutputBounds,const skif::LayerSpace<SkIRect> & unhintedLayerBounds)46 static float print_info(SkCanvas* canvas,
47                         const skif::LayerSpace<SkIRect>& layerContentBounds,
48                         const skif::DeviceSpace<SkIRect>& outputBounds,
49                         std::optional<skif::DeviceSpace<SkIRect>> hintedOutputBounds,
50                         const skif::LayerSpace<SkIRect>& unhintedLayerBounds) {
51     SkFont font(ToolUtils::DefaultTypeface(), 12);
52     SkPaint text;
53     text.setAntiAlias(true);
54 
55     float y = kLineHeight;
56 
57     text.setColor(SK_ColorRED);
58     y = print_size(canvas, "Content (in layer)", SkIRect(layerContentBounds),
59                    kLineInset, y, font, text);
60     text.setColor(SK_ColorDKGRAY);
61     y = print_size(canvas, "Target (in device)", SkIRect(outputBounds),
62                    kLineInset, y, font, text);
63     text.setColor(SK_ColorBLUE);
64     y = print_size(canvas, "Output (w/ hint)",
65                    hintedOutputBounds ? SkIRect(*hintedOutputBounds) : std::optional<SkIRect>{},
66                    kLineInset, y, font, text);
67     text.setColor(SK_ColorGREEN);
68     y = print_size(canvas, "Input (w/ no hint)", SkIRect(unhintedLayerBounds),
69                    kLineInset, y, font, text);
70 
71     return y;
72 }
73 
print_label(SkCanvas * canvas,float x,float y,float value)74 static void print_label(SkCanvas* canvas, float x, float y, float value) {
75     SkFont font(ToolUtils::DefaultTypeface(), 12);
76     SkPaint text;
77     text.setAntiAlias(true);
78 
79     SkString label;
80     label.printf("%.3f", value);
81 
82     canvas->drawString(label, x, y + kLineHeight / 2.f, font, text);
83 }
84 
line_paint(SkColor color,bool dashed=false)85 static SkPaint line_paint(SkColor color, bool dashed = false) {
86     SkPaint paint;
87     paint.setColor(color);
88     paint.setStrokeWidth(0.f);
89     paint.setStyle(SkPaint::kStroke_Style);
90     paint.setAntiAlias(true);
91     if (dashed) {
92         SkScalar dash[2] = {10.f, 10.f};
93         paint.setPathEffect(SkDashPathEffect::Make(dash, 2, 0.f));
94     }
95     return paint;
96 }
97 
create_axis_path(const SkRect & rect,float axisSpace)98 static SkPath create_axis_path(const SkRect& rect, float axisSpace) {
99     SkPath localSpace;
100     for (float y = rect.fTop + axisSpace; y <= rect.fBottom; y += axisSpace) {
101         localSpace.moveTo(rect.fLeft, y);
102         localSpace.lineTo(rect.fRight, y);
103     }
104     for (float x = rect.fLeft + axisSpace; x <= rect.fRight; x += axisSpace) {
105         localSpace.moveTo(x, rect.fTop);
106         localSpace.lineTo(x, rect.fBottom);
107     }
108     return localSpace;
109 }
110 
111 static const SkColor4f kScaleGradientColors[] =
112                 { { 0.05f, 0.0f, 6.f,  1.f },   // Severe downscaling, s < 1/8, log(s) < -3
113                   { 0.6f,  0.6f, 0.8f, 0.6f },  // Okay downscaling,   s < 1/2, log(s) < -1
114                   { 1.f,   1.f,  1.f,  0.2f },  // No scaling,         s = 1,   log(s) = 0
115                   { 0.95f, 0.6f, 0.5f, 0.6f },  // Okay upscaling,     s > 2,   log(s) > 1
116                   { 0.8f,  0.1f, 0.f,  1.f } }; // Severe upscaling,   s > 8,   log(s) > 3
117 static const SkScalar kLogScaleFactors[] = { -3.f, -1.f, 0.f, 1.f, 3.f };
118 static const SkScalar kGradientStops[] = { 0.f, 0.33333f, 0.5f, 0.66667f, 1.f };
119 static const int kStopCount = (int) std::size(kScaleGradientColors);
120 
draw_scale_key(SkCanvas * canvas,float y)121 static void draw_scale_key(SkCanvas* canvas, float y) {
122     SkRect key = SkRect::MakeXYWH(15.f, y + 30.f, 15.f, 100.f);
123     SkPoint pts[] = {{key.centerX(), key.fTop}, {key.centerX(), key.fBottom}};
124     sk_sp<SkShader> gradient = SkGradientShader::MakeLinear(
125             pts, kScaleGradientColors, nullptr, kGradientStops, kStopCount, SkTileMode::kClamp,
126             SkGradientShader::kInterpolateColorsInPremul_Flag, nullptr);
127     SkPaint keyPaint;
128     keyPaint.setShader(gradient);
129     canvas->drawRect(key, keyPaint);
130     for (int i = 0; i < kStopCount; ++i) {
131         print_label(canvas, key.fRight + 5.f, key.fTop + kGradientStops[i] * key.height(),
132                     SkScalarPow(2.f, kLogScaleFactors[i]));
133     }
134 }
135 
draw_scale_factors(SkCanvas * canvas,const skif::Mapping & mapping,const SkRect & rect)136 static void draw_scale_factors(SkCanvas* canvas, const skif::Mapping& mapping, const SkRect& rect) {
137     SkPoint testPoints[5];
138     testPoints[0] = {rect.centerX(), rect.centerY()};
139     rect.toQuad(testPoints + 1);
140     for (int i = 0; i < 5; ++i) {
141         float scale = SkMatrixPriv::DifferentialAreaScale(
142                 mapping.layerToDevice(),
143                 SkPoint(mapping.paramToLayer(skif::ParameterSpace<SkPoint>(testPoints[i]))));
144         SkColor4f color = {0.f, 0.f, 0.f, 1.f};
145 
146         if (SkIsFinite(scale)) {
147             float logScale = SkScalarLog2(scale);
148             for (int j = 0; j <= kStopCount; ++j) {
149                 if (j == kStopCount) {
150                     color = kScaleGradientColors[j - 1];
151                     break;
152                 } else if (kLogScaleFactors[j] >= logScale) {
153                     if (j == 0) {
154                         color = kScaleGradientColors[0];
155                     } else {
156                         SkScalar t = (logScale - kLogScaleFactors[j - 1]) /
157                                     (kLogScaleFactors[j] - kLogScaleFactors[j - 1]);
158 
159                         SkColor4f a = kScaleGradientColors[j - 1] * (1.f - t);
160                         SkColor4f b = kScaleGradientColors[j] * t;
161                         color = {a.fR + b.fR, a.fG + b.fG, a.fB + b.fB, a.fA + b.fA};
162                     }
163                     break;
164                 }
165             }
166         }
167 
168         SkPaint p;
169         p.setAntiAlias(true);
170         p.setColor4f(color, nullptr);
171         canvas->drawRect(SkRect::MakeLTRB(testPoints[i].fX - 4.f, testPoints[i].fY - 4.f,
172                                           testPoints[i].fX + 4.f, testPoints[i].fY + 4.f), p);
173     }
174 }
175 
176 class FilterBoundsSample : public Slide {
177 public:
FilterBoundsSample()178     FilterBoundsSample() { fName = "FilterBounds"; }
179 
load(SkScalar w,SkScalar h)180     void load(SkScalar w, SkScalar h) override {
181         fBlur = SkImageFilters::Blur(8.f, 8.f, nullptr);
182         fImage = ToolUtils::create_checkerboard_image(
183                 300, 300, SK_ColorMAGENTA, SK_ColorLTGRAY, 50);
184     }
185 
draw(SkCanvas * canvas)186     void draw(SkCanvas* canvas) override {
187         // The local content, e.g. what would be submitted to drawRect or the bounds to saveLayer
188         const SkRect localContentRect = SkRect::MakeLTRB(100.f, 20.f, 180.f, 140.f);
189         SkMatrix ctm = canvas->getLocalToDeviceAs3x3();
190 
191         // Base rendering of a filter
192         SkPaint blurPaint;
193         blurPaint.setImageFilter(fBlur);
194         canvas->saveLayer(&localContentRect, &blurPaint);
195         canvas->drawImageRect(fImage.get(), localContentRect, localContentRect,
196                               SkSamplingOptions(SkFilterMode::kLinear),
197                               nullptr, SkCanvas::kFast_SrcRectConstraint);
198         canvas->restore();
199 
200         // Now visualize the underlying bounds calculations used to determine the layer for the blur
201         SkIRect target = ctm.mapRect(localContentRect).roundOut();
202         if (!target.intersect(SkIRect::MakeWH(canvas->imageInfo().width(),
203                                               canvas->imageInfo().height()))) {
204             return;
205         }
206         skif::DeviceSpace<SkIRect> targetOutput(target);
207         skif::ParameterSpace<SkRect> contentBounds(localContentRect);
208         skif::ParameterSpace<SkPoint> contentCenter({localContentRect.centerX(),
209                                                      localContentRect.centerY()});
210         skif::Mapping mapping;
211         SkAssertResult(mapping.decomposeCTM(ctm, fBlur.get(), contentCenter));
212 
213         // Add axis lines, to show perspective distortion
214         canvas->save();
215         canvas->setMatrix(mapping.layerToDevice());
216         canvas->drawPath(create_axis_path(SkRect(mapping.paramToLayer(contentBounds)), 20.f),
217                          line_paint(SK_ColorGRAY));
218         canvas->restore();
219 
220         // Visualize scale factors at the four corners and center of the local rect
221         draw_scale_factors(canvas, mapping, localContentRect);
222 
223         // The device content rect, e.g. the clip bounds if 'localContentRect' were used as a clip
224         // before the draw or saveLayer, representing what the filter must cover if it affects
225         // transparent black or doesn't have a local content hint.
226         canvas->setMatrix(SkMatrix::I());
227         canvas->drawRect(ctm.mapRect(localContentRect), line_paint(SK_ColorDKGRAY));
228 
229         // Layer bounds for the filter, in the layer space compatible with the filter's matrix
230         // type requirements.
231         skif::LayerSpace<SkIRect> targetOutputInLayer = mapping.deviceToLayer(targetOutput);
232         skif::LayerSpace<SkIRect> hintedLayerBounds = as_IFB(fBlur)->getInputBounds(
233                 mapping, targetOutput, contentBounds);
234         skif::LayerSpace<SkIRect> unhintedLayerBounds = as_IFB(fBlur)->getInputBounds(
235                 mapping, targetOutput, {});
236 
237         canvas->setMatrix(mapping.layerToDevice());
238         canvas->drawRect(SkRect::Make(SkIRect(targetOutputInLayer)),
239                          line_paint(SK_ColorDKGRAY, true));
240         canvas->drawRect(SkRect::Make(SkIRect(hintedLayerBounds)), line_paint(SK_ColorRED));
241         canvas->drawRect(SkRect::Make(SkIRect(unhintedLayerBounds)), line_paint(SK_ColorGREEN));
242 
243         // For visualization purposes, we want to show the layer-space output, this is what we get
244         // when contentBounds is provided as a hint in local/parameter space.
245         skif::Mapping layerOnly{mapping.layerMatrix()};
246         std::optional<skif::DeviceSpace<SkIRect>> hintedOutputBounds =
247                 as_IFB(fBlur)->getOutputBounds(layerOnly, contentBounds);
248         if (hintedOutputBounds) {
249             canvas->drawRect(SkRect::Make(SkIRect(*hintedOutputBounds)), line_paint(SK_ColorBLUE));
250         }
251 
252         canvas->resetMatrix();
253         float y = print_info(canvas,
254                              mapping.paramToLayer(contentBounds).roundOut(),
255                              targetOutput,
256                              hintedOutputBounds,
257                              unhintedLayerBounds);
258 
259         // Draw color key for layer visualization
260         draw_scale_key(canvas, y);
261     }
262 
263 private:
264     sk_sp<SkImageFilter> fBlur;
265     sk_sp<SkImage>       fImage;
266 };
267 
268 DEF_SLIDE(return new FilterBoundsSample();)
269