Flutter Engine
The Flutter Engine
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
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
#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
static sk_sp< SkImage > make_image(SkCanvas *destCanvas)
Definition: bitmaprect.cpp:31
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition: SkCanvas.cpp:496
void drawRect(const SkRect &rect, const SkPaint &paint)
Definition: SkCanvas.cpp:1673
void clipRect(const SkRect &rect, SkClipOp op, bool doAntiAlias)
Definition: SkCanvas.cpp:1361
void restore()
Definition: SkCanvas.cpp:461
void translate(SkScalar dx, SkScalar dy)
Definition: SkCanvas.cpp:1278
void drawPaint(const SkPaint &paint)
Definition: SkCanvas.cpp:1668
void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint &paint)
Definition: SkCanvas.cpp:2700
@ 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)
Definition: SkCanvas.cpp:2333
int save()
Definition: SkCanvas.cpp:447
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)
Definition: gm.h:110
const Paint & paint
Definition: color_source.cc:38
VkSurfaceKHR surface
Definition: main.cc:49
const int kNumRows
union flutter::testing::@2836::KeyboardChange::@76 content
double y
double x
sk_sp< const SkImage > image
Definition: SkRecords.h:269
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)
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
DEF_GM(return F(C(clipbox), 0.0f, 0.0f, {})) DEF_GM(return F(C(clipbox)
SkSamplingOptions(SkFilterMode::kLinear))
SkScalar w
SkScalar h
Definition: SkSize.h:16
static constexpr SkISize Make(int32_t w, int32_t h)
Definition: SkSize.h:20
SkImageInfo makeWH(int newWidth, int newHeight) const
Definition: SkImageInfo.h:444
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
void roundIn(SkIRect *dst) const
Definition: SkRect.h:1266
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