2 * Copyright 2013 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
9 #include "SkBlurMask.h"
10 #include "SkBlurMaskFilter.h"
12 #include "SkGradientShader.h"
14 #include "SkTDArray.h"
18 #include "GrContext.h"
19 #include "GrContextOptions.h"
23 /** Holds either a bitmap or image to be rendered and a rect that indicates what part of the bitmap
24 or image should be tested by the GM. The area outside of the rect is present to check
25 for bleed due to filtering/blurring. */
33 sk_sp<SkImage> fImage;
34 SkIRect fRect; // The region of the bitmap/image that should be rendered.
37 /** Creates a bitmap with two one-pixel rings around a checkerboard. The checkerboard is 2x2
38 logically where each check has as many pixels as is necessary to fill the interior. The rect
39 to draw is set to the checkerboard portion. */
40 template<typename PIXEL_TYPE>
41 bool make_ringed_bitmap(TestPixels* result, int width, int height,
42 SkColorType ct, SkAlphaType at,
43 PIXEL_TYPE outerRingColor, PIXEL_TYPE innerRingColor,
44 PIXEL_TYPE checkColor1, PIXEL_TYPE checkColor2) {
45 SkASSERT(0 == width % 2 && 0 == height % 2);
46 SkASSERT(width >= 6 && height >= 6);
48 result->fType = TestPixels::kBitmap;
49 SkImageInfo info = SkImageInfo::Make(width, height, ct, at);
50 size_t rowBytes = SkAlign4(info.minRowBytes());
51 result->fBitmap.allocPixels(info, rowBytes);
53 PIXEL_TYPE* scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, 0);
54 for (int x = 0; x < width; ++x) {
55 scanline[x] = outerRingColor;
57 scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, 1);
58 scanline[0] = outerRingColor;
59 for (int x = 1; x < width - 1; ++x) {
60 scanline[x] = innerRingColor;
62 scanline[width - 1] = outerRingColor;
64 for (int y = 2; y < height / 2; ++y) {
65 scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, y);
66 scanline[0] = outerRingColor;
67 scanline[1] = innerRingColor;
68 for (int x = 2; x < width / 2; ++x) {
69 scanline[x] = checkColor1;
71 for (int x = width / 2; x < width - 2; ++x) {
72 scanline[x] = checkColor2;
74 scanline[width - 2] = innerRingColor;
75 scanline[width - 1] = outerRingColor;
78 for (int y = height / 2; y < height - 2; ++y) {
79 scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, y);
80 scanline[0] = outerRingColor;
81 scanline[1] = innerRingColor;
82 for (int x = 2; x < width / 2; ++x) {
83 scanline[x] = checkColor2;
85 for (int x = width / 2; x < width - 2; ++x) {
86 scanline[x] = checkColor1;
88 scanline[width - 2] = innerRingColor;
89 scanline[width - 1] = outerRingColor;
92 scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, height - 2);
93 scanline[0] = outerRingColor;
94 for (int x = 1; x < width - 1; ++x) {
95 scanline[x] = innerRingColor;
97 scanline[width - 1] = outerRingColor;
99 scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, height - 1);
100 for (int x = 0; x < width; ++x) {
101 scanline[x] = outerRingColor;
103 result->fBitmap.setImmutable();
104 result->fRect.set(2, 2, width - 2, height - 2);
108 /** Create a black and white checked bitmap with 2 1-pixel rings around the outside edge.
109 The inner ring is red and the outer ring is blue. */
110 static bool make_ringed_color_bitmap(TestPixels* result, int width, int height) {
111 const SkPMColor kBlue = SkPreMultiplyColor(SK_ColorBLUE);
112 const SkPMColor kRed = SkPreMultiplyColor(SK_ColorRED);
113 const SkPMColor kBlack = SkPreMultiplyColor(SK_ColorBLACK);
114 const SkPMColor kWhite = SkPreMultiplyColor(SK_ColorWHITE);
115 return make_ringed_bitmap<SkPMColor>(result, width, height, kN32_SkColorType,
116 kPremul_SkAlphaType, kBlue, kRed, kBlack, kWhite);
119 /** Makes a alpha bitmap with 1 wide rect/ring of 0s, an inset of 1s, and the interior is a 2x2
120 checker board of 3/4 and 1/2. The inner checkers are large enough to fill the interior with
121 the 2x2 checker grid. */
122 static bool make_ringed_alpha_bitmap(TestPixels* result, int width, int height) {
123 constexpr uint8_t kZero = 0x00;
124 constexpr uint8_t kHalf = 0x80;
125 constexpr uint8_t k3Q = 0xC0;
126 constexpr uint8_t kOne = 0xFF;
127 return make_ringed_bitmap<uint8_t>(result, width, height, kAlpha_8_SkColorType,
128 kPremul_SkAlphaType, kZero, kOne, k3Q, kHalf);
131 /** Helper to reuse above functions to produce images rather than bmps */
132 static void bmp_to_image(TestPixels* result) {
133 SkASSERT(TestPixels::kBitmap == result->fType);
134 result->fImage = SkImage::MakeFromBitmap(result->fBitmap);
135 SkASSERT(result->fImage);
136 result->fType = TestPixels::kImage;
137 result->fBitmap.reset();
140 /** Color image case. */
141 bool make_ringed_color_image(TestPixels* result, int width, int height) {
142 if (make_ringed_color_bitmap(result, width, height)) {
143 bmp_to_image(result);
149 /** Alpha image case. */
150 bool make_ringed_alpha_image(TestPixels* result, int width, int height) {
151 if (make_ringed_alpha_bitmap(result, width, height)) {
152 bmp_to_image(result);
158 static sk_sp<SkShader> make_shader() {
159 constexpr SkPoint pts[] = { {0, 0}, {20, 20} };
160 constexpr SkColor colors[] = { SK_ColorGREEN, SK_ColorYELLOW };
161 return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kMirror_TileMode);
164 static sk_sp<SkShader> make_null_shader() { return nullptr; }
167 kUseBitmap_BleedTest,
169 kUseAlphaBitmap_BleedTest,
170 kUseAlphaImage_BleedTest,
171 kUseAlphaBitmapShader_BleedTest,
172 kUseAlphaImageShader_BleedTest,
177 bool (*fPixelMaker)(TestPixels* result, int width, int height);
178 sk_sp<SkShader> (*fShaderMaker)();
180 { "bleed", make_ringed_color_bitmap, make_null_shader },
181 { "bleed_image", make_ringed_color_image, make_null_shader },
182 { "bleed_alpha_bmp", make_ringed_alpha_bitmap, make_null_shader },
183 { "bleed_alpha_image", make_ringed_alpha_image, make_null_shader },
184 { "bleed_alpha_bmp_shader", make_ringed_alpha_bitmap, make_shader },
185 { "bleed_alpha_image_shader", make_ringed_alpha_image, make_shader },
188 /** This GM exercises the behavior of the drawBitmapRect & drawImageRect calls. Specifically their
190 - SrcRectConstraint(bleed vs.no - bleed)
191 - handling of the sub - region feature(area - of - interest) of drawBitmap*
192 - handling of 8888 vs. A8 (including presence of a shader in the A8 case).
193 In particular, we should never see the padding outside of an SkBitmap's sub - region (green for
194 8888, 1/4 for alpha). In some instances we can see the two outer rings outside of the area o
195 interest (i.e., the inner four checks) due to AA or filtering if allowed by the
198 class BleedGM : public skiagm::GM {
200 BleedGM(BleedTest bt) : fBT(bt){}
204 SkString onShortName() override {
205 return SkString(gBleedRec[fBT].fName);
208 SkISize onISize() override {
209 return SkISize::Make(1200, 1080);
212 void drawPixels(SkCanvas* canvas, const TestPixels& pixels, const SkRect& src,
213 const SkRect& dst, const SkPaint* paint,
214 SkCanvas::SrcRectConstraint constraint) {
215 if (TestPixels::kBitmap == pixels.fType) {
216 canvas->drawBitmapRect(pixels.fBitmap, src, dst, paint, constraint);
218 canvas->drawImageRect(pixels.fImage.get(), src, dst, paint, constraint);
222 // Draw the area of interest of the small image
223 void drawCase1(SkCanvas* canvas, int transX, int transY, bool aa,
224 SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
226 SkRect src = SkRect::Make(fSmallTestPixels.fRect);
227 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
228 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
231 paint.setFilterQuality(filter);
232 paint.setShader(fShader);
233 paint.setColor(SK_ColorBLUE);
234 paint.setAntiAlias(aa);
236 this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
239 // Draw the area of interest of the large image
240 void drawCase2(SkCanvas* canvas, int transX, int transY, bool aa,
241 SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
242 SkRect src = SkRect::Make(fBigTestPixels.fRect);
243 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
244 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
247 paint.setFilterQuality(filter);
248 paint.setShader(fShader);
249 paint.setColor(SK_ColorBLUE);
250 paint.setAntiAlias(aa);
252 this->drawPixels(canvas, fBigTestPixels, src, dst, &paint, constraint);
255 // Draw upper-left 1/4 of the area of interest of the large image
256 void drawCase3(SkCanvas* canvas, int transX, int transY, bool aa,
257 SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
258 SkRect src = SkRect::MakeXYWH(SkIntToScalar(fBigTestPixels.fRect.fLeft),
259 SkIntToScalar(fBigTestPixels.fRect.fTop),
260 fBigTestPixels.fRect.width()/2.f,
261 fBigTestPixels.fRect.height()/2.f);
262 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
263 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
266 paint.setFilterQuality(filter);
267 paint.setShader(fShader);
268 paint.setColor(SK_ColorBLUE);
269 paint.setAntiAlias(aa);
271 this->drawPixels(canvas, fBigTestPixels, src, dst, &paint, constraint);
274 // Draw the area of interest of the small image with a normal blur
275 void drawCase4(SkCanvas* canvas, int transX, int transY, bool aa,
276 SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
277 SkRect src = SkRect::Make(fSmallTestPixels.fRect);
278 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
279 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
282 paint.setFilterQuality(filter);
283 paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
284 SkBlurMask::ConvertRadiusToSigma(3)));
285 paint.setShader(fShader);
286 paint.setColor(SK_ColorBLUE);
287 paint.setAntiAlias(aa);
289 this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
292 // Draw the area of interest of the small image with a outer blur
293 void drawCase5(SkCanvas* canvas, int transX, int transY, bool aa,
294 SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
295 SkRect src = SkRect::Make(fSmallTestPixels.fRect);
296 SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
297 SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
300 paint.setFilterQuality(filter);
301 paint.setMaskFilter(SkBlurMaskFilter::Make(kOuter_SkBlurStyle,
302 SkBlurMask::ConvertRadiusToSigma(7)));
303 paint.setShader(fShader);
304 paint.setColor(SK_ColorBLUE);
305 paint.setAntiAlias(aa);
307 this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
310 void onOnceBeforeDraw() override {
311 SkAssertResult(gBleedRec[fBT].fPixelMaker(&fSmallTestPixels, kSmallSize, kSmallSize));
312 SkAssertResult(gBleedRec[fBT].fPixelMaker(&fBigTestPixels, 2 * kMaxTileSize,
316 void onDraw(SkCanvas* canvas) override {
317 fShader = gBleedRec[fBT].fShaderMaker();
319 canvas->clear(SK_ColorGRAY);
320 SkTDArray<SkMatrix> matrices;
321 // Draw with identity
322 *matrices.append() = SkMatrix::I();
324 // Draw with rotation and scale down in x, up in y.
326 constexpr SkScalar kBottom = SkIntToScalar(kRow4Y + kBlockSize + kBlockSpacing);
327 m.setTranslate(0, kBottom);
328 m.preRotate(15.f, 0, kBottom + kBlockSpacing);
329 m.preScale(0.71f, 1.22f);
330 *matrices.append() = m;
332 // Align the next set with the middle of the previous in y, translated to the right in x.
333 SkPoint corners[] = {{0, 0}, { 0, kBottom }, { kWidth, kBottom }, {kWidth, 0} };
334 matrices[matrices.count()-1].mapPoints(corners, 4);
335 SkScalar y = (corners[0].fY + corners[1].fY + corners[2].fY + corners[3].fY) / 4;
336 SkScalar x = SkTMax(SkTMax(corners[0].fX, corners[1].fX),
337 SkTMax(corners[2].fX, corners[3].fX));
338 m.setTranslate(x, y);
339 m.preScale(0.2f, 0.2f);
340 *matrices.append() = m;
343 for (int antiAlias = 0; antiAlias < 2; ++antiAlias) {
345 canvas->translate(maxX, 0);
346 for (int m = 0; m < matrices.count(); ++m) {
348 canvas->concat(matrices[m]);
349 bool aa = SkToBool(antiAlias);
351 // First draw a column with no bleeding and no filtering
352 this->drawCase1(canvas, kCol0X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
353 this->drawCase2(canvas, kCol0X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
354 this->drawCase3(canvas, kCol0X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
355 this->drawCase4(canvas, kCol0X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
356 this->drawCase5(canvas, kCol0X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
358 // Then draw a column with no bleeding and low filtering
359 this->drawCase1(canvas, kCol1X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
360 this->drawCase2(canvas, kCol1X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
361 this->drawCase3(canvas, kCol1X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
362 this->drawCase4(canvas, kCol1X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
363 this->drawCase5(canvas, kCol1X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
365 // Then draw a column with no bleeding and high filtering
366 this->drawCase1(canvas, kCol2X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
367 this->drawCase2(canvas, kCol2X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
368 this->drawCase3(canvas, kCol2X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
369 this->drawCase4(canvas, kCol2X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
370 this->drawCase5(canvas, kCol2X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
372 // Then draw a column with bleeding and no filtering (bleed should have no effect w/out blur)
373 this->drawCase1(canvas, kCol3X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
374 this->drawCase2(canvas, kCol3X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
375 this->drawCase3(canvas, kCol3X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
376 this->drawCase4(canvas, kCol3X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
377 this->drawCase5(canvas, kCol3X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
379 // Then draw a column with bleeding and low filtering
380 this->drawCase1(canvas, kCol4X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
381 this->drawCase2(canvas, kCol4X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
382 this->drawCase3(canvas, kCol4X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
383 this->drawCase4(canvas, kCol4X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
384 this->drawCase5(canvas, kCol4X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
386 // Finally draw a column with bleeding and high filtering
387 this->drawCase1(canvas, kCol5X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
388 this->drawCase2(canvas, kCol5X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
389 this->drawCase3(canvas, kCol5X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
390 this->drawCase4(canvas, kCol5X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
391 this->drawCase5(canvas, kCol5X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
393 SkPoint corners[] = { { 0, 0 },{ 0, kBottom },{ kWidth, kBottom },{ kWidth, 0 } };
394 matrices[m].mapPoints(corners, 4);
395 SkScalar x = kBlockSize + SkTMax(SkTMax(corners[0].fX, corners[1].fX),
396 SkTMax(corners[2].fX, corners[3].fX));
397 maxX = SkTMax(maxX, x);
405 void modifyGrContextOptions(GrContextOptions* options) override {
406 options->fMaxTileSizeOverride = kMaxTileSize;
411 static constexpr int kBlockSize = 70;
412 static constexpr int kBlockSpacing = 12;
414 static constexpr int kCol0X = kBlockSpacing;
415 static constexpr int kCol1X = 2*kBlockSpacing + kBlockSize;
416 static constexpr int kCol2X = 3*kBlockSpacing + 2*kBlockSize;
417 static constexpr int kCol3X = 4*kBlockSpacing + 3*kBlockSize;
418 static constexpr int kCol4X = 5*kBlockSpacing + 4*kBlockSize;
419 static constexpr int kCol5X = 6*kBlockSpacing + 5*kBlockSize;
420 static constexpr int kWidth = 7*kBlockSpacing + 6*kBlockSize;
422 static constexpr int kRow0Y = kBlockSpacing;
423 static constexpr int kRow1Y = 2*kBlockSpacing + kBlockSize;
424 static constexpr int kRow2Y = 3*kBlockSpacing + 2*kBlockSize;
425 static constexpr int kRow3Y = 4*kBlockSpacing + 3*kBlockSize;
426 static constexpr int kRow4Y = 5*kBlockSpacing + 4*kBlockSize;
428 static constexpr int kSmallSize = 6;
429 static constexpr int kMaxTileSize = 32;
431 TestPixels fBigTestPixels;
432 TestPixels fSmallTestPixels;
434 sk_sp<SkShader> fShader;
438 typedef GM INHERITED;
442 DEF_GM( return new BleedGM(kUseBitmap_BleedTest); )
443 DEF_GM( return new BleedGM(kUseImage_BleedTest); )
444 DEF_GM( return new BleedGM(kUseAlphaBitmap_BleedTest); )
445 DEF_GM( return new BleedGM(kUseAlphaImage_BleedTest); )
446 DEF_GM( return new BleedGM(kUseAlphaBitmapShader_BleedTest); )
447 DEF_GM( return new BleedGM(kUseAlphaImageShader_BleedTest); )
449 ///////////////////////////////////////////////////////////////////////////////////////////////////
450 #include "SkSurface.h"
452 sk_sp<SkSurface> make_surface(SkCanvas* canvas, const SkImageInfo& info) {
453 auto surface = canvas->makeSurface(info);
455 surface = SkSurface::MakeRaster(info);
460 // Construct an image and return the inner "src" rect. Build the image such that the interior is
461 // blue, with a margin of blue (2px) but then an outer margin of red.
463 // Show that kFast_SrcRectConstraint sees even the red margin (due to mipmapping) when the image
464 // is scaled down far enough.
466 static sk_sp<SkImage> make_image(SkCanvas* canvas, SkRect* srcR) {
467 // Intentially making the size a power of 2 to avoid the noise from how different GPUs will
468 // produce different mipmap filtering when we have an odd sized texture.
469 const int N = 10 + 2 + 8 + 2 + 10;
470 SkImageInfo info = SkImageInfo::MakeN32Premul(N, N);
471 auto surface = make_surface(canvas, info);
472 SkCanvas* c = surface->getCanvas();
473 SkRect r = SkRect::MakeIWH(info.width(), info.height());
476 paint.setColor(SK_ColorRED);
477 c->drawRect(r, paint);
479 paint.setColor(SK_ColorBLUE);
480 c->drawRect(r, paint);
482 *srcR = r.makeInset(2, 2);
483 return surface->makeImageSnapshot();
486 DEF_SIMPLE_GM(bleed_downscale, canvas, 360, 240) {
488 sk_sp<SkImage> img = make_image(canvas, &src);
491 canvas->translate(10, 10);
493 const SkCanvas::SrcRectConstraint constraints[] = {
494 SkCanvas::kStrict_SrcRectConstraint, SkCanvas::kFast_SrcRectConstraint
496 const SkFilterQuality qualities[] = {
497 kNone_SkFilterQuality, kLow_SkFilterQuality, kMedium_SkFilterQuality
499 for (auto constraint : constraints) {
501 for (auto quality : qualities) {
502 paint.setFilterQuality(quality);
503 auto surf = make_surface(canvas, SkImageInfo::MakeN32Premul(1, 1));
504 surf->getCanvas()->drawImageRect(img, src, SkRect::MakeWH(1, 1), &paint, constraint);
505 // now blow up the 1 pixel result
506 canvas->drawImageRect(surf->makeImageSnapshot(), SkRect::MakeWH(100, 100), nullptr);
507 canvas->translate(120, 0);
510 canvas->translate(0, 120);