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