blob: 0e0c691327a82ff03137ef18b069c5a301a264b5 [file] [log] [blame]
reed@google.comdb87c962012-11-02 21:11:12 +00001/*
humper@google.coma99a92c2013-02-20 16:42:06 +00002* Copyright 2012 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*/
reed@google.comdb87c962012-11-02 21:11:12 +00007
Brian Salomonb2d5d402019-09-10 10:11:52 -04008#include <cmath>
Mike Kleinc0bd9f92019-04-23 12:05:21 -05009#include "gm/gm.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040010#include "include/core/SkBitmap.h"
11#include "include/core/SkBlurTypes.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050012#include "include/core/SkCanvas.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040013#include "include/core/SkColor.h"
Robert Phillipse19babf2020-04-06 13:57:30 -040014#include "include/core/SkColorFilter.h"
Brian Salomonb2d5d402019-09-10 10:11:52 -040015#include "include/core/SkImage.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#include "include/core/SkMaskFilter.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040017#include "include/core/SkMatrix.h"
18#include "include/core/SkPaint.h"
Mike Reed06d7c9d2020-08-26 12:56:51 -040019#include "include/core/SkPathBuilder.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040020#include "include/core/SkPoint.h"
21#include "include/core/SkRect.h"
22#include "include/core/SkRefCnt.h"
23#include "include/core/SkScalar.h"
24#include "include/core/SkShader.h"
25#include "include/core/SkSize.h"
26#include "include/core/SkString.h"
Brian Salomonb2d5d402019-09-10 10:11:52 -040027#include "include/core/SkSurface.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040028#include "include/core/SkTileMode.h"
29#include "include/core/SkTypes.h"
30#include "include/effects/SkGradientShader.h"
Robert Phillipsb7bfbc22020-07-01 12:55:01 -040031#include "include/gpu/GrRecordingContext.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050032#include "include/private/SkTo.h"
33#include "src/core/SkBlurMask.h"
Ben Wagner6a34f3a2019-05-01 10:59:30 -040034#include "src/core/SkMask.h"
Robert Phillips95c250c2020-06-29 15:36:12 -040035#include "src/gpu/GrRecordingContextPriv.h"
Brian Salomonb2d5d402019-09-10 10:11:52 -040036#include "tools/timer/TimeUtils.h"
reed@google.comdb87c962012-11-02 21:11:12 +000037
Brian Salomon96c8aeb2020-09-23 11:37:13 -040038#include <vector>
39
reed@google.comdb87c962012-11-02 21:11:12 +000040#define STROKE_WIDTH SkIntToScalar(10)
41
42typedef void (*Proc)(SkCanvas*, const SkRect&, const SkPaint&);
43
44static void fill_rect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
45 canvas->drawRect(r, p);
46}
47
reed@google.comdb87c962012-11-02 21:11:12 +000048static void draw_donut(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
Mike Reed06d7c9d2020-08-26 12:56:51 -040049 SkRect rect;
50 SkPathBuilder path;
skia.committer@gmail.com34587162012-11-20 02:01:23 +000051
reed@google.comdb87c962012-11-02 21:11:12 +000052 rect = r;
53 rect.outset(STROKE_WIDTH/2, STROKE_WIDTH/2);
54 path.addRect(rect);
55 rect = r;
56 rect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
skia.committer@gmail.com34587162012-11-20 02:01:23 +000057
reed@google.comdb87c962012-11-02 21:11:12 +000058 path.addRect(rect);
Mike Reed7d34dc72019-11-26 12:17:17 -050059 path.setFillType(SkPathFillType::kEvenOdd);
skia.committer@gmail.com34587162012-11-20 02:01:23 +000060
Mike Reed06d7c9d2020-08-26 12:56:51 -040061 canvas->drawPath(path.detach(), p);
reed@google.com808b70f2012-11-19 16:14:02 +000062}
reed@google.comdb87c962012-11-02 21:11:12 +000063
reed@google.com808b70f2012-11-19 16:14:02 +000064static void draw_donut_skewed(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
Mike Reed06d7c9d2020-08-26 12:56:51 -040065 SkRect rect;
66 SkPathBuilder path;
skia.committer@gmail.com34587162012-11-20 02:01:23 +000067
reed@google.com808b70f2012-11-19 16:14:02 +000068 rect = r;
69 rect.outset(STROKE_WIDTH/2, STROKE_WIDTH/2);
70 path.addRect(rect);
71 rect = r;
72 rect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
skia.committer@gmail.com34587162012-11-20 02:01:23 +000073
reed@google.com808b70f2012-11-19 16:14:02 +000074 rect.offset(7, -7);
skia.committer@gmail.com34587162012-11-20 02:01:23 +000075
reed@google.com808b70f2012-11-19 16:14:02 +000076 path.addRect(rect);
Mike Reed7d34dc72019-11-26 12:17:17 -050077 path.setFillType(SkPathFillType::kEvenOdd);
skia.committer@gmail.com34587162012-11-20 02:01:23 +000078
Mike Reed06d7c9d2020-08-26 12:56:51 -040079 canvas->drawPath(path.detach(), p);
reed@google.comdb87c962012-11-02 21:11:12 +000080}
81
joshualitt341400e2014-12-18 11:54:13 -080082/*
Kevin Lubickbe03ef12021-06-16 15:28:00 -040083 * Spits out an arbitrary gradient to test blur with shader on paint
joshualitt341400e2014-12-18 11:54:13 -080084 */
Robert Phillips09dfc472017-09-13 15:25:47 -040085static sk_sp<SkShader> make_radial() {
joshualitt341400e2014-12-18 11:54:13 -080086 SkPoint pts[2] = {
87 { 0, 0 },
88 { SkIntToScalar(100), SkIntToScalar(100) }
89 };
Mike Reedfae8fce2019-04-03 10:27:45 -040090 SkTileMode tm = SkTileMode::kClamp;
joshualitt341400e2014-12-18 11:54:13 -080091 const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, };
92 const SkScalar pos[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
93 SkMatrix scale;
94 scale.setScale(0.5f, 0.5f);
95 scale.postTranslate(25.f, 25.f);
96 SkPoint center0, center1;
97 center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
98 SkScalarAve(pts[0].fY, pts[1].fY));
99 center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
100 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
reed2ad1aa62016-03-09 09:50:50 -0800101 return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
102 center0, (pts[1].fX - pts[0].fX) / 2,
103 colors, pos, SK_ARRAY_COUNT(colors), tm,
104 0, &scale);
joshualitt341400e2014-12-18 11:54:13 -0800105}
106
reed@google.com53007a22012-11-26 14:39:50 +0000107typedef void (*PaintProc)(SkPaint*, SkScalar width);
108
reed@google.comdb87c962012-11-02 21:11:12 +0000109class BlurRectGM : public skiagm::GM {
reed@google.comdb87c962012-11-02 21:11:12 +0000110public:
Hal Canary594fe852019-07-18 13:35:49 -0400111 BlurRectGM(const char name[], U8CPU alpha) : fName(name), fAlpha(SkToU8(alpha)) {}
reed@google.comdb87c962012-11-02 21:11:12 +0000112
Hal Canary594fe852019-07-18 13:35:49 -0400113private:
114 sk_sp<SkMaskFilter> fMaskFilters[kLastEnum_SkBlurStyle + 1];
115 const char* fName;
116 SkAlpha fAlpha;
117
mtklein36352bf2015-03-25 18:17:31 -0700118 void onOnceBeforeDraw() override {
commit-bot@chromium.orge3964552014-04-28 16:25:35 +0000119 for (int i = 0; i <= kLastEnum_SkBlurStyle; ++i) {
Mike Reed1be1f8d2018-03-14 13:01:17 -0400120 fMaskFilters[i] = SkMaskFilter::MakeBlur((SkBlurStyle)i,
121 SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(STROKE_WIDTH/2)));
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000122 }
123 }
124
Hal Canary594fe852019-07-18 13:35:49 -0400125 SkString onShortName() override { return SkString(fName); }
reed@google.comdb87c962012-11-02 21:11:12 +0000126
Hal Canary594fe852019-07-18 13:35:49 -0400127 SkISize onISize() override { return {860, 820}; }
reed@google.comdb87c962012-11-02 21:11:12 +0000128
mtklein36352bf2015-03-25 18:17:31 -0700129 void onDraw(SkCanvas* canvas) override {
reed@google.comdb87c962012-11-02 21:11:12 +0000130 canvas->translate(STROKE_WIDTH*3/2, STROKE_WIDTH*3/2);
131
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000132 SkRect r = { 0, 0, 100, 50 };
133 SkScalar scales[] = { SK_Scalar1, 0.6f };
skia.committer@gmail.com8ccf5902012-11-27 02:01:19 +0000134
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000135 for (size_t s = 0; s < SK_ARRAY_COUNT(scales); ++s) {
136 canvas->save();
137 for (size_t f = 0; f < SK_ARRAY_COUNT(fMaskFilters); ++f) {
138 SkPaint paint;
139 paint.setMaskFilter(fMaskFilters[f]);
140 paint.setAlpha(fAlpha);
141
joshualitt341400e2014-12-18 11:54:13 -0800142 SkPaint paintWithRadial = paint;
Robert Phillips09dfc472017-09-13 15:25:47 -0400143 paintWithRadial.setShader(make_radial());
joshualitt341400e2014-12-18 11:54:13 -0800144
mtkleindbfd7ab2016-09-01 11:24:54 -0700145 constexpr Proc procs[] = {
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000146 fill_rect, draw_donut, draw_donut_skewed
147 };
148
149 canvas->save();
150 canvas->scale(scales[s], scales[s]);
151 this->drawProcs(canvas, r, paint, false, procs, SK_ARRAY_COUNT(procs));
152 canvas->translate(r.width() * 4/3, 0);
joshualitt341400e2014-12-18 11:54:13 -0800153 this->drawProcs(canvas, r, paintWithRadial, false, procs, SK_ARRAY_COUNT(procs));
154 canvas->translate(r.width() * 4/3, 0);
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000155 this->drawProcs(canvas, r, paint, true, procs, SK_ARRAY_COUNT(procs));
joshualitt341400e2014-12-18 11:54:13 -0800156 canvas->translate(r.width() * 4/3, 0);
157 this->drawProcs(canvas, r, paintWithRadial, true, procs, SK_ARRAY_COUNT(procs));
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000158 canvas->restore();
159
160 canvas->translate(0, SK_ARRAY_COUNT(procs) * r.height() * 4/3 * scales[s]);
161 }
162 canvas->restore();
joshualitt341400e2014-12-18 11:54:13 -0800163 canvas->translate(4 * r.width() * 4/3 * scales[s], 0);
reed@google.com53007a22012-11-26 14:39:50 +0000164 }
reed@google.comdb87c962012-11-02 21:11:12 +0000165 }
166
reed@google.comdb87c962012-11-02 21:11:12 +0000167 void drawProcs(SkCanvas* canvas, const SkRect& r, const SkPaint& paint,
168 bool doClip, const Proc procs[], size_t procsCount) {
169 SkAutoCanvasRestore acr(canvas, true);
170 for (size_t i = 0; i < procsCount; ++i) {
171 if (doClip) {
172 SkRect clipRect(r);
173 clipRect.inset(STROKE_WIDTH/2, STROKE_WIDTH/2);
174 canvas->save();
175 canvas->clipRect(r);
176 }
177 procs[i](canvas, r, paint);
178 if (doClip) {
179 canvas->restore();
180 }
181 canvas->translate(0, r.height() * 4/3);
182 }
183 }
reed@google.comdb87c962012-11-02 21:11:12 +0000184};
185
halcanary2a243382015-09-09 08:16:41 -0700186DEF_SIMPLE_GM(blurrect_gallery, canvas, 1200, 1024) {
187 const int fGMWidth = 1200;
188 const int fPadding = 10;
189 const int fMargin = 100;
commit-bot@chromium.org3c1594a2014-05-28 21:52:12 +0000190
commit-bot@chromium.org3c1594a2014-05-28 21:52:12 +0000191 const int widths[] = {25, 5, 5, 100, 150, 25};
192 const int heights[] = {100, 100, 5, 25, 150, 25};
193 const SkBlurStyle styles[] = {kNormal_SkBlurStyle, kInner_SkBlurStyle, kOuter_SkBlurStyle};
194 const float radii[] = {20, 5, 10};
195
196 canvas->translate(50,20);
197
198 int cur_x = 0;
199 int cur_y = 0;
200
201 int max_height = 0;
202
203 for (size_t i = 0 ; i < SK_ARRAY_COUNT(widths) ; i++) {
204 int width = widths[i];
205 int height = heights[i];
206 SkRect r;
207 r.setWH(SkIntToScalar(width), SkIntToScalar(height));
208 SkAutoCanvasRestore autoRestore(canvas, true);
209
210 for (size_t j = 0 ; j < SK_ARRAY_COUNT(radii) ; j++) {
211 float radius = radii[j];
212 for (size_t k = 0 ; k < SK_ARRAY_COUNT(styles) ; k++) {
213 SkBlurStyle style = styles[k];
214
215 SkMask mask;
robertphillipse80eb922015-12-17 11:33:12 -0800216 if (!SkBlurMask::BlurRect(SkBlurMask::ConvertRadiusToSigma(radius),
217 &mask, r, style)) {
218 continue;
219 }
commit-bot@chromium.org3c1594a2014-05-28 21:52:12 +0000220
221 SkAutoMaskFreeImage amfi(mask.fImage);
222
223 SkBitmap bm;
224 bm.installMaskPixels(mask);
225
226 if (cur_x + bm.width() >= fGMWidth - fMargin) {
227 cur_x = 0;
228 cur_y += max_height + fPadding;
229 max_height = 0;
230 }
231
232 canvas->save();
commit-bot@chromium.org793ddd92014-05-28 22:42:31 +0000233 canvas->translate((SkScalar)cur_x, (SkScalar)cur_y);
commit-bot@chromium.org3c1594a2014-05-28 21:52:12 +0000234 canvas->translate(-(bm.width() - r.width())/2, -(bm.height()-r.height())/2);
Mike Reed607a3822021-01-24 19:49:21 -0500235 canvas->drawImage(bm.asImage(), 0.f, 0.f);
commit-bot@chromium.org3c1594a2014-05-28 21:52:12 +0000236 canvas->restore();
237
238 cur_x += bm.width() + fPadding;
239 if (bm.height() > max_height)
240 max_height = bm.height();
241 }
242 }
243 }
halcanary2a243382015-09-09 08:16:41 -0700244}
humper@google.com7c7292c2013-01-04 20:29:03 +0000245
Brian Salomonb2d5d402019-09-10 10:11:52 -0400246namespace skiagm {
247
248// Compares actual blur rects with reference masks created by the GM. Animates sigma in viewer.
249class BlurRectCompareGM : public GM {
250protected:
251 SkString onShortName() override { return SkString("blurrect_compare"); }
252
253 SkISize onISize() override { return {900, 1220}; }
254
255 void onOnceBeforeDraw() override { this->prepareReferenceMasks(); }
256
Brian Salomon78be3eb2019-09-10 13:37:23 -0400257 DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
Brian Salomon4fe30e12019-09-10 17:12:58 -0400258 if (canvas->imageInfo().colorType() == kUnknown_SkColorType ||
Robert Phillipsf8f45d92020-07-01 11:11:18 -0400259 (canvas->recordingContext() && !canvas->recordingContext()->asDirectContext())) {
Brian Salomon78be3eb2019-09-10 13:37:23 -0400260 *errorMsg = "Not supported when recording, relies on canvas->makeSurface()";
261 return DrawResult::kSkip;
262 }
Robert Phillips95c250c2020-06-29 15:36:12 -0400263 int32_t ctxID = canvas->recordingContext() ? canvas->recordingContext()->priv().contextID()
264 : 0;
Brian Salomonb2d5d402019-09-10 10:11:52 -0400265 if (fRecalcMasksForAnimation || !fActualMasks[0][0][0] || ctxID != fLastContextUniqueID) {
266 if (fRecalcMasksForAnimation) {
267 // Sigma is changing so references must also be recalculated.
268 this->prepareReferenceMasks();
269 }
270 this->prepareActualMasks(canvas);
271 this->prepareMaskDifferences(canvas);
272 fLastContextUniqueID = ctxID;
273 fRecalcMasksForAnimation = false;
274 }
275 canvas->clear(SK_ColorBLACK);
276 static constexpr float kMargin = 30;
277 float totalW = 0;
278 for (auto w : kSizes) {
279 totalW += w + kMargin;
280 }
281 canvas->translate(kMargin, kMargin);
282 for (int mode = 0; mode < 3; ++mode) {
283 canvas->save();
284 for (size_t sigmaIdx = 0; sigmaIdx < kNumSigmas; ++sigmaIdx) {
285 auto sigma = kSigmas[sigmaIdx] + fSigmaAnimationBoost;
286 for (size_t heightIdx = 0; heightIdx < kNumSizes; ++heightIdx) {
287 auto h = kSizes[heightIdx];
288 canvas->save();
289 for (size_t widthIdx = 0; widthIdx < kNumSizes; ++widthIdx) {
290 auto w = kSizes[widthIdx];
291 SkPaint paint;
292 paint.setColor(SK_ColorWHITE);
293 SkImage* img;
294 switch (mode) {
295 case 0:
296 img = fReferenceMasks[sigmaIdx][heightIdx][widthIdx].get();
297 break;
298 case 1:
299 img = fActualMasks[sigmaIdx][heightIdx][widthIdx].get();
300 break;
301 case 2:
302 img = fMaskDifferences[sigmaIdx][heightIdx][widthIdx].get();
303 // The error images are opaque, use kPlus so they are additive if
304 // the overlap between test cases.
305 paint.setBlendMode(SkBlendMode::kPlus);
306 break;
307 }
308 auto pad = PadForSigma(sigma);
Mike Reedd396cd52021-01-23 21:14:47 -0500309 canvas->drawImage(img, -pad, -pad, SkSamplingOptions(), &paint);
Brian Salomonb2d5d402019-09-10 10:11:52 -0400310#if 0 // Uncomment to hairline stroke around blurred rect in red on top of the blur result.
311 // The rect is defined at integer coords. We inset by 1/2 pixel so our stroke lies on top
312 // of the edge pixels.
313 SkPaint stroke;
314 stroke.setColor(SK_ColorRED);
315 stroke.setStrokeWidth(0.f);
316 stroke.setStyle(SkPaint::kStroke_Style);
317 canvas->drawRect(SkRect::MakeWH(w, h).makeInset(0.5, 0.5), stroke);
318#endif
319 canvas->translate(w + kMargin, 0.f);
320 }
321 canvas->restore();
322 canvas->translate(0, h + kMargin);
323 }
324 }
325 canvas->restore();
326 canvas->translate(totalW + 2 * kMargin, 0);
327 }
Brian Salomon78be3eb2019-09-10 13:37:23 -0400328 return DrawResult::kOk;
Brian Salomonb2d5d402019-09-10 10:11:52 -0400329 }
330 bool onAnimate(double nanos) override {
331 fSigmaAnimationBoost = TimeUtils::SineWave(nanos, 5, 2.5f, 0.f, 2.f);
332 fRecalcMasksForAnimation = true;
333 return true;
334 }
335
336private:
337 void prepareReferenceMasks() {
338 auto create_reference_mask = [](int w, int h, float sigma, int numSubpixels) {
339 int pad = PadForSigma(sigma);
340 int maskW = w + 2 * pad;
341 int maskH = h + 2 * pad;
342 // We'll do all our calculations at subpixel resolution, so adjust params
343 w *= numSubpixels;
344 h *= numSubpixels;
345 sigma *= numSubpixels;
346 auto scale = SK_ScalarRoot2Over2 / sigma;
347 auto def_integral_approx = [scale](float a, float b) {
348 return 0.5f * (std::erf(b * scale) - std::erf(a * scale));
349 };
350 // Do the x-pass. Above/below rect are rows of zero. All rows that intersect the rect
351 // are the same. The row is calculated and stored at subpixel resolution.
352 SkASSERT(!(numSubpixels & 0b1));
353 std::unique_ptr<float[]> row(new float[maskW * numSubpixels]);
354 for (int col = 0; col < maskW * numSubpixels; ++col) {
355 // Compute distance to rect left in subpixel units
356 float ldiff = numSubpixels * pad - (col + 0.5f);
357 float rdiff = ldiff + w;
358 row[col] = def_integral_approx(ldiff, rdiff);
359 }
360 // y-pass
361 SkBitmap bmp;
362 bmp.allocPixels(SkImageInfo::MakeA8(maskW, maskH));
363 std::unique_ptr<float[]> accums(new float[maskW]);
364 const float accumScale = 1.f / (numSubpixels * numSubpixels);
365 for (int y = 0; y < maskH; ++y) {
366 // Initialize subpixel accumulation buffer for this row.
367 std::fill_n(accums.get(), maskW, 0);
368 for (int ys = 0; ys < numSubpixels; ++ys) {
369 // At each subpixel we want to integrate over the kernel centered at the
370 // subpixel multiplied by the x-pass. The x-pass is zero above and below the
371 // rect and constant valued from rect top to rect bottom. So we can get the
372 // integral of just the kernel from rect top to rect bottom and multiply by
373 // the single x-pass value from our precomputed row.
374 float tdiff = numSubpixels * pad - (y * numSubpixels + ys + 0.5f);
375 float bdiff = tdiff + h;
376 auto w = def_integral_approx(tdiff, bdiff);
377 for (int x = 0; x < maskW; ++x) {
378 for (int xs = 0; xs < numSubpixels; ++xs) {
379 int rowIdx = x * numSubpixels + xs;
380 accums[x] += w * row[rowIdx];
381 }
382 }
383 }
384 for (int x = 0; x < maskW; ++x) {
385 auto result = accums[x] * accumScale;
386 *bmp.getAddr8(x, y) = SkToU8(sk_float_round2int(255.f * result));
387 }
388 }
Mike Reedac9f0c92020-12-23 10:11:33 -0500389 return bmp.asImage();
Brian Salomonb2d5d402019-09-10 10:11:52 -0400390 };
391
392 // Number of times to subsample (in both X and Y). If fRecalcMasksForAnimation is true
393 // then we're animating, don't subsample as much to keep fps higher.
394 const int numSubpixels = fRecalcMasksForAnimation ? 2 : 8;
395
396 for (size_t sigmaIdx = 0; sigmaIdx < kNumSigmas; ++sigmaIdx) {
397 auto sigma = kSigmas[sigmaIdx] + fSigmaAnimationBoost;
398 for (size_t heightIdx = 0; heightIdx < kNumSizes; ++heightIdx) {
399 auto h = kSizes[heightIdx];
400 for (size_t widthIdx = 0; widthIdx < kNumSizes; ++widthIdx) {
401 auto w = kSizes[widthIdx];
402 fReferenceMasks[sigmaIdx][heightIdx][widthIdx] =
403 create_reference_mask(w, h, sigma, numSubpixels);
404 }
405 }
406 }
407 }
408
409 void prepareActualMasks(SkCanvas* canvas) {
410 for (size_t sigmaIdx = 0; sigmaIdx < kNumSigmas; ++sigmaIdx) {
411 auto sigma = kSigmas[sigmaIdx] + fSigmaAnimationBoost;
412 for (size_t heightIdx = 0; heightIdx < kNumSizes; ++heightIdx) {
413 auto h = kSizes[heightIdx];
414 for (size_t widthIdx = 0; widthIdx < kNumSizes; ++widthIdx) {
415 auto w = kSizes[widthIdx];
416 auto pad = PadForSigma(sigma);
417 auto ii = SkImageInfo::MakeA8(w + 2 * pad, h + 2 * pad);
418 auto surf = canvas->makeSurface(ii);
419 if (!surf) {
Brian Salomon032cf122019-09-11 18:05:36 -0400420 // Some GPUs don't have renderable A8 :(
421 surf = canvas->makeSurface(ii.makeColorType(kRGBA_8888_SkColorType));
422 if (!surf) {
423 return;
424 }
Brian Salomonb2d5d402019-09-10 10:11:52 -0400425 }
426 auto rect = SkRect::MakeXYWH(pad, pad, w, h);
427 SkPaint paint;
Brian Salomondb47b162019-09-18 11:17:09 -0400428 // Color doesn't matter if we're rendering to A8 but does if we promoted to
429 // RGBA above.
430 paint.setColor(SK_ColorWHITE);
Brian Salomonb2d5d402019-09-10 10:11:52 -0400431 paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma));
432 surf->getCanvas()->drawRect(rect, paint);
433 fActualMasks[sigmaIdx][heightIdx][widthIdx] = surf->makeImageSnapshot();
434 }
435 }
436 }
437 }
438
439 void prepareMaskDifferences(SkCanvas* canvas) {
440 for (size_t sigmaIdx = 0; sigmaIdx < kNumSigmas; ++sigmaIdx) {
441 for (size_t heightIdx = 0; heightIdx < kNumSizes; ++heightIdx) {
442 for (size_t widthIdx = 0; widthIdx < kNumSizes; ++widthIdx) {
443 const auto& r = fReferenceMasks[sigmaIdx][heightIdx][widthIdx];
444 const auto& a = fActualMasks[sigmaIdx][heightIdx][widthIdx];
Brian Salomon96c8aeb2020-09-23 11:37:13 -0400445 auto& d = fMaskDifferences[sigmaIdx][heightIdx][widthIdx];
Brian Salomonb2d5d402019-09-10 10:11:52 -0400446 // The actual image might not be present if we're on an abandoned GrContext.
447 if (!a) {
448 d.reset();
449 continue;
450 }
Brian Salomon96c8aeb2020-09-23 11:37:13 -0400451 SkASSERT(r->width() == a->width());
Brian Salomonb2d5d402019-09-10 10:11:52 -0400452 SkASSERT(r->height() == a->height());
453 auto ii = SkImageInfo::Make(r->width(), r->height(),
454 kRGBA_8888_SkColorType, kPremul_SkAlphaType);
455 auto surf = canvas->makeSurface(ii);
456 if (!surf) {
457 return;
458 }
459 // We visualize the difference by turning both the alpha masks into opaque green
460 // images (where alpha becomes the green channel) and then perform a
461 // SkBlendMode::kDifference between them.
462 SkPaint filterPaint;
463 filterPaint.setColor(SK_ColorWHITE);
464 // Actually 8 * alpha becomes green to really highlight differences.
465 static constexpr float kGreenifyM[] = {0, 0, 0, 0, 0,
466 0, 0, 0, 8, 0,
467 0, 0, 0, 0, 0,
468 0, 0, 0, 0, 1};
469 auto greenifyCF = SkColorFilters::Matrix(kGreenifyM);
470 SkPaint paint;
471 paint.setBlendMode(SkBlendMode::kSrc);
472 paint.setColorFilter(std::move(greenifyCF));
Mike Reedd396cd52021-01-23 21:14:47 -0500473 surf->getCanvas()->drawImage(a, 0, 0, SkSamplingOptions(), &paint);
Brian Salomonb2d5d402019-09-10 10:11:52 -0400474 paint.setBlendMode(SkBlendMode::kDifference);
Mike Reedd396cd52021-01-23 21:14:47 -0500475 surf->getCanvas()->drawImage(r, 0, 0, SkSamplingOptions(), &paint);
Brian Salomonb2d5d402019-09-10 10:11:52 -0400476 d = surf->makeImageSnapshot();
477 }
478 }
479 }
480 }
481
482 // Per side padding around mask images for a sigma. Make this overly generous to ensure bugs
483 // related to big blurs are fully visible.
484 static int PadForSigma(float sigma) { return sk_float_ceil2int(4 * sigma); }
485
486 static constexpr int kSizes[] = {1, 2, 4, 8, 16, 32};
487 static constexpr float kSigmas[] = {0.5f, 1.2f, 2.3f, 3.9f, 7.4f};
488 static constexpr size_t kNumSizes = SK_ARRAY_COUNT(kSizes);
489 static constexpr size_t kNumSigmas = SK_ARRAY_COUNT(kSigmas);
490
491 sk_sp<SkImage> fReferenceMasks[kNumSigmas][kNumSizes][kNumSizes];
492 sk_sp<SkImage> fActualMasks[kNumSigmas][kNumSizes][kNumSizes];
493 sk_sp<SkImage> fMaskDifferences[kNumSigmas][kNumSizes][kNumSizes];
494 int32_t fLastContextUniqueID;
495 // These are used only when animating.
496 float fSigmaAnimationBoost = 0;
497 bool fRecalcMasksForAnimation = false;
498};
499
Brian Salomonb2d5d402019-09-10 10:11:52 -0400500} // namespace skiagm
501
reed@google.comdb87c962012-11-02 21:11:12 +0000502//////////////////////////////////////////////////////////////////////////////
503
commit-bot@chromium.org7cced562014-01-10 23:10:13 +0000504DEF_GM(return new BlurRectGM("blurrects", 0xFF);)
Brian Salomonb2d5d402019-09-10 10:11:52 -0400505DEF_GM(return new skiagm::BlurRectCompareGM();)
Brian Salomon96c8aeb2020-09-23 11:37:13 -0400506
507//////////////////////////////////////////////////////////////////////////////
508
509DEF_SIMPLE_GM(blur_matrix_rect, canvas, 650, 685) {
510 static constexpr auto kRect = SkRect::MakeWH(14, 60);
511 static constexpr float kSigmas[] = {0.5f, 1.2f, 2.3f, 3.9f, 7.4f};
512 static constexpr size_t kNumSigmas = SK_ARRAY_COUNT(kSigmas);
513
514 const SkPoint c = {kRect.centerX(), kRect.centerY()};
515
516 std::vector<SkMatrix> matrices;
517
518 matrices.push_back(SkMatrix::RotateDeg(4.f, c));
519
520 matrices.push_back(SkMatrix::RotateDeg(63.f, c));
521
522 matrices.push_back(SkMatrix::RotateDeg(30.f, c));
523 matrices.back().preScale(1.1f, .5f);
524
525 matrices.push_back(SkMatrix::RotateDeg(147.f, c));
526 matrices.back().preScale(3.f, .1f);
527
528 SkMatrix mirror;
529 mirror.setAll(0, 1, 0,
530 1, 0, 0,
531 0, 0, 1);
532 matrices.push_back(SkMatrix::Concat(mirror, matrices.back()));
533
534 matrices.push_back(SkMatrix::RotateDeg(197.f, c));
535 matrices.back().preSkew(.3f, -.5f);
536
537 auto bounds = SkRect::MakeEmpty();
538 for (const auto& m : matrices) {
539 SkRect mapped;
540 m.mapRect(&mapped, kRect);
541 bounds.joinNonEmptyArg(mapped.makeSorted());
542 }
543 float blurPad = 2.f*kSigmas[kNumSigmas - 1];
544 bounds.outset(blurPad, blurPad);
545 canvas->translate(-bounds.left(), -bounds.top());
546 for (auto sigma : kSigmas) {
547 SkPaint paint;
548 paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma));
549 canvas->save();
550 for (const auto& m : matrices) {
551 canvas->save();
552 canvas->concat(m);
553 canvas->drawRect(kRect, paint);
554 canvas->restore();
555 canvas->translate(0, bounds.height());
556 }
557 canvas->restore();
558 canvas->translate(bounds.width(), 0);
559 }
560}