• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 "gm/gm.h"
9 
10 #include "include/core/SkBlendMode.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkImageFilter.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkSurface.h"
17 #include "include/effects/SkDashPathEffect.h"
18 #include "include/effects/SkGradientShader.h"
19 #include "include/effects/SkImageFilters.h"
20 
21 #include "tools/Resources.h"
22 
23 // TODO(michaelludwig) - This will be made public within SkImageFilters.h at some point
24 #include "src/effects/imagefilters/SkCropImageFilter.h"
25 
26 
27 namespace {
28 
29 static constexpr SkColor kOutputBoundsColor  = SK_ColorRED;
30 static constexpr SkColor kCropRectColor      = SK_ColorGREEN;
31 static constexpr SkColor kContentBoundsColor = SK_ColorBLUE;
32 
33 static constexpr SkRect kExampleBounds = {0.f, 0.f, 100.f, 100.f};
34 
35 // "Crop" refers to the rect passed to the crop image filter, "Rect" refers to some other rect
36 // from context, likely the output bounds or the content bounds.
37 enum class CropRelation {
38     kCropOverlapsRect, // Intersect but doesn't fully contain one way or the other
39     kCropContainsRect,
40     kRectContainsCrop,
41     kCropRectDisjoint,
42 };
43 
make_overlap(const SkRect & r,float amountX,float amountY)44 SkRect make_overlap(const SkRect& r, float amountX, float amountY) {
45     return r.makeOffset(r.width() * amountX, r.height() * amountY);
46 }
47 
make_inset(const SkRect & r,float amountX,float amountY)48 SkRect make_inset(const SkRect& r, float amountX, float amountY) {
49     return r.makeInset(r.width() * amountX, r.height() * amountY);
50 }
51 
make_outset(const SkRect & r,float amountX,float amountY)52 SkRect make_outset(const SkRect& r, float amountX, float amountY) {
53     return r.makeOutset(r.width() * amountX, r.height() * amountY);
54 }
55 
make_disjoint(const SkRect & r,float amountX,float amountY)56 SkRect make_disjoint(const SkRect& r, float amountX, float amountY) {
57     float xOffset = (amountX > 0.f ? (r.width() + r.width() * amountX) :
58                     (amountX < 0.f ? (-r.width() + r.width() * amountX) : 0.f));
59     float yOffset = (amountY > 0.f ? (r.height() + r.height() * amountY) :
60                     (amountY < 0.f ? (-r.height() + r.height() * amountY) : 0.f));
61     return r.makeOffset(xOffset, yOffset);
62 }
63 
get_example_rects(CropRelation outputRelation,CropRelation inputRelation,bool hintContent,SkRect * outputBounds,SkRect * cropRect,SkRect * contentBounds)64 void get_example_rects(CropRelation outputRelation, CropRelation inputRelation, bool hintContent,
65                        SkRect* outputBounds, SkRect* cropRect, SkRect* contentBounds) {
66     *outputBounds = kExampleBounds.makeInset(20.f, 20.f);
67     switch(outputRelation) {
68         case CropRelation::kCropOverlapsRect:
69             *cropRect = make_overlap(*outputBounds, -0.15f, 0.15f);
70             SkASSERT(cropRect->intersects(*outputBounds) &&
71                      !cropRect->contains(*outputBounds) &&
72                      !outputBounds->contains(*cropRect));
73             break;
74         case CropRelation::kCropContainsRect:
75             *cropRect = make_outset(*outputBounds, 0.15f, 0.15f);
76             SkASSERT(cropRect->contains(*outputBounds));
77             break;
78         case CropRelation::kRectContainsCrop:
79             *cropRect = make_inset(*outputBounds, 0.15f, 0.15f);
80             SkASSERT(outputBounds->contains(*cropRect));
81             break;
82         case CropRelation::kCropRectDisjoint:
83             *cropRect = make_disjoint(*outputBounds, 0.15f, 0.0f);
84             SkASSERT(!cropRect->intersects(*outputBounds));
85             break;
86     }
87 
88     // Determine content bounds for example based on computed crop rect and input relation
89     if (hintContent) {
90         switch(inputRelation) {
91             case CropRelation::kCropOverlapsRect:
92                 *contentBounds = make_overlap(*cropRect, 0.075f, -0.75f);
93                 SkASSERT(contentBounds->intersects(*cropRect) &&
94                         !contentBounds->contains(*cropRect) &&
95                         !cropRect->contains(*contentBounds));
96                 break;
97             case CropRelation::kCropContainsRect:
98                 *contentBounds = make_inset(*cropRect, 0.075f, 0.075f);
99                 SkASSERT(cropRect->contains(*contentBounds));
100                 break;
101             case CropRelation::kRectContainsCrop:
102                 *contentBounds = make_outset(*cropRect, 0.1f, 0.1f);
103                 SkASSERT(contentBounds->contains(*cropRect));
104                 break;
105             case CropRelation::kCropRectDisjoint:
106                 *contentBounds = make_disjoint(*cropRect, 0.0f, 0.075f);
107                 SkASSERT(!contentBounds->intersects(*cropRect));
108                 break;
109         }
110     } else {
111         *contentBounds = kExampleBounds;
112     }
113 }
114 
115 // TODO(michaelludwig) - This is a useful test pattern for tile modes and filtering; should
116 // consolidate it with the similar version in gpu_blur_utils if the GMs remain separate at the end.
make_image(SkCanvas * canvas,const SkRect * contentBounds)117 sk_sp<SkImage> make_image(SkCanvas* canvas, const SkRect* contentBounds) {
118     const float w = kExampleBounds.width();
119     const float h = kExampleBounds.height();
120 
121     const auto srcII = SkImageInfo::Make(SkISize::Make(SkScalarCeilToInt(w), SkScalarCeilToInt(h)),
122                                          kRGBA_8888_SkColorType, kPremul_SkAlphaType);
123     auto surf = SkSurface::MakeRaster(srcII);
124 
125     surf->getCanvas()->drawColor(SK_ColorDKGRAY);
126     SkPaint paint;
127     paint.setAntiAlias(true);
128     paint.setStyle(SkPaint::kStroke_Style);
129     // Draw four horizontal lines at 1/4, 3/8, 5/8, 3/4.
130     paint.setStrokeWidth(h/16.f);
131     paint.setColor(SK_ColorRED);
132     surf->getCanvas()->drawLine({0.f, 1.f*h/4.f}, {w, 1.f*h/4.f}, paint);
133     paint.setColor(/* sea foam */ 0xFF71EEB8);
134     surf->getCanvas()->drawLine({0.f, 3.f*h/8.f}, {w, 3.f*h/8.f}, paint);
135     paint.setColor(SK_ColorYELLOW);
136     surf->getCanvas()->drawLine({0.f, 5.f*h/8.f}, {w, 5.f*h/8.f}, paint);
137     paint.setColor(SK_ColorCYAN);
138     surf->getCanvas()->drawLine({0.f, 3.f*h/4.f}, {w, 3.f*h/4.f}, paint);
139 
140     // Draw four vertical lines at 1/4, 3/8, 5/8, 3/4.
141     paint.setStrokeWidth(w/16.f);
142     paint.setColor(/* orange */ 0xFFFFA500);
143     surf->getCanvas()->drawLine({1.f*w/4.f, 0.f}, {1.f*h/4.f, h}, paint);
144     paint.setColor(SK_ColorBLUE);
145     surf->getCanvas()->drawLine({3.f*w/8.f, 0.f}, {3.f*h/8.f, h}, paint);
146     paint.setColor(SK_ColorMAGENTA);
147     surf->getCanvas()->drawLine({5.f*w/8.f, 0.f}, {5.f*h/8.f, h}, paint);
148     paint.setColor(SK_ColorGREEN);
149     surf->getCanvas()->drawLine({3.f*w/4.f, 0.f}, {3.f*h/4.f, h}, paint);
150 
151     // Fill everything outside of the content bounds with red since it shouldn't be sampled from.
152     if (contentBounds) {
153         surf->getCanvas()->clipRect(*contentBounds, SkClipOp::kDifference);
154         surf->getCanvas()->clear(SK_ColorRED);
155     }
156 
157     return surf->makeImageSnapshot();
158 }
159 
draw_example(SkCanvas * canvas,SkTileMode inputMode,SkTileMode outputMode,CropRelation outputRelation,CropRelation inputRelation,bool hintContent)160 void draw_example(
161         SkCanvas* canvas,
162         SkTileMode inputMode,        // the tile mode of the input to the crop filter
163         SkTileMode outputMode,       // the tile mode that the crop filter outputs
164         CropRelation outputRelation, // how crop rect relates to output bounds
165         CropRelation inputRelation,  // how crop rect relates to content bounds
166         bool hintContent) {          // whether or not contentBounds is hinted to saveLayer()
167     SkASSERT(inputMode == SkTileMode::kDecal && outputMode == SkTileMode::kDecal);
168 
169     // Determine crop rect for example based on output relation
170     SkRect outputBounds, cropRect, contentBounds;
171     get_example_rects(outputRelation, inputRelation, hintContent,
172                       &outputBounds, &cropRect, &contentBounds);
173 
174     auto image = make_image(canvas, hintContent ? &contentBounds : nullptr);
175 
176     canvas->save();
177     canvas->clipRect(kExampleBounds);
178     // Visualize the image tiled on the content bounds, semi-transparent
179     {
180         SkRect clippedContentBounds;
181         if (clippedContentBounds.intersect(contentBounds, kExampleBounds)) {
182             auto contentImage = image->makeSubset(clippedContentBounds.roundOut());
183             SkPaint tiledPaint;
184             tiledPaint.setShader(contentImage->makeShader(
185                     inputMode, inputMode, SkSamplingOptions(SkFilterMode::kLinear)));
186             tiledPaint.setAlphaf(0.15f);
187 
188             canvas->save();
189             canvas->translate(clippedContentBounds.fLeft, clippedContentBounds.fTop);
190             canvas->drawPaint(tiledPaint);
191             canvas->restore();
192         }
193     }
194 
195     // Build filter, clip, save layer, draw, restore - the interesting part is in the tile modes
196     // and how the various bounds intersect each other.
197     {
198         sk_sp<SkImageFilter> filter = SkImageFilters::Blur(4.f, 4.f, nullptr);
199         filter = SkMakeCropImageFilter(cropRect, std::move(filter));
200         SkPaint layerPaint;
201         layerPaint.setImageFilter(std::move(filter));
202 
203         canvas->save();
204         canvas->clipRect(outputBounds);
205         canvas->saveLayer(hintContent ? &contentBounds : nullptr, &layerPaint);
206 
207         canvas->drawImageRect(image.get(), contentBounds, contentBounds,
208                               SkSamplingOptions(SkFilterMode::kLinear), nullptr,
209                               SkCanvas::kFast_SrcRectConstraint);
210         canvas->restore();
211         canvas->restore();
212     }
213 
214     // Visualize bounds after the actual rendering.
215     {
216         SkPaint border;
217         border.setStyle(SkPaint::kStroke_Style);
218 
219         border.setColor(kOutputBoundsColor);
220         canvas->drawRect(outputBounds, border);
221 
222         border.setColor(kCropRectColor);
223         canvas->drawRect(cropRect, border);
224 
225         if (hintContent) {
226             border.setColor(kContentBoundsColor);
227             canvas->drawRect(contentBounds, border);
228         }
229     }
230 
231     canvas->restore();
232 }
233 
234 
235 // Draws 2x2 examples for a given input/output tile mode that show 4 relationships between the
236 // output bounds and the crop rect (intersect, output contains crop, crop contains output, and
237 // no intersection).
238 static constexpr SkRect kPaddedTileBounds = {kExampleBounds.fLeft,
239                                              kExampleBounds.fTop,
240                                              2.f * (kExampleBounds.fRight + 1.f),
241                                              2.f * (kExampleBounds.fBottom + 1.f)};
draw_example_tile(SkCanvas * canvas,SkTileMode inputMode,SkTileMode outputMode,CropRelation inputRelation,bool hintContent)242 void draw_example_tile(
243         SkCanvas* canvas,
244         SkTileMode inputMode,
245         SkTileMode outputMode,
246         CropRelation inputRelation,
247         bool hintContent) {
248     auto drawQuadrant = [&](int tx, int ty, CropRelation outputRelation) {
249         canvas->save();
250         canvas->translate(tx * (kExampleBounds.fRight + 1.f), ty * (kExampleBounds.fBottom + 1.f));
251         draw_example(canvas, inputMode, outputMode, outputRelation, inputRelation, hintContent);
252         canvas->restore();
253     };
254 
255     // The 4 examples, here Rect refers to the output bounds
256     drawQuadrant(0, 0, CropRelation::kCropOverlapsRect); // top left
257     drawQuadrant(1, 0, CropRelation::kRectContainsCrop); // top right
258     drawQuadrant(0, 1, CropRelation::kCropRectDisjoint); // bot left
259     drawQuadrant(1, 1, CropRelation::kCropContainsRect); // bot right
260 
261     // Draw dotted lines in the 1px gap between examples
262     SkPaint dottedLine;
263     dottedLine.setColor(SK_ColorGRAY);
264     dottedLine.setStyle(SkPaint::kStroke_Style);
265     dottedLine.setStrokeCap(SkPaint::kSquare_Cap);
266     static const float kDots[2] = {0.f, 5.f};
267     dottedLine.setPathEffect(SkDashPathEffect::Make(kDots, 2, 0.f));
268 
269     canvas->drawLine({kPaddedTileBounds.fLeft + 0.5f, kPaddedTileBounds.centerY() - 0.5f},
270                      {kPaddedTileBounds.fRight - 0.5f, kPaddedTileBounds.centerY() - 0.5f},
271                      dottedLine);
272     canvas->drawLine({kPaddedTileBounds.centerX() - 0.5f, kPaddedTileBounds.fTop + 0.5f},
273                      {kPaddedTileBounds.centerX() - 0.5f, kPaddedTileBounds.fBottom - 0.5f},
274                      dottedLine);
275 }
276 
277 // Draw 5 example tiles in a column for 5 relationships between content bounds and crop rect:
278 // no content hint, intersect, content contains crop, crop contains content, and no intersection
279 static constexpr SkRect kPaddedColumnBounds = {kPaddedTileBounds.fLeft,
280                                                kPaddedTileBounds.fTop,
281                                                kPaddedTileBounds.fRight,
282                                                5.f * kPaddedTileBounds.fBottom - 1.f};
draw_example_column(SkCanvas * canvas,SkTileMode inputMode,SkTileMode outputMode)283 void draw_example_column(
284         SkCanvas* canvas,
285         SkTileMode inputMode,
286         SkTileMode outputMode) {
287     const std::pair<CropRelation, bool> inputRelations[5] = {
288             { CropRelation::kCropOverlapsRect, false },
289             { CropRelation::kCropOverlapsRect, true },
290             { CropRelation::kCropContainsRect, true },
291             { CropRelation::kRectContainsCrop, true },
292             { CropRelation::kCropRectDisjoint, true }
293         };
294 
295     canvas->save();
296     for (auto [inputRelation, hintContent] : inputRelations) {
297         draw_example_tile(canvas, inputMode, outputMode, inputRelation, hintContent);
298         canvas->translate(0.f, kPaddedTileBounds.fBottom);
299     }
300 
301     canvas->restore();
302 }
303 
304 // Draw 5x1 grid of examples covering supported input tile modes and crop rect relations
305 static constexpr int kNumRows = 5;
306 static constexpr int kNumCols = 1;
307 static constexpr float kGridWidth = kNumCols * kPaddedColumnBounds.fRight - 1.f;
308 
draw_example_grid(SkCanvas * canvas,SkTileMode outputMode)309 void draw_example_grid(
310         SkCanvas* canvas,
311         SkTileMode outputMode) {
312     canvas->save();
313     for (auto inputMode : {SkTileMode::kDecal}) {
314         draw_example_column(canvas, inputMode, outputMode);
315         canvas->translate(kPaddedColumnBounds.fRight, 0.f);
316     }
317     canvas->restore();
318 
319     // Draw dashed lines between rows and columns
320     SkPaint dashedLine;
321     dashedLine.setColor(SK_ColorGRAY);
322     dashedLine.setStyle(SkPaint::kStroke_Style);
323     dashedLine.setStrokeCap(SkPaint::kSquare_Cap);
324     static const float kDashes[2] = {5.f, 15.f};
325     dashedLine.setPathEffect(SkDashPathEffect::Make(kDashes, 2, 0.f));
326 
327     for (int y = 1; y < kNumRows; ++y) {
328         canvas->drawLine({0.5f, y * kPaddedTileBounds.fBottom - 0.5f},
329                          {kGridWidth - 0.5f, y * kPaddedTileBounds.fBottom - 0.5f}, dashedLine);
330     }
331     for (int x = 1; x < kNumCols; ++x) {
332         canvas->drawLine({x * kPaddedTileBounds.fRight - 0.5f, 0.5f},
333                          {x * kPaddedTileBounds.fRight - 0.5f, kPaddedColumnBounds.fBottom - 0.5f},
334                          dashedLine);
335     }
336 }
337 
338 }  // namespace
339 
340 namespace skiagm {
341 
342 class CropImageFilterGM : public GM {
343 public:
CropImageFilterGM(SkTileMode outputMode)344     CropImageFilterGM(SkTileMode outputMode) : fOutputMode(outputMode) {}
345 
346 protected:
onISize()347     SkISize onISize() override {
348         return {SkScalarRoundToInt(kGridWidth), SkScalarRoundToInt(kPaddedColumnBounds.fBottom)};
349     }
onShortName()350     SkString onShortName() override {
351         SkString name("crop_imagefilter_");
352         switch (fOutputMode) {
353             case SkTileMode::kDecal:  name.append("decal");  break;
354             case SkTileMode::kClamp:  name.append("clamp");  break;
355             case SkTileMode::kRepeat: name.append("repeat"); break;
356             case SkTileMode::kMirror: name.append("mirror"); break;
357         }
358         return name;
359     }
360 
onDraw(SkCanvas * canvas)361     void onDraw(SkCanvas* canvas) override {
362         draw_example_grid(canvas, fOutputMode);
363     }
364 
365 private:
366     SkTileMode fOutputMode;
367 };
368 
369 DEF_GM( return new CropImageFilterGM(SkTileMode::kDecal); )
370 // TODO(michaelludwig) - will add GM defs for other output tile modes once supported
371 
372 }  // namespace skiagm
373