Erode and dilate image filter effects, CPU and GPU implementations.
authorsenorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 2 Mar 2012 21:05:45 +0000 (21:05 +0000)
committersenorblanco@chromium.org <senorblanco@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 2 Mar 2012 21:05:45 +0000 (21:05 +0000)
Review URL:  http://codereview.appspot.com/5656067/

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

15 files changed:
gm/morphology.cpp [new file with mode: 0644]
gyp/effects.gyp
gyp/gmslides.gypi
include/core/SkImageFilter.h
include/effects/SkMorphologyImageFilter.h [new file with mode: 0644]
include/gpu/GrContext.h
include/gpu/GrSamplerState.h
src/core/SkPaint.cpp
src/effects/SkMorphologyImageFilter.cpp [new file with mode: 0644]
src/gpu/GrContext.cpp
src/gpu/SkGpuDevice.cpp
src/gpu/gl/GrGLProgram.cpp
src/gpu/gl/GrGLProgram.h
src/gpu/gl/GrGpuGL.cpp
src/gpu/gl/GrGpuGLShaders.cpp

diff --git a/gm/morphology.cpp b/gm/morphology.cpp
new file mode 100644 (file)
index 0000000..bfaa406
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkMorphologyImageFilter.h"
+
+#define WIDTH 640
+#define HEIGHT 480
+
+namespace skiagm {
+
+class MorphologyGM : public GM {
+public:
+    MorphologyGM() {
+        this->setBGColor(0xFF000000);
+        fOnce = false;
+    }
+    
+protected:
+    virtual SkString onShortName() {
+        return SkString("morphology");
+    }
+
+    void make_bitmap() {
+        fBitmap.setConfig(SkBitmap::kARGB_8888_Config, 135, 135);
+        fBitmap.allocPixels();
+        SkDevice device(fBitmap);
+        SkCanvas canvas(&device);
+        canvas.clear(0x0);
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        const char* str1 = "ABC";
+        const char* str2 = "XYZ";
+        paint.setColor(0xFFFFFFFF);
+        paint.setTextSize(64);
+        canvas.drawText(str1, strlen(str1), 10, 55, paint);
+        canvas.drawText(str2, strlen(str2), 10, 110, paint);
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(WIDTH, HEIGHT);
+    }
+    virtual void onDraw(SkCanvas* canvas) {
+        if (!fOnce) {
+            make_bitmap();
+            fOnce = true;
+        }
+        struct {
+            int fRadiusX, fRadiusY;
+            bool erode;
+            SkScalar fX, fY;
+        } samples[] = {
+            { 0, 0, false, 0,   0 },
+            { 0, 2, false, 140, 0 },
+            { 2, 0, false, 280, 0 },
+            { 2, 2, false, 420, 0 },
+            { 0, 0, true,  0,   140 },
+            { 0, 2, true,  140, 140 },
+            { 2, 0, true,  280, 140 },
+            { 2, 2, true,  420, 140 },
+        };
+        const char* str = "The quick brown fox jumped over the lazy dog.";
+        SkPaint paint;
+        for (unsigned i = 0; i < SK_ARRAY_COUNT(samples); ++i) {
+            if (samples[i].erode) {
+                paint.setImageFilter(new SkErodeImageFilter(
+                    samples[i].fRadiusX,
+                    samples[i].fRadiusY))->unref();
+            } else {
+                paint.setImageFilter(new SkDilateImageFilter(
+                    samples[i].fRadiusX,
+                    samples[i].fRadiusY))->unref();
+            }
+            SkRect bounds = SkRect::MakeXYWH(samples[i].fX,
+                                             samples[i].fY,
+                                             140, 140);
+            canvas->saveLayer(&bounds, &paint);
+            canvas->drawBitmap(fBitmap, samples[i].fX, samples[i].fY);
+            canvas->restore();
+        }
+    }
+    
+private:
+    typedef GM INHERITED;
+    SkBitmap fBitmap;
+    bool fOnce;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new MorphologyGM; }
+static GMRegistry reg(MyFactory);
+
+}
index 28f0017..ac56510 100644 (file)
@@ -32,6 +32,7 @@
         '../include/effects/SkKernel33MaskFilter.h',
         '../include/effects/SkLayerDrawLooper.h',
         '../include/effects/SkLayerRasterizer.h',
+        '../include/effects/SkMorphologyImageFilter.h',
         '../include/effects/SkPaintFlagsDrawFilter.h',
         '../include/effects/SkPixelXorXfermode.h',
         '../include/effects/SkPorterDuff.h',
@@ -66,6 +67,7 @@
         '../src/effects/SkKernel33MaskFilter.cpp',
         '../src/effects/SkLayerDrawLooper.cpp',
         '../src/effects/SkLayerRasterizer.cpp',
+        '../src/effects/SkMorphologyImageFilter.cpp',
         '../src/effects/SkPaintFlagsDrawFilter.cpp',
         '../src/effects/SkPixelXorXfermode.cpp',
         '../src/effects/SkPorterDuff.cpp',
index 43bf87a..2c96e45 100644 (file)
@@ -26,6 +26,7 @@
     '../gm/imageblur.cpp',
     '../gm/lcdtext.cpp',
     '../gm/linepaths.cpp',
+    '../gm/morphology.cpp',
     '../gm/ninepatchstretch.cpp',
     '../gm/nocolorbleed.cpp',
     '../gm/patheffects.cpp',
index 22b9569..7d7c140 100644 (file)
@@ -80,6 +80,22 @@ public:
      */
     virtual bool asABlur(SkSize* sigma) const;
 
+    /**
+     *  Experimental.
+     *
+     *  If the filter can be expressed as an erode, return true and
+     *  set the radius in X and Y.
+     */
+    virtual bool asAnErode(SkISize* radius) const;
+
+    /**
+     *  Experimental.
+     *
+     *  If the filter can be expressed as a dilation, return true and
+     *  set the radius in X and Y.
+     */
+    virtual bool asADilate(SkISize* radius) const;
+
 protected:
     SkImageFilter() {}
     explicit SkImageFilter(SkFlattenableReadBuffer& rb) : INHERITED(rb) {}
diff --git a/include/effects/SkMorphologyImageFilter.h b/include/effects/SkMorphologyImageFilter.h
new file mode 100644 (file)
index 0000000..2297938
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkMorphologyImageFilter_DEFINED
+#define SkMorphologyImageFilter_DEFINED
+
+#include "SkImageFilter.h"
+
+class SK_API SkMorphologyImageFilter : public SkImageFilter {
+public:
+    explicit SkMorphologyImageFilter(SkFlattenableReadBuffer& buffer);
+    SkMorphologyImageFilter(int radiusX, int radiusY);
+
+protected:
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) SK_OVERRIDE;
+    SkISize    radius() const { return fRadius; }
+
+private:
+    SkISize    fRadius;
+    typedef SkImageFilter INHERITED;
+};
+
+class SK_API SkDilateImageFilter : public SkMorphologyImageFilter {
+public:
+    SkDilateImageFilter(int radiusX, int radiusY) : INHERITED(radiusX, radiusY) {}
+    explicit SkDilateImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
+
+    virtual bool asADilate(SkISize* radius) const SK_OVERRIDE;
+    virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkDilateImageFilter, (buffer));
+    }
+    virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
+    SK_DECLARE_FLATTENABLE_REGISTRAR()
+
+    typedef SkMorphologyImageFilter INHERITED;
+};
+
+class SK_API SkErodeImageFilter : public SkMorphologyImageFilter {
+public:
+    SkErodeImageFilter(int radiusX, int radiusY) : INHERITED(radiusX, radiusY) {}
+    explicit SkErodeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
+
+    virtual bool asAnErode(SkISize* radius) const SK_OVERRIDE;
+    virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkErodeImageFilter, (buffer));
+    }
+    virtual Factory getFactory() SK_OVERRIDE { return CreateProc; }
+    SK_DECLARE_FLATTENABLE_REGISTRAR()
+
+private:
+    typedef SkMorphologyImageFilter INHERITED;
+};
+
+#endif
+
index b939c9e..ffb5065 100644 (file)
@@ -577,30 +577,33 @@ public:
     void resolveRenderTarget(GrRenderTarget* target);
 
     /**
-     * Applies a 1D convolution kernel in the X direction to a rectangle of
+     * Applies a 1D convolution kernel in the given direction to a rectangle of
      * pixels from a given texture.
      * @param texture         the texture to read from
      * @param rect            the destination rectangle
      * @param kernel          the convolution kernel (kernelWidth elements)
      * @param kernelWidth     the width of the convolution kernel
+     * @param direction       the direction in which to apply the kernel
      */
-    void convolveInX(GrTexture* texture,
-                     const SkRect& rect,
-                     const float* kernel,
-                     int kernelWidth);
+    void convolve(GrTexture* texture,
+                  const SkRect& rect,
+                  const float* kernel,
+                  int kernelWidth,
+                  GrSamplerState::FilterDirection direction);
     /**
-     * Applies a 1D convolution kernel in the Y direction to a rectangle of
+     * Applies a 1D morphology in the given direction to a rectangle of
      * pixels from a given texture.
-     * direction.
      * @param texture         the texture to read from
      * @param rect            the destination rectangle
-     * @param kernel          the convolution kernel (kernelWidth elements)
-     * @param kernelWidth     the width of the convolution kernel
-     */
-    void convolveInY(GrTexture* texture,
-                     const SkRect& rect,
-                     const float* kernel,
-                     int kernelWidth);
+     * @param radius          the radius of the morphological operator
+     * @param filter          the filter kernel (must be kDilate or kErode)
+     * @param direction       the direction in which to apply the morphology
+     */
+    void applyMorphology(GrTexture* texture,
+                         const SkRect& rect,
+                         int radius,
+                         GrSamplerState::Filter filter,
+                         GrSamplerState::FilterDirection direction);
     ///////////////////////////////////////////////////////////////////////////
     // Helpers
 
@@ -699,12 +702,6 @@ private:
                                     GrPathFill fill,
                                     bool antiAlias);
 
-    void convolve(GrTexture* texture,
-                  const SkRect& rect,
-                  float imageIncrement[2],
-                  const float* kernel,
-                  int kernelWidth);
-
     /**
      * Flags to the internal read/write pixels funcs
      */
index 81dfdb3..50a6cc9 100644 (file)
@@ -39,6 +39,14 @@ public:
          * Apply a separable convolution kernel.
          */
         kConvolution_Filter,
+        /**
+         * Apply a dilate filter (max over a 1D radius).
+         */
+        kDilate_Filter,
+        /**
+         * Apply an erode filter (min over a 1D radius).
+         */
+        kErode_Filter,
 
         kDefault_Filter = kNearest_Filter
     };
@@ -87,6 +95,17 @@ public:
     };
 
     /**
+     * For the filters which perform more than one texture sample (convolution,
+     * erode, dilate), this determines the direction in which the texture
+     * coordinates will be incremented.
+     */
+    enum FilterDirection {
+        kX_FilterDirection,
+        kY_FilterDirection,
+
+        kDefault_FilterDirection = kX_FilterDirection,
+    };
+    /**
      * Default sampler state is set to clamp, use normal sampling mode, be
      * unfiltered, and use identity matrix.
      */
@@ -99,6 +118,7 @@ public:
 
     WrapMode getWrapX() const { return fWrapX; }
     WrapMode getWrapY() const { return fWrapY; }
+    FilterDirection getFilterDirection() const { return fFilterDirection; }
     SampleMode getSampleMode() const { return fSampleMode; }
     const GrMatrix& getMatrix() const { return fMatrix; }
     const GrRect& getTextureDomain() const { return fTextureDomain; }
@@ -106,7 +126,6 @@ public:
     Filter getFilter() const { return fFilter; }
     int getKernelWidth() const { return fKernelWidth; }
     const float* getKernel() const { return fKernel; }
-    const float* getImageIncrement() const { return fImageIncrement; }
     bool swapsRAndB() const { return fSwapRAndB; }
 
     bool isGradient() const {
@@ -118,6 +137,7 @@ public:
     void setWrapX(WrapMode mode) { fWrapX = mode; }
     void setWrapY(WrapMode mode) { fWrapY = mode; }
     void setSampleMode(SampleMode mode) { fSampleMode = mode; }
+    void setFilterDirection(FilterDirection mode) { fFilterDirection = mode; }
     
     /**
      * Access the sampler's matrix. See SampleMode for explanation of
@@ -158,24 +178,29 @@ public:
 
     void reset(WrapMode wrapXAndY,
                Filter filter,
+               FilterDirection direction,
                const GrMatrix& matrix) {
         fWrapX = wrapXAndY;
         fWrapY = wrapXAndY;
         fSampleMode = kDefault_SampleMode;
         fFilter = filter;
+        fFilterDirection = direction;
         fMatrix = matrix;
         fTextureDomain.setEmpty();
         fSwapRAndB = false;
     }
+    void reset(WrapMode wrapXAndY, Filter filter, const GrMatrix& matrix) {
+        this->reset(wrapXAndY, filter, kDefault_FilterDirection, matrix);
+    }
     void reset(WrapMode wrapXAndY,
                Filter filter) {
-        this->reset(wrapXAndY, filter, GrMatrix::I());
+        this->reset(wrapXAndY, filter, kDefault_FilterDirection, GrMatrix::I());
     }
     void reset(const GrMatrix& matrix) {
-        this->reset(kDefault_WrapMode, kDefault_Filter, matrix);
+        this->reset(kDefault_WrapMode, kDefault_Filter, kDefault_FilterDirection, matrix);
     }
     void reset() {
-        this->reset(kDefault_WrapMode, kDefault_Filter, GrMatrix::I());
+        this->reset(kDefault_WrapMode, kDefault_Filter, kDefault_FilterDirection, GrMatrix::I());
     }
 
     GrScalar getRadial2CenterX1() const { return fRadial2CenterX1; }
@@ -198,37 +223,37 @@ public:
         fRadial2PosRoot = posRoot;
     }
 
-    void setConvolutionParams(int kernelWidth, const float* kernel, float imageIncrement[2]) {
+    void setConvolutionParams(int kernelWidth, const float* kernel) {
         GrAssert(kernelWidth >= 0 && kernelWidth <= MAX_KERNEL_WIDTH);
         fKernelWidth = kernelWidth;
         if (NULL != kernel) {
             memcpy(fKernel, kernel, kernelWidth * sizeof(float));
         }
-        if (NULL != imageIncrement) {
-            memcpy(fImageIncrement, imageIncrement, sizeof(fImageIncrement));
-        } else {
-            memset(fImageIncrement, 0, sizeof(fImageIncrement));
-        }
+    }
+
+    void setMorphologyRadius(int radius) {
+        GrAssert(radius >= 0 && radius <= MAX_KERNEL_WIDTH);
+        fKernelWidth = radius;
     }
 
 private:
-    WrapMode    fWrapX : 8;
-    WrapMode    fWrapY : 8;
-    SampleMode  fSampleMode : 8;
-    Filter      fFilter : 8;
-    GrMatrix    fMatrix;
-    bool        fSwapRAndB;
-    GrRect      fTextureDomain;
+    WrapMode            fWrapX : 8;
+    WrapMode            fWrapY : 8;
+    FilterDirection     fFilterDirection : 8;
+    SampleMode          fSampleMode : 8;
+    Filter              fFilter : 8;
+    GrMatrix            fMatrix;
+    bool                fSwapRAndB;
+    GrRect              fTextureDomain;
 
     // these are undefined unless fSampleMode == kRadial2_SampleMode
-    GrScalar    fRadial2CenterX1;
-    GrScalar    fRadial2Radius0;
-    SkBool8     fRadial2PosRoot;
+    GrScalar            fRadial2CenterX1;
+    GrScalar            fRadial2Radius0;
+    SkBool8             fRadial2PosRoot;
 
     // These are undefined unless fFilter == kConvolution_Filter
-    uint8_t     fKernelWidth;
-    float       fImageIncrement[2];
-    float       fKernel[MAX_KERNEL_WIDTH];
+    uint8_t             fKernelWidth;
+    float               fKernel[MAX_KERNEL_WIDTH];
 };
 
 #endif
index 47743bd..4f5128f 100644 (file)
@@ -2140,6 +2140,14 @@ bool SkImageFilter::asABlur(SkSize* sigma) const {
     return false;
 }
 
+bool SkImageFilter::asAnErode(SkISize* radius) const {
+    return false;
+}
+
+bool SkImageFilter::asADilate(SkISize* radius) const {
+    return false;
+}
+
 //////
 
 bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
new file mode 100644 (file)
index 0000000..78fabc5
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkMorphologyImageFilter.h"
+#include "SkColorPriv.h"
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(SkFlattenableReadBuffer& buffer)
+  : INHERITED(buffer) {
+    fRadius.fWidth = buffer.readScalar();
+    fRadius.fHeight = buffer.readScalar();
+}
+
+SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY)
+    : fRadius(SkISize::Make(radiusX, radiusY)) {
+}
+
+
+void SkMorphologyImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+    buffer.writeScalar(fRadius.fWidth);
+    buffer.writeScalar(fRadius.fHeight);
+}
+
+static void erode(const SkPMColor* src, SkPMColor* dst,
+                  int radius, int width, int height,
+                  int srcStrideX, int srcStrideY,
+                  int dstStrideX, int dstStrideY)
+{
+    const SkPMColor* upperSrc = src + SkMin32(radius, width - 1) * srcStrideX;
+    for (int x = 0; x < width; ++x) {
+        const SkPMColor* lp = src;
+        const SkPMColor* up = upperSrc;
+        SkPMColor* dptr = dst;
+        for (int y = 0; y < height; ++y) {
+            int minB = 255, minG = 255, minR = 255, minA = 255;
+            for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+                int b = SkGetPackedB32(*p);
+                int g = SkGetPackedG32(*p);
+                int r = SkGetPackedR32(*p);
+                int a = SkGetPackedA32(*p);
+                if (b < minB) minB = b;
+                if (g < minG) minG = g;
+                if (r < minR) minR = r;
+                if (a < minA) minA = a;
+            }
+            *dptr = SkPackARGB32(minA, minR, minG, minB);
+            dptr += dstStrideY;
+            lp += srcStrideY;
+            up += srcStrideY;
+        }
+        if (x >= radius) src += srcStrideX;
+        if (x + radius < width - 1) upperSrc += srcStrideX;
+        dst += dstStrideX;
+    }
+}
+
+static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+    erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+          radiusX, src.width(), src.height(),
+          1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+    erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+          radiusY, src.height(), src.width(),
+          src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+static void dilate(const SkPMColor* src, SkPMColor* dst,
+                   int radius, int width, int height,
+                   int srcStrideX, int srcStrideY,
+                   int dstStrideX, int dstStrideY)
+{
+    const SkPMColor* upperSrc = src + SkMin32(radius, width - 1) * srcStrideX;
+    for (int x = 0; x < width; ++x) {
+        const SkPMColor* lp = src;
+        const SkPMColor* up = upperSrc;
+        SkPMColor* dptr = dst;
+        for (int y = 0; y < height; ++y) {
+            int maxB = 0, maxG = 0, maxR = 0, maxA = 0;
+            for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
+                int b = SkGetPackedB32(*p);
+                int g = SkGetPackedG32(*p);
+                int r = SkGetPackedR32(*p);
+                int a = SkGetPackedA32(*p);
+                if (b > maxB) maxB = b;
+                if (g > maxG) maxG = g;
+                if (r > maxR) maxR = r;
+                if (a > maxA) maxA = a;
+            }
+            *dptr = SkPackARGB32(maxA, maxR, maxG, maxB);
+            dptr += dstStrideY;
+            lp += srcStrideY;
+            up += srcStrideY;
+        }
+        if (x >= radius) src += srcStrideX;
+        if (x + radius < width - 1) upperSrc += srcStrideX;
+        dst += dstStrideX;
+    }
+}
+
+static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+{
+    dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+           radiusX, src.width(), src.height(),
+           1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
+}
+
+static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+{
+    dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
+           radiusY, src.height(), src.width(),
+           src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
+}
+
+bool SkErodeImageFilter::onFilterImage(Proxy*,
+                                       const SkBitmap& src, const SkMatrix&,
+                                       SkBitmap* dst, SkIPoint*) {
+    if (src.config() != SkBitmap::kARGB_8888_Config) {
+        return false;
+    }
+
+    SkAutoLockPixels alp(src);
+    if (!src.getPixels()) {
+        return false;
+    }
+
+    dst->setConfig(src.config(), src.width(), src.height());
+    dst->allocPixels();
+
+    int width = radius().width();
+    int height = radius().height();
+
+    if (width < 0 || height < 0) {
+        return false;
+    }
+
+    if (width == 0 && height == 0) {
+        src.copyTo(dst, dst->config());
+        return true;
+    }
+
+    SkBitmap temp;
+    temp.setConfig(dst->config(), dst->width(), dst->height());
+    if (!temp.allocPixels()) {
+        return false;
+    }
+
+    if (width > 0 && height > 0) {
+        erodeX(src, &temp, width);
+        erodeY(temp, dst, height);
+    } else if (width > 0) {
+        erodeX(src, dst, width);
+    } else if (height > 0) {
+        erodeY(src, dst, height);
+    }
+    return true;
+}
+
+bool SkDilateImageFilter::onFilterImage(Proxy*,
+                                        const SkBitmap& src, const SkMatrix&,
+                                        SkBitmap* dst, SkIPoint*) {
+    if (src.config() != SkBitmap::kARGB_8888_Config) {
+        return false;
+    }
+
+    SkAutoLockPixels alp(src);
+    if (!src.getPixels()) {
+        return false;
+    }
+
+    dst->setConfig(src.config(), src.width(), src.height());
+    dst->allocPixels();
+
+    int width = radius().width();
+    int height = radius().height();
+
+    if (width < 0 || height < 0) {
+        return false;
+    }
+
+    if (width == 0 && height == 0) {
+        src.copyTo(dst, dst->config());
+        return true;
+    }
+
+    SkBitmap temp;
+    temp.setConfig(dst->config(), dst->width(), dst->height());
+    if (!temp.allocPixels()) {
+        return false;
+    }
+
+    if (width > 0 && height > 0) {
+        dilateX(src, &temp, width);
+        dilateY(temp, dst, height);
+    } else if (width > 0) {
+        dilateX(src, dst, width);
+    } else if (height > 0) {
+        dilateY(src, dst, height);
+    }
+    return true;
+}
+
+bool SkDilateImageFilter::asADilate(SkISize* radius) const {
+    *radius = this->radius();
+    return true;
+}
+
+bool SkErodeImageFilter::asAnErode(SkISize* radius) const {
+    *radius = this->radius();
+    return true;
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR(SkDilateImageFilter)
+SK_DEFINE_FLATTENABLE_REGISTRAR(SkErodeImageFilter)
index 2de978f..d6ebada 100644 (file)
@@ -2007,31 +2007,11 @@ const GrIndexBuffer* GrContext::getQuadIndexBuffer() const {
     return fGpu->getQuadIndexBuffer();
 }
 
-void GrContext::convolveInX(GrTexture* texture,
-                            const SkRect& rect,
-                            const float* kernel,
-                            int kernelWidth) {
-    ASSERT_OWNED_RESOURCE(texture);
-
-    float imageIncrement[2] = {1.0f / texture->width(), 0.0f};
-    convolve(texture, rect, imageIncrement, kernel, kernelWidth);
-}
-
-void GrContext::convolveInY(GrTexture* texture,
-                            const SkRect& rect,
-                            const float* kernel,
-                            int kernelWidth) {
-    ASSERT_OWNED_RESOURCE(texture);
-
-    float imageIncrement[2] = {0.0f, 1.0f / texture->height()};
-    convolve(texture, rect, imageIncrement, kernel, kernelWidth);
-}
-
 void GrContext::convolve(GrTexture* texture,
                          const SkRect& rect,
-                         float imageIncrement[2],
                          const float* kernel,
-                         int kernelWidth) {
+                         int kernelWidth,
+                         GrSamplerState::FilterDirection direction) {
     ASSERT_OWNED_RESOURCE(texture);
 
     GrDrawTarget::AutoStateRestore asr(fGpu);
@@ -2044,10 +2024,33 @@ void GrContext::convolve(GrTexture* texture,
     drawState->sampler(0)->reset(GrSamplerState::kClamp_WrapMode,
                                  GrSamplerState::kConvolution_Filter,
                                  sampleM);
-    drawState->sampler(0)->setConvolutionParams(kernelWidth,
-                                                kernel,
-                                                imageIncrement);
+    drawState->sampler(0)->setConvolutionParams(kernelWidth, kernel);
+    drawState->sampler(0)->setFilterDirection(direction);
+    drawState->setTexture(0, texture);
+    fGpu->drawSimpleRect(rect, NULL, 1 << 0);
+}
 
+void GrContext::applyMorphology(GrTexture* texture,
+                                const SkRect& rect,
+                                int radius,
+                                GrSamplerState::Filter filter,
+                                GrSamplerState::FilterDirection direction) {
+    ASSERT_OWNED_RESOURCE(texture);
+    GrAssert(filter == GrSamplerState::kErode_Filter ||
+             filter == GrSamplerState::kDilate_Filter);
+
+    GrDrawTarget::AutoStateRestore asr(fGpu);
+    GrDrawState* drawState = fGpu->drawState();
+    GrRenderTarget* target = drawState->getRenderTarget();
+    drawState->reset();
+    drawState->setRenderTarget(target);
+    GrMatrix sampleM;
+    sampleM.setIDiv(texture->width(), texture->height());
+    drawState->sampler(0)->reset(GrSamplerState::kClamp_WrapMode,
+                                 filter,
+                                 sampleM);
+    drawState->sampler(0)->setMorphologyRadius(radius);
+    drawState->sampler(0)->setFilterDirection(direction);
     drawState->setTexture(0, texture);
     fGpu->drawSimpleRect(rect, NULL, 1 << 0);
 }
index 94ac5d2..af59699 100644 (file)
@@ -698,6 +698,36 @@ static GrPathFill skToGrFillType(SkPath::FillType fillType) {
     }
 }
 
+static GrTexture* applyMorphology(GrContext* context, GrTexture* texture,
+                                  const GrRect& srcRect,
+                                  GrTexture* temp1, GrTexture* temp2,
+                                  GrSamplerState::Filter filter,
+                                  SkISize radius) {
+    GrRenderTarget* oldRenderTarget = context->getRenderTarget();
+    GrAutoMatrix avm(context, GrMatrix::I());
+    GrClip oldClip = context->getClip();
+    context->setClip(GrRect::MakeWH(texture->width(), texture->height()));
+    if (radius.fWidth > 0) {
+        context->setRenderTarget(temp1->asRenderTarget());
+        context->applyMorphology(texture, srcRect, radius.fWidth, filter,
+                                 GrSamplerState::kX_FilterDirection);
+        SkIRect clearRect = SkIRect::MakeXYWH(
+            srcRect.fLeft, srcRect.fBottom,
+            srcRect.width(), radius.fHeight);
+        context->clear(&clearRect, 0x0);
+        texture = temp1;
+    }
+    if (radius.fHeight > 0) {
+        context->setRenderTarget(temp2->asRenderTarget());
+        context->applyMorphology(texture, srcRect, radius.fHeight, filter,
+                                 GrSamplerState::kY_FilterDirection);
+        texture = temp2;
+    }
+    context->setRenderTarget(oldRenderTarget);
+    context->setClip(oldClip);
+    return texture;
+}
+
 static void buildKernel(float sigma, float* kernel, int kernelWidth) {
     int halfWidth = (kernelWidth - 1) / 2;
     float sum = 0.0f;
@@ -808,7 +838,8 @@ static GrTexture* gaussianBlur(GrContext* context, GrTexture* srcTexture,
         }
 
         context->setRenderTarget(dstTexture->asRenderTarget());
-        context->convolveInX(srcTexture, srcRect, kernelX, kernelWidthX);
+        context->convolve(srcTexture, srcRect, kernelX, kernelWidthX,
+                          GrSamplerState::kX_FilterDirection);
         SkTSwap(srcTexture, dstTexture);
         if (temp2 && dstTexture == origTexture) dstTexture = temp2->texture();
     }
@@ -827,7 +858,8 @@ static GrTexture* gaussianBlur(GrContext* context, GrTexture* srcTexture,
         }
 
         context->setRenderTarget(dstTexture->asRenderTarget());
-        context->convolveInY(srcTexture, srcRect, kernelY, kernelWidthY);
+        context->convolve(srcTexture, srcRect, kernelY, kernelWidthY,
+                          GrSamplerState::kY_FilterDirection);
         SkTSwap(srcTexture, dstTexture);
         if (temp2 && dstTexture == origTexture) dstTexture = temp2->texture();
     }
@@ -1481,6 +1513,7 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
 
     SkImageFilter* imageFilter = paint.getImageFilter();
     SkSize blurSize;
+    SkISize radius;
     if (NULL != imageFilter && imageFilter->asABlur(&blurSize)) {
         GrAutoScratchTexture temp1, temp2;
         GrTexture* blurTexture = gaussianBlur(fContext,
@@ -1490,6 +1523,32 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
                                               blurSize.height());
         texture = blurTexture;
         grPaint.setTexture(kBitmapTextureIdx, texture);
+    } else if (NULL != imageFilter && imageFilter->asADilate(&radius)) {
+        const GrTextureDesc desc = {
+            kRenderTarget_GrTextureFlagBit,
+            w,
+            h,
+            kRGBA_8888_PM_GrPixelConfig,
+            {0} // samples
+        };
+        GrAutoScratchTexture temp1(fContext, desc), temp2(fContext, desc);
+        texture = applyMorphology(fContext, texture, GrRect::MakeWH(w, h),
+                                  temp1.texture(), temp2.texture(),
+                                  GrSamplerState::kDilate_Filter, radius);
+        grPaint.setTexture(kBitmapTextureIdx, texture);
+    } else if (NULL != imageFilter && imageFilter->asAnErode(&radius)) {
+        const GrTextureDesc desc = {
+            kRenderTarget_GrTextureFlagBit,
+            w,
+            h,
+            kRGBA_8888_PM_GrPixelConfig,
+            {0} // samples
+        };
+        GrAutoScratchTexture temp1(fContext, desc), temp2(fContext, desc);
+        texture = applyMorphology(fContext, texture, GrRect::MakeWH(w, h),
+                                  temp1.texture(), temp2.texture(),
+                                  GrSamplerState::kErode_Filter, radius);
+        grPaint.setTexture(kBitmapTextureIdx, texture);
     } else {
         grPaint.setTexture(kBitmapTextureIdx, texture);
     }
@@ -1541,7 +1600,8 @@ bool SkGpuDevice::filterImage(SkImageFilter* filter, const SkBitmap& src,
                               const SkMatrix& ctm,
                               SkBitmap* result, SkIPoint* offset) {
     SkSize size;
-    if (!filter->asABlur(&size)) {
+    SkISize radius;
+    if (!filter->asABlur(&size) && !filter->asADilate(&radius) && !filter->asAnErode(&radius)) {
         return false;
     }
     SkDevice* dev = this->createCompatibleDevice(SkBitmap::kARGB_8888_Config,
index 7eecf91..2925213 100644 (file)
@@ -166,6 +166,11 @@ inline void convolve_param_names(int stage, GrStringBuilder* k, GrStringBuilder*
     i->appendS32(stage);
 }
 
+inline void image_increment_param_name(int stage, GrStringBuilder* i) {
+    *i = "uImageIncrement";
+    i->appendS32(stage);
+}
+
 inline void tex_domain_name(int stage, GrStringBuilder* s) {
     *s = "uTexDom";
     s->appendS32(stage);
@@ -1655,6 +1660,68 @@ void genConvolutionFS(int stageNum,
     segments->fFSCode.appendf("\t%s = %s%s;\n", fsOutColor,
                               sumVar.c_str(), modulate.c_str());
 }
+void genMorphologyVS(int stageNum,
+                     const StageDesc& desc,
+                     ShaderCodeSegments* segments,
+                     GrGLProgram::StageUniLocations* locations,
+                     const char** imageIncrementName,
+                     const char* varyingVSName) {
+    GrGLShaderVar* imgInc = &segments->fFSUnis.push_back();
+    imgInc->setType(GrGLShaderVar::kVec2f_Type);
+    imgInc->setTypeModifier(GrGLShaderVar::kUniform_TypeModifier);
+
+    image_increment_param_name(stageNum, imgInc->accessName());
+    *imageIncrementName = imgInc->getName().c_str();
+
+    // need image increment in both VS and FS
+    segments->fVSUnis.push_back(*imgInc).setEmitPrecision(true);
+
+    locations->fImageIncrementUni = kUseUniform;
+    segments->fVSCode.appendf("\t%s -= vec2(%d, %d) * %s;\n",
+                                  varyingVSName, desc.fKernelWidth,
+                                  desc.fKernelWidth, *imageIncrementName);
+}
+void genMorphologyFS(int stageNum,
+                     const StageDesc& desc,
+                     ShaderCodeSegments* segments,
+                     const char* samplerName,
+                     const char* swizzle,
+                     const char* imageIncrementName,
+                     const char* fsOutColor,
+                     GrStringBuilder& sampleCoords,
+                     GrStringBuilder& texFunc,
+                     GrStringBuilder& modulate) {
+    GrStringBuilder valueVar("value");
+    valueVar.appendS32(stageNum);
+    GrStringBuilder coordVar("coord");
+    coordVar.appendS32(stageNum);
+    bool isDilate = StageDesc::kDilate_FetchMode == desc.fFetchMode;
+
+   if (isDilate) {
+        segments->fFSCode.appendf("\tvec4 %s = vec4(0, 0, 0, 0);\n",
+                                  valueVar.c_str());
+    } else {
+        segments->fFSCode.appendf("\tvec4 %s = vec4(1, 1, 1, 1);\n",
+                                  valueVar.c_str());
+    }
+    segments->fFSCode.appendf("\tvec2 %s = %s;\n", 
+                              coordVar.c_str(),
+                              sampleCoords.c_str());
+    segments->fFSCode.appendf("\tfor (int i = 0; i < %d; i++) {\n",
+                              desc.fKernelWidth * 2 + 1);
+    segments->fFSCode.appendf("\t\t%s = %s(%s, %s(%s, %s)%s);\n",
+                              valueVar.c_str(), isDilate ? "max" : "min",
+                              valueVar.c_str(), texFunc.c_str(),
+                              samplerName, coordVar.c_str(), swizzle);
+    segments->fFSCode.appendf("\t\t%s += %s;\n",
+                              coordVar.c_str(),
+                              imageIncrementName);
+    segments->fFSCode.appendf("\t}\n");
+    segments->fFSCode.appendf("\t%s = %s%s;\n", fsOutColor,
+                              valueVar.c_str(), modulate.c_str());
+}
 
 }
 
@@ -1755,6 +1822,10 @@ void GrGLProgram::genStageCode(const GrGLContextInfo& gl,
     if (StageDesc::kConvolution_FetchMode == desc.fFetchMode) {
         genConvolutionVS(stageNum, desc, segments, locations,
                          &kernel, &imageIncrementName, varyingVSName);
+    } else if (StageDesc::kDilate_FetchMode == desc.fFetchMode ||
+               StageDesc::kErode_FetchMode == desc.fFetchMode) {
+        genMorphologyVS(stageNum, desc, segments, locations,
+                        &imageIncrementName, varyingVSName);
     }
 
     /// Fragment Shader Stuff
@@ -1866,6 +1937,13 @@ void GrGLProgram::genStageCode(const GrGLContextInfo& gl,
             samplerName, kernel, swizzle, imageIncrementName, fsOutColor,
             sampleCoords, texFunc, modulate);
         break;
+    case StageDesc::kDilate_FetchMode:
+    case StageDesc::kErode_FetchMode:
+        GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
+        genMorphologyFS(stageNum, desc, segments,
+            samplerName, swizzle, imageIncrementName, fsOutColor,
+            sampleCoords, texFunc, modulate);
+        break;
     default:
         if (desc.fInConfigFlags & kMulByAlphaMask) {
             // only one of the mul by alpha flags should be set
index e9030bc..76f9c90 100644 (file)
@@ -110,6 +110,8 @@ public:
                 kSingle_FetchMode,
                 k2x2_FetchMode,
                 kConvolution_FetchMode,
+                kErode_FetchMode,
+                kDilate_FetchMode,
 
                 kFetchModeCnt,
             };
index bee2017..69880e5 100644 (file)
@@ -445,7 +445,7 @@ void GrGpuGL::onResetContext() {
                                                   -GR_ScalarMax,
                                                   true);
         *fHWDrawState.sampler(s)->matrix() = GrMatrix::InvalidMatrix();
-        fHWDrawState.sampler(s)->setConvolutionParams(0, NULL, NULL);
+        fHWDrawState.sampler(s)->setConvolutionParams(0, NULL);
     }
 
     fHWBounds.fScissorRect.invalidate();
@@ -1935,6 +1935,8 @@ unsigned gr_to_gl_filter(GrSamplerState::Filter filter) {
             return GR_GL_LINEAR;
         case GrSamplerState::kNearest_Filter:
         case GrSamplerState::kConvolution_Filter:
+        case GrSamplerState::kErode_Filter:
+        case GrSamplerState::kDilate_Filter:
             return GR_GL_NEAREST;
         default:
             GrAssert(!"Unknown filter type");
index a0a2df5..db7e3a7 100644 (file)
@@ -261,7 +261,9 @@ bool GrGpuGLShaders::programUnitTest() {
             stage.fCoordMapping =  random_int(&random, StageDesc::kCoordMappingCnt);
             stage.fFetchMode = random_int(&random, StageDesc::kFetchModeCnt);
             // convolution shaders don't work with persp tex matrix
-            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode) {
+            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode ||
+                stage.fFetchMode == StageDesc::kDilate_FetchMode ||
+                stage.fFetchMode == StageDesc::kErode_FetchMode) {
                 stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
             }
             stage.setEnabled(VertexUsesStage(s, pdesc.fVertexLayout));
@@ -273,6 +275,8 @@ bool GrGpuGLShaders::programUnitTest() {
                     stage.fKernelWidth = 0;
                     break;
                 case StageDesc::kConvolution_FetchMode:
+                case StageDesc::kDilate_FetchMode:
+                case StageDesc::kErode_FetchMode:
                     stage.fKernelWidth = random_int(&random, 2, 8);
                     stage.fInConfigFlags &= ~kMulByAlphaMask;
                     break;
@@ -560,7 +564,20 @@ void GrGpuGLShaders::flushConvolution(int s) {
     }
     int imageIncrementUni = fProgramData->fUniLocations.fStages[s].fImageIncrementUni;
     if (GrGLProgram::kUnusedUniform != imageIncrementUni) {
-        GL_CALL(Uniform2fv(imageIncrementUni, 1, sampler.getImageIncrement()));
+        const GrGLTexture* texture =
+            static_cast<const GrGLTexture*>(this->getDrawState().getTexture(s));
+        float imageIncrement[2] = { 0 };
+        switch (sampler.getFilterDirection()) {
+            case GrSamplerState::kX_FilterDirection:
+                imageIncrement[0] = 1.0f / texture->width();
+                break;
+            case GrSamplerState::kY_FilterDirection:
+                imageIncrement[1] = 1.0f / texture->height();
+                break;
+            default:
+                GrCrash("Unknown filter direction.");
+        }
+        GL_CALL(Uniform2fv(imageIncrementUni, 1, imageIncrement));
     }
 }
 
@@ -1081,6 +1098,12 @@ void GrGpuGLShaders::buildProgram(GrPrimitiveType type,
                 case GrSamplerState::kConvolution_Filter:
                     stage.fFetchMode = StageDesc::kConvolution_FetchMode;
                     break;
+                case GrSamplerState::kDilate_Filter:
+                    stage.fFetchMode = StageDesc::kDilate_FetchMode;
+                    break;
+                case GrSamplerState::kErode_Filter:
+                    stage.fFetchMode = StageDesc::kErode_FetchMode;
+                    break;
                 default:
                     GrCrash("Unexpected filter!");
                     break;
@@ -1119,7 +1142,9 @@ void GrGpuGLShaders::buildProgram(GrPrimitiveType type,
                 }
             }
 
-            if (sampler.getFilter() == GrSamplerState::kConvolution_Filter) {
+            if (sampler.getFilter() == GrSamplerState::kConvolution_Filter ||
+                sampler.getFilter() == GrSamplerState::kDilate_Filter ||
+                sampler.getFilter() == GrSamplerState::kErode_Filter) {
                 stage.fKernelWidth = sampler.getKernelWidth();
             } else {
                 stage.fKernelWidth = 0;