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