move the guts of SkMaskFilter.h into SkMaskFilterBase.h

Bug: skia:
Change-Id: I29ad0960156562867429542d3cfbf3d639529cab
Reviewed-on: https://skia-review.googlesource.com/98802
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index 3e10dc9..55bf8bf 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2006 The Android Open Source Project
  *
@@ -9,245 +8,19 @@
 #ifndef SkMaskFilter_DEFINED
 #define SkMaskFilter_DEFINED
 
-#include "SkBlurTypes.h"
 #include "SkFlattenable.h"
-#include "SkMask.h"
-#include "SkPaint.h"
-#include "SkStrokeRec.h"
 
-class GrClip;
-class GrContext;
-struct GrFPArgs;
-class GrRenderTargetContext;
-class GrPaint;
-class GrFragmentProcessor;
-class GrRenderTarget;
-class GrResourceProvider;
-class GrTexture;
-class GrTextureProxy;
-class SkBitmap;
-class SkBlitter;
-class SkCachedData;
-class SkMatrix;
-class SkPath;
-class SkRasterClip;
-class SkRRect;
+class SkString;
 
 /** \class SkMaskFilter
 
     SkMaskFilter is the base class for object that perform transformations on
-    an alpha-channel mask before drawing it. A subclass of SkMaskFilter may be
-    installed into a SkPaint. Once there, each time a primitive is drawn, it
-    is first scan converted into a SkMask::kA8_Format mask, and handed to the
-    filter, calling its filterMask() method. If this returns true, then the
-    new mask is used to render into the device.
-
-    Blur and emboss are implemented as subclasses of SkMaskFilter.
+    the mask before drawing it. An example subclass is Blur.
 */
 class SK_API SkMaskFilter : public SkFlattenable {
 public:
-    /** Returns the format of the resulting mask that this subclass will return
-        when its filterMask() method is called.
-    */
-    virtual SkMask::Format getFormat() const = 0;
-
-    /** Create a new mask by filter the src mask.
-        If src.fImage == null, then do not allocate or create the dst image
-        but do fill out the other fields in dstMask.
-        If you do allocate a dst image, use SkMask::AllocImage()
-        If this returns false, dst mask is ignored.
-        @param  dst the result of the filter. If src.fImage == null, dst should not allocate its image
-        @param src the original image to be filtered.
-        @param matrix the CTM
-        @param margin   if not null, return the buffer dx/dy need when calculating the effect. Used when
-                        drawing a clipped object to know how much larger to allocate the src before
-                        applying the filter. If returning false, ignore this parameter.
-        @return true if the dst mask was correctly created.
-    */
-    virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
-                            SkIPoint* margin) const;
-
-#if SK_SUPPORT_GPU
-    /**
-     *  Returns a processor if the filter can be expressed a single-pass GrProcessor without
-     *  requiring an explicit input mask. Per-pixel, the effect receives the incoming mask's
-     *  coverage as the input color and outputs the filtered covereage value. This means that each
-     *  pixel's filtered coverage must only depend on the unfiltered mask value for that pixel and
-     *  not on surrounding values.
-     */
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs& args) const;
-
-    /**
-     *  Returns true iff asFragmentProcessor() will return a processor
-     */
-    bool hasFragmentProcessor() const;
-
-    /**
-     *  If asFragmentProcessor() fails the filter may be implemented on the GPU by a subclass
-     *  overriding filterMaskGPU (declared below). That code path requires constructing a
-     *  src mask as input. Since that is a potentially expensive operation, the subclass must also
-     *  override this function to indicate whether filterTextureMaskGPU would succeeed if the mask
-     *  were to be created.
-     *
-     *  'maskRect' returns the device space portion of the mask that the filter needs. The mask
-     *  passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be
-     *  translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop)
-     *  appears at (0, 0) in the mask).
-     *
-     * Logically, how this works is:
-     *    canFilterMaskGPU is called
-     *    if (it returns true)
-     *        the returned mask rect is used for quick rejecting
-     *        either directFilterMaskGPU or directFilterRRectMaskGPU is then called
-     *        if (neither of them handle the blur)
-     *            the mask rect is used to generate the mask
-     *            filterMaskGPU is called to filter the mask
-     *
-     * TODO: this should work as:
-     *    if (canFilterMaskGPU(devShape, ...)) // rect, rrect, drrect, path
-     *        filterMaskGPU(devShape, ...)
-     * this would hide the RRect special case and the mask generation
-     */
-    virtual bool canFilterMaskGPU(const SkRRect& devRRect,
-                                  const SkIRect& clipBounds,
-                                  const SkMatrix& ctm,
-                                  SkRect* maskRect) const;
-
-    /**
-     *  Try to directly render the mask filter into the target. Returns true if drawing was
-     *  successful. If false is returned then paint is unmodified.
-     */
-    virtual bool directFilterMaskGPU(GrContext*,
-                                     GrRenderTargetContext* renderTargetContext,
-                                     GrPaint&& paint,
-                                     const GrClip&,
-                                     const SkMatrix& viewMatrix,
-                                     const SkStrokeRec& strokeRec,
-                                     const SkPath& path) const;
-    /**
-     *  Try to directly render a rounded rect mask filter into the target.  Returns
-     *  true if drawing was successful.  If false is returned then paint is unmodified.
-     */
-    virtual bool directFilterRRectMaskGPU(GrContext*,
-                                          GrRenderTargetContext* renderTargetContext,
-                                          GrPaint&& paint,
-                                          const GrClip&,
-                                          const SkMatrix& viewMatrix,
-                                          const SkStrokeRec& strokeRec,
-                                          const SkRRect& rrect,
-                                          const SkRRect& devRRect) const;
-
-    /**
-     * This function is used to implement filters that require an explicit src mask. It should only
-     * be called if canFilterMaskGPU returned true and the maskRect param should be the output from
-     * that call.
-     * Implementations are free to get the GrContext from the src texture in order to create
-     * additional textures and perform multiple passes.
-     */
-    virtual sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
-                                                sk_sp<GrTextureProxy> srcProxy,
-                                                const SkMatrix& ctm,
-                                                const SkIRect& maskRect) const;
-#endif
-
-    /**
-     * The fast bounds function is used to enable the paint to be culled early
-     * in the drawing pipeline. This function accepts the current bounds of the
-     * paint as its src param and the filter adjust those bounds using its
-     * current mask and returns the result using the dest param. Callers are
-     * allowed to provide the same struct for both src and dest so each
-     * implementation must accomodate that behavior.
-     *
-     *  The default impl calls filterMask with the src mask having no image,
-     *  but subclasses may override this if they can compute the rect faster.
-     */
-    virtual void computeFastBounds(const SkRect& src, SkRect* dest) const;
-
-    struct BlurRec {
-        SkScalar        fSigma;
-        SkBlurStyle     fStyle;
-        SkBlurQuality   fQuality;
-    };
-    /**
-     *  If this filter can be represented by a BlurRec, return true and (if not null) fill in the
-     *  provided BlurRec parameter. If this effect cannot be represented as a BlurRec, return false
-     *  and ignore the BlurRec parameter.
-     */
-    virtual bool asABlur(BlurRec*) const;
-
     SK_TO_STRING_PUREVIRT()
     SK_DEFINE_FLATTENABLE_TYPE(SkMaskFilter)
-
-protected:
-    SkMaskFilter() {}
-
-#if SK_SUPPORT_GPU
-    virtual std::unique_ptr<GrFragmentProcessor> onAsFragmentProcessor(const GrFPArgs&) const;
-    virtual bool onHasFragmentProcessor() const;
-#endif
-
-    enum FilterReturn {
-        kFalse_FilterReturn,
-        kTrue_FilterReturn,
-        kUnimplemented_FilterReturn
-    };
-
-    class NinePatch : ::SkNoncopyable {
-    public:
-        NinePatch() : fCache(nullptr) { }
-        ~NinePatch();
-
-        SkMask      fMask;      // fBounds must have [0,0] in its top-left
-        SkIRect     fOuterRect; // width/height must be >= fMask.fBounds'
-        SkIPoint    fCenter;    // identifies center row/col for stretching
-        SkCachedData* fCache;
-    };
-
-    /**
-     *  Override if your subclass can filter a rect, and return the answer as
-     *  a ninepatch mask to be stretched over the returned outerRect. On success
-     *  return kTrue_FilterReturn. On failure (e.g. out of memory) return
-     *  kFalse_FilterReturn. If the normal filterMask() entry-point should be
-     *  called (the default) return kUnimplemented_FilterReturn.
-     *
-     *  By convention, the caller will take the center rol/col from the returned
-     *  mask as the slice it can replicate horizontally and vertically as we
-     *  stretch the mask to fit inside outerRect. It is an error for outerRect
-     *  to be smaller than the mask's bounds. This would imply that the width
-     *  and height of the mask should be odd. This is not required, just that
-     *  the caller will call mask.fBounds.centerX() and centerY() to find the
-     *  strips that will be replicated.
-     */
-    virtual FilterReturn filterRectsToNine(const SkRect[], int count,
-                                           const SkMatrix&,
-                                           const SkIRect& clipBounds,
-                                           NinePatch*) const;
-    /**
-     *  Similar to filterRectsToNine, except it performs the work on a round rect.
-     */
-    virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
-                                           const SkIRect& clipBounds,
-                                           NinePatch*) const;
-
-private:
-    friend class SkDraw;
-
-    /** Helper method that, given a path in device space, will rasterize it into a kA8_Format mask
-     and then call filterMask(). If this returns true, the specified blitter will be called
-     to render that mask. Returns false if filterMask() returned false.
-     This method is not exported to java.
-     */
-    bool filterPath(const SkPath& devPath, const SkMatrix& ctm, const SkRasterClip&, SkBlitter*,
-                    SkStrokeRec::InitStyle) const;
-
-    /** Helper method that, given a roundRect in device space, will rasterize it into a kA8_Format
-     mask and then call filterMask(). If this returns true, the specified blitter will be called
-     to render that mask. Returns false if filterMask() returned false.
-     */
-    bool filterRRect(const SkRRect& devRRect, const SkMatrix& ctm, const SkRasterClip&,
-                     SkBlitter*) const;
-
-    typedef SkFlattenable INHERITED;
 };
 
 #endif
diff --git a/include/effects/SkBlurMaskFilter.h b/include/effects/SkBlurMaskFilter.h
index 8b032e5..af074ff 100644
--- a/include/effects/SkBlurMaskFilter.h
+++ b/include/effects/SkBlurMaskFilter.h
@@ -10,9 +10,12 @@
 
 // we include this since our callers will need to at least be able to ref/unref
 #include "SkMaskFilter.h"
+#include "SkRect.h"
 #include "SkScalar.h"
 #include "SkBlurTypes.h"
 
+class SkRRect;
+
 class SK_API SkBlurMaskFilter {
 public:
     /**
diff --git a/include/effects/SkRRectsGaussianEdgeMaskFilter.h b/include/effects/SkRRectsGaussianEdgeMaskFilter.h
index d9e3024..cd7effc 100644
--- a/include/effects/SkRRectsGaussianEdgeMaskFilter.h
+++ b/include/effects/SkRRectsGaussianEdgeMaskFilter.h
@@ -9,6 +9,7 @@
 #define SkRRectsGaussianEdgeMaskFilter_DEFINED
 
 #include "SkMaskFilter.h"
+#include "SkScalar.h"
 
 class SkRRect;
 
diff --git a/include/effects/SkTableMaskFilter.h b/include/effects/SkTableMaskFilter.h
index f226dd1..eb6ccb0 100644
--- a/include/effects/SkTableMaskFilter.h
+++ b/include/effects/SkTableMaskFilter.h
@@ -16,7 +16,7 @@
     Applies a table lookup on each of the alpha values in the mask.
     Helper methods create some common tables (e.g. gamma, clipping)
  */
-class SK_API SkTableMaskFilter : public SkMaskFilter {
+class SK_API SkTableMaskFilter {
 public:
     /** Utility that sets the gamma table
      */
@@ -27,40 +27,11 @@
      */
     static void MakeClipTable(uint8_t table[256], uint8_t min, uint8_t max);
 
-    static SkMaskFilter* Create(const uint8_t table[256]) {
-        return new SkTableMaskFilter(table);
-    }
+    static SkMaskFilter* Create(const uint8_t table[256]);
+    static SkMaskFilter* CreateGamma(SkScalar gamma);
+    static SkMaskFilter* CreateClip(uint8_t min, uint8_t max);
 
-    static SkMaskFilter* CreateGamma(SkScalar gamma) {
-        uint8_t table[256];
-        MakeGammaTable(table, gamma);
-        return new SkTableMaskFilter(table);
-    }
-
-    static SkMaskFilter* CreateClip(uint8_t min, uint8_t max) {
-        uint8_t table[256];
-        MakeClipTable(table, min, max);
-        return new SkTableMaskFilter(table);
-    }
-
-    SkMask::Format getFormat() const override;
-    bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*) const override;
-
-    SK_TO_STRING_OVERRIDE()
-    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTableMaskFilter)
-
-protected:
-    ~SkTableMaskFilter() override;
-
-    void flatten(SkWriteBuffer&) const override;
-
-private:
-    SkTableMaskFilter();
-    explicit SkTableMaskFilter(const uint8_t table[256]);
-
-    uint8_t fTable[256];
-
-    typedef SkMaskFilter INHERITED;
+    SkTableMaskFilter() = delete;
 };
 
 #endif
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index c438ecd..0e88e03 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -16,6 +16,7 @@
 #include "SkImageInfoPriv.h"
 #include "SkMallocPixelRef.h"
 #include "SkMask.h"
+#include "SkMaskFilterBase.h"
 #include "SkMath.h"
 #include "SkPixelRef.h"
 #include "SkPixmapPriv.h"
@@ -543,7 +544,7 @@
     // compute our (larger?) dst bounds if we have a filter
     if (filter) {
         identity.reset();
-        if (!filter->filterMask(&dstM, srcM, identity, nullptr)) {
+        if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) {
             goto NO_FILTER_CASE;
         }
         dstM.fRowBytes = SkAlign4(dstM.fBounds.width());
@@ -567,7 +568,7 @@
     SkAutoMaskFreeImage srcCleanup(srcM.fImage);
 
     GetBitmapAlpha(*this, srcM.fImage, srcM.fRowBytes);
-    if (!filter->filterMask(&dstM, srcM, identity, nullptr)) {
+    if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) {
         goto NO_FILTER_CASE;
     }
     SkAutoMaskFreeImage dstCleanup(dstM.fImage);
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index 1f21e28..f4de215 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -13,7 +13,7 @@
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 #include "SkMask.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPaintPriv.h"
 #include "SkShaderBase.h"
 #include "SkString.h"
@@ -946,7 +946,7 @@
     SkTCopyOnFirstWrite<SkPaint> paint(origPaint);
 
     if (origPaint.getMaskFilter() != nullptr &&
-            origPaint.getMaskFilter()->getFormat() == SkMask::k3D_Format) {
+            as_MFB(origPaint.getMaskFilter())->getFormat() == SkMask::k3D_Format) {
         shader3D = sk_make_sp<Sk3DShader>(sk_ref_sp(shader));
         // we know we haven't initialized lazyPaint yet, so just do it
         paint.writable()->setShader(shader3D);
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index 20ce379..3e4722b 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -18,7 +18,7 @@
 #include "SkDraw.h"
 #include "SkDrawProcs.h"
 #include "SkFindAndPlaceGlyph.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkMatrix.h"
 #include "SkMatrixUtils.h"
 #include "SkPaint.h"
@@ -842,7 +842,7 @@
 
     SkMask dstM;
     if (paint.getMaskFilter() &&
-        paint.getMaskFilter()->filterMask(&dstM, srcM, *fMatrix, nullptr)) {
+        as_MFB(paint.getMaskFilter())->filterMask(&dstM, srcM, *fMatrix, nullptr)) {
         mask = &dstM;
     }
     SkAutoMaskFreeImage ami(dstM.fImage);
@@ -922,7 +922,8 @@
         SkRRect devRRect;
         if (rrect.transform(*fMatrix, &devRRect)) {
             SkAutoBlitterChoose blitter(fDst, *fMatrix, paint);
-            if (paint.getMaskFilter()->filterRRect(devRRect, *fMatrix, *fRC, blitter.get())) {
+            if (as_MFB(paint.getMaskFilter())->filterRRect(devRRect, *fMatrix,
+                                                           *fRC, blitter.get())) {
                 return; // filterRRect() called the blitter, so we're done
             }
         }
@@ -959,7 +960,7 @@
         SkRect pathBounds = devPath.getBounds().makeOutset(1, 1);
 
         if (paint.getMaskFilter()) {
-            paint.getMaskFilter()->computeFastBounds(pathBounds, &pathBounds);
+            as_MFB(paint.getMaskFilter())->computeFastBounds(pathBounds, &pathBounds);
 
             // Need to outset the path to work-around a bug in blurmaskfilter. When that is fixed
             // we can remove this hack. See skbug.com/5542
@@ -984,7 +985,7 @@
     if (paint.getMaskFilter()) {
         SkStrokeRec::InitStyle style = doFill ? SkStrokeRec::kFill_InitStyle
         : SkStrokeRec::kHairline_InitStyle;
-        if (paint.getMaskFilter()->filterPath(devPath, *fMatrix, *fRC, blitter, style)) {
+        if (as_MFB(paint.getMaskFilter())->filterPath(devPath, *fMatrix, *fRC, blitter, style)) {
             return; // filterPath() called the blitter, so we're done
         }
     }
@@ -1659,7 +1660,7 @@
 
         srcM.fBounds = *bounds;
         srcM.fFormat = SkMask::kA8_Format;
-        if (!filter->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
+        if (!as_MFB(filter)->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
             return false;
         }
     }
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index 75d29e1..64767e7 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 
 #include "SkAutoMalloc.h"
 #include "SkBlitter.h"
@@ -20,7 +20,7 @@
 #include "GrFragmentProcessor.h"
 #endif
 
-SkMaskFilter::NinePatch::~NinePatch() {
+SkMaskFilterBase::NinePatch::~NinePatch() {
     if (fCache) {
         SkASSERT((const void*)fMask.fImage == fCache->data());
         fCache->unref();
@@ -29,12 +29,12 @@
     }
 }
 
-bool SkMaskFilter::filterMask(SkMask*, const SkMask&, const SkMatrix&,
+bool SkMaskFilterBase::filterMask(SkMask*, const SkMask&, const SkMatrix&,
                               SkIPoint*) const {
     return false;
 }
 
-bool SkMaskFilter::asABlur(BlurRec*) const {
+bool SkMaskFilterBase::asABlur(BlurRec*) const {
     return false;
 }
 
@@ -213,8 +213,8 @@
     return path.isRect(&rects[0]);
 }
 
-bool SkMaskFilter::filterRRect(const SkRRect& devRRect, const SkMatrix& matrix,
-                               const SkRasterClip& clip, SkBlitter* blitter) const {
+bool SkMaskFilterBase::filterRRect(const SkRRect& devRRect, const SkMatrix& matrix,
+                                   const SkRasterClip& clip, SkBlitter* blitter) const {
     // Attempt to speed up drawing by creating a nine patch. If a nine patch
     // cannot be used, return false to allow our caller to recover and perform
     // the drawing another way.
@@ -230,9 +230,9 @@
     return true;
 }
 
-bool SkMaskFilter::filterPath(const SkPath& devPath, const SkMatrix& matrix,
-                              const SkRasterClip& clip, SkBlitter* blitter,
-                              SkStrokeRec::InitStyle style) const {
+bool SkMaskFilterBase::filterPath(const SkPath& devPath, const SkMatrix& matrix,
+                                  const SkRasterClip& clip, SkBlitter* blitter,
+                                  SkStrokeRec::InitStyle style) const {
     SkRect rects[2];
     int rectCount = 0;
     if (SkStrokeRec::kFill_InitStyle == style) {
@@ -289,20 +289,21 @@
     return true;
 }
 
-SkMaskFilter::FilterReturn
-SkMaskFilter::filterRRectToNine(const SkRRect&, const SkMatrix&,
-                                const SkIRect& clipBounds, NinePatch*) const {
+SkMaskFilterBase::FilterReturn
+SkMaskFilterBase::filterRRectToNine(const SkRRect&, const SkMatrix&,
+                                    const SkIRect& clipBounds, NinePatch*) const {
     return kUnimplemented_FilterReturn;
 }
 
-SkMaskFilter::FilterReturn
-SkMaskFilter::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
-                                const SkIRect& clipBounds, NinePatch*) const {
+SkMaskFilterBase::FilterReturn
+SkMaskFilterBase::filterRectsToNine(const SkRect[], int count, const SkMatrix&,
+                                    const SkIRect& clipBounds, NinePatch*) const {
     return kUnimplemented_FilterReturn;
 }
 
 #if SK_SUPPORT_GPU
-std::unique_ptr<GrFragmentProcessor> SkMaskFilter::asFragmentProcessor(const GrFPArgs& args) const {
+std::unique_ptr<GrFragmentProcessor>
+SkMaskFilterBase::asFragmentProcessor(const GrFPArgs& args) const {
     SkASSERT(args.fLocalMatrix == nullptr);
     auto fp = this->onAsFragmentProcessor(args);
     if (fp) {
@@ -312,52 +313,53 @@
     }
     return fp;
 }
-bool SkMaskFilter::hasFragmentProcessor() const {
+bool SkMaskFilterBase::hasFragmentProcessor() const {
     return this->onHasFragmentProcessor();
 }
 
-std::unique_ptr<GrFragmentProcessor> SkMaskFilter::onAsFragmentProcessor(const GrFPArgs&) const {
+std::unique_ptr<GrFragmentProcessor>
+SkMaskFilterBase::onAsFragmentProcessor(const GrFPArgs&) const {
     return nullptr;
 }
-bool SkMaskFilter::onHasFragmentProcessor() const { return false; }
+bool SkMaskFilterBase::onHasFragmentProcessor() const { return false; }
 
-bool SkMaskFilter::canFilterMaskGPU(const SkRRect& devRRect,
-                                    const SkIRect& clipBounds,
-                                    const SkMatrix& ctm,
-                                    SkRect* maskRect) const {
+bool SkMaskFilterBase::canFilterMaskGPU(const SkRRect& devRRect,
+                                        const SkIRect& clipBounds,
+                                        const SkMatrix& ctm,
+                                        SkRect* maskRect) const {
     return false;
 }
 
-bool SkMaskFilter::directFilterMaskGPU(GrContext*,
-                                       GrRenderTargetContext* renderTargetContext,
-                                       GrPaint&&,
-                                       const GrClip&,
-                                       const SkMatrix& viewMatrix,
-                                       const SkStrokeRec& strokeRec,
-                                       const SkPath& path) const {
+bool SkMaskFilterBase::directFilterMaskGPU(GrContext*,
+                                           GrRenderTargetContext* renderTargetContext,
+                                           GrPaint&&,
+                                           const GrClip&,
+                                           const SkMatrix& viewMatrix,
+                                           const SkStrokeRec& strokeRec,
+                                           const SkPath& path) const {
     return false;
 }
 
-bool SkMaskFilter::directFilterRRectMaskGPU(GrContext*,
-                                            GrRenderTargetContext* renderTargetContext,
-                                            GrPaint&&,
-                                            const GrClip&,
-                                            const SkMatrix& viewMatrix,
-                                            const SkStrokeRec& strokeRec,
-                                            const SkRRect& rrect,
-                                            const SkRRect& devRRect) const {
+bool SkMaskFilterBase::directFilterRRectMaskGPU(GrContext*,
+                                                GrRenderTargetContext* renderTargetContext,
+                                                GrPaint&&,
+                                                const GrClip&,
+                                                const SkMatrix& viewMatrix,
+                                                const SkStrokeRec& strokeRec,
+                                                const SkRRect& rrect,
+                                                const SkRRect& devRRect) const {
     return false;
 }
 
-sk_sp<GrTextureProxy> SkMaskFilter::filterMaskGPU(GrContext*,
-                                                  sk_sp<GrTextureProxy> srcProxy,
-                                                  const SkMatrix& ctm,
-                                                  const SkIRect& maskRect) const {
+sk_sp<GrTextureProxy> SkMaskFilterBase::filterMaskGPU(GrContext*,
+                                                      sk_sp<GrTextureProxy> srcProxy,
+                                                      const SkMatrix& ctm,
+                                                      const SkIRect& maskRect) const {
     return nullptr;
 }
 #endif
 
-void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
+void SkMaskFilterBase::computeFastBounds(const SkRect& src, SkRect* dst) const {
     SkMask  srcM, dstM;
 
     srcM.fBounds = src.roundOut();
diff --git a/src/core/SkMaskFilterBase.h b/src/core/SkMaskFilterBase.h
new file mode 100644
index 0000000..4b4b425
--- /dev/null
+++ b/src/core/SkMaskFilterBase.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkMaskFilterBase_DEFINED
+#define SkMaskFilterBase_DEFINED
+
+#include "SkBlurTypes.h"
+#include "SkFlattenable.h"
+#include "SkMask.h"
+#include "SkMaskFilter.h"
+#include "SkPaint.h"
+#include "SkStrokeRec.h"
+
+class GrClip;
+class GrContext;
+struct GrFPArgs;
+class GrRenderTargetContext;
+class GrPaint;
+class GrFragmentProcessor;
+class GrRenderTarget;
+class GrResourceProvider;
+class GrTexture;
+class GrTextureProxy;
+class SkBitmap;
+class SkBlitter;
+class SkCachedData;
+class SkMatrix;
+class SkPath;
+class SkRasterClip;
+class SkRRect;
+
+class SkMaskFilterBase : public SkMaskFilter {
+public:
+    /** Returns the format of the resulting mask that this subclass will return
+        when its filterMask() method is called.
+    */
+    virtual SkMask::Format getFormat() const = 0;
+
+    /** Create a new mask by filter the src mask.
+        If src.fImage == null, then do not allocate or create the dst image
+        but do fill out the other fields in dstMask.
+        If you do allocate a dst image, use SkMask::AllocImage()
+        If this returns false, dst mask is ignored.
+        @param  dst the result of the filter. If src.fImage == null, dst should not allocate its image
+        @param src the original image to be filtered.
+        @param matrix the CTM
+        @param margin   if not null, return the buffer dx/dy need when calculating the effect. Used when
+                        drawing a clipped object to know how much larger to allocate the src before
+                        applying the filter. If returning false, ignore this parameter.
+        @return true if the dst mask was correctly created.
+    */
+    virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix&,
+                            SkIPoint* margin) const;
+
+#if SK_SUPPORT_GPU
+    /**
+     *  Returns a processor if the filter can be expressed a single-pass GrProcessor without
+     *  requiring an explicit input mask. Per-pixel, the effect receives the incoming mask's
+     *  coverage as the input color and outputs the filtered covereage value. This means that each
+     *  pixel's filtered coverage must only depend on the unfiltered mask value for that pixel and
+     *  not on surrounding values.
+     */
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs& args) const;
+
+    /**
+     *  Returns true iff asFragmentProcessor() will return a processor
+     */
+    bool hasFragmentProcessor() const;
+
+    /**
+     *  If asFragmentProcessor() fails the filter may be implemented on the GPU by a subclass
+     *  overriding filterMaskGPU (declared below). That code path requires constructing a
+     *  src mask as input. Since that is a potentially expensive operation, the subclass must also
+     *  override this function to indicate whether filterTextureMaskGPU would succeeed if the mask
+     *  were to be created.
+     *
+     *  'maskRect' returns the device space portion of the mask that the filter needs. The mask
+     *  passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be
+     *  translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop)
+     *  appears at (0, 0) in the mask).
+     *
+     * Logically, how this works is:
+     *    canFilterMaskGPU is called
+     *    if (it returns true)
+     *        the returned mask rect is used for quick rejecting
+     *        either directFilterMaskGPU or directFilterRRectMaskGPU is then called
+     *        if (neither of them handle the blur)
+     *            the mask rect is used to generate the mask
+     *            filterMaskGPU is called to filter the mask
+     *
+     * TODO: this should work as:
+     *    if (canFilterMaskGPU(devShape, ...)) // rect, rrect, drrect, path
+     *        filterMaskGPU(devShape, ...)
+     * this would hide the RRect special case and the mask generation
+     */
+    virtual bool canFilterMaskGPU(const SkRRect& devRRect,
+                                  const SkIRect& clipBounds,
+                                  const SkMatrix& ctm,
+                                  SkRect* maskRect) const;
+
+    /**
+     *  Try to directly render the mask filter into the target. Returns true if drawing was
+     *  successful. If false is returned then paint is unmodified.
+     */
+    virtual bool directFilterMaskGPU(GrContext*,
+                                     GrRenderTargetContext* renderTargetContext,
+                                     GrPaint&& paint,
+                                     const GrClip&,
+                                     const SkMatrix& viewMatrix,
+                                     const SkStrokeRec& strokeRec,
+                                     const SkPath& path) const;
+    /**
+     *  Try to directly render a rounded rect mask filter into the target.  Returns
+     *  true if drawing was successful.  If false is returned then paint is unmodified.
+     */
+    virtual bool directFilterRRectMaskGPU(GrContext*,
+                                          GrRenderTargetContext* renderTargetContext,
+                                          GrPaint&& paint,
+                                          const GrClip&,
+                                          const SkMatrix& viewMatrix,
+                                          const SkStrokeRec& strokeRec,
+                                          const SkRRect& rrect,
+                                          const SkRRect& devRRect) const;
+
+    /**
+     * This function is used to implement filters that require an explicit src mask. It should only
+     * be called if canFilterMaskGPU returned true and the maskRect param should be the output from
+     * that call.
+     * Implementations are free to get the GrContext from the src texture in order to create
+     * additional textures and perform multiple passes.
+     */
+    virtual sk_sp<GrTextureProxy> filterMaskGPU(GrContext*,
+                                                sk_sp<GrTextureProxy> srcProxy,
+                                                const SkMatrix& ctm,
+                                                const SkIRect& maskRect) const;
+#endif
+
+    /**
+     * The fast bounds function is used to enable the paint to be culled early
+     * in the drawing pipeline. This function accepts the current bounds of the
+     * paint as its src param and the filter adjust those bounds using its
+     * current mask and returns the result using the dest param. Callers are
+     * allowed to provide the same struct for both src and dest so each
+     * implementation must accomodate that behavior.
+     *
+     *  The default impl calls filterMask with the src mask having no image,
+     *  but subclasses may override this if they can compute the rect faster.
+     */
+    virtual void computeFastBounds(const SkRect& src, SkRect* dest) const;
+
+    struct BlurRec {
+        SkScalar        fSigma;
+        SkBlurStyle     fStyle;
+        SkBlurQuality   fQuality;
+    };
+    /**
+     *  If this filter can be represented by a BlurRec, return true and (if not null) fill in the
+     *  provided BlurRec parameter. If this effect cannot be represented as a BlurRec, return false
+     *  and ignore the BlurRec parameter.
+     */
+    virtual bool asABlur(BlurRec*) const;
+
+protected:
+    SkMaskFilterBase() {}
+
+#if SK_SUPPORT_GPU
+    virtual std::unique_ptr<GrFragmentProcessor> onAsFragmentProcessor(const GrFPArgs&) const;
+    virtual bool onHasFragmentProcessor() const;
+#endif
+
+    enum FilterReturn {
+        kFalse_FilterReturn,
+        kTrue_FilterReturn,
+        kUnimplemented_FilterReturn
+    };
+
+    class NinePatch : ::SkNoncopyable {
+    public:
+        NinePatch() : fCache(nullptr) { }
+        ~NinePatch();
+
+        SkMask      fMask;      // fBounds must have [0,0] in its top-left
+        SkIRect     fOuterRect; // width/height must be >= fMask.fBounds'
+        SkIPoint    fCenter;    // identifies center row/col for stretching
+        SkCachedData* fCache;
+    };
+
+    /**
+     *  Override if your subclass can filter a rect, and return the answer as
+     *  a ninepatch mask to be stretched over the returned outerRect. On success
+     *  return kTrue_FilterReturn. On failure (e.g. out of memory) return
+     *  kFalse_FilterReturn. If the normal filterMask() entry-point should be
+     *  called (the default) return kUnimplemented_FilterReturn.
+     *
+     *  By convention, the caller will take the center rol/col from the returned
+     *  mask as the slice it can replicate horizontally and vertically as we
+     *  stretch the mask to fit inside outerRect. It is an error for outerRect
+     *  to be smaller than the mask's bounds. This would imply that the width
+     *  and height of the mask should be odd. This is not required, just that
+     *  the caller will call mask.fBounds.centerX() and centerY() to find the
+     *  strips that will be replicated.
+     */
+    virtual FilterReturn filterRectsToNine(const SkRect[], int count,
+                                           const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*) const;
+    /**
+     *  Similar to filterRectsToNine, except it performs the work on a round rect.
+     */
+    virtual FilterReturn filterRRectToNine(const SkRRect&, const SkMatrix&,
+                                           const SkIRect& clipBounds,
+                                           NinePatch*) const;
+
+private:
+    friend class SkDraw;
+
+    /** Helper method that, given a path in device space, will rasterize it into a kA8_Format mask
+     and then call filterMask(). If this returns true, the specified blitter will be called
+     to render that mask. Returns false if filterMask() returned false.
+     This method is not exported to java.
+     */
+    bool filterPath(const SkPath& devPath, const SkMatrix& ctm, const SkRasterClip&, SkBlitter*,
+                    SkStrokeRec::InitStyle) const;
+
+    /** Helper method that, given a roundRect in device space, will rasterize it into a kA8_Format
+     mask and then call filterMask(). If this returns true, the specified blitter will be called
+     to render that mask. Returns false if filterMask() returned false.
+     */
+    bool filterRRect(const SkRRect& devRRect, const SkMatrix& ctm, const SkRasterClip&,
+                     SkBlitter*) const;
+
+    typedef SkFlattenable INHERITED;
+};
+
+inline SkMaskFilterBase* as_MFB(SkMaskFilter* mf) {
+    return static_cast<SkMaskFilterBase*>(mf);
+}
+
+inline const SkMaskFilterBase* as_MFB(const SkMaskFilter* mf) {
+    return static_cast<const SkMaskFilterBase*>(mf);
+}
+
+inline const SkMaskFilterBase* as_MFB(const sk_sp<SkMaskFilter>& mf) {
+    return static_cast<SkMaskFilterBase*>(mf.get());
+}
+
+#endif
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index 3cfa690..0686c3b 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -1967,7 +1967,7 @@
     *storage = src->makeOutset(radius, radius);
 
     if (this->getMaskFilter()) {
-        this->getMaskFilter()->computeFastBounds(*storage, storage);
+        as_MFB(this->getMaskFilter())->computeFastBounds(*storage, storage);
     }
 
     if (this->getImageFilter()) {
@@ -2034,7 +2034,7 @@
     SkMaskFilter* maskFilter = this->getMaskFilter();
     if (maskFilter) {
         str->append("<dt>MaskFilter:</dt><dd>");
-        maskFilter->toString(str);
+        as_MFB(maskFilter)->toString(str);
         str->append("</dd>");
     }
 
diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h
index 34c3c50..4e0453a 100644
--- a/src/core/SkReadBuffer.h
+++ b/src/core/SkReadBuffer.h
@@ -13,7 +13,7 @@
 #include "SkSerialProcs.h"
 #include "SkDrawLooper.h"
 #include "SkImageFilter.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPath.h"
 #include "SkPathEffect.h"
 #include "SkPicture.h"
@@ -143,7 +143,7 @@
     sk_sp<SkColorFilter> readColorFilter() { return this->readFlattenable<SkColorFilter>(); }
     sk_sp<SkDrawLooper> readDrawLooper() { return this->readFlattenable<SkDrawLooper>(); }
     sk_sp<SkImageFilter> readImageFilter() { return this->readFlattenable<SkImageFilter>(); }
-    sk_sp<SkMaskFilter> readMaskFilter() { return this->readFlattenable<SkMaskFilter>(); }
+    sk_sp<SkMaskFilter> readMaskFilter() { return this->readFlattenable<SkMaskFilterBase>(); }
     sk_sp<SkPathEffect> readPathEffect() { return this->readFlattenable<SkPathEffect>(); }
     sk_sp<SkShader> readShader() { return this->readFlattenable<SkShaderBase>(); }
 
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index fafaf36..d082232 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -175,7 +175,7 @@
         fRec.getMatrixFrom2x2(&matrix);
 
         src.fImage = nullptr;  // only want the bounds from the filter
-        if (fMaskFilter->filterMask(&dst, src, matrix, nullptr)) {
+        if (as_MFB(fMaskFilter)->filterMask(&dst, src, matrix, nullptr)) {
             if (dst.fBounds.isEmpty() || !dst.fBounds.is16Bit()) {
                 goto SK_ERROR;
             }
@@ -504,7 +504,7 @@
 
         fRec.getMatrixFrom2x2(&matrix);
 
-        if (fMaskFilter->filterMask(&dstM, srcM, matrix, nullptr)) {
+        if (as_MFB(fMaskFilter)->filterMask(&dstM, srcM, matrix, nullptr)) {
             int width = SkFastMin32(origGlyph.fWidth, dstM.fBounds.width());
             int height = SkFastMin32(origGlyph.fHeight, dstM.fBounds.height());
             int dstRB = origGlyph.rowBytes();
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index 0d67b4a..d730d62 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -8,6 +8,7 @@
 #include "SkBlurMaskFilter.h"
 #include "SkBlurMask.h"
 #include "SkGpuBlurUtils.h"
+#include "SkMaskFilterBase.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 #include "SkMaskFilter.h"
@@ -40,7 +41,7 @@
     return SkBlurMask::ConvertRadiusToSigma(radius);
 }
 
-class SkBlurMaskFilterImpl : public SkMaskFilter {
+class SkBlurMaskFilterImpl : public SkMaskFilterBase {
 public:
     SkBlurMaskFilterImpl(SkScalar sigma, SkBlurStyle, const SkRect& occluder, uint32_t flags);
 
@@ -459,7 +460,7 @@
   static const bool c_analyticBlurRRect{true};
 #endif
 
-SkMaskFilter::FilterReturn
+SkMaskFilterBase::FilterReturn
 SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& matrix,
                                         const SkIRect& clipBounds,
                                         NinePatch* patch) const {
@@ -596,7 +597,7 @@
 // Use the faster analytic blur approach for ninepatch rects
 static const bool c_analyticBlurNinepatch{true};
 
-SkMaskFilter::FilterReturn
+SkMaskFilterBase::FilterReturn
 SkBlurMaskFilterImpl::filterRectsToNine(const SkRect rects[], int count,
                                         const SkMatrix& matrix,
                                         const SkIRect& clipBounds,
diff --git a/src/effects/SkEmbossMaskFilter.h b/src/effects/SkEmbossMaskFilter.h
index 29b70ad..89bb5c8 100644
--- a/src/effects/SkEmbossMaskFilter.h
+++ b/src/effects/SkEmbossMaskFilter.h
@@ -8,13 +8,13 @@
 #ifndef SkEmbossMaskFilter_DEFINED
 #define SkEmbossMaskFilter_DEFINED
 
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 
 /** \class SkEmbossMaskFilter
 
     This mask filter creates a 3D emboss look, by specifying a light and blur amount.
 */
-class SK_API SkEmbossMaskFilter : public SkMaskFilter {
+class SK_API SkEmbossMaskFilter : public SkMaskFilterBase {
 public:
     struct Light {
         SkScalar    fDirection[3];  // x,y,z
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index 7dff657..17aa7a1 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -10,6 +10,7 @@
 #include "SkCanvas.h"
 #include "SkColorSpaceXformer.h"
 #include "SkColor.h"
+#include "SkMaskFilterBase.h"
 #include "SkReadBuffer.h"
 #include "SkWriteBuffer.h"
 #include "SkLayerDrawLooper.h"
@@ -181,8 +182,8 @@
     if (nullptr == mf) {
         return false;
     }
-    SkMaskFilter::BlurRec maskBlur;
-    if (!mf->asABlur(&maskBlur)) {
+    SkMaskFilterBase::BlurRec maskBlur;
+    if (!as_MFB(mf)->asABlur(&maskBlur)) {
         return false;
     }
 
diff --git a/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
index 09739c0..850e440 100644
--- a/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
+++ b/src/effects/SkRRectsGaussianEdgeMaskFilter.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkMaskFilterBase.h"
 #include "SkRRectsGaussianEdgeMaskFilter.h"
 #include "SkReadBuffer.h"
 #include "SkRRect.h"
@@ -19,7 +20,7 @@
   * The round rects must have the same radii at each corner and the x&y radii
   * must also be equal.
   */
-class SkRRectsGaussianEdgeMaskFilterImpl : public SkMaskFilter {
+class SkRRectsGaussianEdgeMaskFilterImpl : public SkMaskFilterBase {
 public:
     SkRRectsGaussianEdgeMaskFilterImpl(const SkRRect& first, const SkRRect& second,
                                        SkScalar radius)
diff --git a/src/effects/SkShaderMaskFilter.cpp b/src/effects/SkShaderMaskFilter.cpp
index 8693751..f2088fc 100644
--- a/src/effects/SkShaderMaskFilter.cpp
+++ b/src/effects/SkShaderMaskFilter.cpp
@@ -6,12 +6,13 @@
  */
 
 #include "SkCanvas.h"
+#include "SkMaskFilterBase.h"
 #include "SkReadBuffer.h"
 #include "SkShaderMaskFilter.h"
 #include "SkShader.h"
 #include "SkString.h"
 
-class SkShaderMF : public SkMaskFilter {
+class SkShaderMF : public SkMaskFilterBase {
 public:
     SkShaderMF(sk_sp<SkShader> shader) : fShader(std::move(shader)) {}
 
diff --git a/src/effects/SkTableMaskFilter.cpp b/src/effects/SkTableMaskFilter.cpp
index a3b4038..5006f04 100644
--- a/src/effects/SkTableMaskFilter.cpp
+++ b/src/effects/SkTableMaskFilter.cpp
@@ -5,26 +5,48 @@
  * found in the LICENSE file.
  */
 
-
 #include "SkFixed.h"
 #include "SkReadBuffer.h"
 #include "SkString.h"
 #include "SkTableMaskFilter.h"
 #include "SkWriteBuffer.h"
 
-SkTableMaskFilter::SkTableMaskFilter() {
+class SkTableMaskFilterImpl : public SkMaskFilterBase {
+public:
+    explicit SkTableMaskFilterImpl(const uint8_t table[256]);
+
+    SkMask::Format getFormat() const override;
+    bool filterMask(SkMask*, const SkMask&, const SkMatrix&, SkIPoint*) const override;
+
+    SK_TO_STRING_OVERRIDE()
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkTableMaskFilterImpl)
+
+protected:
+    ~SkTableMaskFilterImpl() override;
+
+    void flatten(SkWriteBuffer&) const override;
+
+private:
+    SkTableMaskFilterImpl();
+
+    uint8_t fTable[256];
+
+    typedef SkMaskFilter INHERITED;
+};
+
+SkTableMaskFilterImpl::SkTableMaskFilterImpl() {
     for (int i = 0; i < 256; i++) {
         fTable[i] = i;
     }
 }
 
-SkTableMaskFilter::SkTableMaskFilter(const uint8_t table[256]) {
+SkTableMaskFilterImpl::SkTableMaskFilterImpl(const uint8_t table[256]) {
     memcpy(fTable, table, sizeof(fTable));
 }
 
-SkTableMaskFilter::~SkTableMaskFilter() {}
+SkTableMaskFilterImpl::~SkTableMaskFilterImpl() {}
 
-bool SkTableMaskFilter::filterMask(SkMask* dst, const SkMask& src,
+bool SkTableMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
                                  const SkMatrix&, SkIPoint* margin) const {
     if (src.fFormat != SkMask::kA8_Format) {
         return false;
@@ -66,24 +88,40 @@
     return true;
 }
 
-SkMask::Format SkTableMaskFilter::getFormat() const {
+SkMask::Format SkTableMaskFilterImpl::getFormat() const {
     return SkMask::kA8_Format;
 }
 
-void SkTableMaskFilter::flatten(SkWriteBuffer& wb) const {
+void SkTableMaskFilterImpl::flatten(SkWriteBuffer& wb) const {
     wb.writeByteArray(fTable, 256);
 }
 
-sk_sp<SkFlattenable> SkTableMaskFilter::CreateProc(SkReadBuffer& buffer) {
+sk_sp<SkFlattenable> SkTableMaskFilterImpl::CreateProc(SkReadBuffer& buffer) {
     uint8_t table[256];
     if (!buffer.readByteArray(table, 256)) {
         return nullptr;
     }
-    return sk_sp<SkFlattenable>(Create(table));
+    return sk_sp<SkFlattenable>(SkTableMaskFilter::Create(table));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
+SkMaskFilter* SkTableMaskFilter::Create(const uint8_t table[256]) {
+    return new SkTableMaskFilterImpl(table);
+}
+
+SkMaskFilter* SkTableMaskFilter::CreateGamma(SkScalar gamma) {
+    uint8_t table[256];
+    MakeGammaTable(table, gamma);
+    return new SkTableMaskFilterImpl(table);
+}
+
+SkMaskFilter* SkTableMaskFilter::CreateClip(uint8_t min, uint8_t max) {
+    uint8_t table[256];
+    MakeClipTable(table, min, max);
+    return new SkTableMaskFilterImpl(table);
+}
+
 void SkTableMaskFilter::MakeGammaTable(uint8_t table[256], SkScalar gamma) {
     const float dx = 1 / 255.0f;
     const float g = SkScalarToFloat(gamma);
@@ -131,7 +169,7 @@
 }
 
 #ifndef SK_IGNORE_TO_STRING
-void SkTableMaskFilter::toString(SkString* str) const {
+void SkTableMaskFilterImpl::toString(SkString* str) const {
     str->append("SkTableMaskFilter: (");
 
     str->append("table: ");
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index aff3647..b79a38a 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -17,7 +17,7 @@
 #include "GrTextureProxy.h"
 #include "SkDraw.h"
 #include "SkGr.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPaint.h"
 #include "SkTLazy.h"
 
@@ -65,7 +65,7 @@
     }
     SkAutoMaskFreeImage autoSrc(srcM.fImage);
 
-    if (!filter->filterMask(&dstM, srcM, viewMatrix, nullptr)) {
+    if (!as_MFB(filter)->filterMask(&dstM, srcM, viewMatrix, nullptr)) {
         return false;
     }
     // this will free-up dstM when we're done (allocated in filterMask())
@@ -144,7 +144,7 @@
                                        GrPaint&& paint,
                                        GrAA aa,
                                        const SkMatrix& viewMatrix,
-                                       const SkMaskFilter* maskFilter,
+                                       const SkMaskFilterBase* maskFilter,
                                        const GrStyle& style,
                                        const SkPath* path,
                                        bool pathIsMutable) {
@@ -252,7 +252,7 @@
                                          const GrStyle& style,
                                          bool pathIsMutable) {
     draw_path_with_mask_filter(context, renderTargetContext, clip, std::move(paint), aa, viewMatrix,
-                               mf, style, &path, pathIsMutable);
+                               as_MFB(mf), style, &path, pathIsMutable);
 }
 
 void GrBlurUtils::drawPathWithMaskFilter(GrContext* context,
@@ -297,7 +297,7 @@
         return;
     }
     GrAA aa = GrAA(paint.isAntiAlias());
-    SkMaskFilter* mf = paint.getMaskFilter();
+    SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter());
     if (mf && !mf->hasFragmentProcessor()) {
         // The MaskFilter wasn't already handled in SkPaintToGrPaint
         draw_path_with_mask_filter(context, renderTargetContext, clip, std::move(grPaint), aa,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 3f7dcb4..080d993 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -28,7 +28,7 @@
 #include "SkImageInfoPriv.h"
 #include "SkImage_Base.h"
 #include "SkLatticeIter.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPathEffect.h"
 #include "SkPicture.h"
 #include "SkPictureData.h"
@@ -402,7 +402,7 @@
         return;
     }
 
-    SkMaskFilter* mf = paint.getMaskFilter();
+    SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter());
     if (mf) {
         if (mf->hasFragmentProcessor()) {
             mf = nullptr; // already handled in SkPaintToGrPaint
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 9e980cd..0798f1e 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -15,7 +15,7 @@
 #include "GrTextureMaker.h"
 #include "SkDraw.h"
 #include "SkGr.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "effects/GrBicubicEffect.h"
 #include "effects/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
@@ -323,14 +323,14 @@
         viewMatrix.mapRectScaleTranslate(&devClippedDstRect, clippedDstRect);
 
         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
-        if (mf->directFilterRRectMaskGPU(fContext.get(),
-                                         fRenderTargetContext.get(),
-                                         std::move(grPaint),
-                                         this->clip(),
-                                         viewMatrix,
-                                         rec,
-                                         SkRRect::MakeRect(clippedDstRect),
-                                         SkRRect::MakeRect(devClippedDstRect))) {
+        if (as_MFB(mf)->directFilterRRectMaskGPU(fContext.get(),
+                                                 fRenderTargetContext.get(),
+                                                 std::move(grPaint),
+                                                 this->clip(),
+                                                 viewMatrix,
+                                                 rec,
+                                                 SkRRect::MakeRect(clippedDstRect),
+                                                 SkRRect::MakeRect(devClippedDstRect))) {
             return;
         }
     }
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 6b1c779..4d3ce47 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -24,7 +24,7 @@
 #include "SkConvertPixels.h"
 #include "SkData.h"
 #include "SkImageInfoPriv.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkMessageBus.h"
 #include "SkMipMap.h"
 #include "SkPM4fPriv.h"
@@ -477,7 +477,7 @@
         }
     }
 
-    SkMaskFilter* maskFilter = skPaint.getMaskFilter();
+    SkMaskFilterBase* maskFilter = as_MFB(skPaint.getMaskFilter());
     if (maskFilter) {
         if (auto mfFP = maskFilter->asFragmentProcessor(fpArgs)) {
             grPaint->addCoverageFragmentProcessor(std::move(mfFP));
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
index 69e5e77..7fce4ba 100644
--- a/src/gpu/text/GrAtlasTextBlob.cpp
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -13,6 +13,7 @@
 #include "SkColorFilter.h"
 #include "SkDrawFilter.h"
 #include "SkGlyphCache.h"
+#include "SkMaskFilterBase.h"
 #include "SkTextBlobRunIterator.h"
 #include "ops/GrAtlasTextOp.h"
 
@@ -152,7 +153,7 @@
 }
 
 bool GrAtlasTextBlob::mustRegenerate(const GrTextUtils::Paint& paint,
-                                     const SkMaskFilter::BlurRec& blurRec,
+                                     const SkMaskFilterBase::BlurRec& blurRec,
                                      const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
     // If we have LCD text then our canonical color will be set to transparent, in this case we have
     // to regenerate the blob on any color change
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index 94ec54a..12a80c4 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -14,7 +14,7 @@
 #include "GrMemoryPool.h"
 #include "GrTextUtils.h"
 #include "SkDescriptor.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkOpts.h"
 #include "SkPathEffect.h"
 #include "SkPoint3.h"
@@ -81,7 +81,7 @@
     };
 
     void setupKey(const GrAtlasTextBlob::Key& key,
-                  const SkMaskFilter::BlurRec& blurRec,
+                  const SkMaskFilterBase::BlurRec& blurRec,
                   const SkPaint& paint) {
         fKey = key;
         if (key.fHasBlur) {
@@ -190,7 +190,7 @@
         }
     }
 
-    bool mustRegenerate(const GrTextUtils::Paint&, const SkMaskFilter::BlurRec& blurRec,
+    bool mustRegenerate(const GrTextUtils::Paint&, const SkMaskFilterBase::BlurRec& blurRec,
                         const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
 
     // flush a GrAtlasTextBlob associated with a SkTextBlob
@@ -536,7 +536,7 @@
     GrGlyph** fGlyphs;
     Run* fRuns;
     GrMemoryPool* fPool;
-    SkMaskFilter::BlurRec fBlurRec;
+    SkMaskFilterBase::BlurRec fBlurRec;
     StrokeInfo fStrokeInfo;
     SkTArray<BigGlyph> fBigGlyphs;
     Key fKey;
diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp
index 4193c7d..41c4646 100644
--- a/src/gpu/text/GrAtlasTextContext.cpp
+++ b/src/gpu/text/GrAtlasTextContext.cpp
@@ -15,6 +15,7 @@
 #include "SkGr.h"
 #include "SkGraphics.h"
 #include "SkMakeUnique.h"
+#include "SkMaskFilterBase.h"
 #include "ops/GrMeshDrawOp.h"
 
 // DF sizes and thresholds for usage of the small and medium sizes. For example, above
@@ -113,13 +114,13 @@
     }
 
     sk_sp<GrAtlasTextBlob> cacheBlob;
-    SkMaskFilter::BlurRec blurRec;
+    SkMaskFilterBase::BlurRec blurRec;
     GrAtlasTextBlob::Key key;
     // It might be worth caching these things, but its not clear at this time
     // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
     const SkMaskFilter* mf = skPaint.getMaskFilter();
     bool canCache = !(skPaint.getPathEffect() ||
-                      (mf && !mf->asABlur(&blurRec)) ||
+                      (mf && !as_MFB(mf)->asABlur(&blurRec)) ||
                       drawFilter);
     SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo());
 
diff --git a/src/gpu/text/GrTextBlobCache.h b/src/gpu/text/GrTextBlobCache.h
index ac15124..7a5c3a8 100644
--- a/src/gpu/text/GrTextBlobCache.h
+++ b/src/gpu/text/GrTextBlobCache.h
@@ -48,7 +48,7 @@
 
     sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob,
                                           const GrAtlasTextBlob::Key& key,
-                                          const SkMaskFilter::BlurRec& blurRec,
+                                          const SkMaskFilterBase::BlurRec& blurRec,
                                           const SkPaint& paint) {
         sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob));
         cacheBlob->setupKey(key, blurRec, paint);
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 0628b8f..91f170d 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -21,7 +21,7 @@
 #include "SkImageFilterCache.h"
 #include "SkJpegEncoder.h"
 #include "SkMakeUnique.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPDFBitmap.h"
 #include "SkPDFCanon.h"
 #include "SkPDFDocument.h"
@@ -841,7 +841,7 @@
     SkAutoMaskFreeImage srcAutoMaskFreeImage(sourceMask.fImage);
     SkMask dstMask;
     SkIPoint margin;
-    if (!paint->getMaskFilter()->filterMask(&dstMask, sourceMask, ctm, &margin)) {
+    if (!as_MFB(paint->getMaskFilter())->filterMask(&dstMask, sourceMask, ctm, &margin)) {
         return;
     }
     SkIRect dstMaskBounds = dstMask.fBounds;
diff --git a/src/xps/SkXPSDevice.cpp b/src/xps/SkXPSDevice.cpp
index dcf88aa..94c6b79 100644
--- a/src/xps/SkXPSDevice.cpp
+++ b/src/xps/SkXPSDevice.cpp
@@ -34,7 +34,7 @@
 #include "SkImage.h"
 #include "SkImageEncoder.h"
 #include "SkImagePriv.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkPaint.h"
 #include "SkPathEffect.h"
 #include "SkPathOps.h"
@@ -1615,7 +1615,7 @@
 
             //[Mask -> Mask]
             SkMask filteredMask;
-            if (filter->filterMask(&filteredMask, rasteredMask, matrix, nullptr)) {
+            if (as_MFB(filter)->filterMask(&filteredMask, rasteredMask, matrix, nullptr)) {
                 mask = &filteredMask;
             }
             SkAutoMaskFreeImage filteredAmi(filteredMask.fImage);
diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp
index 6c5ef26..d8d917f 100644
--- a/tests/BlurTest.cpp
+++ b/tests/BlurTest.cpp
@@ -12,6 +12,7 @@
 #include "SkColorFilter.h"
 #include "SkEmbossMaskFilter.h"
 #include "SkLayerDrawLooper.h"
+#include "SkMaskFilterBase.h"
 #include "SkMath.h"
 #include "SkPaint.h"
 #include "SkPath.h"
@@ -456,8 +457,8 @@
                     REPORTER_ASSERT(reporter, sigma <= 0);
                 } else {
                     REPORTER_ASSERT(reporter, sigma > 0);
-                    SkMaskFilter::BlurRec rec;
-                    bool success = mf->asABlur(&rec);
+                    SkMaskFilterBase::BlurRec rec;
+                    bool success = as_MFB(mf)->asABlur(&rec);
                     if (flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag) {
                         REPORTER_ASSERT(reporter, !success);
                     } else {
@@ -483,8 +484,8 @@
             const SkScalar sigma = sigmas[j];
             auto mf(SkEmbossMaskFilter::Make(sigma, light));
             if (mf) {
-                SkMaskFilter::BlurRec rec;
-                bool success = mf->asABlur(&rec);
+                SkMaskFilterBase::BlurRec rec;
+                bool success = as_MFB(mf)->asABlur(&rec);
                 REPORTER_ASSERT(reporter, !success);
             }
         }
diff --git a/tests/FlattenDrawableTest.cpp b/tests/FlattenDrawableTest.cpp
index 9df8ba0..411b082 100644
--- a/tests/FlattenDrawableTest.cpp
+++ b/tests/FlattenDrawableTest.cpp
@@ -284,3 +284,18 @@
     REPORTER_ASSERT(r, out);
     REPORTER_ASSERT(r, !strcmp("SkRecordedDrawable", out->getTypeName()));
 }
+
+// be sure these constructs compile, don't assert, and return null
+DEF_TEST(Flattenable_EmptyDeserialze, reporter) {
+    auto data = SkData::MakeEmpty();
+
+    #define test(name)  REPORTER_ASSERT(reporter, !name::Deserialize(data->data(), data->size()))
+    test(SkPathEffect);
+    test(SkMaskFilter);
+    test(SkShaderBase); // todo: make this just be shader!
+    test(SkColorFilter);
+    test(SkImageFilter);
+    test(SkDrawLooper);
+    #undef test
+}
+
diff --git a/tools/debugger/SkDrawCommand.cpp b/tools/debugger/SkDrawCommand.cpp
index 34189fa..c1f0677 100644
--- a/tools/debugger/SkDrawCommand.cpp
+++ b/tools/debugger/SkDrawCommand.cpp
@@ -15,7 +15,7 @@
 #include "SkDashPathEffect.h"
 #include "SkImageFilter.h"
 #include "SkJsonWriteBuffer.h"
-#include "SkMaskFilter.h"
+#include "SkMaskFilterBase.h"
 #include "SkObjectParser.h"
 #include "SkPaintDefaults.h"
 #include "SkPathEffect.h"
@@ -1071,8 +1071,8 @@
                                    UrlDataManager& urlDataManager) {
     SkMaskFilter* maskFilter = paint.getMaskFilter();
     if (maskFilter != nullptr) {
-        SkMaskFilter::BlurRec blurRec;
-        if (maskFilter->asABlur(&blurRec)) {
+        SkMaskFilterBase::BlurRec blurRec;
+        if (as_MFB(maskFilter)->asABlur(&blurRec)) {
             Json::Value blur(Json::objectValue);
             blur[SKDEBUGCANVAS_ATTRIBUTE_SIGMA] = Json::Value(blurRec.fSigma);
             switch (blurRec.fStyle) {