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