1 /*
2 * Copyright 2013 Google Inc.
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 #include "include/core/SkBitmap.h"
10 #include "include/core/SkBlurTypes.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkImage.h"
14 #include "include/core/SkImageInfo.h"
15 #include "include/core/SkMaskFilter.h"
16 #include "include/core/SkMatrix.h"
17 #include "include/core/SkPaint.h"
18 #include "include/core/SkPoint.h"
19 #include "include/core/SkRect.h"
20 #include "include/core/SkRefCnt.h"
21 #include "include/core/SkScalar.h"
22 #include "include/core/SkShader.h"
23 #include "include/core/SkSize.h"
24 #include "include/core/SkString.h"
25 #include "include/core/SkSurface.h"
26 #include "include/core/SkTileMode.h"
27 #include "include/core/SkTiledImageUtils.h"
28 #include "include/core/SkTypes.h"
29 #include "include/gpu/ganesh/GrContextOptions.h"
30 #include "include/private/base/SkTDArray.h"
31 #include "src/core/SkBlurMask.h"
32 #include "tools/ToolUtils.h"
33
34 #if defined(SK_GRAPHITE)
35 #include "include/gpu/graphite/ContextOptions.h"
36 #include "src/gpu/graphite/ContextOptionsPriv.h"
37 #endif
38
39 /** Creates an image with two one-pixel wide borders around a checkerboard. The checkerboard is 2x2
40 checks where each check has as many pixels as is necessary to fill the interior. It returns
41 the image and a src rect that bounds the checkerboard portion. */
make_ringed_image(SkCanvas * canvas,int width,int height)42 std::tuple<sk_sp<SkImage>, SkRect> make_ringed_image(SkCanvas* canvas, int width, int height) {
43
44 // These are kRGBA_8888_SkColorType values.
45 static constexpr uint32_t kOuterRingColor = 0xFFFF0000,
46 kInnerRingColor = 0xFF0000FF,
47 kCheckColor1 = 0xFF000000,
48 kCheckColor2 = 0xFFFFFFFF;
49
50 SkASSERT(0 == width % 2 && 0 == height % 2);
51 SkASSERT(width >= 6 && height >= 6);
52
53 SkImageInfo info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType,
54 kPremul_SkAlphaType);
55 size_t rowBytes = SkAlign4(info.minRowBytes());
56 SkBitmap bitmap;
57 bitmap.allocPixels(info, rowBytes);
58
59 uint32_t* scanline = bitmap.getAddr32(0, 0);
60 for (int x = 0; x < width; ++x) {
61 scanline[x] = kOuterRingColor;
62 }
63 scanline = bitmap.getAddr32(0, 1);
64 scanline[0] = kOuterRingColor;
65 for (int x = 1; x < width - 1; ++x) {
66 scanline[x] = kInnerRingColor;
67 }
68 scanline[width - 1] = kOuterRingColor;
69
70 for (int y = 2; y < height / 2; ++y) {
71 scanline = bitmap.getAddr32(0, y);
72 scanline[0] = kOuterRingColor;
73 scanline[1] = kInnerRingColor;
74 for (int x = 2; x < width / 2; ++x) {
75 scanline[x] = kCheckColor1;
76 }
77 for (int x = width / 2; x < width - 2; ++x) {
78 scanline[x] = kCheckColor2;
79 }
80 scanline[width - 2] = kInnerRingColor;
81 scanline[width - 1] = kOuterRingColor;
82 }
83
84 for (int y = height / 2; y < height - 2; ++y) {
85 scanline = bitmap.getAddr32(0, y);
86 scanline[0] = kOuterRingColor;
87 scanline[1] = kInnerRingColor;
88 for (int x = 2; x < width / 2; ++x) {
89 scanline[x] = kCheckColor2;
90 }
91 for (int x = width / 2; x < width - 2; ++x) {
92 scanline[x] = kCheckColor1;
93 }
94 scanline[width - 2] = kInnerRingColor;
95 scanline[width - 1] = kOuterRingColor;
96 }
97
98 scanline = bitmap.getAddr32(0, height - 2);
99 scanline[0] = kOuterRingColor;
100 for (int x = 1; x < width - 1; ++x) {
101 scanline[x] = kInnerRingColor;
102 }
103 scanline[width - 1] = kOuterRingColor;
104
105 scanline = bitmap.getAddr32(0, height - 1);
106 for (int x = 0; x < width; ++x) {
107 scanline[x] = kOuterRingColor;
108 }
109 bitmap.setImmutable();
110 return { bitmap.asImage(), SkRect::Make({2, 2, width - 2, height - 2})};
111 }
112
113 /**
114 * These GMs exercise the behavior of the drawImageRect and its SrcRectConstraint parameter. They
115 * tests various matrices, filter qualities, and interaction with mask filters. They also exercise
116 * the tiling image draws of SkGpuDevice by overriding the maximum texture size of the GrContext.
117 */
118 class SrcRectConstraintGM : public skiagm::GM {
119 public:
SrcRectConstraintGM(const char * shortName,SkCanvas::SrcRectConstraint constraint,bool manual)120 SrcRectConstraintGM(const char* shortName, SkCanvas::SrcRectConstraint constraint, bool manual)
121 : fShortName(shortName)
122 , fConstraint(constraint)
123 , fManual(manual) {
124 // Make sure GPU SkSurfaces can be created for this GM.
125 SkASSERT(this->getISize().width() <= kMaxTextureSize &&
126 this->getISize().height() <= kMaxTextureSize);
127 }
128
129 protected:
getName() const130 SkString getName() const override { return fShortName; }
getISize()131 SkISize getISize() override { return SkISize::Make(800, 1000); }
132
drawImage(SkCanvas * canvas,sk_sp<SkImage> image,SkRect srcRect,SkRect dstRect,const SkSamplingOptions & sampling,SkPaint * paint)133 void drawImage(SkCanvas* canvas, sk_sp<SkImage> image, SkRect srcRect, SkRect dstRect,
134 const SkSamplingOptions& sampling, SkPaint* paint) {
135 if (fManual) {
136 SkTiledImageUtils::DrawImageRect(canvas, image.get(), srcRect, dstRect,
137 sampling, paint, fConstraint);
138 } else {
139 canvas->drawImageRect(image.get(), srcRect, dstRect, sampling, paint, fConstraint);
140 }
141 }
142
143 // Draw the area of interest of the small image
drawCase1(SkCanvas * canvas,int transX,int transY,bool aa,const SkSamplingOptions & sampling)144 void drawCase1(SkCanvas* canvas, int transX, int transY, bool aa,
145 const SkSamplingOptions& sampling) {
146 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
147 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
148
149 SkPaint paint;
150 paint.setColor(SK_ColorBLUE);
151 paint.setAntiAlias(aa);
152
153 this->drawImage(canvas, fSmallImage, fSmallSrcRect, dst, sampling, &paint);
154 }
155
156 // Draw the area of interest of the large image
drawCase2(SkCanvas * canvas,int transX,int transY,bool aa,const SkSamplingOptions & sampling)157 void drawCase2(SkCanvas* canvas, int transX, int transY, bool aa,
158 const SkSamplingOptions& sampling) {
159 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
160 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
161
162 SkPaint paint;
163 paint.setColor(SK_ColorBLUE);
164 paint.setAntiAlias(aa);
165
166 this->drawImage(canvas, fBigImage, fBigSrcRect, dst, sampling, &paint);
167 }
168
169 // Draw upper-left 1/4 of the area of interest of the large image
drawCase3(SkCanvas * canvas,int transX,int transY,bool aa,const SkSamplingOptions & sampling)170 void drawCase3(SkCanvas* canvas, int transX, int transY, bool aa,
171 const SkSamplingOptions& sampling) {
172 SkRect src = SkRect::MakeXYWH(fBigSrcRect.fLeft,
173 fBigSrcRect.fTop,
174 fBigSrcRect.width()/2,
175 fBigSrcRect.height()/2);
176 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
177 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
178
179 SkPaint paint;
180 paint.setColor(SK_ColorBLUE);
181 paint.setAntiAlias(aa);
182
183 this->drawImage(canvas, fBigImage, src, dst, sampling, &paint);
184 }
185
186 // Draw the area of interest of the small image with a normal blur
drawCase4(SkCanvas * canvas,int transX,int transY,bool aa,const SkSamplingOptions & sampling)187 void drawCase4(SkCanvas* canvas, int transX, int transY, bool aa,
188 const SkSamplingOptions& sampling) {
189 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
190 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
191
192 SkPaint paint;
193 paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
194 SkBlurMask::ConvertRadiusToSigma(3)));
195 paint.setColor(SK_ColorBLUE);
196 paint.setAntiAlias(aa);
197
198 this->drawImage(canvas, fSmallImage, fSmallSrcRect, dst, sampling, &paint);
199 }
200
201 // Draw the area of interest of the small image with a outer blur
drawCase5(SkCanvas * canvas,int transX,int transY,bool aa,const SkSamplingOptions & sampling)202 void drawCase5(SkCanvas* canvas, int transX, int transY, bool aa,
203 const SkSamplingOptions& sampling) {
204 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
205 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
206
207 SkPaint paint;
208 paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle,
209 SkBlurMask::ConvertRadiusToSigma(7)));
210 paint.setColor(SK_ColorBLUE);
211 paint.setAntiAlias(aa);
212
213 this->drawImage(canvas, fSmallImage, fSmallSrcRect, dst, sampling, &paint);
214 }
215
onDraw(SkCanvas * canvas)216 void onDraw(SkCanvas* canvas) override {
217 if (!fSmallImage) {
218 std::tie(fBigImage, fBigSrcRect) = make_ringed_image(canvas,
219 2*kMaxTextureSize,
220 2*kMaxTextureSize);
221 std::tie(fSmallImage, fSmallSrcRect) = make_ringed_image(canvas,
222 kSmallSize, kSmallSize);
223 }
224
225 canvas->clear(SK_ColorGRAY);
226 std::vector<SkMatrix> matrices;
227 // Draw with identity
228 matrices.push_back(SkMatrix::I());
229
230 // Draw with rotation and scale down in x, up in y.
231 SkMatrix m;
232 constexpr SkScalar kBottom = SkIntToScalar(kRow4Y + kBlockSize + kBlockSpacing);
233 m.setTranslate(0, kBottom);
234 m.preRotate(15.f, 0, kBottom + kBlockSpacing);
235 m.preScale(0.71f, 1.22f);
236 matrices.push_back(m);
237
238 // Align the next set with the middle of the previous in y, translated to the right in x.
239 SkPoint corners[] = {{0, 0}, {0, kBottom}, {kWidth, kBottom}, {kWidth, 0}};
240 matrices.back().mapPoints(corners, 4);
241 m.setTranslate(std::max({corners[0].fX, corners[1].fX, corners[2].fX, corners[3].fX}),
242 (corners[0].fY + corners[1].fY + corners[2].fY + corners[3].fY) / 4);
243 m.preScale(0.2f, 0.2f);
244 matrices.push_back(m);
245
246 const SkSamplingOptions none(SkFilterMode::kNearest);
247 const SkSamplingOptions low(SkFilterMode::kLinear);
248 const SkSamplingOptions high(SkCubicResampler::Mitchell());
249
250 SkScalar maxX = 0;
251 for (bool antiAlias : {false, true}) {
252 canvas->save();
253 canvas->translate(maxX, 0);
254 for (const SkMatrix& matrix : matrices) {
255 canvas->save();
256 canvas->concat(matrix);
257
258 // First draw a column with no filtering
259 this->drawCase1(canvas, kCol0X, kRow0Y, antiAlias, none);
260 this->drawCase2(canvas, kCol0X, kRow1Y, antiAlias, none);
261 this->drawCase3(canvas, kCol0X, kRow2Y, antiAlias, none);
262 this->drawCase4(canvas, kCol0X, kRow3Y, antiAlias, none);
263 this->drawCase5(canvas, kCol0X, kRow4Y, antiAlias, none);
264
265 // Then draw a column with low filtering
266 this->drawCase1(canvas, kCol1X, kRow0Y, antiAlias, low);
267 this->drawCase2(canvas, kCol1X, kRow1Y, antiAlias, low);
268 this->drawCase3(canvas, kCol1X, kRow2Y, antiAlias, low);
269 this->drawCase4(canvas, kCol1X, kRow3Y, antiAlias, low);
270 this->drawCase5(canvas, kCol1X, kRow4Y, antiAlias, low);
271
272 // Then draw a column with high filtering. Skip it if in kStrict mode and MIP
273 // mapping will be used. On GPU we allow bleeding at non-base levels because
274 // building a new MIP chain for the subset is expensive.
275 SkScalar scales[2];
276 SkAssertResult(matrix.getMinMaxScales(scales));
277 if (fConstraint != SkCanvas::kStrict_SrcRectConstraint || scales[0] >= 1.f) {
278 this->drawCase1(canvas, kCol2X, kRow0Y, antiAlias, high);
279 this->drawCase2(canvas, kCol2X, kRow1Y, antiAlias, high);
280 this->drawCase3(canvas, kCol2X, kRow2Y, antiAlias, high);
281 this->drawCase4(canvas, kCol2X, kRow3Y, antiAlias, high);
282 this->drawCase5(canvas, kCol2X, kRow4Y, antiAlias, high);
283 }
284
285 SkPoint innerCorners[] = {{0, 0}, {0, kBottom}, {kWidth, kBottom}, {kWidth, 0}};
286 matrix.mapPoints(innerCorners, 4);
287 SkScalar x = kBlockSize + std::max({innerCorners[0].fX, innerCorners[1].fX,
288 innerCorners[2].fX, innerCorners[3].fX});
289 maxX = std::max(maxX, x);
290 canvas->restore();
291 }
292 canvas->restore();
293 }
294 }
295
modifyGrContextOptions(GrContextOptions * options)296 void modifyGrContextOptions(GrContextOptions* options) override {
297 options->fMaxTextureSizeOverride = kMaxTextureSize;
298 }
299
300 #if defined(SK_GRAPHITE)
modifyGraphiteContextOptions(skgpu::graphite::ContextOptions * options) const301 void modifyGraphiteContextOptions(skgpu::graphite::ContextOptions* options) const override {
302 SkASSERT(options->fOptionsPriv);
303 options->fOptionsPriv->fMaxTextureSizeOverride = kMaxTextureSize;
304 }
305 #endif
306
307 private:
308 inline static constexpr int kBlockSize = 70;
309 inline static constexpr int kBlockSpacing = 12;
310
311 inline static constexpr int kCol0X = kBlockSpacing;
312 inline static constexpr int kCol1X = 2*kBlockSpacing + kBlockSize;
313 inline static constexpr int kCol2X = 3*kBlockSpacing + 2*kBlockSize;
314 inline static constexpr int kWidth = 4*kBlockSpacing + 3*kBlockSize;
315
316 inline static constexpr int kRow0Y = kBlockSpacing;
317 inline static constexpr int kRow1Y = 2*kBlockSpacing + kBlockSize;
318 inline static constexpr int kRow2Y = 3*kBlockSpacing + 2*kBlockSize;
319 inline static constexpr int kRow3Y = 4*kBlockSpacing + 3*kBlockSize;
320 inline static constexpr int kRow4Y = 5*kBlockSpacing + 4*kBlockSize;
321
322 inline static constexpr int kSmallSize = 6;
323 // This must be at least as large as the GM width and height so that a surface can be made, and
324 // a power-of-2 to account for any approx-fitting that the backend may perform.
325 inline static constexpr int kMaxTextureSize = 1024;
326
327 SkString fShortName;
328 sk_sp<SkImage> fBigImage;
329 sk_sp<SkImage> fSmallImage;
330 SkRect fBigSrcRect;
331 SkRect fSmallSrcRect;
332 SkCanvas::SrcRectConstraint fConstraint;
333 bool fManual;
334 using INHERITED = GM;
335 };
336
337 DEF_GM(return new SrcRectConstraintGM("strict_constraint_no_red_allowed",
338 SkCanvas::kStrict_SrcRectConstraint,
339 /* manual= */ false););
340 DEF_GM(return new SrcRectConstraintGM("strict_constraint_no_red_allowed_manual",
341 SkCanvas::kStrict_SrcRectConstraint,
342 /* manual= */ true););
343
344 DEF_GM(return new SrcRectConstraintGM("strict_constraint_batch_no_red_allowed",
345 SkCanvas::kStrict_SrcRectConstraint,
346 /* manual= */ false););
347 DEF_GM(return new SrcRectConstraintGM("strict_constraint_batch_no_red_allowed_manual",
348 SkCanvas::kStrict_SrcRectConstraint,
349 /* manual= */ true););
350
351 DEF_GM(return new SrcRectConstraintGM("fast_constraint_red_is_allowed",
352 SkCanvas::kFast_SrcRectConstraint,
353 /* manual= */ false););
354 DEF_GM(return new SrcRectConstraintGM("fast_constraint_red_is_allowed_manual",
355 SkCanvas::kFast_SrcRectConstraint,
356 /* manual= */ true););
357
358 ///////////////////////////////////////////////////////////////////////////////////////////////////
359
360 // Construct an image and return the inner "src" rect. Build the image such that the interior is
361 // blue, with a margin of blue (2px) but then an outer margin of red.
362 //
363 // Show that kFast_SrcRectConstraint sees even the red margin (due to mipmapping) when the image
364 // is scaled down far enough.
365 //
make_image(SkCanvas * canvas,SkRect * srcR)366 static sk_sp<SkImage> make_image(SkCanvas* canvas, SkRect* srcR) {
367 // Intentially making the size a power of 2 to avoid the noise from how different GPUs will
368 // produce different mipmap filtering when we have an odd sized texture.
369 const int N = 10 + 2 + 8 + 2 + 10;
370 SkImageInfo info = SkImageInfo::MakeN32Premul(N, N);
371 auto surface = ToolUtils::makeSurface(canvas, info);
372 SkCanvas* c = surface->getCanvas();
373 SkRect r = SkRect::MakeIWH(info.width(), info.height());
374 SkPaint paint;
375
376 paint.setColor(SK_ColorRED);
377 c->drawRect(r, paint);
378 r.inset(10, 10);
379 paint.setColor(SK_ColorBLUE);
380 c->drawRect(r, paint);
381
382 *srcR = r.makeInset(2, 2);
383 return surface->makeImageSnapshot();
384 }
385
386 DEF_SIMPLE_GM(bleed_downscale, canvas, 360, 240) {
387 SkRect src;
388 sk_sp<SkImage> img = make_image(canvas, &src);
389 SkPaint paint;
390
391 canvas->translate(10, 10);
392
393 const SkCanvas::SrcRectConstraint constraints[] = {
394 SkCanvas::kStrict_SrcRectConstraint, SkCanvas::kFast_SrcRectConstraint
395 };
396 const SkSamplingOptions samplings[] = {
397 SkSamplingOptions(SkFilterMode::kNearest),
398 SkSamplingOptions(SkFilterMode::kLinear),
399 SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear),
400 };
401 for (auto constraint : constraints) {
402 canvas->save();
403 for (auto sampling : samplings) {
404 auto surf = ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(1, 1));
405 surf->getCanvas()->drawImageRect(img, src, SkRect::MakeWH(1, 1), sampling,
406 nullptr, constraint);
407 // now blow up the 1 pixel result
408 canvas->drawImageRect(surf->makeImageSnapshot(), SkRect::MakeWH(100, 100),
409 SkSamplingOptions());
410 canvas->translate(120, 0);
411 }
412 canvas->restore();
413 canvas->translate(0, 120);
414 }
415 }
416