Home | History | Annotate | Download | only in tests
      1 
      2 /*
      3  * Copyright 2011 Google Inc.
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 #include "Test.h"
      9 #include "SkBlurMask.h"
     10 #include "SkBlurMaskFilter.h"
     11 #include "SkCanvas.h"
     12 #include "SkMath.h"
     13 #include "SkPaint.h"
     14 #if SK_SUPPORT_GPU
     15 #include "GrContextFactory.h"
     16 #include "SkGpuDevice.h"
     17 #endif
     18 
     19 #define WRITE_CSV 0
     20 
     21 ///////////////////////////////////////////////////////////////////////////////
     22 
     23 #define ILLEGAL_MODE    ((SkXfermode::Mode)-1)
     24 
     25 static const int outset = 100;
     26 static const SkColor bgColor = SK_ColorWHITE;
     27 static const int strokeWidth = 4;
     28 
     29 static void create(SkBitmap* bm, SkIRect bound, SkBitmap::Config config) {
     30     bm->setConfig(config, bound.width(), bound.height());
     31     bm->allocPixels();
     32 }
     33 
     34 static void drawBG(SkCanvas* canvas) {
     35     canvas->drawColor(bgColor);
     36 }
     37 
     38 
     39 struct BlurTest {
     40     void (*addPath)(SkPath*);
     41     int viewLen;
     42     SkIRect views[9];
     43 };
     44 
     45 //Path Draw Procs
     46 //Beware that paths themselves my draw differently depending on the clip.
     47 static void draw50x50Rect(SkPath* path) {
     48     path->addRect(0, 0, SkIntToScalar(50), SkIntToScalar(50));
     49 }
     50 
     51 //Tests
     52 static BlurTest tests[] = {
     53     { draw50x50Rect, 3, {
     54         //inner half of blur
     55         { 0, 0, 50, 50 },
     56         //blur, but no path.
     57         { 50 + strokeWidth/2, 50 + strokeWidth/2, 100, 100 },
     58         //just an edge
     59         { 40, strokeWidth, 60, 50 - strokeWidth },
     60     }},
     61 };
     62 
     63 /** Assumes that the ref draw was completely inside ref canvas --
     64     implies that everything outside is "bgColor".
     65     Checks that all overlap is the same and that all non-overlap on the
     66     ref is "bgColor".
     67  */
     68 static bool compare(const SkBitmap& ref, const SkIRect& iref,
     69                     const SkBitmap& test, const SkIRect& itest)
     70 {
     71     const int xOff = itest.fLeft - iref.fLeft;
     72     const int yOff = itest.fTop - iref.fTop;
     73 
     74     SkAutoLockPixels alpRef(ref);
     75     SkAutoLockPixels alpTest(test);
     76 
     77     for (int y = 0; y < test.height(); ++y) {
     78         for (int x = 0; x < test.width(); ++x) {
     79             SkColor testColor = test.getColor(x, y);
     80             int refX = x + xOff;
     81             int refY = y + yOff;
     82             SkColor refColor;
     83             if (refX >= 0 && refX < ref.width() &&
     84                 refY >= 0 && refY < ref.height())
     85             {
     86                 refColor = ref.getColor(refX, refY);
     87             } else {
     88                 refColor = bgColor;
     89             }
     90             if (refColor != testColor) {
     91                 return false;
     92             }
     93         }
     94     }
     95     return true;
     96 }
     97 
     98 static void test_blur_drawing(skiatest::Reporter* reporter) {
     99 
    100     SkPaint paint;
    101     paint.setColor(SK_ColorGRAY);
    102     paint.setStyle(SkPaint::kStroke_Style);
    103     paint.setStrokeWidth(SkIntToScalar(strokeWidth));
    104 
    105     SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(5));
    106     for (int style = 0; style < SkBlurMaskFilter::kBlurStyleCount; ++style) {
    107         SkBlurMaskFilter::BlurStyle blurStyle =
    108             static_cast<SkBlurMaskFilter::BlurStyle>(style);
    109 
    110         const uint32_t flagPermutations = SkBlurMaskFilter::kAll_BlurFlag;
    111         for (uint32_t flags = 0; flags < flagPermutations; ++flags) {
    112             SkMaskFilter* filter;
    113             filter = SkBlurMaskFilter::Create(blurStyle, sigma, flags);
    114 
    115             paint.setMaskFilter(filter);
    116             filter->unref();
    117 
    118             for (size_t test = 0; test < SK_ARRAY_COUNT(tests); ++test) {
    119                 SkPath path;
    120                 tests[test].addPath(&path);
    121                 SkPath strokedPath;
    122                 paint.getFillPath(path, &strokedPath);
    123                 SkRect refBound = strokedPath.getBounds();
    124                 SkIRect iref;
    125                 refBound.roundOut(&iref);
    126                 iref.inset(-outset, -outset);
    127                 SkBitmap refBitmap;
    128                 create(&refBitmap, iref, SkBitmap::kARGB_8888_Config);
    129 
    130                 SkCanvas refCanvas(refBitmap);
    131                 refCanvas.translate(SkIntToScalar(-iref.fLeft),
    132                                     SkIntToScalar(-iref.fTop));
    133                 drawBG(&refCanvas);
    134                 refCanvas.drawPath(path, paint);
    135 
    136                 for (int view = 0; view < tests[test].viewLen; ++view) {
    137                     SkIRect itest = tests[test].views[view];
    138                     SkBitmap testBitmap;
    139                     create(&testBitmap, itest, SkBitmap::kARGB_8888_Config);
    140 
    141                     SkCanvas testCanvas(testBitmap);
    142                     testCanvas.translate(SkIntToScalar(-itest.fLeft),
    143                                          SkIntToScalar(-itest.fTop));
    144                     drawBG(&testCanvas);
    145                     testCanvas.drawPath(path, paint);
    146 
    147                     REPORTER_ASSERT(reporter,
    148                         compare(refBitmap, iref, testBitmap, itest));
    149                 }
    150             }
    151         }
    152     }
    153 }
    154 
    155 ///////////////////////////////////////////////////////////////////////////////
    156 
    157 // Use SkBlurMask::BlurGroundTruth to blur a 'width' x 'height' solid
    158 // white rect. Return the right half of the middle row in 'result'.
    159 static void ground_truth_2d(int width, int height,
    160                             SkScalar sigma,
    161                             int* result, int resultCount) {
    162     SkMask src, dst;
    163 
    164     src.fBounds.set(0, 0, width, height);
    165     src.fFormat = SkMask::kA8_Format;
    166     src.fRowBytes = src.fBounds.width();
    167     src.fImage = SkMask::AllocImage(src.computeTotalImageSize());
    168 
    169     memset(src.fImage, 0xff, src.computeTotalImageSize());
    170 
    171     dst.fImage = NULL;
    172     SkBlurMask::BlurGroundTruth(sigma, &dst, src, SkBlurMask::kNormal_Style);
    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.
    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'.
    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'
    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 
    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     SkMaskFilter* filter = SkBlurMaskFilter::Create(SkBlurMaskFilter::kNormal_BlurStyle,
    235                                                     gaussianSigma,
    236                                                     SkBlurMaskFilter::kHighQuality_BlurFlag);
    237     blurPaint.setMaskFilter(filter)->unref();
    238 
    239     canvas->drawColor(SK_ColorBLACK);
    240     canvas->drawPath(path, blurPaint);
    241 }
    242 
    243 // Readback the blurred draw results from the canvas
    244 static void readback(SkCanvas* canvas, int* result, int resultCount) {
    245     SkBitmap readback;
    246     readback.setConfig(SkBitmap::kARGB_8888_Config, resultCount, 30);
    247     readback.allocPixels();
    248 
    249     SkIRect readBackRect = { 0, 0, resultCount, 30 };
    250 
    251     canvas->readPixels(readBackRect, &readback);
    252 
    253     readback.lockPixels();
    254     SkPMColor* pixels = (SkPMColor*) readback.getAddr32(0, 15);
    255 
    256     for (int i = 0; i < resultCount; ++i) {
    257         result[i] = SkColorGetR(pixels[i]);
    258     }
    259 }
    260 
    261 // Draw a blurred version of the provided path.
    262 // Return the right half of the middle row in 'result'.
    263 static void cpu_blur_path(const SkPath& path, SkScalar gaussianSigma,
    264                           int* result, int resultCount) {
    265 
    266     SkBitmap bitmap;
    267     bitmap.setConfig(SkBitmap::kARGB_8888_Config, resultCount, 30);
    268     bitmap.allocPixels();
    269     SkCanvas canvas(bitmap);
    270 
    271     blur_path(&canvas, path, gaussianSigma);
    272     readback(&canvas, result, resultCount);
    273 }
    274 
    275 #if SK_SUPPORT_GPU
    276 static bool gpu_blur_path(GrContextFactory* factory, const SkPath& path,
    277                           SkScalar gaussianSigma,
    278                           int* result, int resultCount) {
    279 
    280     GrContext* grContext = factory->get(GrContextFactory::kNative_GLContextType);
    281     if (NULL == grContext) {
    282         return false;
    283     }
    284 
    285     GrTextureDesc desc;
    286     desc.fConfig = kSkia8888_GrPixelConfig;
    287     desc.fFlags = kRenderTarget_GrTextureFlagBit;
    288     desc.fWidth = resultCount;
    289     desc.fHeight = 30;
    290     desc.fSampleCnt = 0;
    291 
    292     SkAutoTUnref<GrTexture> texture(grContext->createUncachedTexture(desc, NULL, 0));
    293     SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice, (grContext, texture.get())));
    294     SkCanvas canvas(device.get());
    295 
    296     blur_path(&canvas, path, gaussianSigma);
    297     readback(&canvas, result, resultCount);
    298     return true;
    299 }
    300 #endif
    301 
    302 #if WRITE_CSV
    303 static void write_as_csv(const char* label, SkScalar scale, int* data, int count) {
    304     SkDebugf("%s_%.2f,", label, scale);
    305     for (int i = 0; i < count-1; ++i) {
    306         SkDebugf("%d,", data[i]);
    307     }
    308     SkDebugf("%d\n", data[count-1]);
    309 }
    310 #endif
    311 
    312 static bool match(int* first, int* second, int count, int tol) {
    313     int delta;
    314     for (int i = 0; i < count; ++i) {
    315         delta = first[i] - second[i];
    316         if (delta > tol || delta < -tol) {
    317             return false;
    318         }
    319     }
    320 
    321     return true;
    322 }
    323 
    324 // Test out the normal blur style with a wide range of sigmas
    325 static void test_sigma_range(skiatest::Reporter* reporter, GrContextFactory* factory) {
    326 
    327     static const int kSize = 100;
    328 
    329     // The geometry is offset a smidge to trigger:
    330     // https://code.google.com/p/chromium/issues/detail?id=282418
    331     SkPath rectPath;
    332     rectPath.addRect(0.3f, 0.3f, 100.3f, 100.3f);
    333 
    334     SkPoint polyPts[] = {
    335         { 0.3f, 0.3f },
    336         { 100.3f, 0.3f },
    337         { 100.3f, 100.3f },
    338         { 0.3f, 100.3f },
    339         { 2.3f, 50.3f }     // a little divet to throw off the rect special case
    340     };
    341     SkPath polyPath;
    342     polyPath.addPoly(polyPts, SK_ARRAY_COUNT(polyPts), true);
    343 
    344     int rectSpecialCaseResult[kSize];
    345     int generalCaseResult[kSize];
    346 #if SK_SUPPORT_GPU
    347     int gpuResult[kSize];
    348 #endif
    349     int groundTruthResult[kSize];
    350     int bruteForce1DResult[kSize];
    351 
    352     SkScalar sigma = 10.0f;
    353 
    354     for (int i = 0; i < 4; ++i, sigma /= 10) {
    355 
    356         cpu_blur_path(rectPath, sigma, rectSpecialCaseResult, kSize);
    357         cpu_blur_path(polyPath, sigma, generalCaseResult, kSize);
    358 #if SK_SUPPORT_GPU
    359         bool haveGPUResult = gpu_blur_path(factory, rectPath, sigma, gpuResult, kSize);
    360 #endif
    361         ground_truth_2d(100, 100, sigma, groundTruthResult, kSize);
    362         brute_force_1d(-50.0f, 50.0f, sigma, bruteForce1DResult, kSize);
    363 
    364         REPORTER_ASSERT(reporter, match(rectSpecialCaseResult, bruteForce1DResult, kSize, 5));
    365         REPORTER_ASSERT(reporter, match(generalCaseResult, bruteForce1DResult, kSize, 15));
    366 #if SK_SUPPORT_GPU
    367         if (haveGPUResult) {
    368             // 1 works everywhere but: Ubuntu13 & Nexus4
    369             REPORTER_ASSERT(reporter, match(gpuResult, bruteForce1DResult, kSize, 10));
    370         }
    371 #endif
    372         REPORTER_ASSERT(reporter, match(groundTruthResult, bruteForce1DResult, kSize, 1));
    373 
    374 #if WRITE_CSV
    375         write_as_csv("RectSpecialCase", sigma, rectSpecialCaseResult, kSize);
    376         write_as_csv("GeneralCase", sigma, generalCaseResult, kSize);
    377 #if SK_SUPPORT_GPU
    378         write_as_csv("GPU", sigma, gpuResult, kSize);
    379 #endif
    380         write_as_csv("GroundTruth2D", sigma, groundTruthResult, kSize);
    381         write_as_csv("BruteForce1D", sigma, bruteForce1DResult, kSize);
    382 #endif
    383     }
    384 }
    385 
    386 ///////////////////////////////////////////////////////////////////////////////
    387 
    388 static void test_blur(skiatest::Reporter* reporter, GrContextFactory* factory) {
    389     test_blur_drawing(reporter);
    390     test_sigma_range(reporter, factory);
    391 }
    392 
    393 #include "TestClassDef.h"
    394 DEFINE_GPUTESTCLASS("BlurMaskFilter", BlurTestClass, test_blur)
    395