Implement bounds traversals for tile and matrix convolution filters.
authorsenorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 29 Apr 2014 15:20:39 +0000 (15:20 +0000)
committersenorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 29 Apr 2014 15:20:39 +0000 (15:20 +0000)
Add a new GM that exercises tiled drawing all pixel-moving filters
(and some non-pixel-moving ones) and compares it against non-tiled
drawing of the same filters. Fixing this test revealed that tile and
matrix convolution filters had no onFilterBounds() traversals
(test-driven development FTW). Tile requires (conservatively) the
bounds to include the whole source rect, since it may end up in the
result. Matrix convolution requires the bounds to be offset by the
kernel size and target.

R=reed@google.com
BUG=skia:

Review URL: https://codereview.chromium.org/258243005

git-svn-id: http://skia.googlecode.com/svn/trunk@14432 2bbb7eff-a529-9590-31e7-b0007b416f81

expectations/gm/ignored-tests.txt
gm/tileimagefilter.cpp
include/effects/SkMatrixConvolutionImageFilter.h
include/effects/SkTileImageFilter.h
src/effects/SkMatrixConvolutionImageFilter.cpp
src/effects/SkTileImageFilter.cpp
tests/ImageFilterTest.cpp

index f02913bc4a00d02fa94daff3fed4d2c8328b1b82..b92e5f77f89726d1a53ec4c52919be997c2634c1 100644 (file)
@@ -60,3 +60,7 @@ peekpixels
 # humper: https://codereview.chromium.org/248613004/
 # Changed the test in a few ways, will need rebaselining.
 simpleblurroundrect
+
+# senorblanco: http://YOUR_CODE_REVIEW_HERE/
+# Added a new test case; reduced the text bitmap size
+tileimagefilter
index d6c8d4cbfc26bca9df18460b0410ff07fc60795c..c7842ee97cd611116614120037a69ca7aa3726e1 100644 (file)
@@ -6,6 +6,8 @@
  */
 
 #include "gm.h"
+#include "SkColorMatrixFilter.h"
+#include "SkColorFilterImageFilter.h"
 #include "SkTileImageFilter.h"
 #include "SkBitmapSource.h"
 
@@ -27,15 +29,15 @@ protected:
     }
 
     void make_bitmap() {
-        fBitmap.allocN32Pixels(80, 80);
+        fBitmap.allocN32Pixels(50, 50);
         SkCanvas canvas(fBitmap);
         canvas.clear(0xFF000000);
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setColor(0xD000D000);
-        paint.setTextSize(SkIntToScalar(96));
+        paint.setTextSize(SkIntToScalar(50));
         const char* str = "e";
-        canvas.drawText(str, strlen(str), SkIntToScalar(15), SkIntToScalar(65), paint);
+        canvas.drawText(str, strlen(str), SkIntToScalar(10), SkIntToScalar(45), paint);
     }
 
     void make_checkerboard() {
@@ -70,7 +72,6 @@ protected:
             fInitialized = true;
         }
         canvas->clear(0x00000000);
-        SkPaint paint;
 
         int x = 0, y = 0;
         for (size_t i = 0; i < 4; i++) {
@@ -88,6 +89,7 @@ protected:
                 SkTileImageFilter::Create(srcRect, dstRect, tileInput));
             canvas->save();
             canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
+            SkPaint paint;
             paint.setImageFilter(filter);
             canvas->drawBitmap(fBitmap, 0, 0, &paint);
             canvas->restore();
@@ -97,6 +99,29 @@ protected:
                 y += bitmap->height() + MARGIN;
             }
         }
+
+        SkScalar matrix[20] = { SK_Scalar1, 0, 0, 0, 0,
+                                0, SK_Scalar1, 0, 0, 0,
+                                0, 0, SK_Scalar1, 0, 0,
+                                0, 0, 0, SK_Scalar1, 0 };
+
+        SkRect srcRect = SkRect::MakeWH(SkIntToScalar(fBitmap.width()),
+                                        SkIntToScalar(fBitmap.height()));
+        SkRect dstRect = SkRect::MakeWH(SkIntToScalar(fBitmap.width() * 2),
+                                        SkIntToScalar(fBitmap.height() * 2));
+        SkAutoTUnref<SkImageFilter> tile(SkTileImageFilter::Create(srcRect, dstRect, NULL));
+        SkAutoTUnref<SkColorFilter> cf(SkColorMatrixFilter::Create(matrix));
+
+        SkAutoTUnref<SkImageFilter> cfif(SkColorFilterImageFilter::Create(cf, tile.get()));
+        SkPaint paint;
+        paint.setImageFilter(cfif);
+        canvas->save();
+        canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
+        canvas->clipRect(dstRect);
+        canvas->saveLayer(&dstRect, &paint);
+        canvas->drawBitmap(fBitmap, 0, 0);
+        canvas->restore();
+        canvas->restore();
     }
 private:
     typedef GM INHERITED;
index 5b02fbaab59bcf1ce4ba4c01524d9df5113ddf06..ee1bfcf3d9853be4aca2d40799f0c1737786e577 100644 (file)
@@ -73,6 +73,8 @@ protected:
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
                                SkBitmap* result, SkIPoint* loc) const SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const SK_OVERRIDE;
+
 
 #if SK_SUPPORT_GPU
     virtual bool asNewEffect(GrEffectRef** effect,
index 6c0fa68f3cd900bcb9ca2ba646a41f0e1aed4ae1..c26ae9aef8561793f22049e6c9208106d7d96ce0 100644 (file)
@@ -26,6 +26,8 @@ public:
 
     virtual bool onFilterImage(Proxy* proxy, const SkBitmap& src, const Context& ctx,
                                SkBitmap* dst, SkIPoint* offset) const SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
+                                SkIRect* dst) const SK_OVERRIDE;
 
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTileImageFilter)
 
index 878cbae795ea1d218ab2a74811cdba69eda04a9c..3c9fc87787799611e07e657708e6c0b3ee6de447 100644 (file)
@@ -306,6 +306,19 @@ bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
     return true;
 }
 
+bool SkMatrixConvolutionImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                                    SkIRect* dst) const {
+    SkIRect bounds = src;
+    bounds.fRight += fKernelSize.width() - 1;
+    bounds.fBottom += fKernelSize.height() - 1;
+    bounds.offset(-fKernelOffset);
+    if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
+        return false;
+    }
+    *dst = bounds;
+    return true;
+}
+
 #if SK_SUPPORT_GPU
 
 ///////////////////////////////////////////////////////////////////////////////
index f3bad76345232436f96318f67b8ed2bca10c6856..73c0a581e98e5ae57c7e3cbcd7c92195a6d33a4c 100644 (file)
@@ -75,6 +75,17 @@ bool SkTileImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
     return true;
 }
 
+bool SkTileImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                       SkIRect* dst) const {
+    SkRect srcRect;
+    ctm.mapRect(&srcRect, fSrcRect);
+    SkIRect srcIRect;
+    srcRect.roundOut(&srcIRect);
+    srcIRect.join(src);
+    *dst = srcIRect;
+    return true;
+}
+
 SkTileImageFilter::SkTileImageFilter(SkReadBuffer& buffer)
   : INHERITED(1, buffer) {
     buffer.readRect(&fSrcRect);
index f968a4e9c5ea216893fcb2dc1cabf18d966f4d88..dac948ef40d3daeb2ca8d756427bd3d490f4b65d 100644 (file)
@@ -17,6 +17,7 @@
 #include "SkDisplacementMapEffect.h"
 #include "SkDropShadowImageFilter.h"
 #include "SkFlattenableBuffers.h"
+#include "SkGradientShader.h"
 #include "SkLightingImageFilter.h"
 #include "SkMatrixConvolutionImageFilter.h"
 #include "SkMatrixImageFilter.h"
@@ -261,6 +262,116 @@ static void test_crop_rects(SkBaseDevice* device, skiatest::Reporter* reporter)
     }
 }
 
+static SkBitmap make_gradient_circle(int width, int height) {
+    SkBitmap bitmap;
+    SkScalar x = SkIntToScalar(width / 2);
+    SkScalar y = SkIntToScalar(height / 2);
+    SkScalar radius = SkMinScalar(x, y) * 0.8f;
+    bitmap.allocN32Pixels(width, height);
+    SkCanvas canvas(bitmap);
+    canvas.clear(0x00000000);
+    SkColor colors[2];
+    colors[0] = SK_ColorWHITE;
+    colors[1] = SK_ColorBLACK;
+    SkAutoTUnref<SkShader> shader(
+        SkGradientShader::CreateRadial(SkPoint::Make(x, y), radius, colors, NULL, 2,
+                                       SkShader::kClamp_TileMode)
+    );
+    SkPaint paint;
+    paint.setShader(shader);
+    canvas.drawCircle(x, y, radius, paint);
+    return bitmap;
+}
+
+DEF_TEST(ImageFilterDrawTiled, reporter) {
+    // Check that all filters when drawn tiled (with subsequent clip rects) exactly
+    // match the same filters drawn with a single full-canvas bitmap draw.
+    // Tests pass by not asserting.
+
+    SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(SK_ColorRED, SkXfermode::kSrcIn_Mode));
+    SkPoint3 location(0, 0, SK_Scalar1);
+    SkPoint3 target(SK_Scalar1, SK_Scalar1, SK_Scalar1);
+    SkScalar kernel[9] = {
+        SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+        SkIntToScalar( 1), SkIntToScalar(-7), SkIntToScalar( 1),
+        SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
+    };
+    SkISize kernelSize = SkISize::Make(3, 3);
+    SkScalar gain = SK_Scalar1, bias = 0;
+
+    SkAutoTUnref<SkImageFilter> gradient_source(SkBitmapSource::Create(make_gradient_circle(64, 64)));
+
+    struct {
+        const char*    fName;
+        SkImageFilter* fFilter;
+    } filters[] = {
+        { "color filter", SkColorFilterImageFilter::Create(cf.get()) },
+        { "displacement map", SkDisplacementMapEffect::Create(
+              SkDisplacementMapEffect::kR_ChannelSelectorType,
+              SkDisplacementMapEffect::kB_ChannelSelectorType,
+              40.0f, gradient_source.get()) },
+        { "blur", SkBlurImageFilter::Create(SK_Scalar1, SK_Scalar1) },
+        { "drop shadow", SkDropShadowImageFilter::Create(
+              SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_ColorGREEN) },
+        { "diffuse lighting", SkLightingImageFilter::CreatePointLitDiffuse(
+              location, SK_ColorGREEN, 0, 0) },
+        { "specular lighting",
+              SkLightingImageFilter::CreatePointLitSpecular(location, SK_ColorGREEN, 0, 0, 0) },
+        { "matrix convolution",
+              SkMatrixConvolutionImageFilter::Create(
+                  kernelSize, kernel, gain, bias, SkIPoint::Make(1, 1),
+                  SkMatrixConvolutionImageFilter::kRepeat_TileMode, false) },
+        { "merge", SkMergeImageFilter::Create(NULL, NULL, SkXfermode::kSrcOver_Mode) },
+        { "offset", SkOffsetImageFilter::Create(SK_Scalar1, SK_Scalar1) },
+        { "dilate", SkDilateImageFilter::Create(3, 2) },
+        { "erode", SkErodeImageFilter::Create(2, 3) },
+        { "tile", SkTileImageFilter::Create(SkRect::MakeXYWH(0, 0, 50, 50),
+                                            SkRect::MakeXYWH(0, 0, 100, 100), NULL) },
+    };
+
+    SkBitmap untiledResult, tiledResult;
+    int width = 64, height = 64;
+    untiledResult.allocN32Pixels(width, height);
+    tiledResult.allocN32Pixels(width, height);
+    SkCanvas tiledCanvas(tiledResult);
+    SkCanvas untiledCanvas(untiledResult);
+    tiledCanvas.clear(0);
+    untiledCanvas.clear(0);
+    int tileSize = 16;
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
+        SkPaint paint;
+        paint.setImageFilter(filters[i].fFilter);
+        paint.setTextSize(SkIntToScalar(height));
+        paint.setColor(SK_ColorWHITE);
+        SkString str;
+        const char* text = "ABC";
+        SkScalar ypos = SkIntToScalar(height);
+        untiledCanvas.drawText(text, strlen(text), 0, ypos, paint);
+        for (int y = 0; y < height; y += tileSize) {
+            for (int x = 0; x < width; x += tileSize) {
+                tiledCanvas.save();
+                tiledCanvas.clipRect(SkRect::Make(SkIRect::MakeXYWH(x, y, tileSize, tileSize)));
+                tiledCanvas.drawText(text, strlen(text), 0, ypos, paint);
+                tiledCanvas.restore();
+            }
+        }
+        untiledCanvas.flush();
+        tiledCanvas.flush();
+        for (int y = 0; y < height; y++) {
+            int diffs = memcmp(untiledResult.getAddr32(0, y), tiledResult.getAddr32(0, y), untiledResult.rowBytes());
+            REPORTER_ASSERT_MESSAGE(reporter, !diffs, filters[i].fName);
+            if (diffs) {
+                break;
+            }
+        }
+    }
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
+        SkSafeUnref(filters[i].fFilter);
+    }
+}
+
 DEF_TEST(ImageFilterCropRect, reporter) {
     SkBitmap temp;
     temp.allocN32Pixels(100, 100);