BlurTest.cpp revision fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68e
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 "SkBlurMask.h"
9#include "SkBlurMaskFilter.h"
10#include "SkCanvas.h"
11#include "SkMath.h"
12#include "SkPaint.h"
13#include "Test.h"
14
15#if SK_SUPPORT_GPU
16#include "GrContextFactory.h"
17#include "SkGpuDevice.h"
18#endif
19
20#define WRITE_CSV 0
21
22///////////////////////////////////////////////////////////////////////////////
23
24#define ILLEGAL_MODE    ((SkXfermode::Mode)-1)
25
26static const int outset = 100;
27static const SkColor bgColor = SK_ColorWHITE;
28static const int strokeWidth = 4;
29
30static void create(SkBitmap* bm, const SkIRect& bound) {
31    bm->allocN32Pixels(bound.width(), bound.height());
32}
33
34static void drawBG(SkCanvas* canvas) {
35    canvas->drawColor(bgColor);
36}
37
38
39struct 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.
47static void draw50x50Rect(SkPath* path) {
48    path->addRect(0, 0, SkIntToScalar(50), SkIntToScalar(50));
49}
50
51//Tests
52static 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 */
68static 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
98static 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);
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);
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'.
159static 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.
192static 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'.
200static 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'
208static 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
224static 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
244static void readback(SkCanvas* canvas, int* result, int resultCount) {
245    SkBitmap readback;
246    readback.allocN32Pixels(resultCount, 30);
247
248    SkIRect readBackRect = { 0, 0, resultCount, 30 };
249
250    canvas->readPixels(readBackRect, &readback);
251
252    readback.lockPixels();
253    SkPMColor* pixels = (SkPMColor*) readback.getAddr32(0, 15);
254
255    for (int i = 0; i < resultCount; ++i) {
256        result[i] = SkColorGetR(pixels[i]);
257    }
258}
259
260// Draw a blurred version of the provided path.
261// Return the right half of the middle row in 'result'.
262static void cpu_blur_path(const SkPath& path, SkScalar gaussianSigma,
263                          int* result, int resultCount) {
264
265    SkBitmap bitmap;
266    bitmap.allocN32Pixels(resultCount, 30);
267    SkCanvas canvas(bitmap);
268
269    blur_path(&canvas, path, gaussianSigma);
270    readback(&canvas, result, resultCount);
271}
272
273#if SK_SUPPORT_GPU
274static bool gpu_blur_path(GrContextFactory* factory, const SkPath& path,
275                          SkScalar gaussianSigma,
276                          int* result, int resultCount) {
277
278    GrContext* grContext = factory->get(GrContextFactory::kNative_GLContextType);
279    if (NULL == grContext) {
280        return false;
281    }
282
283    GrTextureDesc desc;
284    desc.fConfig = kSkia8888_GrPixelConfig;
285    desc.fFlags = kRenderTarget_GrTextureFlagBit;
286    desc.fWidth = resultCount;
287    desc.fHeight = 30;
288    desc.fSampleCnt = 0;
289
290    SkAutoTUnref<GrTexture> texture(grContext->createUncachedTexture(desc, NULL, 0));
291    SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice, (grContext, texture.get())));
292    SkCanvas canvas(device.get());
293
294    blur_path(&canvas, path, gaussianSigma);
295    readback(&canvas, result, resultCount);
296    return true;
297}
298#endif
299
300#if WRITE_CSV
301static void write_as_csv(const char* label, SkScalar scale, int* data, int count) {
302    SkDebugf("%s_%.2f,", label, scale);
303    for (int i = 0; i < count-1; ++i) {
304        SkDebugf("%d,", data[i]);
305    }
306    SkDebugf("%d\n", data[count-1]);
307}
308#endif
309
310static bool match(int* first, int* second, int count, int tol) {
311    int delta;
312    for (int i = 0; i < count; ++i) {
313        delta = first[i] - second[i];
314        if (delta > tol || delta < -tol) {
315            return false;
316        }
317    }
318
319    return true;
320}
321
322// Test out the normal blur style with a wide range of sigmas
323static void test_sigma_range(skiatest::Reporter* reporter, GrContextFactory* factory) {
324
325    static const int kSize = 100;
326
327    // The geometry is offset a smidge to trigger:
328    // https://code.google.com/p/chromium/issues/detail?id=282418
329    SkPath rectPath;
330    rectPath.addRect(0.3f, 0.3f, 100.3f, 100.3f);
331
332    SkPoint polyPts[] = {
333        { 0.3f, 0.3f },
334        { 100.3f, 0.3f },
335        { 100.3f, 100.3f },
336        { 0.3f, 100.3f },
337        { 2.3f, 50.3f }     // a little divet to throw off the rect special case
338    };
339    SkPath polyPath;
340    polyPath.addPoly(polyPts, SK_ARRAY_COUNT(polyPts), true);
341
342    int rectSpecialCaseResult[kSize];
343    int generalCaseResult[kSize];
344#if SK_SUPPORT_GPU
345    int gpuResult[kSize];
346#endif
347    int groundTruthResult[kSize];
348    int bruteForce1DResult[kSize];
349
350    SkScalar sigma = 10.0f;
351
352    for (int i = 0; i < 4; ++i, sigma /= 10) {
353
354        cpu_blur_path(rectPath, sigma, rectSpecialCaseResult, kSize);
355        cpu_blur_path(polyPath, sigma, generalCaseResult, kSize);
356#if SK_SUPPORT_GPU
357        bool haveGPUResult = gpu_blur_path(factory, rectPath, sigma, gpuResult, kSize);
358#endif
359        ground_truth_2d(100, 100, sigma, groundTruthResult, kSize);
360        brute_force_1d(-50.0f, 50.0f, sigma, bruteForce1DResult, kSize);
361
362        REPORTER_ASSERT(reporter, match(rectSpecialCaseResult, bruteForce1DResult, kSize, 5));
363        REPORTER_ASSERT(reporter, match(generalCaseResult, bruteForce1DResult, kSize, 15));
364#if SK_SUPPORT_GPU
365        if (haveGPUResult) {
366            // 1 works everywhere but: Ubuntu13 & Nexus4
367            REPORTER_ASSERT(reporter, match(gpuResult, bruteForce1DResult, kSize, 10));
368        }
369#endif
370        REPORTER_ASSERT(reporter, match(groundTruthResult, bruteForce1DResult, kSize, 1));
371
372#if WRITE_CSV
373        write_as_csv("RectSpecialCase", sigma, rectSpecialCaseResult, kSize);
374        write_as_csv("GeneralCase", sigma, generalCaseResult, kSize);
375#if SK_SUPPORT_GPU
376        write_as_csv("GPU", sigma, gpuResult, kSize);
377#endif
378        write_as_csv("GroundTruth2D", sigma, groundTruthResult, kSize);
379        write_as_csv("BruteForce1D", sigma, bruteForce1DResult, kSize);
380#endif
381    }
382}
383
384DEF_GPUTEST(Blur, reporter, factory) {
385    test_blur_drawing(reporter);
386    test_sigma_range(reporter, factory);
387}
388