• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2011 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 "SkBlurMask.h"
9 #include "SkBlurMaskFilter.h"
10 #include "SkBlurDrawLooper.h"
11 #include "SkCanvas.h"
12 #include "SkColorFilter.h"
13 #include "SkEmbossMaskFilter.h"
14 #include "SkLayerDrawLooper.h"
15 #include "SkMath.h"
16 #include "SkPaint.h"
17 #include "SkPath.h"
18 #include "Test.h"
19 
20 #if SK_SUPPORT_GPU
21 #include "GrContextFactory.h"
22 #include "SkGpuDevice.h"
23 #endif
24 
25 #define WRITE_CSV 0
26 
27 ///////////////////////////////////////////////////////////////////////////////
28 
29 #define ILLEGAL_MODE    ((SkXfermode::Mode)-1)
30 
31 static const int outset = 100;
32 static const SkColor bgColor = SK_ColorWHITE;
33 static const int strokeWidth = 4;
34 
create(SkBitmap * bm,const SkIRect & bound)35 static void create(SkBitmap* bm, const SkIRect& bound) {
36     bm->allocN32Pixels(bound.width(), bound.height());
37 }
38 
drawBG(SkCanvas * canvas)39 static void drawBG(SkCanvas* canvas) {
40     canvas->drawColor(bgColor);
41 }
42 
43 
44 struct BlurTest {
45     void (*addPath)(SkPath*);
46     int viewLen;
47     SkIRect views[9];
48 };
49 
50 //Path Draw Procs
51 //Beware that paths themselves my draw differently depending on the clip.
draw50x50Rect(SkPath * path)52 static void draw50x50Rect(SkPath* path) {
53     path->addRect(0, 0, SkIntToScalar(50), SkIntToScalar(50));
54 }
55 
56 //Tests
57 static BlurTest tests[] = {
58     { draw50x50Rect, 3, {
59         //inner half of blur
60         { 0, 0, 50, 50 },
61         //blur, but no path.
62         { 50 + strokeWidth/2, 50 + strokeWidth/2, 100, 100 },
63         //just an edge
64         { 40, strokeWidth, 60, 50 - strokeWidth },
65     }},
66 };
67 
68 /** Assumes that the ref draw was completely inside ref canvas --
69     implies that everything outside is "bgColor".
70     Checks that all overlap is the same and that all non-overlap on the
71     ref is "bgColor".
72  */
compare(const SkBitmap & ref,const SkIRect & iref,const SkBitmap & test,const SkIRect & itest)73 static bool compare(const SkBitmap& ref, const SkIRect& iref,
74                     const SkBitmap& test, const SkIRect& itest)
75 {
76     const int xOff = itest.fLeft - iref.fLeft;
77     const int yOff = itest.fTop - iref.fTop;
78 
79     SkAutoLockPixels alpRef(ref);
80     SkAutoLockPixels alpTest(test);
81 
82     for (int y = 0; y < test.height(); ++y) {
83         for (int x = 0; x < test.width(); ++x) {
84             SkColor testColor = test.getColor(x, y);
85             int refX = x + xOff;
86             int refY = y + yOff;
87             SkColor refColor;
88             if (refX >= 0 && refX < ref.width() &&
89                 refY >= 0 && refY < ref.height())
90             {
91                 refColor = ref.getColor(refX, refY);
92             } else {
93                 refColor = bgColor;
94             }
95             if (refColor != testColor) {
96                 return false;
97             }
98         }
99     }
100     return true;
101 }
102 
DEF_TEST(BlurDrawing,reporter)103 DEF_TEST(BlurDrawing, reporter) {
104     SkPaint paint;
105     paint.setColor(SK_ColorGRAY);
106     paint.setStyle(SkPaint::kStroke_Style);
107     paint.setStrokeWidth(SkIntToScalar(strokeWidth));
108 
109     SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(5));
110     for (int style = 0; style <= kLastEnum_SkBlurStyle; ++style) {
111         SkBlurStyle blurStyle = static_cast<SkBlurStyle>(style);
112 
113         const uint32_t flagPermutations = SkBlurMaskFilter::kAll_BlurFlag;
114         for (uint32_t flags = 0; flags < flagPermutations; ++flags) {
115             paint.setMaskFilter(SkBlurMaskFilter::Make(blurStyle, sigma, flags));
116 
117             for (size_t test = 0; test < SK_ARRAY_COUNT(tests); ++test) {
118                 SkPath path;
119                 tests[test].addPath(&path);
120                 SkPath strokedPath;
121                 paint.getFillPath(path, &strokedPath);
122                 SkRect refBound = strokedPath.getBounds();
123                 SkIRect iref;
124                 refBound.roundOut(&iref);
125                 iref.inset(-outset, -outset);
126                 SkBitmap refBitmap;
127                 create(&refBitmap, iref);
128 
129                 SkCanvas refCanvas(refBitmap);
130                 refCanvas.translate(SkIntToScalar(-iref.fLeft),
131                                     SkIntToScalar(-iref.fTop));
132                 drawBG(&refCanvas);
133                 refCanvas.drawPath(path, paint);
134 
135                 for (int view = 0; view < tests[test].viewLen; ++view) {
136                     SkIRect itest = tests[test].views[view];
137                     SkBitmap testBitmap;
138                     create(&testBitmap, itest);
139 
140                     SkCanvas testCanvas(testBitmap);
141                     testCanvas.translate(SkIntToScalar(-itest.fLeft),
142                                          SkIntToScalar(-itest.fTop));
143                     drawBG(&testCanvas);
144                     testCanvas.drawPath(path, paint);
145 
146                     REPORTER_ASSERT(reporter,
147                         compare(refBitmap, iref, testBitmap, itest));
148                 }
149             }
150         }
151     }
152 }
153 
154 ///////////////////////////////////////////////////////////////////////////////
155 
156 // Use SkBlurMask::BlurGroundTruth to blur a 'width' x 'height' solid
157 // white rect. Return the right half of the middle row in 'result'.
ground_truth_2d(int width,int height,SkScalar sigma,int * result,int resultCount)158 static void ground_truth_2d(int width, int height,
159                             SkScalar sigma,
160                             int* result, int resultCount) {
161     SkMask src, dst;
162 
163     src.fBounds.set(0, 0, width, height);
164     src.fFormat = SkMask::kA8_Format;
165     src.fRowBytes = src.fBounds.width();
166     src.fImage = SkMask::AllocImage(src.computeTotalImageSize());
167 
168     memset(src.fImage, 0xff, src.computeTotalImageSize());
169 
170     if (!SkBlurMask::BlurGroundTruth(sigma, &dst, src, kNormal_SkBlurStyle)) {
171         return;
172     }
173 
174     int midX = dst.fBounds.centerX();
175     int midY = dst.fBounds.centerY();
176     uint8_t* bytes = dst.getAddr8(midX, midY);
177     int i;
178     for (i = 0; i < dst.fBounds.width()-(midX-dst.fBounds.fLeft); ++i) {
179         if (i < resultCount) {
180             result[i] = bytes[i];
181         }
182     }
183     for ( ; i < resultCount; ++i) {
184         result[i] = 0;
185     }
186 
187     SkMask::FreeImage(src.fImage);
188     SkMask::FreeImage(dst.fImage);
189 }
190 
191 // Implement a step function that is 255 between min and max; 0 elsewhere.
step(int x,SkScalar min,SkScalar max)192 static int step(int x, SkScalar min, SkScalar max) {
193     if (min < x && x < max) {
194         return 255;
195     }
196     return 0;
197 }
198 
199 // Implement a Gaussian function with 0 mean and std.dev. of 'sigma'.
gaussian(int x,SkScalar sigma)200 static float gaussian(int x, SkScalar sigma) {
201     float k = SK_Scalar1/(sigma * sqrtf(2.0f*SK_ScalarPI));
202     float exponent = -(x * x) / (2 * sigma * sigma);
203     return k * expf(exponent);
204 }
205 
206 // Perform a brute force convolution of a step function with a Gaussian.
207 // Return the right half in 'result'
brute_force_1d(SkScalar stepMin,SkScalar stepMax,SkScalar gaussianSigma,int * result,int resultCount)208 static void brute_force_1d(SkScalar stepMin, SkScalar stepMax,
209                            SkScalar gaussianSigma,
210                            int* result, int resultCount) {
211 
212     int gaussianRange = SkScalarCeilToInt(10 * gaussianSigma);
213 
214     for (int i = 0; i < resultCount; ++i) {
215         SkScalar sum = 0.0f;
216         for (int j = -gaussianRange; j < gaussianRange; ++j) {
217             sum += gaussian(j, gaussianSigma) * step(i-j, stepMin, stepMax);
218         }
219 
220         result[i] = SkClampMax(SkClampPos(int(sum + 0.5f)), 255);
221     }
222 }
223 
blur_path(SkCanvas * canvas,const SkPath & path,SkScalar gaussianSigma)224 static void blur_path(SkCanvas* canvas, const SkPath& path,
225                       SkScalar gaussianSigma) {
226 
227     SkScalar midX = path.getBounds().centerX();
228     SkScalar midY = path.getBounds().centerY();
229 
230     canvas->translate(-midX, -midY);
231 
232     SkPaint blurPaint;
233     blurPaint.setColor(SK_ColorWHITE);
234     blurPaint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, gaussianSigma,
235                                                    SkBlurMaskFilter::kHighQuality_BlurFlag));
236 
237     canvas->drawColor(SK_ColorBLACK);
238     canvas->drawPath(path, blurPaint);
239 }
240 
241 // Readback the blurred draw results from the canvas
readback(SkCanvas * canvas,int * result,int resultCount)242 static void readback(SkCanvas* canvas, int* result, int resultCount) {
243     SkBitmap readback;
244     readback.allocN32Pixels(resultCount, 30);
245 
246     SkIRect readBackRect = { 0, 0, resultCount, 30 };
247 
248     canvas->readPixels(readBackRect, &readback);
249 
250     readback.lockPixels();
251     SkPMColor* pixels = (SkPMColor*) readback.getAddr32(0, 15);
252 
253     for (int i = 0; i < resultCount; ++i) {
254         result[i] = SkColorGetR(pixels[i]);
255     }
256 }
257 
258 // Draw a blurred version of the provided path.
259 // Return the right half of the middle row in 'result'.
cpu_blur_path(const SkPath & path,SkScalar gaussianSigma,int * result,int resultCount)260 static void cpu_blur_path(const SkPath& path, SkScalar gaussianSigma,
261                           int* result, int resultCount) {
262 
263     SkBitmap bitmap;
264     bitmap.allocN32Pixels(resultCount, 30);
265     SkCanvas canvas(bitmap);
266 
267     blur_path(&canvas, path, gaussianSigma);
268     readback(&canvas, result, resultCount);
269 }
270 
271 #if SK_SUPPORT_GPU
272 #if 0
273 // temporary disable; see below for explanation
274 static bool gpu_blur_path(GrContext* context, const SkPath& path,
275                           SkScalar gaussianSigma,
276                           int* result, int resultCount) {
277     GrSurfaceDesc desc;
278     desc.fConfig = kSkia8888_GrPixelConfig;
279     desc.fFlags = kRenderTarget_GrSurfaceFlag;
280     desc.fWidth = resultCount;
281     desc.fHeight = 30;
282     desc.fSampleCnt = 0;
283 
284     sk_sp<GrTexture> texture(grContext->createTexture(desc, false, nullptr, 0));
285     sk_sp<SkGpuDevice> device(new SkGpuDevice(grContext, texture.get()));
286     SkCanvas canvas(device.get());
287 
288     blur_path(&canvas, path, gaussianSigma);
289     readback(&canvas, result, resultCount);
290     return true;
291 }
292 #endif
293 #endif
294 
295 #if WRITE_CSV
write_as_csv(const char * label,SkScalar scale,int * data,int count)296 static void write_as_csv(const char* label, SkScalar scale, int* data, int count) {
297     SkDebugf("%s_%.2f,", label, scale);
298     for (int i = 0; i < count-1; ++i) {
299         SkDebugf("%d,", data[i]);
300     }
301     SkDebugf("%d\n", data[count-1]);
302 }
303 #endif
304 
match(int * first,int * second,int count,int tol)305 static bool match(int* first, int* second, int count, int tol) {
306     int delta;
307     for (int i = 0; i < count; ++i) {
308         delta = first[i] - second[i];
309         if (delta > tol || delta < -tol) {
310             return false;
311         }
312     }
313 
314     return true;
315 }
316 
317 // Test out the normal blur style with a wide range of sigmas
DEF_TEST(BlurSigmaRange,reporter)318 DEF_TEST(BlurSigmaRange, reporter) {
319     static const int kSize = 100;
320 
321     // The geometry is offset a smidge to trigger:
322     // https://code.google.com/p/chromium/issues/detail?id=282418
323     SkPath rectPath;
324     rectPath.addRect(0.3f, 0.3f, 100.3f, 100.3f);
325 
326     SkPoint polyPts[] = {
327         { 0.3f, 0.3f },
328         { 100.3f, 0.3f },
329         { 100.3f, 100.3f },
330         { 0.3f, 100.3f },
331         { 2.3f, 50.3f }     // a little divet to throw off the rect special case
332     };
333     SkPath polyPath;
334     polyPath.addPoly(polyPts, SK_ARRAY_COUNT(polyPts), true);
335 
336     int rectSpecialCaseResult[kSize];
337     int generalCaseResult[kSize];
338     int groundTruthResult[kSize];
339     int bruteForce1DResult[kSize];
340 
341     SkScalar sigma = 10.0f;
342 
343     for (int i = 0; i < 4; ++i, sigma /= 10) {
344 
345         cpu_blur_path(rectPath, sigma, rectSpecialCaseResult, kSize);
346         cpu_blur_path(polyPath, sigma, generalCaseResult, kSize);
347 
348         ground_truth_2d(100, 100, sigma, groundTruthResult, kSize);
349         brute_force_1d(-50.0f, 50.0f, sigma, bruteForce1DResult, kSize);
350 
351         REPORTER_ASSERT(reporter, match(rectSpecialCaseResult, bruteForce1DResult, kSize, 5));
352         REPORTER_ASSERT(reporter, match(generalCaseResult, bruteForce1DResult, kSize, 15));
353 #if SK_SUPPORT_GPU
354 #if 0
355         int gpuResult[kSize];
356         bool haveGPUResult = gpu_blur_path(context, rectPath, sigma, gpuResult, kSize);
357         // Disabling this test for now -- I don't think it's a legit comparison.
358         // Will continue to investigate this.
359         if (haveGPUResult) {
360             // 1 works everywhere but: Ubuntu13 & Nexus4
361             REPORTER_ASSERT(reporter, match(gpuResult, bruteForce1DResult, kSize, 10));
362         }
363 #endif
364 #endif
365         REPORTER_ASSERT(reporter, match(groundTruthResult, bruteForce1DResult, kSize, 1));
366 
367 #if WRITE_CSV
368         write_as_csv("RectSpecialCase", sigma, rectSpecialCaseResult, kSize);
369         write_as_csv("GeneralCase", sigma, generalCaseResult, kSize);
370 #if SK_SUPPORT_GPU
371         write_as_csv("GPU", sigma, gpuResult, kSize);
372 #endif
373         write_as_csv("GroundTruth2D", sigma, groundTruthResult, kSize);
374         write_as_csv("BruteForce1D", sigma, bruteForce1DResult, kSize);
375 #endif
376     }
377 }
378 
379 ///////////////////////////////////////////////////////////////////////////////////////////
380 
blurMaskFilterFlags_as_quality(uint32_t blurMaskFilterFlags)381 static SkBlurQuality blurMaskFilterFlags_as_quality(uint32_t blurMaskFilterFlags) {
382     return (blurMaskFilterFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ?
383             kHigh_SkBlurQuality : kLow_SkBlurQuality;
384 }
385 
test_blurDrawLooper(skiatest::Reporter * reporter,SkScalar sigma,SkBlurStyle style,uint32_t blurMaskFilterFlags)386 static void test_blurDrawLooper(skiatest::Reporter* reporter, SkScalar sigma,
387                                 SkBlurStyle style, uint32_t blurMaskFilterFlags) {
388     if (kNormal_SkBlurStyle != style) {
389         return; // blurdrawlooper only supports normal
390     }
391 
392     const SkColor color = 0xFF335577;
393     const SkScalar dx = 10;
394     const SkScalar dy = -5;
395     sk_sp<SkDrawLooper> lp(SkBlurDrawLooper::Make(color, sigma, dx, dy));
396     const bool expectSuccess = sigma > 0;
397 
398     if (nullptr == lp) {
399         REPORTER_ASSERT(reporter, sigma <= 0);
400     } else {
401         SkDrawLooper::BlurShadowRec rec;
402         bool success = lp->asABlurShadow(&rec);
403         REPORTER_ASSERT(reporter, success == expectSuccess);
404         if (success) {
405             REPORTER_ASSERT(reporter, rec.fSigma == sigma);
406             REPORTER_ASSERT(reporter, rec.fOffset.x() == dx);
407             REPORTER_ASSERT(reporter, rec.fOffset.y() == dy);
408             REPORTER_ASSERT(reporter, rec.fColor == color);
409             REPORTER_ASSERT(reporter, rec.fStyle == style);
410             REPORTER_ASSERT(reporter, rec.fQuality == kLow_SkBlurQuality);
411         }
412     }
413 }
414 
test_looper(skiatest::Reporter * reporter,sk_sp<SkDrawLooper> lp,SkScalar sigma,SkBlurStyle style,SkBlurQuality quality,bool expectSuccess)415 static void test_looper(skiatest::Reporter* reporter, sk_sp<SkDrawLooper> lp, SkScalar sigma,
416                         SkBlurStyle style, SkBlurQuality quality, bool expectSuccess) {
417     SkDrawLooper::BlurShadowRec rec;
418     bool success = lp->asABlurShadow(&rec);
419     REPORTER_ASSERT(reporter, success == expectSuccess);
420     if (success != expectSuccess) {
421         lp->asABlurShadow(&rec);
422     }
423     if (success) {
424         REPORTER_ASSERT(reporter, rec.fSigma == sigma);
425         REPORTER_ASSERT(reporter, rec.fStyle == style);
426         REPORTER_ASSERT(reporter, rec.fQuality == quality);
427     }
428 }
429 
make_noop_layer(SkLayerDrawLooper::Builder * builder)430 static void make_noop_layer(SkLayerDrawLooper::Builder* builder) {
431     SkLayerDrawLooper::LayerInfo info;
432 
433     info.fPaintBits = 0;
434     info.fColorMode = SkBlendMode::kDst;
435     builder->addLayer(info);
436 }
437 
make_blur_layer(SkLayerDrawLooper::Builder * builder,sk_sp<SkMaskFilter> mf)438 static void make_blur_layer(SkLayerDrawLooper::Builder* builder, sk_sp<SkMaskFilter> mf) {
439     SkLayerDrawLooper::LayerInfo info;
440 
441     info.fPaintBits = SkLayerDrawLooper::kMaskFilter_Bit;
442     info.fColorMode = SkBlendMode::kSrc;
443     SkPaint* paint = builder->addLayer(info);
444     paint->setMaskFilter(std::move(mf));
445 }
446 
test_layerDrawLooper(skiatest::Reporter * reporter,sk_sp<SkMaskFilter> mf,SkScalar sigma,SkBlurStyle style,SkBlurQuality quality,bool expectSuccess)447 static void test_layerDrawLooper(skiatest::Reporter* reporter, sk_sp<SkMaskFilter> mf,
448                                  SkScalar sigma, SkBlurStyle style, SkBlurQuality quality,
449                                  bool expectSuccess) {
450 
451     SkLayerDrawLooper::LayerInfo info;
452     SkLayerDrawLooper::Builder builder;
453 
454     // 1 layer is too few
455     make_noop_layer(&builder);
456     test_looper(reporter, builder.detach(), sigma, style, quality, false);
457 
458     // 2 layers is good, but need blur
459     make_noop_layer(&builder);
460     make_noop_layer(&builder);
461     test_looper(reporter, builder.detach(), sigma, style, quality, false);
462 
463     // 2 layers is just right
464     make_noop_layer(&builder);
465     make_blur_layer(&builder, mf);
466     test_looper(reporter, builder.detach(), sigma, style, quality, expectSuccess);
467 
468     // 3 layers is too many
469     make_noop_layer(&builder);
470     make_blur_layer(&builder, mf);
471     make_noop_layer(&builder);
472     test_looper(reporter, builder.detach(), sigma, style, quality, false);
473 }
474 
DEF_TEST(BlurAsABlur,reporter)475 DEF_TEST(BlurAsABlur, reporter) {
476     const SkBlurStyle styles[] = {
477         kNormal_SkBlurStyle, kSolid_SkBlurStyle, kOuter_SkBlurStyle, kInner_SkBlurStyle
478     };
479     const SkScalar sigmas[] = {
480         // values <= 0 should not success for a blur
481         -1, 0, 0.5f, 2
482     };
483 
484     // Test asABlur for SkBlurMaskFilter
485     //
486     for (size_t i = 0; i < SK_ARRAY_COUNT(styles); ++i) {
487         const SkBlurStyle style = styles[i];
488         for (size_t j = 0; j < SK_ARRAY_COUNT(sigmas); ++j) {
489             const SkScalar sigma = sigmas[j];
490             for (int flags = 0; flags <= SkBlurMaskFilter::kAll_BlurFlag; ++flags) {
491                 const SkBlurQuality quality = blurMaskFilterFlags_as_quality(flags);
492 
493                 sk_sp<SkMaskFilter> mf(SkBlurMaskFilter::Make(style, sigma, flags));
494                 if (nullptr == mf.get()) {
495                     REPORTER_ASSERT(reporter, sigma <= 0);
496                 } else {
497                     REPORTER_ASSERT(reporter, sigma > 0);
498                     SkMaskFilter::BlurRec rec;
499                     bool success = mf->asABlur(&rec);
500                     if (flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
501                         REPORTER_ASSERT(reporter, !success);
502                     } else {
503                         REPORTER_ASSERT(reporter, success);
504                         REPORTER_ASSERT(reporter, rec.fSigma == sigma);
505                         REPORTER_ASSERT(reporter, rec.fStyle == style);
506                         REPORTER_ASSERT(reporter, rec.fQuality == quality);
507                     }
508                     test_layerDrawLooper(reporter, std::move(mf), sigma, style, quality, success);
509                 }
510                 test_blurDrawLooper(reporter, sigma, style, flags);
511             }
512         }
513     }
514 
515     // Test asABlur for SkEmbossMaskFilter -- should never succeed
516     //
517     {
518         SkEmbossMaskFilter::Light light = {
519             { 1, 1, 1 }, 0, 127, 127
520         };
521         for (size_t j = 0; j < SK_ARRAY_COUNT(sigmas); ++j) {
522             const SkScalar sigma = sigmas[j];
523             auto mf(SkEmbossMaskFilter::Make(sigma, light));
524             if (mf) {
525                 SkMaskFilter::BlurRec rec;
526                 bool success = mf->asABlur(&rec);
527                 REPORTER_ASSERT(reporter, !success);
528             }
529         }
530     }
531 }
532 
533 #if SK_SUPPORT_GPU
534 
535 // This exercises the problem discovered in crbug.com/570232. The return value from
536 // SkBlurMask::BoxBlur wasn't being checked in SkBlurMaskFilter.cpp::GrRRectBlurEffect::Create
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SmallBoxBlurBug,reporter,ctxInfo)537 DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SmallBoxBlurBug, reporter, ctxInfo) {
538 
539     SkImageInfo info = SkImageInfo::MakeN32Premul(128, 128);
540     auto surface(SkSurface::MakeRenderTarget(ctxInfo.grContext(), SkBudgeted::kNo, info));
541     SkCanvas* canvas = surface->getCanvas();
542 
543     SkRect r = SkRect::MakeXYWH(10, 10, 100, 100);
544     SkRRect rr = SkRRect::MakeRectXY(r, 10, 10);
545 
546     SkPaint p;
547     p.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, 0.01f));
548 
549     canvas->drawRRect(rr, p);
550 }
551 
552 #endif
553 
554 
DEF_TEST(BlurredRRectNinePatchComputation,reporter)555 DEF_TEST(BlurredRRectNinePatchComputation, reporter) {
556     const SkRect r = SkRect::MakeXYWH(10, 10, 100, 100);
557     static const SkScalar kBlurRad = 3.0f;
558 
559     bool ninePatchable;
560     SkRRect rrectToDraw;
561     SkISize size;
562     SkScalar rectXs[SkBlurMaskFilter::kMaxDivisions], rectYs[SkBlurMaskFilter::kMaxDivisions];
563     SkScalar texXs[SkBlurMaskFilter::kMaxDivisions], texYs[SkBlurMaskFilter::kMaxDivisions];
564     int numX, numY;
565     uint32_t skipMask;
566 
567     // not nine-patchable
568     {
569         SkVector radii[4] = { { 100, 100 }, { 0, 0 }, { 100, 100 }, { 0, 0 } };
570 
571         SkRRect rr;
572         rr.setRectRadii(r, radii);
573 
574         ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(),
575                                                                     kBlurRad, kBlurRad,
576                                                                     &rrectToDraw, &size,
577                                                                     rectXs, rectYs, texXs, texYs,
578                                                                     &numX, &numY, &skipMask);
579         REPORTER_ASSERT(reporter, !ninePatchable);
580     }
581 
582     // simple circular
583     {
584         static const SkScalar kCornerRad = 10.0f;
585         SkRRect rr;
586         rr.setRectXY(r, kCornerRad, kCornerRad);
587 
588         ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(),
589                                                                     kBlurRad, kBlurRad,
590                                                                     &rrectToDraw, &size,
591                                                                     rectXs, rectYs, texXs, texYs,
592                                                                     &numX, &numY, &skipMask);
593 
594         static const SkScalar kAns = 12.0f * kBlurRad + 2.0f * kCornerRad + 1.0f;
595         REPORTER_ASSERT(reporter, ninePatchable);
596         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kAns));
597         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kAns));
598         REPORTER_ASSERT(reporter, 4 == numX && 4 == numY);
599         REPORTER_ASSERT(reporter, !skipMask);
600     }
601 
602     // simple elliptical
603     {
604         static const SkScalar kXCornerRad = 2.0f;
605         static const SkScalar kYCornerRad = 10.0f;
606         SkRRect rr;
607         rr.setRectXY(r, kXCornerRad, kYCornerRad);
608 
609         ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(rr, rr, SkRect::MakeEmpty(),
610                                                                     kBlurRad, kBlurRad,
611                                                                     &rrectToDraw, &size,
612                                                                     rectXs, rectYs, texXs, texYs,
613                                                                     &numX, &numY, &skipMask);
614 
615         static const SkScalar kXAns = 12.0f * kBlurRad + 2.0f * kXCornerRad + 1.0f;
616         static const SkScalar kYAns = 12.0f * kBlurRad + 2.0f * kYCornerRad + 1.0f;
617 
618         REPORTER_ASSERT(reporter, ninePatchable);
619         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kXAns));
620         REPORTER_ASSERT(reporter, SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kYAns));
621         REPORTER_ASSERT(reporter, 4 == numX && 4 == numY);
622         REPORTER_ASSERT(reporter, !skipMask);
623     }
624 
625     // test-out occlusion
626     {
627         static const SkScalar kCornerRad = 10.0f;
628         SkRRect rr;
629         rr.setRectXY(r, kCornerRad, kCornerRad);
630 
631         // The rectXs & rectYs should be { 1, 29, 91, 119 }. Add two more points around each.
632         SkScalar testLocs[] = {
633              -18.0f, -9.0f,
634                1.0f,
635                9.0f, 18.0f,
636               29.0f,
637               39.0f, 49.0f,
638               91.0f,
639              109.0f, 118.0f,
640              119.0f,
641              139.0f, 149.0f
642         };
643 
644         for (int minY = 0; minY < (int)SK_ARRAY_COUNT(testLocs); ++minY) {
645             for (int maxY = minY+1; maxY < (int)SK_ARRAY_COUNT(testLocs); ++maxY) {
646                 for (int minX = 0; minX < (int)SK_ARRAY_COUNT(testLocs); ++minX) {
647                     for (int maxX = minX+1; maxX < (int)SK_ARRAY_COUNT(testLocs); ++maxX) {
648                         SkRect occluder = SkRect::MakeLTRB(testLocs[minX], testLocs[minY],
649                                                            testLocs[maxX], testLocs[maxY]);
650                         if (occluder.isEmpty()) {
651                             continue;
652                         }
653 
654                         ninePatchable = SkBlurMaskFilter::ComputeBlurredRRectParams(
655                                                                     rr, rr, occluder,
656                                                                     kBlurRad, kBlurRad,
657                                                                     &rrectToDraw, &size,
658                                                                     rectXs, rectYs, texXs, texYs,
659                                                                     &numX, &numY, &skipMask);
660 
661                         static const SkScalar kAns = 12.0f * kBlurRad + 2.0f * kCornerRad + 1.0f;
662                         REPORTER_ASSERT(reporter, ninePatchable);
663                         REPORTER_ASSERT(reporter,
664                                             SkScalarNearlyEqual(SkIntToScalar(size.fWidth), kAns));
665                         REPORTER_ASSERT(reporter,
666                                             SkScalarNearlyEqual(SkIntToScalar(size.fHeight), kAns));
667 
668                         int checkBit = 0x1;
669                         for (int y = 0; y < numY-1; ++y) {
670                             for (int x = 0; x < numX-1; ++x) {
671                                 SkRect cell = SkRect::MakeLTRB(rectXs[x], rectYs[y],
672                                                                rectXs[x+1], rectYs[y+1]);
673                                 REPORTER_ASSERT(reporter,
674                                                     SkToBool(skipMask & checkBit) ==
675                                                     (cell.isEmpty() || occluder.contains(cell)));
676 
677                                 REPORTER_ASSERT(reporter, texXs[x] >= 0 &&
678                                                           texXs[x] <= size.fWidth);
679                                 REPORTER_ASSERT(reporter, texYs[y] >= 0 &&
680                                                           texXs[y] <= size.fHeight);
681 
682                                 checkBit <<= 1;
683                             }
684                         }
685                     }
686                 }
687             }
688         }
689 
690 
691     }
692 
693 }
694 
695 ///////////////////////////////////////////////////////////////////////////////////////////
696