Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
crop_imagefilter.cpp
Go to the documentation of this file.
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
16#include "include/core/SkRect.h"
21#include "tools/GpuToolUtils.h"
22#include "tools/Resources.h"
23#include "tools/ToolUtils.h"
24
25
26namespace {
27
28static constexpr SkColor kOutputBoundsColor = SK_ColorRED;
29static constexpr SkColor kCropRectColor = SK_ColorGREEN;
30static constexpr SkColor kContentBoundsColor = SK_ColorBLUE;
31
32static constexpr SkRect kExampleBounds = {0.f, 0.f, 100.f, 100.f};
33
34// "Crop" refers to the rect passed to the crop image filter, "Rect" refers to some other rect
35// from context, likely the output bounds or the content bounds.
36enum class CropRelation {
37 kCropOverlapsRect, // Intersect but doesn't fully contain one way or the other
38 kCropContainsRect,
39 kRectContainsCrop,
40 kCropRectDisjoint,
41};
42
43SkRect make_overlap(const SkRect& r, float amountX, float amountY) {
44 return r.makeOffset(r.width() * amountX, r.height() * amountY);
45}
46
47SkRect make_inset(const SkRect& r, float amountX, float amountY) {
48 return r.makeInset(r.width() * amountX, r.height() * amountY);
49}
50
51SkRect make_outset(const SkRect& r, float amountX, float amountY) {
52 return r.makeOutset(r.width() * amountX, r.height() * amountY);
53}
54
55SkRect make_disjoint(const SkRect& r, float amountX, float amountY) {
56 float xOffset = (amountX > 0.f ? (r.width() + r.width() * amountX) :
57 (amountX < 0.f ? (-r.width() + r.width() * amountX) : 0.f));
58 float yOffset = (amountY > 0.f ? (r.height() + r.height() * amountY) :
59 (amountY < 0.f ? (-r.height() + r.height() * amountY) : 0.f));
60 return r.makeOffset(xOffset, yOffset);
61}
62
63void get_example_rects(CropRelation outputRelation, CropRelation inputRelation, bool hintContent,
64 SkRect* outputBounds, SkRect* cropRect, SkRect* contentBounds) {
65 *outputBounds = kExampleBounds.makeInset(20.f, 20.f);
66 switch(outputRelation) {
67 case CropRelation::kCropOverlapsRect:
68 *cropRect = make_overlap(*outputBounds, -0.15f, 0.15f);
69 SkASSERT(cropRect->intersects(*outputBounds) &&
70 !cropRect->contains(*outputBounds) &&
71 !outputBounds->contains(*cropRect));
72 break;
73 case CropRelation::kCropContainsRect:
74 *cropRect = make_outset(*outputBounds, 0.15f, 0.15f);
75 SkASSERT(cropRect->contains(*outputBounds));
76 break;
77 case CropRelation::kRectContainsCrop:
78 *cropRect = make_inset(*outputBounds, 0.15f, 0.15f);
79 SkASSERT(outputBounds->contains(*cropRect));
80 break;
81 case CropRelation::kCropRectDisjoint:
82 *cropRect = make_disjoint(*outputBounds, 0.15f, 0.0f);
83 SkASSERT(!cropRect->intersects(*outputBounds));
84 break;
85 }
86
87 SkAssertResult(cropRect->intersect(kExampleBounds));
88
89 // Determine content bounds for example based on computed crop rect and input relation
90 if (hintContent) {
91 switch(inputRelation) {
92 case CropRelation::kCropOverlapsRect:
93 *contentBounds = make_overlap(*cropRect, 0.075f, -0.75f);
94 SkASSERT(contentBounds->intersects(*cropRect) &&
95 !contentBounds->contains(*cropRect) &&
96 !cropRect->contains(*contentBounds));
97 break;
98 case CropRelation::kCropContainsRect:
99 *contentBounds = make_inset(*cropRect, 0.075f, 0.075f);
100 SkASSERT(cropRect->contains(*contentBounds));
101 break;
102 case CropRelation::kRectContainsCrop:
103 *contentBounds = make_outset(*cropRect, 0.1f, 0.1f);
104 SkASSERT(contentBounds->contains(*cropRect));
105 break;
106 case CropRelation::kCropRectDisjoint:
107 *contentBounds = make_disjoint(*cropRect, 0.0f, 0.075f);
108 SkASSERT(!contentBounds->intersects(*cropRect));
109 break;
110 }
111
112 SkAssertResult(contentBounds->intersect(kExampleBounds));
113 } else {
114 *contentBounds = kExampleBounds;
115 }
116}
117
118// TODO(michaelludwig) - This is a useful test pattern for tile modes and filtering; should
119// consolidate it with the similar version in gpu_blur_utils if the GMs remain separate at the end.
120sk_sp<SkImage> make_image(SkCanvas* canvas, const SkRect* contentBounds) {
121 const float w = kExampleBounds.width();
122 const float h = kExampleBounds.height();
123
125 kN32_SkColorType, kPremul_SkAlphaType);
126 auto surf = SkSurfaces::Raster(srcII);
127
128 surf->getCanvas()->drawColor(SK_ColorDKGRAY);
130 paint.setAntiAlias(true);
132 // Draw four horizontal lines at 1/4, 3/8, 5/8, 3/4.
133 paint.setStrokeWidth(h/16.f);
134 paint.setColor(SK_ColorRED);
135 surf->getCanvas()->drawLine({0.f, 1.f*h/4.f}, {w, 1.f*h/4.f}, paint);
136 paint.setColor(/* sea foam */ 0xFF71EEB8);
137 surf->getCanvas()->drawLine({0.f, 3.f*h/8.f}, {w, 3.f*h/8.f}, paint);
138 paint.setColor(SK_ColorYELLOW);
139 surf->getCanvas()->drawLine({0.f, 5.f*h/8.f}, {w, 5.f*h/8.f}, paint);
140 paint.setColor(SK_ColorCYAN);
141 surf->getCanvas()->drawLine({0.f, 3.f*h/4.f}, {w, 3.f*h/4.f}, paint);
142
143 // Draw four vertical lines at 1/4, 3/8, 5/8, 3/4.
144 paint.setStrokeWidth(w/16.f);
145 paint.setColor(/* orange */ 0xFFFFA500);
146 surf->getCanvas()->drawLine({1.f*w/4.f, 0.f}, {1.f*h/4.f, h}, paint);
147 paint.setColor(SK_ColorBLUE);
148 surf->getCanvas()->drawLine({3.f*w/8.f, 0.f}, {3.f*h/8.f, h}, paint);
149 paint.setColor(SK_ColorMAGENTA);
150 surf->getCanvas()->drawLine({5.f*w/8.f, 0.f}, {5.f*h/8.f, h}, paint);
151 paint.setColor(SK_ColorGREEN);
152 surf->getCanvas()->drawLine({3.f*w/4.f, 0.f}, {3.f*h/4.f, h}, paint);
153
154 // Fill everything outside of the content bounds with red since it shouldn't be sampled from.
155 if (contentBounds) {
156 SkRect buffer = contentBounds->makeOutset(1.f, 1.f);
157 surf->getCanvas()->clipRect(buffer, SkClipOp::kDifference);
158 surf->getCanvas()->clear(SK_ColorRED);
159 }
160
161 return surf->makeImageSnapshot();
162}
163
164// Subset 'image' to contentBounds, apply 'contentTile' mode to fill 'cropRect'-sized image.
165sk_sp<SkImage> make_cropped_image(sk_sp<SkImage> image,
166 const SkRect& contentBounds,
167 SkTileMode contentTile,
168 const SkRect& cropRect) {
171 SkScalarCeilToInt(cropRect.height())));
172 auto content = image->makeSubset(nullptr,
173 contentTile == SkTileMode::kDecal ? contentBounds.roundOut()
174 : contentBounds.roundIn());
175 if (!content || !surface) {
176 return nullptr;
177 }
178 SkPaint tiledContent;
179 tiledContent.setShader(content->makeShader(contentTile, contentTile,
181 SkMatrix::Translate(contentBounds.left(),
182 contentBounds.top())));
183 surface->getCanvas()->translate(-cropRect.left(), -cropRect.top());
184 surface->getCanvas()->drawPaint(tiledContent);
185 return surface->makeImageSnapshot();
186}
187
188void draw_example_tile(
189 SkCanvas* canvas,
190 SkTileMode inputMode, // the tile mode applied to content bounds
191 CropRelation inputRelation, // how crop rect relates to content bounds
192 bool hintContent, // whether or not contentBounds is hinted to saveLayer()
193 SkTileMode outputMode, // the tile mode applied to the crop rect output
194 CropRelation outputRelation) {// how crop rect relates to output bounds (clip pre-saveLayer)
195
196 // Determine crop rect for example based on output relation
197 SkRect outputBounds, cropRect, contentBounds;
198 get_example_rects(outputRelation, inputRelation, hintContent,
199 &outputBounds, &cropRect, &contentBounds);
200 SkASSERT(kExampleBounds.contains(outputBounds) &&
201 kExampleBounds.contains(cropRect) &&
202 kExampleBounds.contains(contentBounds));
203
204 auto image = make_image(canvas, hintContent ? &contentBounds : nullptr);
205
206 canvas->save();
207 // Visualize the image tiled on the content bounds (blue border) and then tiled on the crop
208 // rect (green) border, semi-transparent
209 {
210 auto cropImage = ToolUtils::MakeTextureImage(
211 canvas, make_cropped_image(image, contentBounds, inputMode, cropRect));
212 if (cropImage) {
213 SkPaint tiledPaint;
214 tiledPaint.setShader(cropImage->makeShader(outputMode, outputMode,
216 SkMatrix::Translate(cropRect.left(),
217 cropRect.top())));
218 tiledPaint.setAlphaf(0.25f);
219
220 canvas->save();
221 canvas->clipRect(kExampleBounds);
222 canvas->drawPaint(tiledPaint);
223 canvas->restore();
224 }
225 }
226
227 // Build filter, clip, save layer, draw, restore - the interesting part is in the tile modes
228 // and how the various bounds intersect each other.
229 {
230 sk_sp<SkImageFilter> filter = SkImageFilters::Crop(contentBounds, inputMode, nullptr);
231 filter = SkImageFilters::Blur(4.f, 4.f, std::move(filter));
232 filter = SkImageFilters::Crop(cropRect, outputMode, std::move(filter));
233 SkPaint layerPaint;
234 layerPaint.setImageFilter(std::move(filter));
235
236 canvas->save();
237 canvas->clipRect(outputBounds);
238 canvas->saveLayer(hintContent ? &contentBounds : nullptr, &layerPaint);
239
240 auto tmp = ToolUtils::MakeTextureImage(canvas, image);
241 canvas->drawImageRect(tmp, contentBounds, contentBounds,
244 canvas->restore();
245 canvas->restore();
246 }
247
248 // Visualize bounds after the actual rendering.
249 {
250 SkPaint border;
252
253 border.setColor(kOutputBoundsColor);
254 canvas->drawRect(outputBounds, border);
255
256 border.setColor(kCropRectColor);
257 canvas->drawRect(cropRect, border);
258
259 if (hintContent) {
260 border.setColor(kContentBoundsColor);
261 canvas->drawRect(contentBounds, border);
262 }
263 }
264
265 canvas->restore();
266}
267
268// Draw 5 example tiles in a column for 5 relationships between content bounds and crop rect:
269// no content hint, intersect, content contains crop, crop contains content, and no intersection
270void draw_example_column(
271 SkCanvas* canvas,
272 SkTileMode inputMode,
273 SkTileMode outputMode,
274 CropRelation outputRelation) {
275 const std::pair<CropRelation, bool> inputRelations[5] = {
276 { CropRelation::kCropOverlapsRect, false },
277 { CropRelation::kCropOverlapsRect, true },
278 { CropRelation::kCropContainsRect, true },
279 { CropRelation::kRectContainsCrop, true },
280 { CropRelation::kCropRectDisjoint, true }
281 };
282
283 canvas->save();
284 for (auto [inputRelation, hintContent] : inputRelations) {
285 draw_example_tile(canvas, inputMode, inputRelation, hintContent,
286 outputMode, outputRelation);
287 canvas->translate(0.f, kExampleBounds.fBottom + 1.f);
288 }
289
290 canvas->restore();
291}
292
293// Draw 5x4 grid of examples covering supported input tile modes and crop rect relations
294static constexpr int kNumRows = 5;
295static constexpr int kNumCols = 4;
296static constexpr float kGridWidth = kNumCols * (kExampleBounds.fRight+1.f) - 1.f;
297static constexpr float kGridHeight = kNumRows * (kExampleBounds.fBottom+1.f) - 1.f;
298
299void draw_example_grid(
300 SkCanvas* canvas,
301 SkTileMode inputMode,
302 SkTileMode outputMode) {
303 canvas->save();
304 for (auto outputRelation : { CropRelation::kCropOverlapsRect,
305 CropRelation::kCropContainsRect,
306 CropRelation::kRectContainsCrop,
307 CropRelation::kCropRectDisjoint }) {
308 draw_example_column(canvas, inputMode, outputMode, outputRelation);
309 canvas->translate(kExampleBounds.fRight + 1.f, 0.f);
310 }
311 canvas->restore();
312
313 // Draw dashed lines between rows and columns
314 SkPaint dashedLine;
315 dashedLine.setColor(SK_ColorGRAY);
318 static const float kDashes[2] = {5.f, 15.f};
319 dashedLine.setPathEffect(SkDashPathEffect::Make(kDashes, 2, 0.f));
320
321 for (int y = 1; y < kNumRows; ++y) {
322 canvas->drawLine({0.5f, y * (kExampleBounds.fBottom+1.f) - 0.5f},
323 {kGridWidth - 0.5f, y * (kExampleBounds.fBottom+1.f) - 0.5f},
324 dashedLine);
325 }
326 for (int x = 1; x < kNumCols; ++x) {
327 canvas->drawLine({x * (kExampleBounds.fRight+1.f) - 0.5f, 0.5f},
328 {x * (kExampleBounds.fRight+1.f) - 0.5f, kGridHeight - 0.5f},
329 dashedLine);
330 }
331}
332
333} // namespace
334
335namespace skiagm {
336
337class CropImageFilterGM : public GM {
338public:
340 : fInputMode(inputMode)
341 , fOutputMode(outputMode) {}
342
343protected:
344 SkISize getISize() override {
345 return {SkScalarRoundToInt(4.f * (kExampleBounds.fRight + 1.f) - 1.f),
346 SkScalarRoundToInt(5.f * (kExampleBounds.fBottom + 1.f) - 1.f)};
347 }
348 SkString getName() const override {
349 SkString name("crop_imagefilter_");
350 switch(fInputMode) {
351 case SkTileMode::kDecal: name.append("decal"); break;
352 case SkTileMode::kClamp: name.append("clamp"); break;
353 case SkTileMode::kRepeat: name.append("repeat"); break;
354 case SkTileMode::kMirror: name.append("mirror"); break;
355 }
356 name.append("-in_");
357
358 switch (fOutputMode) {
359 case SkTileMode::kDecal: name.append("decal"); break;
360 case SkTileMode::kClamp: name.append("clamp"); break;
361 case SkTileMode::kRepeat: name.append("repeat"); break;
362 case SkTileMode::kMirror: name.append("mirror"); break;
363 }
364 name.append("-out");
365 return name;
366 }
367
368 void onDraw(SkCanvas* canvas) override {
369 draw_example_grid(canvas, fInputMode, fOutputMode);
370 }
371
372private:
373 SkTileMode fInputMode;
374 SkTileMode fOutputMode;
375};
376
377DEF_GM( return new CropImageFilterGM(SkTileMode::kDecal, SkTileMode::kDecal); )
378DEF_GM( return new CropImageFilterGM(SkTileMode::kDecal, SkTileMode::kClamp); )
379DEF_GM( return new CropImageFilterGM(SkTileMode::kDecal, SkTileMode::kRepeat); )
380DEF_GM( return new CropImageFilterGM(SkTileMode::kDecal, SkTileMode::kMirror); )
381
382DEF_GM( return new CropImageFilterGM(SkTileMode::kClamp, SkTileMode::kDecal); )
383DEF_GM( return new CropImageFilterGM(SkTileMode::kClamp, SkTileMode::kClamp); )
384DEF_GM( return new CropImageFilterGM(SkTileMode::kClamp, SkTileMode::kRepeat); )
385DEF_GM( return new CropImageFilterGM(SkTileMode::kClamp, SkTileMode::kMirror); )
386
387DEF_GM( return new CropImageFilterGM(SkTileMode::kRepeat, SkTileMode::kDecal); )
388DEF_GM( return new CropImageFilterGM(SkTileMode::kRepeat, SkTileMode::kClamp); )
389DEF_GM( return new CropImageFilterGM(SkTileMode::kRepeat, SkTileMode::kRepeat); )
390DEF_GM( return new CropImageFilterGM(SkTileMode::kRepeat, SkTileMode::kMirror); )
391
392DEF_GM( return new CropImageFilterGM(SkTileMode::kMirror, SkTileMode::kDecal); )
393DEF_GM( return new CropImageFilterGM(SkTileMode::kMirror, SkTileMode::kClamp); )
394DEF_GM( return new CropImageFilterGM(SkTileMode::kMirror, SkTileMode::kRepeat); )
395DEF_GM( return new CropImageFilterGM(SkTileMode::kMirror, SkTileMode::kMirror); )
396
397} // namespace skiagm
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr SkColor SK_ColorYELLOW
Definition SkColor.h:139
constexpr SkColor SK_ColorMAGENTA
Definition SkColor.h:147
uint32_t SkColor
Definition SkColor.h:37
constexpr SkColor SK_ColorCYAN
Definition SkColor.h:143
constexpr SkColor SK_ColorGRAY
Definition SkColor.h:113
constexpr SkColor SK_ColorBLUE
Definition SkColor.h:135
constexpr SkColor SK_ColorRED
Definition SkColor.h:126
constexpr SkColor SK_ColorGREEN
Definition SkColor.h:131
constexpr SkColor SK_ColorDKGRAY
Definition SkColor.h:108
#define SkScalarRoundToInt(x)
Definition SkScalar.h:37
#define SkScalarCeilToInt(x)
Definition SkScalar.h:36
SkTileMode
Definition SkTileMode.h:13
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition SkCanvas.cpp:500
void drawRect(const SkRect &rect, const SkPaint &paint)
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
void restore()
Definition SkCanvas.cpp:465
void translate(SkScalar dx, SkScalar dy)
void drawPaint(const SkPaint &paint)
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
@ kStrict_SrcRectConstraint
sample only inside bounds; slower
Definition SkCanvas.h:1542
void drawImageRect(const SkImage *, const SkRect &src, const SkRect &dst, const SkSamplingOptions &, const SkPaint *, SrcRectConstraint)
int save()
Definition SkCanvas.cpp:451
static sk_sp< SkPathEffect > Make(const SkScalar intervals[], int count, SkScalar phase)
static sk_sp< SkImageFilter > Blur(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
const SkImageInfo & imageInfo() const
Definition SkImage.h:279
virtual sk_sp< SkImage > makeSubset(GrDirectContext *direct, const SkIRect &subset) const =0
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
@ kSquare_Cap
adds square
Definition SkPaint.h:336
void setStyle(Style style)
Definition SkPaint.cpp:105
void setColor(SkColor color)
Definition SkPaint.cpp:119
void setImageFilter(sk_sp< SkImageFilter > imageFilter)
void setStrokeCap(Cap cap)
Definition SkPaint.cpp:179
@ kStroke_Style
set to stroke geometry
Definition SkPaint.h:194
void setShader(sk_sp< SkShader > shader)
void setPathEffect(sk_sp< SkPathEffect > pathEffect)
void setAlphaf(float a)
Definition SkPaint.cpp:130
SkString getName() const override
void onDraw(SkCanvas *canvas) override
CropImageFilterGM(SkTileMode inputMode, SkTileMode outputMode)
const Paint & paint
VkSurfaceKHR surface
Definition main.cc:49
sk_sp< SkImage > image
Definition examples.cpp:29
const int kNumRows
static const uint8_t buffer[]
const char * name
Definition fuchsia.cc:50
#define DEF_GM(CODE)
Definition gm.h:40
union flutter::testing::@2838::KeyboardChange::@76 content
static sk_sp< SkImage > make_image()
Definition mipmap.cpp:21
double y
double x
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
sk_sp< SkImage > MakeTextureImage(SkCanvas *canvas, sk_sp< SkImage > orig)
SkScalar w
SkScalar h
int32_t height
int32_t width
static constexpr SkISize Make(int32_t w, int32_t h)
Definition SkSize.h:20
SkImageInfo makeWH(int newWidth, int newHeight) const
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
constexpr float left() const
Definition SkRect.h:734
constexpr SkRect makeOffset(float dx, float dy) const
Definition SkRect.h:965
constexpr float top() const
Definition SkRect.h:741
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
SkRect makeOutset(float dx, float dy) const
Definition SkRect.h:1002
bool intersects(const SkRect &r) const
Definition SkRect.h:1121
SkRect makeInset(float dx, float dy) const
Definition SkRect.h:987
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
bool contains(SkScalar x, SkScalar y) const
Definition extension.cpp:19
void roundOut(SkIRect *dst) const
Definition SkRect.h:1241
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
static constexpr int kNumCols