xref: /aosp_15_r20/external/skia/gm/bleed.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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