Move SkImageFilter functionality into private SkImageFilter_Base

Bug: skia:9281
Change-Id: I189dbf652580805641f8c4b9a6587cf15a9049dd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/231256
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 3f60698..e80b042 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -14,6 +14,6 @@
    - Provide new factory API in include/effects/SkImageFilters
    - Consolidated enum types to use SkTileMode and SkColorChannel
    - Hide filter implementation classes
-   - Bumps SkPicture version number
+   - Hide previously public functions on SkImageFilter that were intended for internal use only
 
  * SkColorFilters::HSLAMatrix - new matrix color filter operating in HSLA space.
diff --git a/fuzz/FuzzPolyUtils.cpp b/fuzz/FuzzPolyUtils.cpp
index 9a9d388..e4b3c8b 100644
--- a/fuzz/FuzzPolyUtils.cpp
+++ b/fuzz/FuzzPolyUtils.cpp
@@ -6,6 +6,8 @@
  */
 
 #include "fuzz/Fuzz.h"
+
+#include "include/private/SkTemplates.h"
 #include "src/utils/SkPolyUtils.h"
 
 void inline ignoreResult(bool ) {}
diff --git a/gm/imagefiltersbase.cpp b/gm/imagefiltersbase.cpp
index 4989bef..80f28cb 100644
--- a/gm/imagefiltersbase.cpp
+++ b/gm/imagefiltersbase.cpp
@@ -27,7 +27,7 @@
 #include "include/effects/SkColorFilterImageFilter.h"
 #include "include/effects/SkDropShadowImageFilter.h"
 #include "include/utils/SkTextUtils.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkSpecialImage.h"
 #include "tools/ToolUtils.h"
 
@@ -35,7 +35,7 @@
 
 class SkReadBuffer;
 
-class FailImageFilter : public SkImageFilter {
+class FailImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<SkImageFilter> Make() {
         return sk_sp<SkImageFilter>(new FailImageFilter);
@@ -52,7 +52,7 @@
 
 private:
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 sk_sp<SkFlattenable> FailImageFilter::CreateProc(SkReadBuffer& buffer) {
@@ -60,7 +60,7 @@
     return FailImageFilter::Make();
 }
 
-class IdentityImageFilter : public SkImageFilter {
+class IdentityImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<SkImageFilter> Make(sk_sp<SkImageFilter> input) {
         return sk_sp<SkImageFilter>(new IdentityImageFilter(std::move(input)));
@@ -78,7 +78,7 @@
 private:
     IdentityImageFilter(sk_sp<SkImageFilter> input) : INHERITED(&input, 1, nullptr) {}
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 // Register these image filters as deserializable before main().
diff --git a/gm/offsetimagefilter.cpp b/gm/offsetimagefilter.cpp
index 4b9fcaa..38ae539 100644
--- a/gm/offsetimagefilter.cpp
+++ b/gm/offsetimagefilter.cpp
@@ -20,6 +20,7 @@
 #include "include/core/SkString.h"
 #include "include/effects/SkImageSource.h"
 #include "include/effects/SkOffsetImageFilter.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "tools/ToolUtils.h"
 
 #include <utility>
@@ -128,8 +129,9 @@
             p.setStyle(SkPaint::kFill_Style);
         }
 
-        if (imgf && imgf->cropRectIsSet()) {
-            SkImageFilter::CropRect cr = imgf->getCropRect();
+        // Visualize the crop rect for debugging
+        if (imgf && as_IFB(imgf)->cropRectIsSet()) {
+            SkImageFilter::CropRect cr = as_IFB(imgf)->getCropRect();
 
             p.setColor(0x66FF00FF);
             p.setStyle(SkPaint::kStroke_Style);
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index 5991fdb..d6da4d7 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -8,87 +8,28 @@
 #ifndef SkImageFilter_DEFINED
 #define SkImageFilter_DEFINED
 
-#include "include/core/SkColorSpace.h"
 #include "include/core/SkFilterQuality.h"
 #include "include/core/SkFlattenable.h"
-#include "include/core/SkImageInfo.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkRect.h"
-#include "include/private/SkMutex.h"
-#include "include/private/SkTArray.h"
-#include "include/private/SkTemplates.h"
-#if SK_SUPPORT_GPU
-#include "include/gpu/GrTypes.h"
-#endif
 
-class GrFragmentProcessor;
 class SkColorFilter;
-struct SkIPoint;
-class GrRecordingContext;
-class SkSpecialImage;
-class SkImageFilterCache;
-struct SkImageFilterCacheKey;
 
 /**
- *  Base class for image filters. If one is installed in the paint, then
- *  all drawing occurs as usual, but it is as if the drawing happened into an
- *  offscreen (before the xfermode is applied). This offscreen bitmap will
- *  then be handed to the imagefilter, who in turn creates a new bitmap which
- *  is what will finally be drawn to the device (using the original xfermode).
+ *  Base class for image filters. If one is installed in the paint, then all drawing occurs as
+ *  usual, but it is as if the drawing happened into an offscreen (before the xfermode is applied).
+ *  This offscreen bitmap will then be handed to the imagefilter, who in turn creates a new bitmap
+ *  which is what will finally be drawn to the device (using the original xfermode).
+ *
+ *  The local space of image filters matches the local space of the drawn geometry. For instance if
+ *  there is rotation on the canvas, the blur will be computed along those rotated axes and not in
+ *  the device space. In order to achieve this result, the actual drawing of the geometry may happen
+ *  in an unrotated coordinate system so that the filtered image can be computed more easily, and
+ *  then it will be post transformed to match what would have been produced if the geometry were
+ *  drawn with the total canvas matrix to begin with.
  */
 class SK_API SkImageFilter : public SkFlattenable {
 public:
-    // Extra information about the output of a filter DAG. For now, this is just the color space
-    // (of the original requesting device). This is used when constructing intermediate rendering
-    // surfaces, so that we ensure we land in a surface that's similar/compatible to the final
-    // consumer of the DAG's output.
-    class OutputProperties {
-    public:
-        explicit OutputProperties(SkColorType colorType, SkColorSpace* colorSpace)
-            : fColorType(colorType), fColorSpace(colorSpace) {}
-
-        SkColorType colorType() const { return fColorType; }
-        SkColorSpace* colorSpace() const { return fColorSpace; }
-
-    private:
-        SkColorType fColorType;
-        // This will be a pointer to the device's color space, and our lifetime is bounded by
-        // the device, so we can store a bare pointer.
-        SkColorSpace* fColorSpace;
-    };
-
-    class Context {
-    public:
-        Context(const SkMatrix& ctm, const SkIRect& clipBounds, SkImageFilterCache* cache,
-                const OutputProperties& outputProperties)
-            : fCTM(ctm)
-            , fClipBounds(clipBounds)
-            , fCache(cache)
-            , fOutputProperties(outputProperties)
-        {}
-
-        const SkMatrix& ctm() const { return fCTM; }
-        const SkIRect& clipBounds() const { return fClipBounds; }
-        SkImageFilterCache* cache() const { return fCache; }
-        const OutputProperties& outputProperties() const { return fOutputProperties; }
-
-        /**
-         *  Since a context can be build directly, its constructor has no chance to
-         *  "return null" if it's given invalid or unsupported inputs. Call this to
-         *  know of the the context can be used.
-         *
-         *  The SkImageFilterCache Key, for example, requires a finite ctm (no infinities
-         *  or NaN), so that test is part of isValid.
-         */
-        bool isValid() const { return fCTM.isFinite(); }
-
-    private:
-        SkMatrix               fCTM;
-        SkIRect                fClipBounds;
-        SkImageFilterCache*    fCache;
-        OutputProperties       fOutputProperties;
-    };
-
     class CropRect {
     public:
         enum CropEdge {
@@ -105,14 +46,14 @@
         const SkRect& rect() const { return fRect; }
 
         /**
-         *  Apply this cropRect to the imageBounds. If a given edge of the cropRect is not
-         *  set, then the corresponding edge from imageBounds will be used. If "embiggen"
-         *  is true, the crop rect is allowed to enlarge the size of the rect, otherwise
-         *  it may only reduce the rect. Filters that can affect transparent black should
-         *  pass "true", while all other filters should pass "false".
+         *  Apply this cropRect to the imageBounds. If a given edge of the cropRect is not set, then
+         *  the corresponding edge from imageBounds will be used. If "embiggen" is true, the crop
+         *  rect is allowed to enlarge the size of the rect, otherwise it may only reduce the rect.
+         *  Filters that can affect transparent black should pass "true", while all other filters
+         *  should pass "false".
          *
-         *  Note: imageBounds is in "device" space, as the output cropped rectangle will be,
-         *  so the matrix is ignored for those. It is only applied the croprect's bounds.
+         *  Note: imageBounds is in "device" space, as the output cropped rectangle will be, so the
+         *  matrix is ignored for those. It is only applied to the cropRect's bounds.
          */
         void applyTo(const SkIRect& imageBounds, const SkMatrix& matrix, bool embiggen,
                      SkIRect* cropped) const;
@@ -122,61 +63,26 @@
         uint32_t fFlags;
     };
 
-    enum TileUsage {
-        kPossible_TileUsage,    //!< the created device may be drawn tiled
-        kNever_TileUsage,       //!< the created device will never be drawn tiled
-    };
-
-    /**
-     *  Request a new filtered image to be created from the src image.
-     *
-     *  The context contains the environment in which the filter is occurring.
-     *  It includes the clip bounds, CTM and cache.
-     *
-     *  Offset is the amount to translate the resulting image relative to the
-     *  src when it is drawn. This is an out-param.
-     *
-     *  If the result image cannot be created, or the result would be
-     *  transparent black, return null, in which case the offset parameter
-     *  should be ignored by the caller.
-     *
-     *  TODO: Right now the imagefilters sometimes return empty result bitmaps/
-     *        specialimages. That doesn't seem quite right.
-     */
-    sk_sp<SkSpecialImage> filterImage(SkSpecialImage* src, const Context& context,
-                                      SkIPoint* offset) const;
-
     enum MapDirection {
         kForward_MapDirection,
         kReverse_MapDirection,
     };
     /**
-     * Map a device-space rect recursively forward or backward through the
-     * filter DAG. kForward_MapDirection is used to determine which pixels of
-     * the destination canvas a source image rect would touch after filtering.
-     * kReverse_MapDirection is used to determine which rect of the source
-     * image would be required to fill the given rect (typically, clip bounds).
-     * Used for clipping and temp-buffer allocations, so the result need not
-     * be exact, but should never be smaller than the real answer. The default
-     * implementation recursively unions all input bounds, or returns the
-     * source rect if no inputs.
+     * Map a device-space rect recursively forward or backward through the filter DAG.
+     * kForward_MapDirection is used to determine which pixels of the destination canvas a source
+     * image rect would touch after filtering. kReverse_MapDirection is used to determine which rect
+     * of the source image would be required to fill the given rect (typically, clip bounds). Used
+     * for clipping and temp-buffer allocations, so the result need not be exact, but should never
+     * be smaller than the real answer. The default implementation recursively unions all input
+     * bounds, or returns the source rect if no inputs.
      *
      * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward
-     * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting
-     * answer may be incorrect.
+     * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting answer
+     * may be incorrect.
      */
     SkIRect filterBounds(const SkIRect& src, const SkMatrix& ctm,
                          MapDirection, const SkIRect* inputRect = nullptr) const;
 
-#if SK_SUPPORT_GPU
-    static sk_sp<SkSpecialImage> DrawWithFP(GrRecordingContext* context,
-                                            std::unique_ptr<GrFragmentProcessor>
-                                                    fp,
-                                            const SkIRect& bounds,
-                                            const OutputProperties& outputProperties,
-                                            GrProtected isProtected = GrProtected::kNo);
-#endif
-
     /**
      *  Returns whether this image filter is a color filter and puts the color filter into the
      *  "filterPtr" parameter if it can. Does nothing otherwise.
@@ -184,51 +90,30 @@
      *  If this returns true, then if filterPtr is not null, it must be set to a ref'd colorfitler
      *  (i.e. it may not be set to NULL).
      */
-    bool isColorFilterNode(SkColorFilter** filterPtr) const {
-        return this->onIsColorFilterNode(filterPtr);
-    }
+    bool isColorFilterNode(SkColorFilter** filterPtr) const;
 
     // DEPRECATED : use isColorFilterNode() instead
     bool asColorFilter(SkColorFilter** filterPtr) const {
         return this->isColorFilterNode(filterPtr);
     }
 
-    void removeKey(const SkImageFilterCacheKey& key) const;
-
     /**
      *  Returns true (and optionally returns a ref'd filter) if this imagefilter can be completely
-     *  replaced by the returned colorfilter. i.e. the two effects will affect drawing in the
-     *  same way.
+     *  replaced by the returned colorfilter. i.e. the two effects will affect drawing in the same
+     *  way.
      */
     bool asAColorFilter(SkColorFilter** filterPtr) const;
 
     /**
-     *  Returns the number of inputs this filter will accept (some inputs can
-     *  be NULL).
+     *  Returns the number of inputs this filter will accept (some inputs can be NULL).
      */
-    int countInputs() const { return fInputs.count(); }
+    int countInputs() const;
 
     /**
-     *  Returns the input filter at a given index, or NULL if no input is
-     *  connected.  The indices used are filter-specific.
+     *  Returns the input filter at a given index, or NULL if no input is connected.  The indices
+     *  used are filter-specific.
      */
-    SkImageFilter* getInput(int i) const {
-        SkASSERT(i < fInputs.count());
-        return fInputs[i].get();
-    }
-
-    /**
-     *  Returns whether any edges of the crop rect have been set. The crop
-     *  rect is set at construction time, and determines which pixels from the
-     *  input image will be processed, and which pixels in the output image will be allowed.
-     *  The size of the crop rect should be
-     *  used as the size of the destination image. The origin of this rect
-     *  should be used to offset access to the input images, and should also
-     *  be added to the "offset" parameter in onFilterImage.
-     */
-    bool cropRectIsSet() const { return fCropRect.flags() != 0x0; }
-
-    CropRect getCropRect() const { return fCropRect; }
+    const SkImageFilter* getInput(int i) const;
 
     // Default impl returns union of all input bounds.
     virtual SkRect computeFastBounds(const SkRect& bounds) const;
@@ -243,13 +128,6 @@
     sk_sp<SkImageFilter> makeWithLocalMatrix(const SkMatrix& matrix) const;
 
     /**
-     *  ImageFilters can natively handle scaling and translate components in the CTM. Only some of
-     *  them can handle affine (or more complex) matrices. This call returns true iff the filter
-     *  and all of its (non-null) inputs can handle these more complex matrices.
-     */
-    bool canHandleComplexCTM() const;
-
-    /**
      * Return an imagefilter which transforms its input by the given matrix.
      * DEPRECATED: Use include/effects/SkImageFilters::MatrixTransform
      */
@@ -268,206 +146,17 @@
     static sk_sp<SkImageFilter> Deserialize(const void* data, size_t size,
                                           const SkDeserialProcs* procs = nullptr) {
         return sk_sp<SkImageFilter>(static_cast<SkImageFilter*>(
-                                  SkFlattenable::Deserialize(
-                                  kSkImageFilter_Type, data, size, procs).release()));
+                SkFlattenable::Deserialize(kSkImageFilter_Type, data, size, procs).release()));
     }
 
 protected:
-    class Common {
-    public:
-        /**
-         *  Attempt to unflatten the cropRect and the expected number of input filters.
-         *  If any number of input filters is valid, pass -1.
-         *  If this fails (i.e. corrupt buffer or contents) then return false and common will
-         *  be left uninitialized.
-         *  If this returns true, then inputCount() is the number of found input filters, each
-         *  of which may be NULL or a valid imagefilter.
-         */
-        bool unflatten(SkReadBuffer&, int expectedInputs);
-
-        const CropRect& cropRect() const { return fCropRect; }
-        int             inputCount() const { return fInputs.count(); }
-        sk_sp<SkImageFilter>* inputs() { return fInputs.begin(); }
-
-        sk_sp<SkImageFilter> getInput(int index) { return fInputs[index]; }
-
-    private:
-        CropRect fCropRect;
-        // most filters accept at most 2 input-filters
-        SkSTArray<2, sk_sp<SkImageFilter>, true> fInputs;
-    };
-
-    SkImageFilter(sk_sp<SkImageFilter> const* inputs, int inputCount, const CropRect* cropRect);
-
-    ~SkImageFilter() override;
-
-    /**
-     *  Constructs a new SkImageFilter read from an SkReadBuffer object.
-     *
-     *  @param inputCount    The exact number of inputs expected for this SkImageFilter object.
-     *                       -1 can be used if the filter accepts any number of inputs.
-     *  @param rb            SkReadBuffer object from which the SkImageFilter is read.
-     */
-    explicit SkImageFilter(int inputCount, SkReadBuffer& rb);
-
-    void flatten(SkWriteBuffer&) const override;
-
-    const CropRect* getCropRectIfSet() const {
-        return this->cropRectIsSet() ? &fCropRect : nullptr;
-    }
-
-    /**
-     *  This is the virtual which should be overridden by the derived class
-     *  to perform image filtering.
-     *
-     *  src is the original primitive bitmap. If the filter has a connected
-     *  input, it should recurse on that input and use that in place of src.
-     *
-     *  The matrix is the current matrix on the canvas.
-     *
-     *  Offset is the amount to translate the resulting image relative to the
-     *  src when it is drawn. This is an out-param.
-     *
-     *  If the result image cannot be created (either because of error or if, say, the result
-     *  is entirely clipped out), this should return nullptr.
-     *  Callers that affect transparent black should explicitly handle nullptr
-     *  results and press on. In the error case this behavior will produce a better result
-     *  than nothing and is necessary for the clipped out case.
-     *  If the return value is nullptr then offset should be ignored.
-     */
-    virtual sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* src, const Context&,
-                                                SkIPoint* offset) const = 0;
-
-    /**
-     * This function recurses into its inputs with the given rect (first
-     * argument), calls filterBounds() with the given map direction on each,
-     * and returns the union of those results. If a derived class has special
-     * recursion requirements (e.g., it has an input which does not participate
-     * in bounds computation), it can be overridden here.
-     * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward
-     * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting
-     * answer may be incorrect.
-     *
-     * Note that this function is *not* responsible for mapping the rect for
-     * this node's filter bounds requirements (i.e., calling
-     * onFilterNodeBounds()); that is handled by filterBounds().
-     */
-    virtual SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm,
-                                   MapDirection, const SkIRect* inputRect) const;
-
-    /**
-     * Performs a forwards or reverse mapping of the given rect to accommodate
-     * this filter's margin requirements. kForward_MapDirection is used to
-     * determine the destination pixels which would be touched by filtering
-     * the given source rect (e.g., given source bitmap bounds,
-     * determine the optimal bounds of the filtered offscreen bitmap).
-     * kReverse_MapDirection is used to determine which pixels of the
-     * input(s) would be required to fill the given destination rect
-     * (e.g., clip bounds). NOTE: these operations may not be the
-     * inverse of the other. For example, blurring expands the given rect
-     * in both forward and reverse directions. Unlike
-     * onFilterBounds(), this function is non-recursive.
-     * In kReverse mode, 'inputRect' will be the device space bounds of the input pixels. In
-     * kForward mode, 'inputRect' should always be null. If 'inputRect' is null in kReverse mode
-     * the resulting answer may be incorrect.
-     */
-    virtual SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
-                                       MapDirection, const SkIRect* inputRect) const;
-
-    // Helper function which invokes filter processing on the input at the
-    // specified "index". If the input is null, it returns "src" and leaves
-    // "offset" untouched. If the input is non-null, it
-    // calls filterImage() on that input, and returns the result.
-    sk_sp<SkSpecialImage> filterInput(int index,
-                                      SkSpecialImage* src,
-                                      const Context&,
-                                      SkIPoint* offset) const;
-
-    /**
-     *  Return true (and return a ref'd colorfilter) if this node in the DAG is just a
-     *  colorfilter w/o CropRect constraints.
-     */
-    virtual bool onIsColorFilterNode(SkColorFilter** /*filterPtr*/) const {
-        return false;
-    }
-
-    /**
-     *  Override this to describe the behavior of your subclass - as a leaf node. The caller will
-     *  take care of calling your inputs (and return false if any of them could not handle it).
-     */
-    virtual bool onCanHandleComplexCTM() const { return false; }
-
-    /** Given a "srcBounds" rect, computes destination bounds for this filter.
-     *  "dstBounds" are computed by transforming the crop rect by the context's
-     *  CTM, applying it to the initial bounds, and intersecting the result with
-     *  the context's clip bounds.  "srcBounds" (if non-null) are computed by
-     *  intersecting the initial bounds with "dstBounds", to ensure that we never
-     *  sample outside of the crop rect (this restriction may be relaxed in the
-     *  future).
-     */
-    bool applyCropRect(const Context&, const SkIRect& srcBounds, SkIRect* dstBounds) const;
-
-    /** A variant of the above call which takes the original source bitmap and
-     *  source offset. If the resulting crop rect is not entirely contained by
-     *  the source bitmap's bounds, it creates a new bitmap in "result" and
-     *  pads the edges with transparent black. In that case, the srcOffset is
-     *  modified to be the same as the bounds, since no further adjustment is
-     *  needed by the caller. This version should only be used by filters
-     *  which are not capable of processing a smaller source bitmap into a
-     *  larger destination.
-     */
-    sk_sp<SkSpecialImage> applyCropRectAndPad(const Context&, SkSpecialImage* src,
-                                              SkIPoint* srcOffset, SkIRect* bounds) const;
-
-    /**
-     *  Creates a modified Context for use when recursing up the image filter DAG.
-     *  The clip bounds are adjusted to accommodate any margins that this
-     *  filter requires by calling this node's
-     *  onFilterNodeBounds(..., kReverse_MapDirection).
-     */
-    Context mapContext(const Context& ctx) const;
-
-#if SK_SUPPORT_GPU
-    /**
-     *  Returns a version of the passed-in image (possibly the original), that is in a colorspace
-     *  with the same gamut as the one from the OutputProperties. This allows filters that do many
-     *  texture samples to guarantee that any color space conversion has happened before running.
-     */
-    static sk_sp<SkSpecialImage> ImageToColorSpace(SkSpecialImage* src, const OutputProperties&);
-#endif
 
     sk_sp<SkImageFilter> refMe() const {
         return sk_ref_sp(const_cast<SkImageFilter*>(this));
     }
 
-    // If 'srcBounds' will sample outside the border of 'originalSrcBounds' (i.e., the sample
-    // will wrap around to the other side) we must preserve the far side of the src along that
-    // axis (e.g., if we will sample beyond the left edge of the src, the right side must be
-    // preserved for the repeat sampling to work).
-    static SkIRect DetermineRepeatedSrcBound(const SkIRect& srcBounds,
-                                             const SkIVector& filterOffset,
-                                             const SkISize& filterSize,
-                                             const SkIRect& originalSrcBounds);
-
 private:
-    friend class SkGraphics;
-    friend bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b);
-    // Helper method to inspect onFilterNodeBounds() without going through filterBounds()
-    friend SkIRect SkFilterNodeBounds(const SkImageFilter*, const SkIRect& srcRect, const SkMatrix&,
-                                      MapDirection, const SkIRect* inputRect);
-
-    static void PurgeCache();
-
-    void init(sk_sp<SkImageFilter> const* inputs, int inputCount, const CropRect* cropRect);
-
-    bool usesSrcInput() const { return fUsesSrcInput; }
-    virtual bool affectsTransparentBlack() const { return false; }
-
-    SkAutoSTArray<2, sk_sp<SkImageFilter>> fInputs;
-
-    bool fUsesSrcInput;
-    CropRect fCropRect;
-    uint32_t fUniqueID; // Globally unique
+    friend class SkImageFilter_Base;
 
     typedef SkFlattenable INHERITED;
 };
diff --git a/include/private/SkFloatingPoint.h b/include/private/SkFloatingPoint.h
index adac689..f0ca4bb 100644
--- a/include/private/SkFloatingPoint.h
+++ b/include/private/SkFloatingPoint.h
@@ -31,6 +31,7 @@
 
 constexpr float SK_FloatSqrt2 = 1.41421356f;
 constexpr float SK_FloatPI    = 3.14159265f;
+constexpr double SK_DoublePI  = 3.14159265358979323846264338327950288;
 
 // C++98 cmath std::pow seems to be the earliest portable way to get float pow.
 // However, on Linux including cmath undefines isfinite.
diff --git a/samplecode/SampleImageFilterDAG.cpp b/samplecode/SampleImageFilterDAG.cpp
index a2f9e22..40301d4 100644
--- a/samplecode/SampleImageFilterDAG.cpp
+++ b/samplecode/SampleImageFilterDAG.cpp
@@ -23,7 +23,7 @@
 #include "include/effects/SkGradientShader.h"
 #include "include/effects/SkImageFilters.h"
 
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkSpecialImage.h"
 
 #include "tools/ToolUtils.h"
@@ -122,9 +122,8 @@
 
             // For isolated forward filtering, it uses the same input but should not be propagated
             // to the inputs, so get the filter node bounds directly.
-            fForwardIsolatedBounds = SkFilterNodeBounds(
-                    fFilter.get(), srcRect, fLocalCTM,
-                    SkImageFilter::kForward_MapDirection, nullptr);
+            fForwardIsolatedBounds = as_IFB(fFilter)->filterNodeBounds(
+                    srcRect, fLocalCTM, SkImageFilter::kForward_MapDirection, nullptr);
         } else {
             fForwardBounds = srcRect;
             fForwardIsolatedBounds = srcRect;
@@ -138,9 +137,8 @@
 
     void computeReverseLocalIsolatedBounds(const SkIRect& srcRect) {
         if (fFilter) {
-            fReverseLocalIsolatedBounds = SkFilterNodeBounds(
-                    fFilter.get(), srcRect, fLocalCTM,
-                    SkImageFilter::kReverse_MapDirection, &srcRect);
+            fReverseLocalIsolatedBounds = as_IFB(fFilter)->filterNodeBounds(
+                    srcRect, fLocalCTM, SkImageFilter::kReverse_MapDirection, &srcRect);
         } else {
             fReverseLocalIsolatedBounds = srcRect;
         }
@@ -182,9 +180,8 @@
                 // To calculate the appropriate intermediate reverse bounds for the children, we
                 // need this node's onFilterNodeBounds() results based on its parents' bounds (the
                 // current 'srcRect').
-                nextSrcRect = SkFilterNodeBounds(
-                    fFilter.get(), srcRect, fLocalCTM,
-                    SkImageFilter::kReverse_MapDirection, &srcRect);
+                nextSrcRect = as_IFB(fFilter)->filterNodeBounds(
+                    srcRect, fLocalCTM, SkImageFilter::kReverse_MapDirection, &srcRect);
             }
 
             // Fill in the children. The union of these bounds should equal the value calculated
@@ -242,14 +239,14 @@
                             const SkImageFilter* rootFilter) {
     // Emulate SkCanvas::internalSaveLayer's decomposition of the CTM.
     SkMatrix local;
-    sk_sp<SkImageFilter> finalFilter = SkApplyCTMToFilter(rootFilter, ctm, &local);
+    sk_sp<SkImageFilter> finalFilter = as_IFB(rootFilter)->applyCTM(ctm, &local);
 
     // In ApplyCTMToFilter, the CTM is decomposed such that CTM = remainder * local. The matrix
     // that is embedded in 'finalFilter' is actually local^-1*remainder*local to account for
     // how SkMatrixImageFilter is specified, but we want the true remainder since it is what should
     // transform the results to put in the correct place after filtering.
     SkMatrix invLocal, remaining;
-    if (!SkIsSameFilter(finalFilter.get(), rootFilter) && local.invert(&invLocal)) {
+    if (as_IFB(rootFilter)->uniqueID() != as_IFB(finalFilter)->uniqueID()) {
         remaining = SkMatrix::Concat(ctm, invLocal);
     } else {
         remaining = SkMatrix::I();
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index 162bf11..bf0d4e5 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -18,6 +18,7 @@
 #include "src/core/SkDraw.h"
 #include "src/core/SkGlyphRun.h"
 #include "src/core/SkImageFilterCache.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkMakeUnique.h"
 #include "src/core/SkRasterClip.h"
 #include "src/core/SkSpecialImage.h"
@@ -613,10 +614,11 @@
             SkMatrix::MakeTrans(SkIntToScalar(-x), SkIntToScalar(-y)), this->ctm());
         const SkIRect clipBounds = fRCStack.rc().getBounds().makeOffset(-x, -y);
         sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
-        SkImageFilter::OutputProperties outputProperties(fBitmap.colorType(), fBitmap.colorSpace());
-        SkImageFilter::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
+        SkImageFilter_Base::OutputProperties outputProperties(fBitmap.colorType(),
+                                                              fBitmap.colorSpace());
+        SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
 
-        filteredImage = filter->filterImage(src, ctx, &offset);
+        filteredImage = as_IFB(filter)->filterImage(src, ctx, &offset);
         if (!filteredImage) {
             return;
         }
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 02a29ba..fd6e093 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -28,7 +28,7 @@
 #include "src/core/SkDraw.h"
 #include "src/core/SkGlyphRun.h"
 #include "src/core/SkImageFilterCache.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkLatticeIter.h"
 #include "src/core/SkMSAN.h"
 #include "src/core/SkMakeUnique.h"
@@ -907,7 +907,7 @@
         snapBounds = newBounds;
 
         SkMatrix localCTM;
-        sk_sp<SkImageFilter> modifiedFilter = SkApplyCTMToBackdropFilter(filter, ctm, &localCTM);
+        sk_sp<SkImageFilter> modifiedFilter = as_IFB(filter)->applyCTMForBackdrop(ctm, &localCTM);
         // Account for the origin offset in the CTM
         localCTM.postTranslate(-dstOrigin.x(), -dstOrigin.y());
 
@@ -915,7 +915,7 @@
         // since there's no device CTM stack that provides it to the image filter context.
         // FIXME skbug.com/9074 - once perspective is properly supported, drop the
         // localCTM.hasPerspective condition from assert.
-        SkASSERT(localCTM.isScaleTranslate() || filter->canHandleComplexCTM() ||
+        SkASSERT(localCTM.isScaleTranslate() || as_IFB(filter)->canHandleComplexCTM() ||
                  localCTM.hasPerspective());
         p.setImageFilter(modifiedFilter->makeWithLocalMatrix(localCTM));
     }
@@ -983,11 +983,11 @@
      */
     if (imageFilter) {
         SkMatrix modifiedCTM;
-        sk_sp<SkImageFilter> modifiedFilter = SkApplyCTMToFilter(imageFilter, stashedMatrix,
-                                                                 &modifiedCTM);
-        if (!SkIsSameFilter(modifiedFilter.get(), imageFilter)) {
+        sk_sp<SkImageFilter> modifiedFilter = as_IFB(imageFilter)->applyCTM(stashedMatrix,
+                                                                            &modifiedCTM);
+        if (as_IFB(modifiedFilter)->uniqueID() != as_IFB(imageFilter)->uniqueID()) {
             // The original filter couldn't support the CTM entirely
-            SkASSERT(modifiedCTM.isScaleTranslate() || imageFilter->canHandleComplexCTM());
+            SkASSERT(modifiedCTM.isScaleTranslate() || as_IFB(imageFilter)->canHandleComplexCTM());
             modifiedRec = fMCRec;
             this->internalSetMatrix(modifiedCTM);
             SkPaint* p = lazyP.set(*paint);
diff --git a/src/core/SkGraphics.cpp b/src/core/SkGraphics.cpp
index c8e4ba3..5db7daa 100644
--- a/src/core/SkGraphics.cpp
+++ b/src/core/SkGraphics.cpp
@@ -8,7 +8,6 @@
 #include "include/core/SkGraphics.h"
 
 #include "include/core/SkCanvas.h"
-#include "include/core/SkImageFilter.h"
 #include "include/core/SkMath.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPath.h"
@@ -20,6 +19,7 @@
 #include "src/core/SkBlitter.h"
 #include "src/core/SkCpu.h"
 #include "src/core/SkGeometry.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkOpts.h"
 #include "src/core/SkResourceCache.h"
 #include "src/core/SkScalerContext.h"
@@ -46,7 +46,7 @@
 void SkGraphics::PurgeAllCaches() {
     SkGraphics::PurgeFontCache();
     SkGraphics::PurgeResourceCache();
-    SkImageFilter::PurgeCache();
+    SkImageFilter_Base::PurgeCache();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 5bfa7ac..a7eb4cb 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -13,6 +13,7 @@
 #include "include/private/SkSafe32.h"
 #include "src/core/SkFuzzLogging.h"
 #include "src/core/SkImageFilterCache.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkLocalMatrixImageFilter.h"
 #include "src/core/SkMatrixImageFilter.h"
 #include "src/core/SkReadBuffer.h"
@@ -33,10 +34,225 @@
 #endif
 #include <atomic>
 
-void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds,
-                                      const SkMatrix& ctm,
-                                      bool embiggen,
-                                      SkIRect* cropped) const {
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SkImageFilter - A number of the public APIs on SkImageFilter downcast to SkImageFilter_Base
+// in order to perform their actual work.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  Returns the number of inputs this filter will accept (some inputs can
+ *  be NULL).
+ */
+int SkImageFilter::countInputs() const { return as_IFB(this)->fInputs.count(); }
+
+/**
+ *  Returns the input filter at a given index, or NULL if no input is
+ *  connected.  The indices used are filter-specific.
+ */
+const SkImageFilter* SkImageFilter::getInput(int i) const {
+    SkASSERT(i < this->countInputs());
+    return as_IFB(this)->fInputs[i].get();
+}
+
+bool SkImageFilter::isColorFilterNode(SkColorFilter** filterPtr) const {
+    return as_IFB(this)->onIsColorFilterNode(filterPtr);
+}
+
+SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                    MapDirection direction, const SkIRect* inputRect) const {
+    if (kReverse_MapDirection == direction) {
+        SkIRect bounds = as_IFB(this)->onFilterNodeBounds(src, ctm, direction, inputRect);
+        return as_IFB(this)->onFilterBounds(bounds, ctm, direction, &bounds);
+    } else {
+        SkASSERT(!inputRect);
+        SkIRect bounds = as_IFB(this)->onFilterBounds(src, ctm, direction, nullptr);
+        bounds = as_IFB(this)->onFilterNodeBounds(bounds, ctm, direction, nullptr);
+        SkIRect dst;
+        as_IFB(this)->getCropRect().applyTo(
+                bounds, ctm, as_IFB(this)->affectsTransparentBlack(), &dst);
+        return dst;
+    }
+}
+
+SkRect SkImageFilter::computeFastBounds(const SkRect& src) const {
+    if (0 == this->countInputs()) {
+        return src;
+    }
+    SkRect combinedBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
+    for (int i = 1; i < this->countInputs(); i++) {
+        const SkImageFilter* input = this->getInput(i);
+        if (input) {
+            combinedBounds.join(input->computeFastBounds(src));
+        } else {
+            combinedBounds.join(src);
+        }
+    }
+    return combinedBounds;
+}
+
+bool SkImageFilter::canComputeFastBounds() const {
+    if (as_IFB(this)->affectsTransparentBlack()) {
+        return false;
+    }
+    for (int i = 0; i < this->countInputs(); i++) {
+        const SkImageFilter* input = this->getInput(i);
+        if (input && !input->canComputeFastBounds()) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const {
+    SkASSERT(nullptr != filterPtr);
+    if (!this->isColorFilterNode(filterPtr)) {
+        return false;
+    }
+    if (nullptr != this->getInput(0) || (*filterPtr)->affectsTransparentBlack()) {
+        (*filterPtr)->unref();
+        return false;
+    }
+    return true;
+}
+
+sk_sp<SkImageFilter> SkImageFilter::MakeMatrixFilter(const SkMatrix& matrix,
+                                                     SkFilterQuality filterQuality,
+                                                     sk_sp<SkImageFilter> input) {
+    return SkMatrixImageFilter::Make(matrix, filterQuality, std::move(input));
+}
+
+sk_sp<SkImageFilter> SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const {
+    return SkLocalMatrixImageFilter::Make(matrix, this->refMe());
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// SkImageFilter_Base
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static int32_t next_image_filter_unique_id() {
+    static std::atomic<int32_t> nextID{1};
+
+    int32_t id;
+    do {
+        id = nextID++;
+    } while (id == 0);
+    return id;
+}
+
+SkImageFilter_Base::SkImageFilter_Base(sk_sp<SkImageFilter> const* inputs,
+                                       int inputCount, const CropRect* cropRect)
+        : fUsesSrcInput(false)
+        , fUniqueID(next_image_filter_unique_id()) {
+    fCropRect = cropRect ? *cropRect : CropRect(SkRect(), 0x0);
+
+    fInputs.reset(inputCount);
+
+    for (int i = 0; i < inputCount; ++i) {
+        if (!inputs[i] || as_IFB(inputs[i])->fUsesSrcInput) {
+            fUsesSrcInput = true;
+        }
+        fInputs[i] = inputs[i];
+    }
+}
+
+SkImageFilter_Base::~SkImageFilter_Base() {
+    SkImageFilterCache::Get()->purgeByImageFilter(this);
+}
+
+bool SkImageFilter_Base::Common::unflatten(SkReadBuffer& buffer, int expectedCount) {
+    const int count = buffer.readInt();
+    if (!buffer.validate(count >= 0)) {
+        return false;
+    }
+    if (!buffer.validate(expectedCount < 0 || count == expectedCount)) {
+        return false;
+    }
+
+    SkASSERT(fInputs.empty());
+    for (int i = 0; i < count; i++) {
+        fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr);
+        if (!buffer.isValid()) {
+            return false;
+        }
+    }
+    SkRect rect;
+    buffer.readRect(&rect);
+    if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) {
+        return false;
+    }
+
+    uint32_t flags = buffer.readUInt();
+    fCropRect = CropRect(rect, flags);
+    return buffer.isValid();
+}
+
+void SkImageFilter_Base::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeInt(fInputs.count());
+    for (int i = 0; i < fInputs.count(); i++) {
+        const SkImageFilter* input = this->getInput(i);
+        buffer.writeBool(input != nullptr);
+        if (input != nullptr) {
+            buffer.writeFlattenable(input);
+        }
+    }
+    buffer.writeRect(fCropRect.rect());
+    buffer.writeUInt(fCropRect.flags());
+}
+
+sk_sp<SkSpecialImage> SkImageFilter_Base::filterImage(SkSpecialImage* src, const Context& context,
+                                                      SkIPoint* offset) const {
+    SkASSERT(src && offset);
+    if (!context.isValid()) {
+        return nullptr;
+    }
+
+    uint32_t srcGenID = fUsesSrcInput ? src->uniqueID() : 0;
+    const SkIRect srcSubset = fUsesSrcInput ? src->subset() : SkIRect::MakeWH(0, 0);
+    SkImageFilterCacheKey key(fUniqueID, context.ctm(), context.clipBounds(), srcGenID, srcSubset);
+    if (context.cache()) {
+        sk_sp<SkSpecialImage> result = context.cache()->get(key, offset);
+        if (result) {
+            return result;
+        }
+    }
+
+    sk_sp<SkSpecialImage> result(this->onFilterImage(src, context, offset));
+
+#if SK_SUPPORT_GPU
+    if (src->isTextureBacked() && result && !result->isTextureBacked()) {
+        // Keep the result on the GPU - this is still required for some
+        // image filters that don't support GPU in all cases
+        auto context = src->getContext();
+        result = result->makeTextureImage(context);
+    }
+#endif
+
+    if (result && context.cache()) {
+        context.cache()->set(key, result.get(), *offset, this);
+    }
+
+    return result;
+}
+
+bool SkImageFilter_Base::canHandleComplexCTM() const {
+    // CropRects need to apply in the source coordinate system, but are not aware of complex CTMs
+    // when performing clipping. For a simple fix, any filter with a crop rect set cannot support
+    // complex CTMs until that's updated.
+    if (this->cropRectIsSet() || !this->onCanHandleComplexCTM()) {
+        return false;
+    }
+    const int count = this->countInputs();
+    for (int i = 0; i < count; ++i) {
+        const SkImageFilter_Base* input = as_IFB(this->getInput(i));
+        if (input && !input->canHandleComplexCTM()) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds, const SkMatrix& ctm,
+                                      bool embiggen, SkIRect* cropped) const {
     *cropped = imageBounds;
     if (fFlags) {
         SkRect devCropR;
@@ -71,182 +287,131 @@
     }
 }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-static int32_t next_image_filter_unique_id() {
-    static std::atomic<int32_t> nextID{1};
-
-    int32_t id;
-    do {
-        id = nextID++;
-    } while (id == 0);
-    return id;
+bool SkImageFilter_Base::applyCropRect(const Context& ctx, const SkIRect& srcBounds,
+                                       SkIRect* dstBounds) const {
+    SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection, nullptr);
+    fCropRect.applyTo(tmpDst, ctx.ctm(), this->affectsTransparentBlack(), dstBounds);
+    // Intersect against the clip bounds, in case the crop rect has
+    // grown the bounds beyond the original clip. This can happen for
+    // example in tiling, where the clip is much smaller than the filtered
+    // primitive. If we didn't do this, we would be processing the filter
+    // at the full crop rect size in every tile.
+    return dstBounds->intersect(ctx.clipBounds());
 }
 
-bool SkImageFilter::Common::unflatten(SkReadBuffer& buffer, int expectedCount) {
-    const int count = buffer.readInt();
-    if (!buffer.validate(count >= 0)) {
-        return false;
-    }
-    if (!buffer.validate(expectedCount < 0 || count == expectedCount)) {
-        return false;
-    }
-
-    SkASSERT(fInputs.empty());
-    for (int i = 0; i < count; i++) {
-        fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr);
-        if (!buffer.isValid()) {
-            return false;
-        }
-    }
-    SkRect rect;
-    buffer.readRect(&rect);
-    if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) {
-        return false;
-    }
-
-    uint32_t flags = buffer.readUInt();
-    fCropRect = CropRect(rect, flags);
-    return buffer.isValid();
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-void SkImageFilter::init(sk_sp<SkImageFilter> const* inputs,
-                         int inputCount,
-                         const CropRect* cropRect) {
-    fCropRect = cropRect ? *cropRect : CropRect(SkRect(), 0x0);
-
-    fInputs.reset(inputCount);
-
-    for (int i = 0; i < inputCount; ++i) {
-        if (!inputs[i] || inputs[i]->usesSrcInput()) {
-            fUsesSrcInput = true;
-        }
-        fInputs[i] = inputs[i];
-    }
-}
-
-SkImageFilter::SkImageFilter(sk_sp<SkImageFilter> const* inputs,
-                             int inputCount,
-                             const CropRect* cropRect)
-    : fUsesSrcInput(false)
-    , fUniqueID(next_image_filter_unique_id()) {
-    this->init(inputs, inputCount, cropRect);
-}
-
-SkImageFilter::~SkImageFilter() {
-    SkImageFilterCache::Get()->purgeByImageFilter(this);
-}
-
-SkImageFilter::SkImageFilter(int inputCount, SkReadBuffer& buffer)
-    : fUsesSrcInput(false)
-    , fCropRect(SkRect(), 0x0)
-    , fUniqueID(next_image_filter_unique_id()) {
-    Common common;
-    if (common.unflatten(buffer, inputCount)) {
-        this->init(common.inputs(), common.inputCount(), &common.cropRect());
-    }
-}
-
-void SkImageFilter::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeInt(fInputs.count());
-    for (int i = 0; i < fInputs.count(); i++) {
-        SkImageFilter* input = this->getInput(i);
-        buffer.writeBool(input != nullptr);
-        if (input != nullptr) {
-            buffer.writeFlattenable(input);
-        }
-    }
-    buffer.writeRect(fCropRect.rect());
-    buffer.writeUInt(fCropRect.flags());
-}
-
-sk_sp<SkSpecialImage> SkImageFilter::filterImage(SkSpecialImage* src, const Context& context,
-                                                 SkIPoint* offset) const {
-    SkASSERT(src && offset);
-    if (!context.isValid()) {
+// Return a larger (newWidth x newHeight) copy of 'src' with black padding
+// around it.
+static sk_sp<SkSpecialImage> pad_image(SkSpecialImage* src,
+                                       const SkImageFilter_Base::OutputProperties& outProps,
+                                       int newWidth, int newHeight, int offX, int offY) {
+    // We would like to operate in the source's color space (so that we return an "identical"
+    // image, other than the padding. To achieve that, we'd create new output properties:
+    //
+    // SkImageFilter::OutputProperties outProps(src->getColorSpace());
+    //
+    // That fails in at least two ways. For formats that are texturable but not renderable (like
+    // F16 on some ES implementations), we can't create a surface to do the work. For sRGB, images
+    // may be tagged with an sRGB color space (which leads to an sRGB config in makeSurface). But
+    // the actual config of that sRGB image on a device with no sRGB support is non-sRGB.
+    //
+    // Rather than try to special case these situations, we execute the image padding in the
+    // destination color space. This should not affect the output of the DAG in (almost) any case,
+    // because the result of this call is going to be used as an input, where it would have been
+    // switched to the destination space anyway. The one exception would be a filter that expected
+    // to consume unclamped F16 data, but the padded version of the image is pre-clamped to 8888.
+    // We can revisit this logic if that ever becomes an actual problem.
+    sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps, SkISize::Make(newWidth, newHeight)));
+    if (!surf) {
         return nullptr;
     }
 
-    uint32_t srcGenID = fUsesSrcInput ? src->uniqueID() : 0;
-    const SkIRect srcSubset = fUsesSrcInput ? src->subset() : SkIRect::MakeWH(0, 0);
-    SkImageFilterCacheKey key(fUniqueID, context.ctm(), context.clipBounds(), srcGenID, srcSubset);
-    if (context.cache()) {
-        sk_sp<SkSpecialImage> result = context.cache()->get(key, offset);
-        if (result) {
-            return result;
+    SkCanvas* canvas = surf->getCanvas();
+    SkASSERT(canvas);
+
+    canvas->clear(0x0);
+
+    src->draw(canvas, offX, offY, nullptr);
+
+    return surf->makeImageSnapshot();
+}
+
+sk_sp<SkSpecialImage> SkImageFilter_Base::applyCropRectAndPad(const Context& ctx,
+                                                              SkSpecialImage* src,
+                                                              SkIPoint* srcOffset,
+                                                              SkIRect* bounds) const {
+    const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset->x(), srcOffset->y(),
+                                                src->width(), src->height());
+
+    if (!this->applyCropRect(ctx, srcBounds, bounds)) {
+        return nullptr;
+    }
+
+    if (srcBounds.contains(*bounds)) {
+        return sk_sp<SkSpecialImage>(SkRef(src));
+    } else {
+        sk_sp<SkSpecialImage> img(pad_image(src, ctx.outputProperties(),
+                                            bounds->width(), bounds->height(),
+                                            Sk32_sat_sub(srcOffset->x(), bounds->x()),
+                                            Sk32_sat_sub(srcOffset->y(), bounds->y())));
+        *srcOffset = SkIPoint::Make(bounds->x(), bounds->y());
+        return img;
+    }
+}
+
+SkIRect SkImageFilter_Base::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                           MapDirection dir, const SkIRect* inputRect) const {
+    if (this->countInputs() < 1) {
+        return src;
+    }
+
+    SkIRect totalBounds;
+    for (int i = 0; i < this->countInputs(); ++i) {
+        const SkImageFilter* filter = this->getInput(i);
+        SkIRect rect = filter ? filter->filterBounds(src, ctm, dir, inputRect) : src;
+        if (0 == i) {
+            totalBounds = rect;
+        } else {
+            totalBounds.join(rect);
         }
     }
 
-    sk_sp<SkSpecialImage> result(this->onFilterImage(src, context, offset));
+    return totalBounds;
+}
 
-#if SK_SUPPORT_GPU
-    if (src->isTextureBacked() && result && !result->isTextureBacked()) {
-        // Keep the result on the GPU - this is still required for some
-        // image filters that don't support GPU in all cases
-        auto context = src->getContext();
-        result = result->makeTextureImage(context);
-    }
-#endif
+SkIRect SkImageFilter_Base::onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
+                                               MapDirection, const SkIRect*) const {
+    return src;
+}
 
-    if (result && context.cache()) {
-        context.cache()->set(key, result.get(), *offset, this);
+sk_sp<SkSpecialImage> SkImageFilter_Base::filterInput(int index,
+                                                      SkSpecialImage* src,
+                                                      const Context& ctx,
+                                                      SkIPoint* offset) const {
+    const SkImageFilter* input = this->getInput(index);
+    if (!input) {
+        return sk_sp<SkSpecialImage>(SkRef(src));
     }
 
+    sk_sp<SkSpecialImage> result(as_IFB(input)->filterImage(src, this->mapContext(ctx), offset));
+
+    SkASSERT(!result || src->isTextureBacked() == result->isTextureBacked());
+
     return result;
 }
 
-SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                    MapDirection direction, const SkIRect* inputRect) const {
-    if (kReverse_MapDirection == direction) {
-        SkIRect bounds = this->onFilterNodeBounds(src, ctm, direction, inputRect);
-        return this->onFilterBounds(bounds, ctm, direction, &bounds);
-    } else {
-        SkASSERT(!inputRect);
-        SkIRect bounds = this->onFilterBounds(src, ctm, direction, nullptr);
-        bounds = this->onFilterNodeBounds(bounds, ctm, direction, nullptr);
-        SkIRect dst;
-        this->getCropRect().applyTo(bounds, ctm, this->affectsTransparentBlack(), &dst);
-        return dst;
-    }
-}
-
-SkRect SkImageFilter::computeFastBounds(const SkRect& src) const {
-    if (0 == this->countInputs()) {
-        return src;
-    }
-    SkRect combinedBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
-    for (int i = 1; i < this->countInputs(); i++) {
-        SkImageFilter* input = this->getInput(i);
-        if (input) {
-            combinedBounds.join(input->computeFastBounds(src));
-        } else {
-            combinedBounds.join(src);
-        }
-    }
-    return combinedBounds;
-}
-
-bool SkImageFilter::canComputeFastBounds() const {
-    if (this->affectsTransparentBlack()) {
-        return false;
-    }
-    for (int i = 0; i < this->countInputs(); i++) {
-        SkImageFilter* input = this->getInput(i);
-        if (input && !input->canComputeFastBounds()) {
-            return false;
-        }
-    }
-    return true;
+SkImageFilter_Base::Context SkImageFilter_Base::mapContext(const Context& ctx) const {
+    SkIRect clipBounds = this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(),
+                                                  MapDirection::kReverse_MapDirection,
+                                                  &ctx.clipBounds());
+    return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.outputProperties());
 }
 
 #if SK_SUPPORT_GPU
-sk_sp<SkSpecialImage> SkImageFilter::DrawWithFP(GrRecordingContext* context,
-                                                std::unique_ptr<GrFragmentProcessor> fp,
-                                                const SkIRect& bounds,
-                                                const OutputProperties& outputProperties,
-                                                GrProtected isProtected) {
+sk_sp<SkSpecialImage> SkImageFilter_Base::DrawWithFP(GrRecordingContext* context,
+                                                     std::unique_ptr<GrFragmentProcessor> fp,
+                                                     const SkIRect& bounds,
+                                                     const OutputProperties& outputProperties,
+                                                     GrProtected isProtected) {
     GrPaint paint;
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
@@ -282,52 +447,9 @@
             renderTargetContext->asTextureProxyRef(),
             renderTargetContext->colorSpaceInfo().refColorSpace());
 }
-#endif
 
-bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const {
-    SkASSERT(nullptr != filterPtr);
-    if (!this->isColorFilterNode(filterPtr)) {
-        return false;
-    }
-    if (nullptr != this->getInput(0) || (*filterPtr)->affectsTransparentBlack()) {
-        (*filterPtr)->unref();
-        return false;
-    }
-    return true;
-}
-
-bool SkImageFilter::canHandleComplexCTM() const {
-    // CropRects need to apply in the source coordinate system, but are not aware of complex CTMs
-    // when performing clipping. For a simple fix, any filter with a crop rect set cannot support
-    // complex CTMs until that's updated.
-    if (this->cropRectIsSet() || !this->onCanHandleComplexCTM()) {
-        return false;
-    }
-    const int count = this->countInputs();
-    for (int i = 0; i < count; ++i) {
-        SkImageFilter* input = this->getInput(i);
-        if (input && !input->canHandleComplexCTM()) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool SkImageFilter::applyCropRect(const Context& ctx, const SkIRect& srcBounds,
-                                  SkIRect* dstBounds) const {
-    SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection, nullptr);
-    fCropRect.applyTo(tmpDst, ctx.ctm(), this->affectsTransparentBlack(), dstBounds);
-    // Intersect against the clip bounds, in case the crop rect has
-    // grown the bounds beyond the original clip. This can happen for
-    // example in tiling, where the clip is much smaller than the filtered
-    // primitive. If we didn't do this, we would be processing the filter
-    // at the full crop rect size in every tile.
-    return dstBounds->intersect(ctx.clipBounds());
-}
-
-#if SK_SUPPORT_GPU
-sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
-                                                       const OutputProperties& outProps) {
+sk_sp<SkSpecialImage> SkImageFilter_Base::ImageToColorSpace(SkSpecialImage* src,
+                                                            const OutputProperties& outProps) {
     // There are several conditions that determine if we actually need to convert the source to the
     // destination's color space. Rather than duplicate that logic here, just try to make an xform
     // object. If that produces something, then both are tagged, and the source is in a different
@@ -356,134 +478,12 @@
 }
 #endif
 
-// Return a larger (newWidth x newHeight) copy of 'src' with black padding
-// around it.
-static sk_sp<SkSpecialImage> pad_image(SkSpecialImage* src,
-                                       const SkImageFilter::OutputProperties& outProps,
-                                       int newWidth, int newHeight, int offX, int offY) {
-    // We would like to operate in the source's color space (so that we return an "identical"
-    // image, other than the padding. To achieve that, we'd create new output properties:
-    //
-    // SkImageFilter::OutputProperties outProps(src->getColorSpace());
-    //
-    // That fails in at least two ways. For formats that are texturable but not renderable (like
-    // F16 on some ES implementations), we can't create a surface to do the work. For sRGB, images
-    // may be tagged with an sRGB color space (which leads to an sRGB config in makeSurface). But
-    // the actual config of that sRGB image on a device with no sRGB support is non-sRGB.
-    //
-    // Rather than try to special case these situations, we execute the image padding in the
-    // destination color space. This should not affect the output of the DAG in (almost) any case,
-    // because the result of this call is going to be used as an input, where it would have been
-    // switched to the destination space anyway. The one exception would be a filter that expected
-    // to consume unclamped F16 data, but the padded version of the image is pre-clamped to 8888.
-    // We can revisit this logic if that ever becomes an actual problem.
-    sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps, SkISize::Make(newWidth, newHeight)));
-    if (!surf) {
-        return nullptr;
-    }
-
-    SkCanvas* canvas = surf->getCanvas();
-    SkASSERT(canvas);
-
-    canvas->clear(0x0);
-
-    src->draw(canvas, offX, offY, nullptr);
-
-    return surf->makeImageSnapshot();
-}
-
-sk_sp<SkSpecialImage> SkImageFilter::applyCropRectAndPad(const Context& ctx,
-                                                         SkSpecialImage* src,
-                                                         SkIPoint* srcOffset,
-                                                         SkIRect* bounds) const {
-    const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset->x(), srcOffset->y(),
-                                                src->width(), src->height());
-
-    if (!this->applyCropRect(ctx, srcBounds, bounds)) {
-        return nullptr;
-    }
-
-    if (srcBounds.contains(*bounds)) {
-        return sk_sp<SkSpecialImage>(SkRef(src));
-    } else {
-        sk_sp<SkSpecialImage> img(pad_image(src, ctx.outputProperties(),
-                                            bounds->width(), bounds->height(),
-                                            Sk32_sat_sub(srcOffset->x(), bounds->x()),
-                                            Sk32_sat_sub(srcOffset->y(), bounds->y())));
-        *srcOffset = SkIPoint::Make(bounds->x(), bounds->y());
-        return img;
-    }
-}
-
-SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                      MapDirection dir, const SkIRect* inputRect) const {
-    if (this->countInputs() < 1) {
-        return src;
-    }
-
-    SkIRect totalBounds;
-    for (int i = 0; i < this->countInputs(); ++i) {
-        SkImageFilter* filter = this->getInput(i);
-        SkIRect rect = filter ? filter->filterBounds(src, ctm, dir, inputRect) : src;
-        if (0 == i) {
-            totalBounds = rect;
-        } else {
-            totalBounds.join(rect);
-        }
-    }
-
-    return totalBounds;
-}
-
-SkIRect SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
-                                          MapDirection, const SkIRect*) const {
-    return src;
-}
-
-
-SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const {
-    SkIRect clipBounds = this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(),
-                                                  MapDirection::kReverse_MapDirection,
-                                                  &ctx.clipBounds());
-    return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.outputProperties());
-}
-
-sk_sp<SkImageFilter> SkImageFilter::MakeMatrixFilter(const SkMatrix& matrix,
-                                                     SkFilterQuality filterQuality,
-                                                     sk_sp<SkImageFilter> input) {
-    return SkMatrixImageFilter::Make(matrix, filterQuality, std::move(input));
-}
-
-sk_sp<SkImageFilter> SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const {
-    return SkLocalMatrixImageFilter::Make(matrix, this->refMe());
-}
-
-sk_sp<SkSpecialImage> SkImageFilter::filterInput(int index,
-                                                 SkSpecialImage* src,
-                                                 const Context& ctx,
-                                                 SkIPoint* offset) const {
-    SkImageFilter* input = this->getInput(index);
-    if (!input) {
-        return sk_sp<SkSpecialImage>(SkRef(src));
-    }
-
-    sk_sp<SkSpecialImage> result(input->filterImage(src, this->mapContext(ctx), offset));
-
-    SkASSERT(!result || src->isTextureBacked() == result->isTextureBacked());
-
-    return result;
-}
-
-void SkImageFilter::PurgeCache() {
-    SkImageFilterCache::Get()->purge();
-}
-
 // In repeat mode, when we are going to sample off one edge of the srcBounds we require the
 // opposite side be preserved.
-SkIRect SkImageFilter::DetermineRepeatedSrcBound(const SkIRect& srcBounds,
-                                                 const SkIVector& filterOffset,
-                                                 const SkISize& filterSize,
-                                                 const SkIRect& originalSrcBounds) {
+SkIRect SkImageFilter_Base::DetermineRepeatedSrcBound(const SkIRect& srcBounds,
+                                                      const SkIVector& filterOffset,
+                                                      const SkISize& filterSize,
+                                                      const SkIRect& originalSrcBounds) {
     SkIRect tmp = srcBounds;
     tmp.adjust(-filterOffset.fX, -filterOffset.fY,
                filterSize.fWidth - filterOffset.fX, filterSize.fHeight - filterOffset.fY);
@@ -500,11 +500,13 @@
     return tmp;
 }
 
-/////////////////////////////////////////////////////////////////////////////////////////////////
+void SkImageFilter_Base::PurgeCache() {
+    SkImageFilterCache::Get()->purge();
+}
 
 static sk_sp<SkImageFilter> apply_ctm_to_filter(sk_sp<SkImageFilter> input, const SkMatrix& ctm,
                                                 SkMatrix* remainder, bool asBackdrop) {
-    if (ctm.isScaleTranslate() || input->canHandleComplexCTM()) {
+    if (ctm.isScaleTranslate() || as_IFB(input)->canHandleComplexCTM()) {
         // The filter supports the CTM, so leave it as-is and 'remainder' stores the whole CTM
         *remainder = ctm;
         return input;
@@ -556,29 +558,14 @@
                             SkMatrixImageFilter::Make(invEmbed, kLow_SkFilterQuality, nullptr));
         }
     }
-    return SkMatrixImageFilter::Make(ctmToEmbed, kLow_SkFilterQuality, std::move(input));
+    return SkMatrixImageFilter::Make(ctmToEmbed, kLow_SkFilterQuality, input);
 }
 
-sk_sp<SkImageFilter> SkApplyCTMToFilter(const SkImageFilter* filter, const SkMatrix& ctm,
-                                        SkMatrix* remainder) {
-    return apply_ctm_to_filter(sk_ref_sp(filter), ctm, remainder, false);
+sk_sp<SkImageFilter> SkImageFilter_Base::applyCTM(const SkMatrix& ctm, SkMatrix* remainder) const {
+    return apply_ctm_to_filter(this->refMe(), ctm, remainder, false);
 }
 
-sk_sp<SkImageFilter> SkApplyCTMToBackdropFilter(const SkImageFilter* filter, const SkMatrix& ctm,
-                                                SkMatrix* remainder) {
-    return apply_ctm_to_filter(sk_ref_sp(filter), ctm, remainder, true);
-}
-
-bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b) {
-    if (!a || !b) {
-        // The filters are the "same" if they're both null
-        return !a && !b;
-    } else {
-        return a->fUniqueID == b->fUniqueID;
-    }
-}
-
-SkIRect SkFilterNodeBounds(const SkImageFilter* filter, const SkIRect& srcRect, const SkMatrix& ctm,
-                           SkImageFilter::MapDirection dir, const SkIRect* inputRect) {
-    return filter->onFilterNodeBounds(srcRect, ctm, dir, inputRect);
+sk_sp<SkImageFilter> SkImageFilter_Base::applyCTMForBackdrop(const SkMatrix& ctm,
+                                                             SkMatrix* remainder) const {
+    return apply_ctm_to_filter(this->refMe(), ctm, remainder, true);
 }
diff --git a/src/core/SkImageFilterPriv.h b/src/core/SkImageFilterPriv.h
deleted file mode 100644
index 690979e..0000000
--- a/src/core/SkImageFilterPriv.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkImageFilterPriv_DEFINED
-#define SkImageFilterPriv_DEFINED
-
-#include "include/core/SkImageFilter.h"
-
-/**
- *  Helper to unflatten the common data, and return nullptr if we fail.
- */
-#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)    \
-    Common localVar;                                                \
-    do {                                                            \
-        if (!localVar.unflatten(buffer, expectedCount)) {           \
-            return nullptr;                                         \
-        }                                                           \
-    } while (0)
-
-/**
- * Return an image filter representing this filter applied with the given ctm. This will modify the
- * DAG as needed if this filter does not support complex CTMs and 'ctm' is not simple. The ctm
- * matrix will be decomposed such that ctm = A*B; B will be incorporated directly into the DAG and A
- * must be the ctm set on the context passed to filterImage(). 'remainder' will be set to A.
- *
- * If this filter supports complex ctms, or 'ctm' is not complex, then A = ctm and B = I. When the
- * filter does not support complex ctms, and the ctm is complex, then A represents the extracted
- * simple portion of the ctm, and the complex portion is baked into a new DAG using a matrix filter.
- *
- * This will never return null.
- */
-sk_sp<SkImageFilter> SkApplyCTMToFilter(const SkImageFilter* filter, const SkMatrix& ctm,
-                                        SkMatrix* remainder);
-
-/**
- * Similar to SkApplyCTMToFilter except this assumes the input content is an existing backdrop image
- * to be filtered. As such,  the input to this filter will also be transformed by B^-1 if the filter
- * can't support complex CTMs, since backdrop content is already in device space and must be
- * transformed back into the CTM's local space.
- */
-sk_sp<SkImageFilter> SkApplyCTMToBackdropFilter(const SkImageFilter* filter, const SkMatrix& ctm,
-                                                SkMatrix* remainder);
-
-bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b);
-
-// Exposes just the behavior of the protected SkImageFilter::onFilterNodeBounds()
-SkIRect SkFilterNodeBounds(const SkImageFilter* filter, const SkIRect& srcRect, const SkMatrix& ctm,
-                           SkImageFilter::MapDirection dir, const SkIRect* inputRect);
-
-#endif
diff --git a/src/core/SkImageFilter_Base.h b/src/core/SkImageFilter_Base.h
new file mode 100644
index 0000000..b2306e4
--- /dev/null
+++ b/src/core/SkImageFilter_Base.h
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkImageFilter_Base_DEFINED
+#define SkImageFilter_Base_DEFINED
+
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImageFilter.h"
+#include "include/core/SkImageInfo.h"
+#include "include/private/SkTArray.h"
+
+#if SK_SUPPORT_GPU
+#include "include/gpu/GrTypes.h"
+#endif
+
+class GrFragmentProcessor;
+class GrRecordingContext;
+class SkSpecialImage;
+class SkSpecialSurface;
+class SkImageFilterCache;
+struct SkImageFilterCacheKey;
+
+// True base class that all SkImageFilter implementations need to extend from. This provides the
+// actual API surface that Skia will use to compute the filtered images.
+class SkImageFilter_Base : public SkImageFilter {
+public:
+    // Extra information about the output of a filter DAG. For now, this is just the color space
+    // (of the original requesting device). This is used when constructing intermediate rendering
+    // surfaces, so that we ensure we land in a surface that's similar/compatible to the final
+    // consumer of the DAG's output.
+    class OutputProperties {
+    public:
+        explicit OutputProperties(SkColorType colorType, SkColorSpace* colorSpace)
+            : fColorType(colorType), fColorSpace(colorSpace) {}
+
+        SkColorType colorType() const { return fColorType; }
+        SkColorSpace* colorSpace() const { return fColorSpace; }
+
+    private:
+        SkColorType fColorType;
+        // This will be a pointer to the device's color space, and our lifetime is bounded by
+        // the device, so we can store a bare pointer.
+        SkColorSpace* fColorSpace;
+    };
+
+    class Context {
+    public:
+        Context(const SkMatrix& ctm, const SkIRect& clipBounds, SkImageFilterCache* cache,
+                const OutputProperties& outputProperties)
+            : fCTM(ctm)
+            , fClipBounds(clipBounds)
+            , fCache(cache)
+            , fOutputProperties(outputProperties)
+        {}
+
+        const SkMatrix& ctm() const { return fCTM; }
+        const SkIRect& clipBounds() const { return fClipBounds; }
+        SkImageFilterCache* cache() const { return fCache; }
+        const OutputProperties& outputProperties() const { return fOutputProperties; }
+
+        /**
+         *  Since a context can be build directly, its constructor has no chance to
+         *  "return null" if it's given invalid or unsupported inputs. Call this to
+         *  know of the the context can be used.
+         *
+         *  The SkImageFilterCache Key, for example, requires a finite ctm (no infinities
+         *  or NaN), so that test is part of isValid.
+         */
+        bool isValid() const { return fCTM.isFinite(); }
+
+    private:
+        SkMatrix               fCTM;
+        SkIRect                fClipBounds;
+        SkImageFilterCache*    fCache;
+        OutputProperties       fOutputProperties;
+    };
+
+    /**
+     *  Request a new filtered image to be created from the src image.
+     *
+     *  The context contains the environment in which the filter is occurring.
+     *  It includes the clip bounds, CTM and cache.
+     *
+     *  Offset is the amount to translate the resulting image relative to the
+     *  src when it is drawn. This is an out-param.
+     *
+     *  If the result image cannot be created, or the result would be
+     *  transparent black, return null, in which case the offset parameter
+     *  should be ignored by the caller.
+     *
+     *  TODO: Right now the imagefilters sometimes return empty result bitmaps/
+     *        specialimages. That doesn't seem quite right.
+     */
+    sk_sp<SkSpecialImage> filterImage(SkSpecialImage* src, const Context& context,
+                                      SkIPoint* offset) const;
+
+    /**
+     *  Returns whether any edges of the crop rect have been set. The crop
+     *  rect is set at construction time, and determines which pixels from the
+     *  input image will be processed, and which pixels in the output image will be allowed.
+     *  The size of the crop rect should be
+     *  used as the size of the destination image. The origin of this rect
+     *  should be used to offset access to the input images, and should also
+     *  be added to the "offset" parameter in onFilterImage.
+     */
+    bool cropRectIsSet() const { return fCropRect.flags() != 0x0; }
+
+    CropRect getCropRect() const { return fCropRect; }
+
+    // Expose isolated node bounds behavior for SampleImageFilterDAG and debugging
+    SkIRect filterNodeBounds(const SkIRect& srcRect, const SkMatrix& ctm,
+                             MapDirection dir, const SkIRect* inputRect) const {
+        return this->onFilterNodeBounds(srcRect, ctm, dir, inputRect);
+    }
+
+    /**
+     *  ImageFilters can natively handle scaling and translate components in the CTM. Only some of
+     *  them can handle affine (or more complex) matrices. This call returns true iff the filter
+     *  and all of its (non-null) inputs can handle these more complex matrices.
+     */
+    bool canHandleComplexCTM() const;
+
+    /**
+     * Return an image filter representing this filter applied with the given ctm. This will modify
+     * the DAG as needed if this filter does not support complex CTMs and 'ctm' is not simple. The
+     * ctm matrix will be decomposed such that ctm = A*B; B will be incorporated directly into the
+     * DAG and A must be the ctm set on the context passed to filterImage(). 'remainder' will be set
+     * to A.
+     *
+     * If this filter supports complex ctms, or 'ctm' is not complex, then A = ctm and B = I. When
+     * the filter does not support complex ctms, and the ctm is complex, then A represents the
+     * extracted simple portion of the ctm, and the complex portion is baked into a new DAG using a
+     * matrix filter.
+     *
+     * This will never return null.
+     */
+    sk_sp<SkImageFilter> applyCTM(const SkMatrix& ctm, SkMatrix* remainder) const;
+    /**
+     * Similar to SkApplyCTMToFilter except this assumes the input content is an existing backdrop
+     * image to be filtered. As such,  the input to this filter will also be transformed by B^-1 if
+     * the filter can't support complex CTMs, since backdrop content is already in device space and
+     * must be transformed back into the CTM's local space.
+     */
+    sk_sp<SkImageFilter> applyCTMForBackdrop(const SkMatrix& ctm, SkMatrix* remainder) const;
+
+    uint32_t uniqueID() const { return fUniqueID; }
+
+protected:
+    class Common {
+    public:
+        /**
+         *  Attempt to unflatten the cropRect and the expected number of input filters.
+         *  If any number of input filters is valid, pass -1.
+         *  If this fails (i.e. corrupt buffer or contents) then return false and common will
+         *  be left uninitialized.
+         *  If this returns true, then inputCount() is the number of found input filters, each
+         *  of which may be NULL or a valid imagefilter.
+         */
+        bool unflatten(SkReadBuffer&, int expectedInputs);
+
+        const CropRect& cropRect() const { return fCropRect; }
+        int inputCount() const { return fInputs.count(); }
+        sk_sp<SkImageFilter>* inputs() { return fInputs.begin(); }
+
+        sk_sp<SkImageFilter> getInput(int index) { return fInputs[index]; }
+
+    private:
+        CropRect fCropRect;
+        // most filters accept at most 2 input-filters
+        SkSTArray<2, sk_sp<SkImageFilter>, true> fInputs;
+    };
+
+    SkImageFilter_Base(sk_sp<SkImageFilter> const* inputs, int inputCount,
+                       const CropRect* cropRect);
+
+    ~SkImageFilter_Base() override;
+
+    void flatten(SkWriteBuffer&) const override;
+
+    virtual bool affectsTransparentBlack() const { return false; }
+
+    /**
+     *  This is the virtual which should be overridden by the derived class
+     *  to perform image filtering.
+     *
+     *  src is the original primitive bitmap. If the filter has a connected
+     *  input, it should recurse on that input and use that in place of src.
+     *
+     *  The matrix is the matrix used to draw the geometry into the current
+     *  layer that produced the 'src' image. This may be the total canvas'
+     *  matrix, or part of its decomposition (depending on what the filter DAG
+     *  is able to support).
+     *
+     *  Offset is the amount to translate the resulting image relative to the
+     *  src when it is drawn. This is an out-param.
+     *
+     *  If the result image cannot be created (either because of error or if, say, the result
+     *  is entirely clipped out), this should return nullptr.
+     *  Callers that affect transparent black should explicitly handle nullptr
+     *  results and press on. In the error case this behavior will produce a better result
+     *  than nothing and is necessary for the clipped out case.
+     *  If the return value is nullptr then offset should be ignored.
+     */
+    virtual sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* src, const Context&,
+                                                SkIPoint* offset) const = 0;
+
+    /**
+     * This function recurses into its inputs with the given rect (first
+     * argument), calls filterBounds() with the given map direction on each,
+     * and returns the union of those results. If a derived class has special
+     * recursion requirements (e.g., it has an input which does not participate
+     * in bounds computation), it can be overridden here.
+     * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward
+     * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting
+     * answer may be incorrect.
+     *
+     * Note that this function is *not* responsible for mapping the rect for
+     * this node's filter bounds requirements (i.e., calling
+     * onFilterNodeBounds()); that is handled by filterBounds().
+     */
+    virtual SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm,
+                                   MapDirection, const SkIRect* inputRect) const;
+
+    /**
+     * Performs a forwards or reverse mapping of the given rect to accommodate
+     * this filter's margin requirements. kForward_MapDirection is used to
+     * determine the destination pixels which would be touched by filtering
+     * the given source rect (e.g., given source bitmap bounds,
+     * determine the optimal bounds of the filtered offscreen bitmap).
+     * kReverse_MapDirection is used to determine which pixels of the
+     * input(s) would be required to fill the given destination rect
+     * (e.g., clip bounds). NOTE: these operations may not be the
+     * inverse of the other. For example, blurring expands the given rect
+     * in both forward and reverse directions. Unlike
+     * onFilterBounds(), this function is non-recursive.
+     * In kReverse mode, 'inputRect' will be the device space bounds of the input pixels. In
+     * kForward mode, 'inputRect' should always be null. If 'inputRect' is null in kReverse mode
+     * the resulting answer may be incorrect.
+     */
+    virtual SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
+                                       MapDirection, const SkIRect* inputRect) const;
+
+    // Helper function which invokes filter processing on the input at the
+    // specified "index". If the input is null, it returns "src" and leaves
+    // "offset" untouched. If the input is non-null, it
+    // calls filterImage() on that input, and returns the result.
+    sk_sp<SkSpecialImage> filterInput(int index,
+                                      SkSpecialImage* src,
+                                      const Context&,
+                                      SkIPoint* offset) const;
+
+    /**
+     *  Return true (and returns a ref'd colorfilter) if this node in the DAG is just a
+     *  colorfilter w/o CropRect constraints.
+     */
+    virtual bool onIsColorFilterNode(SkColorFilter** /*filterPtr*/) const {
+        return false;
+    }
+
+    /**
+     *  Override this to describe the behavior of your subclass - as a leaf node. The caller will
+     *  take care of calling your inputs (and return false if any of them could not handle it).
+     */
+    virtual bool onCanHandleComplexCTM() const { return false; }
+
+    const CropRect* getCropRectIfSet() const {
+        return this->cropRectIsSet() ? &fCropRect : nullptr;
+    }
+
+    /** Given a "srcBounds" rect, computes destination bounds for this filter.
+     *  "dstBounds" are computed by transforming the crop rect by the context's
+     *  CTM, applying it to the initial bounds, and intersecting the result with
+     *  the context's clip bounds.  "srcBounds" (if non-null) are computed by
+     *  intersecting the initial bounds with "dstBounds", to ensure that we never
+     *  sample outside of the crop rect (this restriction may be relaxed in the
+     *  future).
+     */
+    bool applyCropRect(const Context&, const SkIRect& srcBounds, SkIRect* dstBounds) const;
+
+    /** A variant of the above call which takes the original source bitmap and
+     *  source offset. If the resulting crop rect is not entirely contained by
+     *  the source bitmap's bounds, it creates a new bitmap in "result" and
+     *  pads the edges with transparent black. In that case, the srcOffset is
+     *  modified to be the same as the bounds, since no further adjustment is
+     *  needed by the caller. This version should only be used by filters
+     *  which are not capable of processing a smaller source bitmap into a
+     *  larger destination.
+     */
+    sk_sp<SkSpecialImage> applyCropRectAndPad(const Context&, SkSpecialImage* src,
+                                              SkIPoint* srcOffset, SkIRect* bounds) const;
+
+    /**
+     *  Creates a modified Context for use when recursing up the image filter DAG.
+     *  The clip bounds are adjusted to accommodate any margins that this
+     *  filter requires by calling this node's
+     *  onFilterNodeBounds(..., kReverse_MapDirection).
+     */
+    Context mapContext(const Context& ctx) const;
+
+#if SK_SUPPORT_GPU
+    static sk_sp<SkSpecialImage> DrawWithFP(GrRecordingContext* context,
+                                            std::unique_ptr<GrFragmentProcessor>
+                                                    fp,
+                                            const SkIRect& bounds,
+                                            const OutputProperties& outputProperties,
+                                            GrProtected isProtected = GrProtected::kNo);
+
+    /**
+     *  Returns a version of the passed-in image (possibly the original), that is in a colorspace
+     *  with the same gamut as the one from the OutputProperties. This allows filters that do many
+     *  texture samples to guarantee that any color space conversion has happened before running.
+     */
+    static sk_sp<SkSpecialImage> ImageToColorSpace(SkSpecialImage* src, const OutputProperties&);
+#endif
+
+    // If 'srcBounds' will sample outside the border of 'originalSrcBounds' (i.e., the sample
+    // will wrap around to the other side) we must preserve the far side of the src along that
+    // axis (e.g., if we will sample beyond the left edge of the src, the right side must be
+    // preserved for the repeat sampling to work).
+    static SkIRect DetermineRepeatedSrcBound(const SkIRect& srcBounds,
+                                             const SkIVector& filterOffset,
+                                             const SkISize& filterSize,
+                                             const SkIRect& originalSrcBounds);
+
+private:
+    friend class SkImageFilter;
+    // For PurgeCache()
+    friend class SkGraphics;
+
+    static void PurgeCache();
+
+    void init(sk_sp<SkImageFilter> const* inputs, int inputCount, const CropRect* cropRect);
+
+    SkAutoSTArray<2, sk_sp<SkImageFilter>> fInputs;
+
+    bool fUsesSrcInput;
+    CropRect fCropRect;
+    uint32_t fUniqueID; // Globally unique
+
+    typedef SkImageFilter INHERITED;
+};
+
+static inline SkImageFilter_Base* as_IFB(SkImageFilter* filter) {
+    return static_cast<SkImageFilter_Base*>(filter);
+}
+
+static inline SkImageFilter_Base* as_IFB(const sk_sp<SkImageFilter>& filter) {
+    return static_cast<SkImageFilter_Base*>(filter.get());
+}
+
+static inline const SkImageFilter_Base* as_IFB(const SkImageFilter* filter) {
+    return static_cast<const SkImageFilter_Base*>(filter);
+}
+
+/**
+ *  Helper to unflatten the common data, and return nullptr if we fail.
+ */
+#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)    \
+    Common localVar;                                                \
+    do {                                                            \
+        if (!localVar.unflatten(buffer, expectedCount)) {           \
+            return nullptr;                                         \
+        }                                                           \
+    } while (0)
+
+#endif // SkImageFilter_Base_DEFINED
diff --git a/src/core/SkLocalMatrixImageFilter.cpp b/src/core/SkLocalMatrixImageFilter.cpp
index 97d8002..9092c5a 100644
--- a/src/core/SkLocalMatrixImageFilter.cpp
+++ b/src/core/SkLocalMatrixImageFilter.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "include/core/SkString.h"
-#include "src/core/SkImageFilterPriv.h"
 #include "src/core/SkLocalMatrixImageFilter.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
@@ -19,7 +18,7 @@
     if (localM.isIdentity()) {
         return input;
     }
-    if (!input->canHandleComplexCTM() && !localM.isScaleTranslate()) {
+    if (!as_IFB(input)->canHandleComplexCTM() && !localM.isScaleTranslate()) {
         // Nothing we can do at this point
         return nullptr;
     }
diff --git a/src/core/SkLocalMatrixImageFilter.h b/src/core/SkLocalMatrixImageFilter.h
index a96e609..132c302 100644
--- a/src/core/SkLocalMatrixImageFilter.h
+++ b/src/core/SkLocalMatrixImageFilter.h
@@ -9,13 +9,13 @@
 #define SkLocalMatrixImageFilter_DEFINED
 
 #include "include/core/SkFlattenable.h"
-#include "include/core/SkImageFilter.h"
+#include "src/core/SkImageFilter_Base.h"
 
 /**
  *  Wraps another imagefilter + matrix, such that using this filter will give the same result
  *  as using the wrapped filter with the matrix applied to its context.
  */
-class SkLocalMatrixImageFilter : public SkImageFilter {
+class SkLocalMatrixImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<SkImageFilter> Make(const SkMatrix& localM, sk_sp<SkImageFilter> input);
 
@@ -35,7 +35,7 @@
 
     SkMatrix fLocalM;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 #endif
diff --git a/src/core/SkMatrixImageFilter.cpp b/src/core/SkMatrixImageFilter.cpp
index 66ea469..9a69c8e 100644
--- a/src/core/SkMatrixImageFilter.cpp
+++ b/src/core/SkMatrixImageFilter.cpp
@@ -9,7 +9,6 @@
 
 #include "include/core/SkCanvas.h"
 #include "include/core/SkRect.h"
-#include "src/core/SkImageFilterPriv.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
diff --git a/src/core/SkMatrixImageFilter.h b/src/core/SkMatrixImageFilter.h
index b13efb2..b8c571d 100644
--- a/src/core/SkMatrixImageFilter.h
+++ b/src/core/SkMatrixImageFilter.h
@@ -9,15 +9,15 @@
 #define SkMatrixImageFilter_DEFINED
 
 #include "include/core/SkFlattenable.h"
-#include "include/core/SkImageFilter.h"
 #include "include/core/SkMatrix.h"
+#include "src/core/SkImageFilter_Base.h"
 
 /*! \class SkMatrixImageFilter
     Matrix transformation image filter.  This filter draws its source
     input transformed by the given matrix.
  */
 
-class SkMatrixImageFilter : public SkImageFilter {
+class SkMatrixImageFilter : public SkImageFilter_Base {
 public:
     /** Construct a 2D transformation image filter.
      *  @param transform     The matrix to apply when drawing the src bitmap
@@ -48,7 +48,7 @@
 
     SkMatrix              fTransform;
     SkFilterQuality       fFilterQuality;
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 #endif
diff --git a/src/core/SkPicturePriv.h b/src/core/SkPicturePriv.h
index c14751c..fc3b06d 100644
--- a/src/core/SkPicturePriv.h
+++ b/src/core/SkPicturePriv.h
@@ -73,6 +73,7 @@
     // V70: Image filters definitions hidden, registered names updated to include "Impl"
     // V71: Unify erode and dilate image filters
     // V72: SkColorFilter_Matrix domain (rgba vs. hsla)
+
     enum Version {
         kTileModeInBlurImageFilter_Version  = 56,
         kTileInfoInSweepGradient_Version    = 57,
diff --git a/src/core/SkResourceCache.cpp b/src/core/SkResourceCache.cpp
index 1208937d..61197f8 100644
--- a/src/core/SkResourceCache.cpp
+++ b/src/core/SkResourceCache.cpp
@@ -11,6 +11,7 @@
 #include "include/private/SkMutex.h"
 #include "include/private/SkTo.h"
 #include "src/core/SkDiscardableMemory.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkMessageBus.h"
 #include "src/core/SkMipMap.h"
 #include "src/core/SkOpts.h"
@@ -566,7 +567,7 @@
 }
 
 void SkGraphics::PurgeResourceCache() {
-    SkImageFilter::PurgeCache();
+    SkImageFilter_Base::PurgeCache();
     return SkResourceCache::PurgeAll();
 }
 
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
index 4e113a4..2f5cf2d 100644
--- a/src/core/SkSpecialImage.cpp
+++ b/src/core/SkSpecialImage.cpp
@@ -57,16 +57,17 @@
     // from the content rect by the non-virtual makeSubset().
     virtual sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const = 0;
 
-    virtual sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
-                                                  const SkISize& size, SkAlphaType at,
-                                                  const SkSurfaceProps* = nullptr) const = 0;
+    virtual sk_sp<SkSpecialSurface> onMakeSurface(
+            const SkImageFilter_Base::OutputProperties& outProps, const SkISize& size,
+            SkAlphaType at, const SkSurfaceProps* = nullptr) const = 0;
 
     // This subset (when not null) is relative to the backing store's coordinate frame, it has
     // already been mapped from the content rect by the non-virtual asImage().
     virtual sk_sp<SkImage> onAsImage(const SkIRect* subset) const = 0;
 
-    virtual sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter::OutputProperties& outProps,
-                                                const SkISize& size, SkAlphaType at) const = 0;
+    virtual sk_sp<SkSurface> onMakeTightSurface(
+            const SkImageFilter_Base::OutputProperties& outProps,
+            const SkISize& size, SkAlphaType at) const = 0;
 
 private:
     typedef SkSpecialImage INHERITED;
@@ -157,14 +158,15 @@
 }
 #endif
 
-sk_sp<SkSpecialSurface> SkSpecialImage::makeSurface(const SkImageFilter::OutputProperties& outProps,
-                                                    const SkISize& size, SkAlphaType at,
-                                                    const SkSurfaceProps* props) const {
+sk_sp<SkSpecialSurface> SkSpecialImage::makeSurface(
+        const SkImageFilter_Base::OutputProperties& outProps, const SkISize& size,
+        SkAlphaType at, const SkSurfaceProps* props) const {
     return as_SIB(this)->onMakeSurface(outProps, size, at, props);
 }
 
-sk_sp<SkSurface> SkSpecialImage::makeTightSurface(const SkImageFilter::OutputProperties& outProps,
-                                                  const SkISize& size, SkAlphaType at) const {
+sk_sp<SkSurface> SkSpecialImage::makeTightSurface(
+        const SkImageFilter_Base::OutputProperties& outProps, const SkISize& size,
+        SkAlphaType at) const {
     return as_SIB(this)->onMakeTightSurface(outProps, size, at);
 }
 
@@ -263,7 +265,7 @@
     }
 #endif
 
-    sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                           const SkISize& size, SkAlphaType at,
                                           const SkSurfaceProps* props) const override {
         SkColorSpace* colorSpace = outProps.colorSpace();
@@ -292,7 +294,7 @@
         return SkImage::MakeFromBitmap(fBitmap);
     }
 
-    sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                         const SkISize& size, SkAlphaType at) const override {
         SkColorSpace* colorSpace = outProps.colorSpace();
         SkColorType colorType = kN32_SkColorType;   // TODO: find ways to allow f16
@@ -453,7 +455,7 @@
         return fColorSpace.get();
     }
 
-    sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                           const SkISize& size, SkAlphaType at,
                                           const SkSurfaceProps* props) const override {
         if (!fContext) {
@@ -507,7 +509,7 @@
         return wrap_proxy_in_image(fContext, fTextureProxy, fAlphaType, fColorSpace);
     }
 
-    sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                         const SkISize& size, SkAlphaType at) const override {
         SkColorSpace* colorSpace = outProps.colorSpace();
         SkColorType colorType = colorSpace && colorSpace->gammaIsLinear()
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
index 77b9cbf..eb9aa3e 100644
--- a/src/core/SkSpecialImage.h
+++ b/src/core/SkSpecialImage.h
@@ -8,13 +8,12 @@
 #ifndef SkSpecialImage_DEFINED
 #define SkSpecialImage_DEFINED
 
+#include "include/core/SkImageInfo.h"
 #include "include/core/SkRefCnt.h"
 #include "include/core/SkSurfaceProps.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkNextID.h"
 
-#include "include/core/SkImageFilter.h"
-#include "include/core/SkImageInfo.h"
-
 class GrRecordingContext;
 class GrTextureProxy;
 class SkBitmap;
@@ -92,7 +91,7 @@
     /**
      *  Create a new special surface with a backend that is compatible with this special image.
      */
-    sk_sp<SkSpecialSurface> makeSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSpecialSurface> makeSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                         const SkISize& size,
                                         SkAlphaType at = kPremul_SkAlphaType,
                                         const SkSurfaceProps* props = nullptr) const;
@@ -101,7 +100,7 @@
      * Create a new surface with a backend that is compatible with this special image.
      * TODO: switch this to makeSurface once we resolved the naming issue
      */
-    sk_sp<SkSurface> makeTightSurface(const SkImageFilter::OutputProperties& outProps,
+    sk_sp<SkSurface> makeTightSurface(const SkImageFilter_Base::OutputProperties& outProps,
                                       const SkISize& size,
                                       SkAlphaType at = kPremul_SkAlphaType) const;
 
diff --git a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
index 8a15884..110ef7c 100644
--- a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
+++ b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkBitmap.h"
 #include "include/core/SkRegion.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
@@ -29,7 +29,7 @@
 
 namespace {
 
-class SkAlphaThresholdFilterImpl final : public SkImageFilter {
+class SkAlphaThresholdFilterImpl final : public SkImageFilter_Base {
 public:
     SkAlphaThresholdFilterImpl(const SkRegion& region, SkScalar innerThreshold,
                                SkScalar outerThreshold, sk_sp<SkImageFilter> input,
@@ -59,7 +59,7 @@
     SkScalar fInnerThreshold;
     SkScalar fOuterThreshold;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 }; // end namespace
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 9eb794a..4ef8a07 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -10,7 +10,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/effects/SkXfermodeImageFilter.h"
 #include "include/private/SkNx.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -48,7 +48,7 @@
 
 namespace {
 
-class ArithmeticImageFilterImpl final : public SkImageFilter {
+class ArithmeticImageFilterImpl final : public SkImageFilter_Base {
 public:
     ArithmeticImageFilterImpl(float k1, float k2, float k3, float k4, bool enforcePMColor,
                               sk_sp<SkImageFilter> inputs[2], const CropRect* cropRect)
@@ -84,7 +84,7 @@
     const float fK[4];
     const bool fEnforcePMColor;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 }; // end namespace
@@ -273,7 +273,7 @@
                                                   MapDirection dir,
                                                   const SkIRect* inputRect) const {
     if (kReverse_MapDirection == dir) {
-        return SkImageFilter::onFilterBounds(src, ctm, dir, inputRect);
+        return INHERITED::onFilterBounds(src, ctm, dir, inputRect);
     }
 
     SkASSERT(2 == this->countInputs());
diff --git a/src/effects/imagefilters/SkBlurImageFilter.cpp b/src/effects/imagefilters/SkBlurImageFilter.cpp
index 9a8161f..3f0d5e6 100644
--- a/src/effects/imagefilters/SkBlurImageFilter.cpp
+++ b/src/effects/imagefilters/SkBlurImageFilter.cpp
@@ -17,7 +17,7 @@
 #include "src/core/SkArenaAlloc.h"
 #include "src/core/SkAutoPixmapStorage.h"
 #include "src/core/SkGpuBlurUtils.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkOpts.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
@@ -29,11 +29,9 @@
 #include "src/gpu/SkGr.h"
 #endif
 
-static constexpr double kPi = 3.14159265358979323846264338327950288;
-
 namespace {
 
-class SkBlurImageFilterImpl final : public SkImageFilter {
+class SkBlurImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkBlurImageFilterImpl(SkScalar sigmaX, SkScalar sigmaY,  SkTileMode tileMode,
                           sk_sp<SkImageFilter> input, const CropRect* cropRect)
@@ -64,7 +62,7 @@
     SkSize     fSigma;
     SkTileMode fTileMode;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
@@ -168,7 +166,7 @@
     //   window = floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5)
     //   For window <= 255, the largest value for sigma is 136.
     sigma = SkTPin(sigma, 0.0, 136.0);
-    auto possibleWindow = static_cast<int>(floor(sigma * 3 * sqrt(2 * kPi) / 4 + 0.5));
+    auto possibleWindow = static_cast<int>(floor(sigma * 3 * sqrt(2 * SK_DoublePI) / 4 + 0.5));
     return std::max(1, possibleWindow);
 }
 
diff --git a/src/effects/imagefilters/SkColorFilterImageFilter.cpp b/src/effects/imagefilters/SkColorFilterImageFilter.cpp
index 9baaa27..0d44a68 100644
--- a/src/effects/imagefilters/SkColorFilterImageFilter.cpp
+++ b/src/effects/imagefilters/SkColorFilterImageFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColorFilter.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -17,7 +17,7 @@
 
 namespace {
 
-class SkColorFilterImageFilterImpl final : public SkImageFilter {
+class SkColorFilterImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkColorFilterImageFilterImpl(sk_sp<SkColorFilter> cf, sk_sp<SkImageFilter> input,
                                  const CropRect* cropRect)
@@ -38,7 +38,7 @@
 
     sk_sp<SkColorFilter> fColorFilter;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkComposeImageFilter.cpp b/src/effects/imagefilters/SkComposeImageFilter.cpp
index a09006a..48ec150 100644
--- a/src/effects/imagefilters/SkComposeImageFilter.cpp
+++ b/src/effects/imagefilters/SkComposeImageFilter.cpp
@@ -7,14 +7,14 @@
 
 #include "include/effects/SkComposeImageFilter.h"
 
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
 
 namespace {
 
-class SkComposeImageFilterImpl final : public SkImageFilter {
+class SkComposeImageFilterImpl final : public SkImageFilter_Base {
 public:
     explicit SkComposeImageFilterImpl(sk_sp<SkImageFilter> inputs[2])
             : INHERITED(inputs, 2, nullptr) {
@@ -35,7 +35,7 @@
     friend void SkComposeImageFilter::RegisterFlattenables();
     SK_FLATTENABLE_HOOKS(SkComposeImageFilterImpl)
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
@@ -66,8 +66,8 @@
 }
 
 SkRect SkComposeImageFilterImpl::computeFastBounds(const SkRect& src) const {
-    SkImageFilter* outer = this->getInput(0);
-    SkImageFilter* inner = this->getInput(1);
+    const SkImageFilter* outer = this->getInput(0);
+    const SkImageFilter* inner = this->getInput(1);
 
     return outer->computeFastBounds(inner->computeFastBounds(src));
 }
@@ -106,8 +106,8 @@
 
 SkIRect SkComposeImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                                                  MapDirection dir, const SkIRect* inputRect) const {
-    SkImageFilter* outer = this->getInput(0);
-    SkImageFilter* inner = this->getInput(1);
+    const SkImageFilter* outer = this->getInput(0);
+    const SkImageFilter* inner = this->getInput(1);
 
     const SkIRect innerRect = inner->filterBounds(src, ctm, dir, inputRect);
     return outer->filterBounds(innerRect, ctm, dir,
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index 0eb16ab..7d67b1a 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -10,7 +10,7 @@
 #include "include/core/SkBitmap.h"
 #include "include/core/SkUnPreMultiply.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
@@ -34,7 +34,7 @@
 
 namespace {
 
-class SkDisplacementMapEffectImpl final : public SkImageFilter {
+class SkDisplacementMapEffectImpl final : public SkImageFilter_Base {
 public:
     SkDisplacementMapEffectImpl(SkColorChannel xChannelSelector, SkColorChannel yChannelSelector,
                                 SkScalar scale, sk_sp<SkImageFilter> inputs[2],
@@ -68,7 +68,7 @@
     const SkImageFilter* getDisplacementInput() const { return getInput(0); }
     const SkImageFilter* getColorInput() const { return getInput(1); }
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 // Shift values to extract channels from an SkColor (SkColorGetR, SkColorGetG, etc)
diff --git a/src/effects/imagefilters/SkDropShadowImageFilter.cpp b/src/effects/imagefilters/SkDropShadowImageFilter.cpp
index ba5a67a..fd69078 100644
--- a/src/effects/imagefilters/SkDropShadowImageFilter.cpp
+++ b/src/effects/imagefilters/SkDropShadowImageFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkCanvas.h"
 #include "include/effects/SkBlurImageFilter.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -17,7 +17,7 @@
 
 namespace {
 
-class SkDropShadowImageFilterImpl final : public SkImageFilter {
+class SkDropShadowImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkDropShadowImageFilterImpl(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY,
                                 SkColor color, bool shadowOnly, sk_sp<SkImageFilter> input,
@@ -47,7 +47,7 @@
     SkColor  fColor;
     bool     fShadowOnly;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkImageSource.cpp b/src/effects/imagefilters/SkImageSource.cpp
index f0893a6..e14526e 100644
--- a/src/effects/imagefilters/SkImageSource.cpp
+++ b/src/effects/imagefilters/SkImageSource.cpp
@@ -10,6 +10,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkImage.h"
 #include "include/core/SkString.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -17,7 +18,7 @@
 
 namespace {
 
-class SkImageSourceImpl final : public SkImageFilter {
+class SkImageSourceImpl final : public SkImageFilter_Base {
 public:
     SkImageSourceImpl(sk_sp<SkImage> image, const SkRect& srcRect, const SkRect& dstRect,
                       SkFilterQuality filterQuality)
@@ -46,7 +47,7 @@
     SkRect           fSrcRect, fDstRect;
     SkFilterQuality  fFilterQuality;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
@@ -160,7 +161,7 @@
                                               MapDirection direction,
                                               const SkIRect* inputRect) const {
     if (kReverse_MapDirection == direction) {
-        return SkImageFilter::onFilterNodeBounds(src, ctm, direction, inputRect);
+        return INHERITED::onFilterNodeBounds(src, ctm, direction, inputRect);
     }
 
     SkRect dstRect = fDstRect;
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index d971cc8..1da1729 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -11,7 +11,7 @@
 #include "include/core/SkPoint3.h"
 #include "include/core/SkTypes.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
@@ -398,7 +398,7 @@
     kBoundaryModeCount,
 };
 
-class SkLightingImageFilterInternal : public SkImageFilter {
+class SkLightingImageFilterInternal : public SkImageFilter_Base {
 protected:
     SkLightingImageFilterInternal(sk_sp<SkImageFilterLight> light,
                                   SkScalar surfaceScale,
@@ -448,7 +448,7 @@
     sk_sp<SkImageFilterLight> fLight;
     SkScalar fSurfaceScale;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 #if SK_SUPPORT_GPU
diff --git a/src/effects/imagefilters/SkMagnifierImageFilter.cpp b/src/effects/imagefilters/SkMagnifierImageFilter.cpp
index 2fd4c15..85c8894 100644
--- a/src/effects/imagefilters/SkMagnifierImageFilter.cpp
+++ b/src/effects/imagefilters/SkMagnifierImageFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkBitmap.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkValidationUtils.h"
@@ -30,7 +30,7 @@
 
 namespace {
 
-class SkMagnifierImageFilterImpl final : public SkImageFilter {
+class SkMagnifierImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkMagnifierImageFilterImpl(const SkRect& srcRect, SkScalar inset, sk_sp<SkImageFilter> input,
                                const CropRect* cropRect)
@@ -53,7 +53,7 @@
     SkRect   fSrcRect;
     SkScalar fInset;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
index 362108a..3c63b05 100644
--- a/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/imagefilters/SkMatrixConvolutionImageFilter.cpp
@@ -12,7 +12,7 @@
 #include "include/core/SkTileMode.h"
 #include "include/core/SkUnPreMultiply.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
@@ -25,7 +25,7 @@
 
 namespace {
 
-class SkMatrixConvolutionImageFilterImpl final : public SkImageFilter {
+class SkMatrixConvolutionImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkMatrixConvolutionImageFilterImpl(const SkISize& kernelSize, const SkScalar* kernel,
                                        SkScalar gain, SkScalar bias, const SkIPoint& kernelOffset,
@@ -95,7 +95,7 @@
                             const SkIRect& rect,
                             const SkIRect& bounds) const;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 class UncheckedPixelFetcher {
diff --git a/src/effects/imagefilters/SkMergeImageFilter.cpp b/src/effects/imagefilters/SkMergeImageFilter.cpp
index b514c7c..07dbf41 100644
--- a/src/effects/imagefilters/SkMergeImageFilter.cpp
+++ b/src/effects/imagefilters/SkMergeImageFilter.cpp
@@ -8,6 +8,7 @@
 #include "include/effects/SkMergeImageFilter.h"
 
 #include "include/core/SkCanvas.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -16,7 +17,7 @@
 
 namespace {
 
-class SkMergeImageFilterImpl final : public SkImageFilter {
+class SkMergeImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkMergeImageFilterImpl(sk_sp<SkImageFilter>* const filters, int count,
                            const CropRect* cropRect)
@@ -33,7 +34,7 @@
     friend void SkMergeImageFilter::RegisterFlattenables();
     SK_FLATTENABLE_HOOKS(SkMergeImageFilterImpl)
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
index b41d87d..f46b30c 100644
--- a/src/effects/imagefilters/SkMorphologyImageFilter.cpp
+++ b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
@@ -10,7 +10,7 @@
 #include "include/core/SkBitmap.h"
 #include "include/core/SkRect.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkWriteBuffer.h"
@@ -42,7 +42,7 @@
 
 enum class MorphDirection { kX, kY };
 
-class SkMorphologyImageFilterImpl final : public SkImageFilter {
+class SkMorphologyImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkMorphologyImageFilterImpl(MorphType type, int radiusX, int radiusY,
                                 sk_sp<SkImageFilter> input, const CropRect* cropRect)
@@ -91,7 +91,7 @@
     MorphType fType;
     SkISize   fRadius;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
@@ -547,7 +547,7 @@
 static sk_sp<SkSpecialImage> apply_morphology(
         GrRecordingContext* context, SkSpecialImage* input, const SkIRect& rect,
         MorphType morphType, SkISize radius,
-        const SkImageFilter::OutputProperties& outputProperties) {
+        const SkImageFilter_Base::OutputProperties& outputProperties) {
     sk_sp<GrTextureProxy> srcTexture(input->asTextureProxyRef(context));
     SkASSERT(srcTexture);
     sk_sp<SkColorSpace> colorSpace = sk_ref_sp(outputProperties.colorSpace());
diff --git a/src/effects/imagefilters/SkOffsetImageFilter.cpp b/src/effects/imagefilters/SkOffsetImageFilter.cpp
index facd138..cd47f94 100644
--- a/src/effects/imagefilters/SkOffsetImageFilter.cpp
+++ b/src/effects/imagefilters/SkOffsetImageFilter.cpp
@@ -10,7 +10,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkMatrix.h"
 #include "include/core/SkPaint.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkPointPriv.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
@@ -19,7 +19,7 @@
 
 namespace {
 
-class SkOffsetImageFilterImpl final : public SkImageFilter {
+class SkOffsetImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkOffsetImageFilterImpl(SkScalar dx, SkScalar dy, sk_sp<SkImageFilter> input,
                             const CropRect* cropRect)
@@ -42,7 +42,7 @@
 
     SkVector fOffset;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkPaintImageFilter.cpp b/src/effects/imagefilters/SkPaintImageFilter.cpp
index 4c2565e..3d44b1f 100644
--- a/src/effects/imagefilters/SkPaintImageFilter.cpp
+++ b/src/effects/imagefilters/SkPaintImageFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkCanvas.h"
 #include "include/core/SkPaint.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -17,7 +17,7 @@
 
 namespace {
 
-class SkPaintImageFilterImpl final : public SkImageFilter {
+class SkPaintImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkPaintImageFilterImpl(const SkPaint& paint, const CropRect* rect)
             : INHERITED(nullptr, 0, rect)
@@ -36,7 +36,7 @@
 
     SkPaint fPaint;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkPictureImageFilter.cpp b/src/effects/imagefilters/SkPictureImageFilter.cpp
index 47e3a02..63a1a7a 100644
--- a/src/effects/imagefilters/SkPictureImageFilter.cpp
+++ b/src/effects/imagefilters/SkPictureImageFilter.cpp
@@ -10,6 +10,7 @@
 #include "include/core/SkCanvas.h"
 #include "include/core/SkPicture.h"
 #include "include/effects/SkImageSource.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkPicturePriv.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
@@ -19,7 +20,7 @@
 
 namespace {
 
-class SkPictureImageFilterImpl final : public SkImageFilter {
+class SkPictureImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkPictureImageFilterImpl(sk_sp<SkPicture> picture, const SkRect& cropRect)
             : INHERITED(nullptr, 0, nullptr)
@@ -44,7 +45,7 @@
     sk_sp<SkPicture>    fPicture;
     SkRect              fCropRect;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkTileImageFilter.cpp b/src/effects/imagefilters/SkTileImageFilter.cpp
index 3558be6..b9f1ee4 100644
--- a/src/effects/imagefilters/SkTileImageFilter.cpp
+++ b/src/effects/imagefilters/SkTileImageFilter.cpp
@@ -14,7 +14,7 @@
 #include "include/core/SkShader.h"
 #include "include/core/SkSurface.h"
 #include "include/effects/SkOffsetImageFilter.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -23,7 +23,7 @@
 
 namespace {
 
-class SkTileImageFilterImpl final : public SkImageFilter {
+class SkTileImageFilterImpl final : public SkImageFilter_Base {
 public:
     SkTileImageFilterImpl(const SkRect& srcRect, const SkRect& dstRect, sk_sp<SkImageFilter> input)
             : INHERITED(&input, 1, nullptr)
@@ -49,7 +49,7 @@
     SkRect fSrcRect;
     SkRect fDstRect;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
diff --git a/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
index c8f7cb8..d51f435 100644
--- a/src/effects/imagefilters/SkXfermodeImageFilter.cpp
+++ b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
@@ -9,7 +9,7 @@
 
 #include "include/core/SkCanvas.h"
 #include "include/private/SkColorData.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -32,7 +32,7 @@
 
 namespace {
 
-class SkXfermodeImageFilterImpl : public SkImageFilter {
+class SkXfermodeImageFilterImpl : public SkImageFilter_Base {
 public:
     SkXfermodeImageFilterImpl(SkBlendMode mode, sk_sp<SkImageFilter> inputs[2],
                               const CropRect* cropRect)
@@ -70,7 +70,7 @@
 
     SkBlendMode fMode;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 } // end namespace
@@ -184,7 +184,7 @@
                                                   MapDirection dir,
                                                   const SkIRect* inputRect) const {
     if (kReverse_MapDirection == dir) {
-        return SkImageFilter::onFilterBounds(src, ctm, dir, inputRect);
+        return INHERITED::onFilterBounds(src, ctm, dir, inputRect);
     }
 
     SkASSERT(!inputRect);
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index e5eb2d3..a463867 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -20,6 +20,7 @@
 #include "src/core/SkClipStack.h"
 #include "src/core/SkDraw.h"
 #include "src/core/SkImageFilterCache.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkLatticeIter.h"
 #include "src/core/SkMakeUnique.h"
 #include "src/core/SkPictureData.h"
@@ -176,11 +177,11 @@
     if (colorType == kUnknown_SkColorType) {
         colorType = kRGBA_8888_SkColorType;
     }
-    SkImageFilter::OutputProperties outputProperties(
+    SkImageFilter_Base::OutputProperties outputProperties(
             colorType, fRenderTargetContext->colorSpaceInfo().colorSpace());
-    SkImageFilter::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
+    SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
 
-    return filter->filterImage(srcImg, ctx, offset);
+    return as_IFB(filter)->filterImage(srcImg, ctx, offset);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index c869bb3..079992a 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -18,6 +18,7 @@
 #include "src/core/SkCachedData.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkImageFilterCache.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkImagePriv.h"
 #include "src/core/SkNextID.h"
 #include "src/core/SkSpecialImage.h"
@@ -282,17 +283,18 @@
 
     sk_sp<SkImageFilterCache> cache(
         SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
-    SkImageFilter::OutputProperties outputProperties(fInfo.colorType(), fInfo.colorSpace());
+    SkImageFilter_Base::OutputProperties outputProperties(fInfo.colorType(), fInfo.colorSpace());
 
     // The filters operate in the local space of the src image, where (0,0) corresponds to the
     // subset's top left corner. But the clip bounds and any crop rects on the filters are in the
     // original coordinate system, so configure the CTM to correct crop rects and explicitly adjust
     // the clip bounds (since it is assumed to already be in image space).
-    SkImageFilter::Context context(SkMatrix::MakeTrans(-subset.x(), -subset.y()),
-                                   clipBounds.makeOffset(-subset.x(), -subset.y()),
-                                   cache.get(), outputProperties);
+    SkImageFilter_Base::Context context(SkMatrix::MakeTrans(-subset.x(), -subset.y()),
+                                        clipBounds.makeOffset(-subset.x(), -subset.y()),
+                                        cache.get(), outputProperties);
 
-    sk_sp<SkSpecialImage> result = filter->filterImage(srcSpecialImage.get(), context, offset);
+    sk_sp<SkSpecialImage> result = as_IFB(filter)->filterImage(srcSpecialImage.get(), context,
+                                                               offset);
     if (!result) {
         return nullptr;
     }
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index cdb35a7..73e3651 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -28,6 +28,7 @@
 #include "src/core/SkDraw.h"
 #include "src/core/SkGlyphRun.h"
 #include "src/core/SkImageFilterCache.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkMakeUnique.h"
 #include "src/core/SkMaskFilterBase.h"
 #include "src/core/SkRasterClip.h"
@@ -1728,10 +1729,11 @@
         sk_sp<SkImageFilterCache> cache(this->getImageFilterCache());
         // TODO: Should PDF be operating in a specified color type/space? For now, run the filter
         // in the same color space as the source (this is different from all other backends).
-        SkImageFilter::OutputProperties outputProperties(kN32_SkColorType, srcImg->getColorSpace());
-        SkImageFilter::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
+        SkImageFilter_Base::OutputProperties outputProperties(
+                kN32_SkColorType, srcImg->getColorSpace());
+        SkImageFilter_Base::Context ctx(matrix, clipBounds, cache.get(), outputProperties);
 
-        sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
+        sk_sp<SkSpecialImage> resultImg(as_IFB(filter)->filterImage(srcImg, ctx, &offset));
         if (resultImg) {
             SkPaint tmpUnfiltered(paint);
             tmpUnfiltered.setImageFilter(nullptr);
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index c3ac060..2dc5b0d 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -39,6 +39,7 @@
 #include "include/utils/SkNWayCanvas.h"
 #include "include/utils/SkPaintFilterCanvas.h"
 #include "src/core/SkClipOpPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/utils/SkCanvasStack.h"
 #include "tests/Test.h"
@@ -595,7 +596,7 @@
 
 namespace {
 
-class ZeroBoundsImageFilter : public SkImageFilter {
+class ZeroBoundsImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); }
 
@@ -613,7 +614,7 @@
 
     ZeroBoundsImageFilter() : INHERITED(nullptr, 0, nullptr) {}
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) {
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index e1427b1..3a340a0 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -33,7 +33,7 @@
 #include "include/effects/SkTableColorFilter.h"
 #include "include/effects/SkTileImageFilter.h"
 #include "include/effects/SkXfermodeImageFilter.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
 #include "src/core/SkSpecialSurface.h"
@@ -49,7 +49,7 @@
 
 namespace {
 
-class MatrixTestImageFilter : public SkImageFilter {
+class MatrixTestImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<SkImageFilter> Make(skiatest::Reporter* reporter,
                                      const SkMatrix& expectedMatrix) {
@@ -80,12 +80,12 @@
     skiatest::Reporter* fReporter;
     SkMatrix fExpectedMatrix;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
-class FailImageFilter : public SkImageFilter {
+class FailImageFilter : public SkImageFilter_Base {
 public:
-    FailImageFilter() : SkImageFilter(nullptr, 0, nullptr) { }
+    FailImageFilter() : INHERITED(nullptr, 0, nullptr) { }
 
     sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source,
                                         const Context& ctx,
@@ -96,7 +96,7 @@
     SK_FLATTENABLE_HOOKS(FailImageFilter)
 
 private:
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 sk_sp<SkFlattenable> FailImageFilter::CreateProc(SkReadBuffer& buffer) {
@@ -275,10 +275,10 @@
     SkTArray<Filter> fFilters;
 };
 
-class FixedBoundsImageFilter : public SkImageFilter {
+class FixedBoundsImageFilter : public SkImageFilter_Base {
 public:
     FixedBoundsImageFilter(const SkIRect& bounds)
-            : SkImageFilter(nullptr, 0, nullptr), fBounds(bounds) {}
+            : INHERITED(nullptr, 0, nullptr), fBounds(bounds) {}
 
 private:
     Factory getFactory() const override { return nullptr; }
@@ -295,6 +295,8 @@
     }
 
     SkIRect fBounds;
+
+    typedef SkImageFilter_Base INHERITED;
 };
 }
 
@@ -523,9 +525,10 @@
     for (int i = 0; i < filters.count(); ++i) {
         SkImageFilter* filter = filters.getFilter(i);
         SkIPoint offset;
-        SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-        SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, noColorSpace);
-        sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg.get(), ctx, &offset));
+        SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+        SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr,
+                                        noColorSpace);
+        sk_sp<SkSpecialImage> resultImg(as_IFB(filter)->filterImage(srcImg.get(), ctx, &offset));
         REPORTER_ASSERT(reporter, resultImg, filters.getName(i));
         REPORTER_ASSERT(reporter, offset.fX == 20 && offset.fY == 30, filters.getName(i));
     }
@@ -546,28 +549,28 @@
                                                                 gradient));
 
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(32, 32), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(32, 32), nullptr, noColorSpace);
 
-    sk_sp<SkSpecialImage> positiveResult1(positiveFilter->filterImage(imgSrc.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> positiveResult1(
+            as_IFB(positiveFilter)->filterImage(imgSrc.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, positiveResult1);
 
-    sk_sp<SkSpecialImage> negativeResult1(negativeFilter->filterImage(imgSrc.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> negativeResult1(
+            as_IFB(negativeFilter)->filterImage(imgSrc.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, negativeResult1);
 
     SkMatrix negativeScale;
     negativeScale.setScale(-SK_Scalar1, SK_Scalar1);
-    SkImageFilter::Context negativeCTX(negativeScale, SkIRect::MakeWH(32, 32), nullptr,
-                                       noColorSpace);
+    SkImageFilter_Base::Context negativeCTX(negativeScale, SkIRect::MakeWH(32, 32), nullptr,
+                                            noColorSpace);
 
-    sk_sp<SkSpecialImage> negativeResult2(positiveFilter->filterImage(imgSrc.get(),
-                                                                      negativeCTX,
-                                                                      &offset));
+    sk_sp<SkSpecialImage> negativeResult2(
+            as_IFB(positiveFilter)->filterImage(imgSrc.get(), negativeCTX, &offset));
     REPORTER_ASSERT(reporter, negativeResult2);
 
-    sk_sp<SkSpecialImage> positiveResult2(negativeFilter->filterImage(imgSrc.get(),
-                                                                      negativeCTX,
-                                                                      &offset));
+    sk_sp<SkSpecialImage> positiveResult2(
+            as_IFB(negativeFilter)->filterImage(imgSrc.get(), negativeCTX, &offset));
     REPORTER_ASSERT(reporter, positiveResult2);
 
 
@@ -623,10 +626,10 @@
     sk_sp<SkSpecialImage> image(surf->makeImageSnapshot());
 
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(32, 32), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(32, 32), nullptr, noColorSpace);
 
-    sk_sp<SkSpecialImage> result(filter->filterImage(image.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> result(as_IFB(filter)->filterImage(image.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, offset.fX == 5 && offset.fY == 0);
     REPORTER_ASSERT(reporter, result);
     REPORTER_ASSERT(reporter, result->width() == 5 && result->height() == 10);
@@ -660,14 +663,15 @@
 static void test_fail_affects_transparent_black(skiatest::Reporter* reporter, GrContext* context) {
     sk_sp<FailImageFilter> failFilter(new FailImageFilter());
     sk_sp<SkSpecialImage> source(create_empty_special_image(context, 5));
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 1, 1), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 1, 1), nullptr,
+                                    noColorSpace);
     sk_sp<SkColorFilter> green(SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kSrc));
     SkASSERT(green->affectsTransparentBlack());
     sk_sp<SkImageFilter> greenFilter(SkColorFilterImageFilter::Make(std::move(green),
                                                                     std::move(failFilter)));
     SkIPoint offset;
-    sk_sp<SkSpecialImage> result(greenFilter->filterImage(source.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> result(as_IFB(greenFilter)->filterImage(source.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, nullptr != result.get());
     if (result.get()) {
         SkBitmap resultBM;
@@ -951,12 +955,12 @@
 
     sk_sp<SkSpecialImage> srcImg(create_empty_special_image(context, 1));
 
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 100, 100), nullptr,
-                               noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 100, 100), nullptr,
+                                    noColorSpace);
     SkIPoint offset;
 
-    sk_sp<SkSpecialImage> resultImg(merge->filterImage(srcImg.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> resultImg(as_IFB(merge)->filterImage(srcImg.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, resultImg);
 
     REPORTER_ASSERT(reporter, resultImg->width() == 20 && resultImg->height() == 20);
@@ -1120,9 +1124,10 @@
     SkASSERT(srcImg);
 
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, noColorSpace);
-    sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg.get(), ctx, &offset));
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr,
+                                    noColorSpace);
+    sk_sp<SkSpecialImage> resultImg(as_IFB(filter)->filterImage(srcImg.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, resultImg);
     REPORTER_ASSERT(reporter, SkToBool(context) == resultImg->isTextureBacked());
     REPORTER_ASSERT(reporter, resultImg->width() == 100 && resultImg->height() == 100);
@@ -1192,10 +1197,11 @@
     sk_sp<SkImageFilter> imageFilter(SkPictureImageFilter::Make(picture));
 
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(1, 1, 1, 1), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(1, 1, 1, 1), nullptr,
+                                    noColorSpace);
 
-    sk_sp<SkSpecialImage> resultImage(imageFilter->filterImage(srcImg.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> resultImage(as_IFB(imageFilter)->filterImage(srcImg.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, !resultImage);
 }
 
@@ -1444,10 +1450,12 @@
     sk_sp<SkImageFilter> composedFilter(SkComposeImageFilter::Make(std::move(blurFilter),
                                                                    std::move(offsetFilter)));
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr,
+                                    noColorSpace);
 
-    sk_sp<SkSpecialImage> resultImg(composedFilter->filterImage(srcImg.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> resultImg(
+            as_IFB(composedFilter)->filterImage(srcImg.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, resultImg);
     REPORTER_ASSERT(reporter, offset.fX == 1 && offset.fY == 0);
 }
@@ -1479,10 +1487,12 @@
                                                                    std::move(pictureFilter)));
 
     sk_sp<SkSpecialImage> sourceImage(create_empty_special_image(context, 100));
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr,
+                                    noColorSpace);
     SkIPoint offset;
-    sk_sp<SkSpecialImage> result(composedFilter->filterImage(sourceImage.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> result(
+            as_IFB(composedFilter)->filterImage(sourceImage.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, offset.isZero());
     REPORTER_ASSERT(reporter, result);
     REPORTER_ASSERT(reporter, result->subset().size() == SkISize::Make(100, 100));
@@ -1507,10 +1517,11 @@
         SkImageFilter::CropRect::kHasWidth_CropEdge | SkImageFilter::CropRect::kHasHeight_CropEdge);
     sk_sp<SkImageFilter> filter(make_grayscale(nullptr, &cropRect));
     SkIPoint offset;
-    SkImageFilter::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
-    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, noColorSpace);
+    SkImageFilter_Base::OutputProperties noColorSpace(kN32_SkColorType, nullptr);
+    SkImageFilter_Base::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr,
+                                    noColorSpace);
 
-    sk_sp<SkSpecialImage> resultImg(filter->filterImage(srcImg.get(), ctx, &offset));
+    sk_sp<SkSpecialImage> resultImg(as_IFB(filter)->filterImage(srcImg.get(), ctx, &offset));
     REPORTER_ASSERT(reporter, resultImg);
 
     REPORTER_ASSERT(reporter, offset.fX == 0);
@@ -1791,7 +1802,7 @@
     };
 
     for (const auto& rec : recs) {
-        const bool canHandle = rec.fFilter->canHandleComplexCTM();
+        const bool canHandle = as_IFB(rec.fFilter)->canHandleComplexCTM();
         REPORTER_ASSERT(reporter, canHandle == rec.fExpectCanHandle);
     }
 }
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 6c3c311..4a11a00 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -21,7 +21,7 @@
 #include "include/effects/SkPerlinNoiseShader.h"
 #include "include/private/SkTo.h"
 #include "src/core/SkGlyphRun.h"
-#include "src/core/SkImageFilterPriv.h"
+#include "src/core/SkImageFilter_Base.h"
 #include "src/core/SkMakeUnique.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkSpecialImage.h"
@@ -247,7 +247,7 @@
 
 namespace {
 
-class DummyImageFilter : public SkImageFilter {
+class DummyImageFilter : public SkImageFilter_Base {
 public:
     static sk_sp<DummyImageFilter> Make(bool visited = false) {
         return sk_sp<DummyImageFilter>(new DummyImageFilter(visited));
@@ -269,7 +269,7 @@
 
     mutable bool fVisited;
 
-    typedef SkImageFilter INHERITED;
+    typedef SkImageFilter_Base INHERITED;
 };
 
 sk_sp<SkFlattenable> DummyImageFilter::CreateProc(SkReadBuffer& buffer) {
diff --git a/tests/SpecialImageTest.cpp b/tests/SpecialImageTest.cpp
index 1c6b36c..42dbcb8 100644
--- a/tests/SpecialImageTest.cpp
+++ b/tests/SpecialImageTest.cpp
@@ -81,7 +81,7 @@
 
     //--------------
     // Test that draw restricts itself to the subset
-    SkImageFilter::OutputProperties outProps(kN32_SkColorType, img->getColorSpace());
+    SkImageFilter_Base::OutputProperties outProps(kN32_SkColorType, img->getColorSpace());
     sk_sp<SkSpecialSurface> surf(img->makeSurface(outProps, SkISize::Make(kFullSize, kFullSize),
                                                   kPremul_SkAlphaType));
 
@@ -118,7 +118,7 @@
         REPORTER_ASSERT(reporter, isGPUBacked != !!tightImg->peekPixels(&tmpPixmap));
     }
     {
-        SkImageFilter::OutputProperties outProps(kN32_SkColorType, img->getColorSpace());
+        SkImageFilter_Base::OutputProperties outProps(kN32_SkColorType, img->getColorSpace());
         sk_sp<SkSurface> tightSurf(img->makeTightSurface(outProps, subset.size()));
 
         REPORTER_ASSERT(reporter, tightSurf->width() == subset.width());